diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a8860c13..6733b51d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,7 +10,7 @@ on: jobs: ci: - name: 🚀 Deploy web to Cloudflare + name: 💚 Run CI runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 6cb82086..523d8d56 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ build dist .env .turbo -.wrangler \ No newline at end of file +.wrangler +.idea diff --git a/apps/www/migrations/0006_brainy_random.sql b/apps/www/migrations/0006_brainy_random.sql new file mode 100644 index 00000000..673967bb --- /dev/null +++ b/apps/www/migrations/0006_brainy_random.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD `role` text DEFAULT 'normal' NOT NULL; \ No newline at end of file diff --git a/apps/www/migrations/0007_silent_silver_fox.sql b/apps/www/migrations/0007_silent_silver_fox.sql new file mode 100644 index 00000000..ac2ce57d --- /dev/null +++ b/apps/www/migrations/0007_silent_silver_fox.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD `additional_beers` integer DEFAULT 0 NOT NULL; \ No newline at end of file diff --git a/apps/www/migrations/0008_flawless_supernaut.sql b/apps/www/migrations/0008_flawless_supernaut.sql new file mode 100644 index 00000000..76024c72 --- /dev/null +++ b/apps/www/migrations/0008_flawless_supernaut.sql @@ -0,0 +1 @@ +ALTER TABLE `user` DROP COLUMN `additional_beers`; \ No newline at end of file diff --git a/apps/www/migrations/0009_spooky_oracle.sql b/apps/www/migrations/0009_spooky_oracle.sql new file mode 100644 index 00000000..ac2ce57d --- /dev/null +++ b/apps/www/migrations/0009_spooky_oracle.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD `additional_beers` integer DEFAULT 0 NOT NULL; \ No newline at end of file diff --git a/apps/www/migrations/meta/0006_snapshot.json b/apps/www/migrations/meta/0006_snapshot.json new file mode 100644 index 00000000..cfe136a2 --- /dev/null +++ b/apps/www/migrations/meta/0006_snapshot.json @@ -0,0 +1,327 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "c060a25d-16fc-4142-8edd-750be00dbe6c", + "prevId": "2e96d076-87b4-4771-81bd-f9a8602e555a", + "tables": { + "event": { + "name": "event", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shift": { + "name": "shift", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end": { + "name": "end", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "shift_event_id_event_id_fk": { + "name": "shift_event_id_event_id_fk", + "tableFrom": "shift", + "tableTo": "event", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_shift": { + "name": "user_shift", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shift_id": { + "name": "shift_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_beer_claimed": { + "name": "is_beer_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_shift_user_id_user_id_fk": { + "name": "user_shift_user_id_user_id_fk", + "tableFrom": "user_shift", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_shift_shift_id_shift_id_fk": { + "name": "user_shift_shift_id_shift_id_fk", + "tableFrom": "user_shift", + "tableTo": "shift", + "columnsFrom": [ + "shift_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feide_id": { + "name": "feide_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'normal'" + } + }, + "indexes": { + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": true + }, + "feide_id_idx": { + "name": "feide_id_idx", + "columns": [ + "feide_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/www/migrations/meta/0007_snapshot.json b/apps/www/migrations/meta/0007_snapshot.json new file mode 100644 index 00000000..c5c4f5c0 --- /dev/null +++ b/apps/www/migrations/meta/0007_snapshot.json @@ -0,0 +1,335 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "af4392b5-881a-41d5-9a0b-acc6679c5bf8", + "prevId": "c060a25d-16fc-4142-8edd-750be00dbe6c", + "tables": { + "event": { + "name": "event", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shift": { + "name": "shift", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end": { + "name": "end", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "shift_event_id_event_id_fk": { + "name": "shift_event_id_event_id_fk", + "tableFrom": "shift", + "tableTo": "event", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_shift": { + "name": "user_shift", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shift_id": { + "name": "shift_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_beer_claimed": { + "name": "is_beer_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_shift_user_id_user_id_fk": { + "name": "user_shift_user_id_user_id_fk", + "tableFrom": "user_shift", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_shift_shift_id_shift_id_fk": { + "name": "user_shift_shift_id_shift_id_fk", + "tableFrom": "user_shift", + "tableTo": "shift", + "columnsFrom": [ + "shift_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feide_id": { + "name": "feide_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'normal'" + }, + "additional_beers": { + "name": "additional_beers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": true + }, + "feide_id_idx": { + "name": "feide_id_idx", + "columns": [ + "feide_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/www/migrations/meta/0008_snapshot.json b/apps/www/migrations/meta/0008_snapshot.json new file mode 100644 index 00000000..47607ea1 --- /dev/null +++ b/apps/www/migrations/meta/0008_snapshot.json @@ -0,0 +1,327 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "7aa90d9f-ff74-40cb-8b0b-b2766ded8d7e", + "prevId": "af4392b5-881a-41d5-9a0b-acc6679c5bf8", + "tables": { + "event": { + "name": "event", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shift": { + "name": "shift", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end": { + "name": "end", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "shift_event_id_event_id_fk": { + "name": "shift_event_id_event_id_fk", + "tableFrom": "shift", + "tableTo": "event", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_shift": { + "name": "user_shift", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shift_id": { + "name": "shift_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_beer_claimed": { + "name": "is_beer_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_shift_user_id_user_id_fk": { + "name": "user_shift_user_id_user_id_fk", + "tableFrom": "user_shift", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_shift_shift_id_shift_id_fk": { + "name": "user_shift_shift_id_shift_id_fk", + "tableFrom": "user_shift", + "tableTo": "shift", + "columnsFrom": [ + "shift_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feide_id": { + "name": "feide_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'normal'" + } + }, + "indexes": { + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": true + }, + "feide_id_idx": { + "name": "feide_id_idx", + "columns": [ + "feide_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/www/migrations/meta/0009_snapshot.json b/apps/www/migrations/meta/0009_snapshot.json new file mode 100644 index 00000000..1a0bf042 --- /dev/null +++ b/apps/www/migrations/meta/0009_snapshot.json @@ -0,0 +1,335 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "251bc0a3-4203-4ba6-8216-80b2b90125c8", + "prevId": "7aa90d9f-ff74-40cb-8b0b-b2766ded8d7e", + "tables": { + "event": { + "name": "event", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shift": { + "name": "shift", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end": { + "name": "end", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "shift_event_id_event_id_fk": { + "name": "shift_event_id_event_id_fk", + "tableFrom": "shift", + "tableTo": "event", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_shift": { + "name": "user_shift", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shift_id": { + "name": "shift_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_beer_claimed": { + "name": "is_beer_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_shift_user_id_user_id_fk": { + "name": "user_shift_user_id_user_id_fk", + "tableFrom": "user_shift", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_shift_shift_id_shift_id_fk": { + "name": "user_shift_shift_id_shift_id_fk", + "tableFrom": "user_shift", + "tableTo": "shift", + "columnsFrom": [ + "shift_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feide_id": { + "name": "feide_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'normal'" + }, + "additional_beers": { + "name": "additional_beers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": true + }, + "feide_id_idx": { + "name": "feide_id_idx", + "columns": [ + "feide_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/www/migrations/meta/_journal.json b/apps/www/migrations/meta/_journal.json index 7f35f721..fe2a6c8e 100644 --- a/apps/www/migrations/meta/_journal.json +++ b/apps/www/migrations/meta/_journal.json @@ -43,6 +43,34 @@ "when": 1730150522538, "tag": "0005_silky_cargill", "breakpoints": true + }, + { + "idx": 6, + "version": "6", + "when": 1730295114616, + "tag": "0006_brainy_random", + "breakpoints": true + }, + { + "idx": 7, + "version": "6", + "when": 1733148002421, + "tag": "0007_silent_silver_fox", + "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1733148493243, + "tag": "0008_flawless_supernaut", + "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1733148799391, + "tag": "0009_spooky_oracle", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/www/scripts/users.ts b/apps/www/scripts/users.ts index f2beba4f..3008d056 100644 --- a/apps/www/scripts/users.ts +++ b/apps/www/scripts/users.ts @@ -20,7 +20,8 @@ const main = async () => { const fakeUsers = Array.from({ length: 100 }, () => ({ id: nanoid(), email: faker.internet.email(), - name: faker.person.fullName() + name: faker.person.fullName(), + role: 'normal' })); for (const user of fakeUsers) { diff --git a/apps/www/src/app.d.ts b/apps/www/src/app.d.ts index 45de96ac..6af0b4e8 100644 --- a/apps/www/src/app.d.ts +++ b/apps/www/src/app.d.ts @@ -28,6 +28,7 @@ declare global { eventService: import('$lib/services/event.service').EventService; userService: import('$lib/services/user.service').UserService; shiftService: import('$lib/services/shift.service').ShiftService; + beerService: import('$lib/services/beer.service').BeerService; } // interface PageData {} // interface PageState {} diff --git a/apps/www/src/hooks.server.ts b/apps/www/src/hooks.server.ts index e446250c..0d6e4aef 100644 --- a/apps/www/src/hooks.server.ts +++ b/apps/www/src/hooks.server.ts @@ -2,6 +2,7 @@ import { dev } from '$app/environment'; import { createAuth } from '$lib/auth/lucia'; import { createFeideProvider } from '$lib/auth/providers/feide'; import { createDatabase } from '$lib/db/drizzle'; +import { BeerService } from '$lib/services/beer.service'; import { EmailService } from '$lib/services/email.service'; import { EventService } from '$lib/services/event.service'; import { InvitationService } from '$lib/services/invitation.service'; @@ -51,6 +52,9 @@ export const handle: Handle = async ({ event, resolve }) => { const shiftService = new ShiftService(db); event.locals.shiftService = shiftService; + const beerService = new BeerService(db, shiftService); + event.locals.beerService = beerService; + // Validate auth const sessionId = event.cookies.get(auth.sessionCookieName); @@ -72,7 +76,15 @@ export const handle: Handle = async ({ event, resolve }) => { }); } - // Protect /portal/* from unauthenticated users + if (event.url.pathname.startsWith('/portal/admin') && event.locals.user?.role !== 'board') { + return new Response(null, { + status: 307, + headers: { + location: '/logg-inn' + } + }); + } + if (event.url.pathname.startsWith('/portal') && !event.locals.user) { return new Response(null, { status: 307, diff --git a/apps/www/src/lib/components/cards/UserCard.svelte b/apps/www/src/lib/components/cards/UserCard.svelte new file mode 100644 index 00000000..5fb58745 --- /dev/null +++ b/apps/www/src/lib/components/cards/UserCard.svelte @@ -0,0 +1,57 @@ + + +
+ +
+
+ {initials(user.name)} +
+
+ +
+

{user.name}

+

+ {#if user.role === 'board'} + + Styret + + {:else} + + Frivillig + + {/if} +

+

+ {user.email} +

+
+
diff --git a/apps/www/src/lib/components/cards/UserDetailModal.svelte b/apps/www/src/lib/components/cards/UserDetailModal.svelte new file mode 100644 index 00000000..d3b84f23 --- /dev/null +++ b/apps/www/src/lib/components/cards/UserDetailModal.svelte @@ -0,0 +1,131 @@ + + +
+
+ + {#if currentUserRole === 'board'} +
+ + {#if menuOpen} + +
+
    +
  • + +
  • +
  • + +
  • +
+
+ {/if} +
+ {/if} +

{selectedUser.name}'s Detaljer

+

E-post: {selectedUser.email}

+

Antall ganger stått: {selectedUser.timesVolunteered}

+
+ +
+
+ + +
+
+
diff --git a/apps/www/src/lib/components/portal/Header.svelte b/apps/www/src/lib/components/portal/Header.svelte index 12b56dca..a2ee5922 100644 --- a/apps/www/src/lib/components/portal/Header.svelte +++ b/apps/www/src/lib/components/portal/Header.svelte @@ -1,5 +1,8 @@
@@ -18,6 +21,11 @@ >Status +
  • + Cash Out +
  • ArrangementBrukere
  • + + {#if auth.user?.role === 'board'} +
  • + Admin +
  • + {/if} diff --git a/apps/www/src/lib/db/schema.ts b/apps/www/src/lib/db/schema.ts index 9073adec..ffd046cf 100644 --- a/apps/www/src/lib/db/schema.ts +++ b/apps/www/src/lib/db/schema.ts @@ -5,14 +5,17 @@ import { nanoid } from 'nanoid'; /** * Users */ - export const users = sqliteTable( 'user', { id: text('id').notNull().primaryKey(), name: text('name').notNull(), email: text('email').notNull(), - feideId: text('feide_id') + feideId: text('feide_id'), + role: text('role', { enum: ['board', 'normal'] }) + .notNull() + .default('normal'), + additionalBeers: integer('additional_beers').default(0).notNull() }, (t) => ({ emailIdx: uniqueIndex('email_idx').on(t.email), @@ -31,7 +34,6 @@ export type UserInsert = InferInsertModel; /** * Sessions */ - export const sessions = sqliteTable('session', { id: text('id').primaryKey(), userId: text('user_id').notNull(), @@ -48,7 +50,6 @@ export const sessionsRelations = relations(sessions, ({ one }) => ({ /** * Invitations */ - export const invitations = sqliteTable( 'invitation', { @@ -66,7 +67,6 @@ export const invitations = sqliteTable( /** * Events */ - export const events = sqliteTable('event', { id: text('id').notNull().primaryKey().$defaultFn(nanoid), name: text('name').notNull(), @@ -82,7 +82,6 @@ export type Event = InferSelectModel; /** * Shifts */ - export const shifts = sqliteTable('shift', { id: text('id').notNull().primaryKey().$defaultFn(nanoid), eventId: text('event_id') @@ -107,7 +106,6 @@ export type Shift = InferSelectModel; /** * Users to shifts */ - export const userShifts = sqliteTable('user_shift', { userId: text('user_id') .notNull() diff --git a/apps/www/src/lib/services/beer.service.ts b/apps/www/src/lib/services/beer.service.ts new file mode 100644 index 00000000..1e61ba08 --- /dev/null +++ b/apps/www/src/lib/services/beer.service.ts @@ -0,0 +1,93 @@ +import type { Database } from '$lib/db/drizzle'; +import { users, userShifts } from '$lib/db/schema'; +import { eq, and } from 'drizzle-orm'; +import { ShiftService } from './shift.service'; + +export class BeerService { + #db: Database; + #shiftService: ShiftService; + + constructor(db: Database, shiftService: ShiftService) { + this.#db = db; + this.#shiftService = shiftService; + } + + async claimBeer(userId: string): Promise { + try { + const unclaimedShifts = await this.#shiftService.findShiftsWithUnclaimedBeersByUserId(userId); + + if (unclaimedShifts.length > 0) { + const shiftToUpdate = unclaimedShifts[0].shift; + + if (shiftToUpdate) { + await this.#db + .update(userShifts) + .set({ isBeerClaimed: true }) + .where(and(eq(userShifts.shiftId, shiftToUpdate.id), eq(userShifts.userId, userId))); + return true; + } + } + + const user = await this.#db + .select({ additionalBeers: users.additionalBeers }) + .from(users) + .where(eq(users.id, userId)) + .limit(1); + + if (user.length > 0 && user[0].additionalBeers > 0) { + await this.#db + .update(users) + .set({ additionalBeers: user[0].additionalBeers - 1 }) + .where(eq(users.id, userId)); + return true; + } + + return false; + } catch (error) { + console.error('Error claiming beer:', error); + return false; + } + } + + async getTotalAvailableBeers(userId: string): Promise { + try { + const unclaimedShifts = await this.#shiftService.findShiftsWithUnclaimedBeersByUserId(userId); + const unclaimedShiftBeers = unclaimedShifts.length; + + const userResult = await this.#db + .select({ additionalBeers: users.additionalBeers }) + .from(users) + .where(eq(users.id, userId)) + .limit(1); + + const additionalBeers = Number(userResult[0]?.additionalBeers ?? 0); + const totalBeers = unclaimedShiftBeers + additionalBeers; + + return totalBeers; + } catch (error) { + console.error('Error getting total available beers:', error); + return 0; + } + } + // Checks the gives back the difference between newBeercount and shiftBeer, so that we can type in an amount we want and not have the shift beer added afterwards + async updateBeers(userId: string, newBeerCount: number): Promise { + if (!Number.isInteger(newBeerCount) || newBeerCount < 0) { + console.error('Invalid additional beer count:', newBeerCount); + return false; + } + + try { + const unclaimedShifts = await this.#shiftService.findShiftsWithUnclaimedBeersByUserId(userId); + const unclaimedShiftBeers = unclaimedShifts.length; + + const reqBeers = Math.max(newBeerCount - unclaimedShiftBeers, 0); + + await this.#db.update(users).set({ additionalBeers: reqBeers }).where(eq(users.id, userId)); + + return true; + } catch (error) { + console.error('Error updating additional beers:', error); + return false; + } + } +} diff --git a/apps/www/src/lib/services/user.service.ts b/apps/www/src/lib/services/user.service.ts index 7a5e85f1..de1ceb31 100644 --- a/apps/www/src/lib/services/user.service.ts +++ b/apps/www/src/lib/services/user.service.ts @@ -1,4 +1,5 @@ import type { Database } from '$lib/db/drizzle'; +import { eq } from 'drizzle-orm'; import { users, type UserInsert } from '$lib/db/schema'; export class UserService { @@ -25,4 +26,23 @@ export class UserService { async findAll() { return await this.#db.query.users.findMany(); } + + async updateUserRole(userId: string, role: 'board' | 'normal') { + return await this.#db + .update(users) + .set({ role }) + .where(eq(users.id, userId)) + .returning() + .then((rows) => rows[0]); + } + + async findById(userId: string) { + const user = await this.#db + .select() + .from(users) + .where(eq(users.id, userId)) + .then((results) => results[0]); + + return user; + } } diff --git a/apps/www/src/routes/portal/+page.server.ts b/apps/www/src/routes/portal/+page.server.ts index d3f6d27f..9895cc00 100644 --- a/apps/www/src/routes/portal/+page.server.ts +++ b/apps/www/src/routes/portal/+page.server.ts @@ -8,13 +8,15 @@ export const load: PageServerLoad = async ({ locals }) => { const [userShifts, unclaimedBeers, upcomingShifts] = await Promise.all([ locals.shiftService.findCompletedShiftsByUserId(locals.user.id), - locals.shiftService.findShiftsWithUnclaimedBeersByUserId(locals.user.id), + locals.beerService.getTotalAvailableBeers(locals.user.id), locals.shiftService.findUpcomingShiftsByUserId(locals.user.id) ]); + console.log(locals.beerService.getTotalAvailableBeers(locals.user.id)); + return { shiftsCompleted: userShifts.length, - unclaimedBeers: unclaimedBeers.length, + unclaimedBeers: unclaimedBeers, upcomingShifts }; }; diff --git a/apps/www/src/routes/portal/admin/+page.server.ts b/apps/www/src/routes/portal/admin/+page.server.ts new file mode 100644 index 00000000..f13bba4b --- /dev/null +++ b/apps/www/src/routes/portal/admin/+page.server.ts @@ -0,0 +1,39 @@ +import { redirect, fail, type Actions } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ locals }) => { + if (!locals.user || locals.user.role !== 'board') { + throw redirect(303, '/portal'); + } + + const users = await locals.userService.findAll(); + + return { + user: locals.user, + users + }; +}; + +export const actions: Actions = { + default: async ({ request, locals }) => { + const formData = await request.formData(); + const userId = formData.get('userId') as string; + const role = formData.get('role'); + + if (typeof role !== 'string' || (role !== 'board' && role !== 'normal')) { + return fail(400, { error: 'Invalid role specified' }); + } + + if (!locals.user || locals.user.role !== 'board') { + return fail(401, { error: 'Unauthorized' }); + } + + const success = await locals.userService.updateUserRole(userId, role); + + if (success) { + return { success: true }; + } else { + return fail(500, { error: 'Failed to update user role' }); + } + } +}; diff --git a/apps/www/src/routes/portal/admin/+page.svelte b/apps/www/src/routes/portal/admin/+page.svelte new file mode 100644 index 00000000..1855fbf3 --- /dev/null +++ b/apps/www/src/routes/portal/admin/+page.svelte @@ -0,0 +1,125 @@ + + + + Admin - User Management + + +{#if isModalOpen && selectedUser} + updateRole(event.userId, event.newRole)} + /> +{/if} + +
    + Styret +
      + {#each boardMembers as user (user.id)} + + {/each} +
    +
    + +
    + Frivillige +
    + +
    +
      + {#each normalMembers as user (user.id)} + handleUserClick(user)} /> + {/each} +
    +
    diff --git a/apps/www/src/routes/portal/admin/user/[id]/+server.ts b/apps/www/src/routes/portal/admin/user/[id]/+server.ts new file mode 100644 index 00000000..af23fc18 --- /dev/null +++ b/apps/www/src/routes/portal/admin/user/[id]/+server.ts @@ -0,0 +1,64 @@ +import { json, error } from '@sveltejs/kit'; + +interface BeerUpdateData { + additionalBeers: number; +} + +export async function GET({ params, locals }: { params: { id: string }; locals: App.Locals }) { + const userId = params.id; + + if (!locals.user || locals.user.role !== 'board') { + throw error(401, 'Unauthorized'); + } + + const user = await locals.userService.findById(userId); + + if (!user) { + throw error(404, 'User not found'); + } + + const userShifts = await locals.shiftService.findCompletedShiftsByUserId(userId); + const unclaimedBeers = await locals.beerService.getTotalAvailableBeers(userId); + + return json({ + ...user, + timesVolunteered: userShifts.length, + unclaimedBeers: unclaimedBeers + }); +} + +export async function POST({ + params, + request, + locals +}: { + params: { id: string }; + request: Request; + locals: App.Locals; +}) { + const userId = params.id; + + if (!locals.user || locals.user.role !== 'board') { + throw error(401, 'Unauthorized'); + } + + try { + const data = (await request.json()) as BeerUpdateData; + const newBeerCount = Number(data.additionalBeers); + + if (!Number.isInteger(newBeerCount) || newBeerCount < 0) { + throw error(400, 'Invalid additional beer count'); + } + + const success = await locals.beerService.updateBeers(userId, newBeerCount); + + if (success) { + return json({ success: true }); + } else { + throw error(500, 'Failed to update additional beers'); + } + } catch (err) { + console.error('Error updating additional beers:', err); + throw error(500, 'Server error'); + } +} diff --git a/apps/www/src/routes/portal/arrangementer/+page.svelte b/apps/www/src/routes/portal/arrangementer/+page.svelte index f4efb051..875dd0ce 100644 --- a/apps/www/src/routes/portal/arrangementer/+page.svelte +++ b/apps/www/src/routes/portal/arrangementer/+page.svelte @@ -1,8 +1,11 @@ @@ -10,10 +13,11 @@ Arrangementer - -

    - Nytt arrangement -

    +{#if auth.user?.role == 'board'} +

    + Nytt arrangement +

    +{/if}
      {#each data.events as event} diff --git a/apps/www/src/routes/portal/brukere/+page.svelte b/apps/www/src/routes/portal/brukere/+page.svelte index 2347329e..e6e5e089 100644 --- a/apps/www/src/routes/portal/brukere/+page.svelte +++ b/apps/www/src/routes/portal/brukere/+page.svelte @@ -1,9 +1,10 @@ + +
      +

      🍺 Claim Your Beer

      +
      + +
      + + {#if message} +

      {message}

      + {/if} + + {#if error} +

      {error}

      + {/if} +
      + + diff --git a/apps/www/src/routes/portal/claim-beer/+server.ts b/apps/www/src/routes/portal/claim-beer/+server.ts new file mode 100644 index 00000000..d7f566b8 --- /dev/null +++ b/apps/www/src/routes/portal/claim-beer/+server.ts @@ -0,0 +1,21 @@ +import { json, error } from '@sveltejs/kit'; + +export async function POST({ locals }) { + const userId = locals.user?.id; + + if (!userId) { + throw error(401, 'Unauthorized: Please log in to claim your beer.'); + } + + try { + const success = await locals.beerService.claimBeer(userId); + if (success) { + return json({ success: true }); + } else { + return json({ message: 'No more beers left to claim.' }, { status: 400 }); + } + } catch (err) { + console.error('Error claiming beer:', err); + throw error(500, 'Server encountered an unexpected issue.'); + } +}