From 4aabc9eb7929bdc77da2f18b0877349bea0eae5f Mon Sep 17 00:00:00 2001 From: FluffyFire <62662486+sturla1511@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:06:26 +0200 Subject: [PATCH 1/4] added active and inActive users --- .../migrations/0026_rare_skrulls.sql | 1 + .../migrations/meta/0026_snapshot.json | 1146 +++++++++++++++++ .../migrations/meta/_journal.json | 7 + .../src/lib/api/remotes/events.remote.ts | 4 +- .../src/lib/server/db/schemas/users.ts | 1 + .../lib/server/services/event.service.test.ts | 52 + .../src/lib/server/services/event.service.ts | 16 + .../src/lib/server/services/user.service.ts | 14 + .../routes/(portal)/portal/admin/+page.svelte | 4 +- .../portal/admin/bruker/[id]/+page.svelte | 4 +- .../admin/bruker/[id]/rediger/+page.server.ts | 6 +- .../admin/bruker/[id]/rediger/+page.svelte | 17 +- .../portal/arrangementer/[id]/+page.svelte | 2 +- .../[id]/rediger/+page.server.ts | 19 +- .../portal/arrangementer/ny/+page.server.ts | 2 +- .../portal/frivillige/+page.server.ts | 2 +- 16 files changed, 1286 insertions(+), 11 deletions(-) create mode 100644 programmerbar-web/migrations/0026_rare_skrulls.sql create mode 100644 programmerbar-web/migrations/meta/0026_snapshot.json create mode 100644 programmerbar-web/src/lib/server/services/event.service.test.ts diff --git a/programmerbar-web/migrations/0026_rare_skrulls.sql b/programmerbar-web/migrations/0026_rare_skrulls.sql new file mode 100644 index 00000000..c15961d2 --- /dev/null +++ b/programmerbar-web/migrations/0026_rare_skrulls.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD `is_active` integer DEFAULT true NOT NULL; \ No newline at end of file diff --git a/programmerbar-web/migrations/meta/0026_snapshot.json b/programmerbar-web/migrations/meta/0026_snapshot.json new file mode 100644 index 00000000..0e04ac07 --- /dev/null +++ b/programmerbar-web/migrations/meta/0026_snapshot.json @@ -0,0 +1,1146 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "db9e22cc-9aa4-473d-a9f0-971f68aff4f4", + "prevId": "ee04feed-009b-41fe-becd-df6062fbb1e9", + "tables": { + "claimed_credits": { + "name": "claimed_credits", + "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 + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credit_cost": { + "name": "credit_cost", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "claimed_credits_user_id_user_id_fk": { + "name": "claimed_credits_user_id_user_id_fk", + "tableFrom": "claimed_credits", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contact_submission": { + "name": "contact_submission", + "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 + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "submitted_at": { + "name": "submitted_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "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 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "event_slug_idx": { + "name": "event_slug_idx", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "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": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification": { + "name": "notification", + "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 + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_notifications_user_id": { + "name": "idx_notifications_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_notifications_archived_at": { + "name": "idx_notifications_archived_at", + "columns": [ + "archived_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notification_user_id_user_id_fk": { + "name": "notification_user_id_user_id_fk", + "tableFrom": "notification", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "push_subscription": { + "name": "push_subscription", + "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 + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "p256dh": { + "name": "p256dh", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth": { + "name": "auth", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "push_subscription_endpoint_unique": { + "name": "push_subscription_endpoint_unique", + "columns": [ + "endpoint" + ], + "isUnique": true + }, + "idx_push_subscriptions_user_id": { + "name": "idx_push_subscriptions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "push_subscription_user_id_user_id_fk": { + "name": "push_subscription_user_id_user_id_fk", + "tableFrom": "push_subscription", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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_at": { + "name": "expires_at", + "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_at": { + "name": "start_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_at": { + "name": "end_at", + "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": "'accepted'" + } + }, + "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 + }, + "alt_email": { + "name": "alt_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "is_deleted": { + "name": "is_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_trained": { + "name": "is_trained", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "can_refer": { + "name": "can_refer", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "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": {} + }, + "producers": { + "name": "producers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image_id": { + "name": "image_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "product_types": { + "name": "product_types", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "products": { + "name": "products", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sku": { + "name": "sku", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_sold_out": { + "name": "is_sold_out", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "ordinary_price": { + "name": "ordinary_price", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "student_price": { + "name": "student_price", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "internal_price": { + "name": "internal_price", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credits": { + "name": "credits", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "volume": { + "name": "volume", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "alcohol_content": { + "name": "alcohol_content", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "variants": { + "name": "variants", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image_id": { + "name": "image_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "producer_id": { + "name": "producer_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "products_producer_id_producers_id_fk": { + "name": "products_producer_id_producers_id_fk", + "tableFrom": "products", + "tableTo": "producers", + "columnsFrom": [ + "producer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "product_product_types": { + "name": "product_product_types", + "columns": { + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "product_type_id": { + "name": "product_type_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "product_product_types_product_id_products_id_fk": { + "name": "product_product_types_product_id_products_id_fk", + "tableFrom": "product_product_types", + "tableTo": "products", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "product_product_types_product_type_id_product_types_id_fk": { + "name": "product_product_types_product_type_id_product_types_id_fk", + "tableFrom": "product_product_types", + "tableTo": "product_types", + "columnsFrom": [ + "product_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "images": { + "name": "images", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "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": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "referral": { + "name": "referral", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "referred_by": { + "name": "referred_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "referred": { + "name": "referred", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "referral_referred_by_user_id_fk": { + "name": "referral_referred_by_user_id_fk", + "tableFrom": "referral", + "tableTo": "user", + "columnsFrom": [ + "referred_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "referral_referred_user_id_fk": { + "name": "referral_referred_user_id_fk", + "tableFrom": "referral", + "tableTo": "user", + "columnsFrom": [ + "referred" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "pending_application": { + "name": "pending_application", + "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": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "pending_application_email_idx": { + "name": "pending_application_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "pending_application_feide_id_idx": { + "name": "pending_application_feide_id_idx", + "columns": [ + "feide_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/programmerbar-web/migrations/meta/_journal.json b/programmerbar-web/migrations/meta/_journal.json index fc4e8dba..cfe1c291 100644 --- a/programmerbar-web/migrations/meta/_journal.json +++ b/programmerbar-web/migrations/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1761506366435, "tag": "0025_early_wither", "breakpoints": true + }, + { + "idx": 26, + "version": "6", + "when": 1788816908576, + "tag": "0026_rare_skrulls", + "breakpoints": true } ] } \ No newline at end of file diff --git a/programmerbar-web/src/lib/api/remotes/events.remote.ts b/programmerbar-web/src/lib/api/remotes/events.remote.ts index f3c62c9c..b8970d20 100644 --- a/programmerbar-web/src/lib/api/remotes/events.remote.ts +++ b/programmerbar-web/src/lib/api/remotes/events.remote.ts @@ -7,7 +7,7 @@ import { command, getRequestEvent } from '$app/server'; export const createEvent = command(CreateEventSchema, async (event) => { const { locals, platform } = getRequestEvent(); - if (!locals.user) { + if (locals.user?.role !== 'board') { return { success: false, message: 'Unauthorized' @@ -16,6 +16,8 @@ export const createEvent = command(CreateEventSchema, async (event) => { const { name, date, slug, description, shifts } = event; + await locals.eventService.assertActiveVolunteers(shifts.flatMap((shift) => shift.users)); + const createdEvent = await locals.eventService.create(name, date, slug, description); if (!createdEvent) { return { diff --git a/programmerbar-web/src/lib/server/db/schemas/users.ts b/programmerbar-web/src/lib/server/db/schemas/users.ts index 479255a0..7378fe2f 100644 --- a/programmerbar-web/src/lib/server/db/schemas/users.ts +++ b/programmerbar-web/src/lib/server/db/schemas/users.ts @@ -18,6 +18,7 @@ export const users = sqliteTable( .default('normal'), additionalBeers: integer().default(0).notNull(), altEmail: text(), + isActive: integer({ mode: 'boolean' }).default(true).notNull(), isDeleted: integer({ mode: 'boolean' }).default(false).notNull(), phone: text(), isTrained: integer({ mode: 'boolean' }).default(false).notNull(), diff --git a/programmerbar-web/src/lib/server/services/event.service.test.ts b/programmerbar-web/src/lib/server/services/event.service.test.ts new file mode 100644 index 00000000..09f8dca7 --- /dev/null +++ b/programmerbar-web/src/lib/server/services/event.service.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { Database } from '../db/drizzle'; + +vi.mock('$lib/server/db/schemas', () => ({ events: {}, shifts: {}, userShifts: {} })); + +import { EventService } from './event.service'; + +describe('volunteer assignments', () => { + function setup(eligible: { id: string }[]) { + const values = vi.fn().mockResolvedValue(undefined); + const insert = vi.fn().mockReturnValue({ values }); + const findMany = vi.fn().mockResolvedValue(eligible); + const db = { query: { users: { findMany } }, insert } as unknown as Database; + return { service: new EventService(db), insert, values, findMany }; + } + + it('rejects the entire batch if a volunteer is inactive, deleted or missing', async () => { + const { service, insert } = setup([{ id: 'active' }]); + await expect( + service.createUserShifts([ + { userId: 'active', shiftId: 'shift' }, + { userId: 'inactive', shiftId: 'shift' } + ]) + ).rejects.toMatchObject({ status: 400 }); + expect(insert).not.toHaveBeenCalled(); + }); + + it('blocks self-registration for an ineligible volunteer', async () => { + const { service, insert } = setup([]); + await expect( + service.createUserShift({ userId: 'inactive', shiftId: 'shift', status: 'accepted' }) + ).rejects.toMatchObject({ status: 400 }); + expect(insert).not.toHaveBeenCalled(); + }); + + it('allows the same active volunteer on multiple shifts', async () => { + const { service, values } = setup([{ id: 'active' }]); + const assignments = [ + { userId: 'active', shiftId: 'one' }, + { userId: 'active', shiftId: 'two' } + ]; + await service.createUserShifts(assignments); + expect(values).toHaveBeenCalledWith(assignments); + }); + + it('allows empty shifts without querying users', async () => { + const { service, findMany, insert } = setup([]); + await service.createUserShifts([]); + expect(findMany).not.toHaveBeenCalled(); + expect(insert).not.toHaveBeenCalled(); + }); +}); diff --git a/programmerbar-web/src/lib/server/services/event.service.ts b/programmerbar-web/src/lib/server/services/event.service.ts index 8ff6cf35..14072eb7 100644 --- a/programmerbar-web/src/lib/server/services/event.service.ts +++ b/programmerbar-web/src/lib/server/services/event.service.ts @@ -6,6 +6,7 @@ import { type ShiftInsert, type UserShiftInsert } from '$lib/server/db/schemas'; +import { error } from '@sveltejs/kit'; import { and, eq } from 'drizzle-orm'; export class EventService { @@ -40,11 +41,26 @@ export class EventService { return createdShifts; } + async assertActiveVolunteers(userIds: string[]) { + const ids = [...new Set(userIds)]; + if (ids.length === 0) return; + const eligible = await this.#db.query.users.findMany({ + where: (row, { and, inArray, eq, not }) => + and(inArray(row.id, ids), eq(row.isActive, true), not(row.isDeleted)), + columns: { id: true } + }); + if (eligible.length !== ids.length) { + error(400, 'Bare aktive frivillige kan legges til på vakter.'); + } + } + async createUserShifts(values: Array) { if (values.length === 0) { return; } + await this.assertActiveVolunteers(values.map((value) => value.userId)); + const result = await this.#db.insert(userShifts).values(values); return result; } diff --git a/programmerbar-web/src/lib/server/services/user.service.ts b/programmerbar-web/src/lib/server/services/user.service.ts index 0b319d79..8e8240e1 100644 --- a/programmerbar-web/src/lib/server/services/user.service.ts +++ b/programmerbar-web/src/lib/server/services/user.service.ts @@ -61,6 +61,20 @@ export class UserService { return users; } + async findAllActiveVolunteers() { + return await this.#db.query.users.findMany({ + where: (row, { and, eq, not }) => and(eq(row.isActive, true), not(row.isDeleted)) + }); + } + + async updateActiveStatus(userId: string, isActive: boolean) { + return await this.#db + .update(users) + .set({ isActive }) + .where(and(eq(users.id, userId), not(users.isDeleted))) + .returning(); + } + async updateUserRole(userId: string, role: 'board' | 'normal') { const updatedUser = await this.#db .update(users) diff --git a/programmerbar-web/src/routes/(portal)/portal/admin/+page.svelte b/programmerbar-web/src/routes/(portal)/portal/admin/+page.svelte index c8349b41..049dd233 100644 --- a/programmerbar-web/src/routes/(portal)/portal/admin/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/admin/+page.svelte @@ -221,7 +221,7 @@
{@render trainingStatus(user)} - {user.role === 'board' ? 'Styret' : 'Frivillig'} + {!user.isActive ? 'Inaktiv' : user.role === 'board' ? 'Styret' : 'Frivillig'} - {user.role === 'board' ? 'Styret' : 'Frivillig'} + {!user.isActive ? 'Inaktiv' : user.role === 'board' ? 'Styret' : 'Frivillig'} diff --git a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/+page.svelte b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/+page.svelte index db9c699f..8ee8bc30 100644 --- a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/+page.svelte @@ -86,7 +86,7 @@ {user.name}
- {user.role === 'board' ? 'Styret' : 'Frivillig'} + {!user.isActive ? 'Inaktiv' : user.role === 'board' ? 'Styret' : 'Frivillig'}
@@ -135,7 +135,7 @@
Rolle
- {user.role === 'board' ? 'Styret' : 'Frivillig'} + {!user.isActive ? 'Inaktiv' : user.role === 'board' ? 'Styret' : 'Frivillig'}
diff --git a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts index c60b18df..3cf7c065 100644 --- a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts +++ b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts @@ -1,4 +1,5 @@ -import { error, redirect } from '@sveltejs/kit'; +import { z } from 'zod'; +import { error, fail, redirect } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; export const load: PageServerLoad = async ({ params, locals }) => { @@ -30,6 +31,8 @@ export const actions: Actions = { const userId = params.id; const formData = await request.formData(); + const activeStatus = z.enum(['true', 'false']).safeParse(formData.get('isActive')); + if (!activeStatus.success) return fail(400, { message: 'Ugyldig frivilligstatus' }); const role = formData.get('role')?.toString() as 'board' | 'normal'; const phone = formData.get('phone')?.toString(); const canRefer = formData.get('canRefer') === 'true'; @@ -40,6 +43,7 @@ export const actions: Actions = { if (role) { await locals.userService.updateUserRole(userId, role); } + await locals.userService.updateActiveStatus(userId, activeStatus.data === 'true'); await locals.userService.updateCanRefer(userId, canRefer); throw redirect(303, `/portal/admin/bruker/${userId}`); diff --git a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte index f230ec16..d0416b04 100644 --- a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte @@ -15,12 +15,14 @@ let user = $derived(data.user as User); let editForm = $state({ + isActive: user.isActive, role: user.role, phone: user.phone || '', canRefer: user.canRefer ?? true }); $effect(() => { + editForm.isActive = data.user.isActive; editForm.role = data.user.role; editForm.phone = data.user.phone || ''; editForm.canRefer = data.user.canRefer ?? true; @@ -76,7 +78,7 @@ {user.name}
- {user.role === 'board' ? 'Styret' : 'Frivillig'} + {!user.isActive ? 'Inaktiv' : user.role === 'board' ? 'Styret' : 'Frivillig'}
@@ -176,6 +178,18 @@ +
+ + +

+ Inaktive kan fortsatt logge inn og bruke bongene sine, men kan ikke legges til på nye + vakter. +

+
+
diff --git a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte index 6f374f4b..528ebf43 100644 --- a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte @@ -250,7 +250,7 @@

- {#if !isPastEvent} + {#if !isPastEvent && (isInShift || user.current?.isActive)}
{#if !isInShift}
diff --git a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.server.ts b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.server.ts index a6c65bf5..cb856d02 100644 --- a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.server.ts +++ b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.server.ts @@ -8,7 +8,7 @@ export const load: PageServerLoad = async ({ locals, params }) => { throw error(404, 'Event not found'); } - const users = await locals.userService.findAll().then((users) => + const users = await locals.userService.findAllActiveVolunteers().then((users) => users.map((user) => ({ label: user.name, value: user.id @@ -39,6 +39,23 @@ export const actions: Actions = { const formData = await request.formData(); const eventId = params.id; + const originalEvent = await locals.eventService.findFullEventById(eventId); + if (!originalEvent) return fail(404, { message: 'Event not found' }); + const addedUserIds: string[] = []; + const count = Number(formData.get('shiftsCount') || 0); + for (let i = 0; i < count; i++) { + const shiftId = formData.get(`shift[${i}].id`)?.toString(); + const existing = originalEvent.shifts.find((shift) => shift.id === shiftId); + const userCount = Number(formData.get(`shift[${i}].userCount`) || 0); + for (let j = 0; j < userCount; j++) { + const userId = formData.get(`shift[${i}].user[${j}].id`)?.toString(); + if (userId?.trim() && !existing?.members.some((member) => member.user.id === userId)) { + addedUserIds.push(userId); + } + } + } + await locals.eventService.assertActiveVolunteers(addedUserIds); + const shouldBePublic = formData.get('shouldBePublic') === 'true'; await locals.eventService.updateEvent(params.id, { diff --git a/programmerbar-web/src/routes/(portal)/portal/arrangementer/ny/+page.server.ts b/programmerbar-web/src/routes/(portal)/portal/arrangementer/ny/+page.server.ts index efec3a64..5779c00c 100644 --- a/programmerbar-web/src/routes/(portal)/portal/arrangementer/ny/+page.server.ts +++ b/programmerbar-web/src/routes/(portal)/portal/arrangementer/ny/+page.server.ts @@ -5,7 +5,7 @@ export const load: PageServerLoad = async ({ locals }) => { if (!locals.user || locals.user.role !== 'board') { throw redirect(303, '/portal'); } - const users = await locals.userService.findAll().then((users) => + const users = await locals.userService.findAllActiveVolunteers().then((users) => users.map((user) => ({ label: user.name, value: user.id diff --git a/programmerbar-web/src/routes/(portal)/portal/frivillige/+page.server.ts b/programmerbar-web/src/routes/(portal)/portal/frivillige/+page.server.ts index d2a69340..d20f5d78 100644 --- a/programmerbar-web/src/routes/(portal)/portal/frivillige/+page.server.ts +++ b/programmerbar-web/src/routes/(portal)/portal/frivillige/+page.server.ts @@ -3,7 +3,7 @@ import type { Actions, PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ locals }) => { const [users, invitations] = await Promise.all([ - locals.userService.findAll(), + locals.userService.findAllActiveVolunteers(), locals.invitationService.findAllUnused() ]); From ef9d5c778a963d5ea6bcc75e85d1710503eb8a1b Mon Sep 17 00:00:00 2001 From: FluffyFire <62662486+sturla1511@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:34:14 +0200 Subject: [PATCH 2/4] added UserBadge --- .../components/portal/PortalSidebar.svelte | 3 +- .../lib/components/portal/UserBadge.svelte | 10 +++++ .../src/lib/components/portal/UserCard.svelte | 15 +------ .../src/lib/utils/user-presentation.ts | 33 ++++++++++++++ .../routes/(portal)/portal/admin/+page.svelte | 16 +++---- .../portal/admin/bruker/[id]/+page.svelte | 9 ++-- .../admin/bruker/[id]/rediger/+page.svelte | 44 +++++-------------- .../(portal)/portal/profil/+page.svelte | 9 ++-- .../portal/profil/rediger/+page.svelte | 6 +-- 9 files changed, 70 insertions(+), 75 deletions(-) create mode 100644 programmerbar-web/src/lib/components/portal/UserBadge.svelte create mode 100644 programmerbar-web/src/lib/utils/user-presentation.ts diff --git a/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte b/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte index ab50c059..7217cbfb 100644 --- a/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte +++ b/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte @@ -1,4 +1,5 @@ + +{presentation.label} diff --git a/programmerbar-web/src/lib/components/portal/UserCard.svelte b/programmerbar-web/src/lib/components/portal/UserCard.svelte index 766d8caa..983402a9 100644 --- a/programmerbar-web/src/lib/components/portal/UserCard.svelte +++ b/programmerbar-web/src/lib/components/portal/UserCard.svelte @@ -1,4 +1,5 @@ diff --git a/programmerbar-web/src/lib/server/db/schemas/users.ts b/programmerbar-web/src/lib/server/db/schemas/users.ts index 7378fe2f..7ff67a38 100644 --- a/programmerbar-web/src/lib/server/db/schemas/users.ts +++ b/programmerbar-web/src/lib/server/db/schemas/users.ts @@ -13,12 +13,11 @@ export const users = sqliteTable( name: text().notNull(), email: text().notNull(), feideId: text(), - role: text({ enum: ['board', 'normal'] }) + role: text({ enum: ['board', 'normal', 'inactive'] }) .notNull() .default('normal'), additionalBeers: integer().default(0).notNull(), altEmail: text(), - isActive: integer({ mode: 'boolean' }).default(true).notNull(), isDeleted: integer({ mode: 'boolean' }).default(false).notNull(), phone: text(), isTrained: integer({ mode: 'boolean' }).default(false).notNull(), diff --git a/programmerbar-web/src/lib/server/services/event.service.ts b/programmerbar-web/src/lib/server/services/event.service.ts index af856895..de983504 100644 --- a/programmerbar-web/src/lib/server/services/event.service.ts +++ b/programmerbar-web/src/lib/server/services/event.service.ts @@ -45,8 +45,8 @@ export class EventService { const ids = [...new Set(userIds)]; if (ids.length === 0) return; const eligible = await this.#db.query.users.findMany({ - where: (row, { and, inArray, eq, not }) => - and(inArray(row.id, ids), eq(row.isActive, true), not(row.isDeleted)), + where: (row, { and, inArray, ne, not }) => + and(inArray(row.id, ids), ne(row.role, 'inactive'), not(row.isDeleted)), columns: { id: true } }); if (eligible.length !== ids.length) { diff --git a/programmerbar-web/src/lib/server/services/user.service.ts b/programmerbar-web/src/lib/server/services/user.service.ts index 8e8240e1..7b3851cf 100644 --- a/programmerbar-web/src/lib/server/services/user.service.ts +++ b/programmerbar-web/src/lib/server/services/user.service.ts @@ -1,6 +1,6 @@ import type { Database } from '$lib/server/db/drizzle'; import { eq, and, not, inArray, or, sql } from 'drizzle-orm'; -import { users, type UserInsert } from '$lib/server/db/schemas'; +import { users, type User, type UserInsert } from '$lib/server/db/schemas'; export class UserService { #db: Database; @@ -63,19 +63,11 @@ export class UserService { async findAllActiveVolunteers() { return await this.#db.query.users.findMany({ - where: (row, { and, eq, not }) => and(eq(row.isActive, true), not(row.isDeleted)) + where: (row, { and, ne, not }) => and(ne(row.role, 'inactive'), not(row.isDeleted)) }); } - async updateActiveStatus(userId: string, isActive: boolean) { - return await this.#db - .update(users) - .set({ isActive }) - .where(and(eq(users.id, userId), not(users.isDeleted))) - .returning(); - } - - async updateUserRole(userId: string, role: 'board' | 'normal') { + async updateUserRole(userId: string, role: User['role']) { const updatedUser = await this.#db .update(users) .set({ role }) diff --git a/programmerbar-web/src/lib/utils/user-presentation.ts b/programmerbar-web/src/lib/utils/user-presentation.ts index 129c20d5..210f9390 100644 --- a/programmerbar-web/src/lib/utils/user-presentation.ts +++ b/programmerbar-web/src/lib/utils/user-presentation.ts @@ -9,14 +9,10 @@ type Presentation = { export const USER_ROLES = { normal: { label: 'Frivillig', pluralLabel: 'Frivillige', variant: 'blue' }, - board: { label: 'Styret', pluralLabel: 'Styret', variant: 'purple' } + board: { label: 'Styret', pluralLabel: 'Styret', variant: 'purple' }, + inactive: { label: 'Inaktiv', pluralLabel: 'Inaktive', variant: 'gray' } } as const satisfies Record; -export const VOLUNTEER_STATUS = { - active: { label: 'Aktiv frivillig', variant: 'green' }, - inactive: { label: 'Inaktiv', variant: 'gray' } -} as const satisfies Record<'active' | 'inactive', Presentation>; - export const USER_ROLE_OPTIONS = Object.entries(USER_ROLES).map(([value, role]) => ({ value: value as User['role'], label: role.label @@ -27,7 +23,6 @@ export const USER_ROLE_FILTER_OPTIONS = USER_ROLE_OPTIONS.map(({ value }) => ({ label: USER_ROLES[value].pluralLabel })); -/** Inactive status takes precedence over the role in user summaries. */ -export function getUserPresentation(user: Pick): Presentation { - return user.isActive ? USER_ROLES[user.role] : VOLUNTEER_STATUS.inactive; +export function getUserPresentation(user: Pick): Presentation { + return USER_ROLES[user.role]; } diff --git a/programmerbar-web/src/lib/validators.ts b/programmerbar-web/src/lib/validators.ts index 84cf4c2b..9720313a 100644 --- a/programmerbar-web/src/lib/validators.ts +++ b/programmerbar-web/src/lib/validators.ts @@ -1,6 +1,8 @@ import { z } from 'zod'; import { zfd } from 'zod-form-data'; +export const UserRoleSchema = z.enum(['board', 'normal', 'inactive']); + export const CreateEventSchema = z.object({ name: z.string(), date: z.coerce.date(), diff --git a/programmerbar-web/src/routes/(portal)/portal/admin/+page.server.ts b/programmerbar-web/src/routes/(portal)/portal/admin/+page.server.ts index 27ba19d6..33e21909 100644 --- a/programmerbar-web/src/routes/(portal)/portal/admin/+page.server.ts +++ b/programmerbar-web/src/routes/(portal)/portal/admin/+page.server.ts @@ -1,3 +1,4 @@ +import { UserRoleSchema } from '$lib/validators'; import { isTrainingComplete } from '$lib/utils/training'; import { redirect, fail, type Actions } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; @@ -47,9 +48,9 @@ export const actions: Actions = { updateRole: async ({ request, locals }) => { const formData = await request.formData(); const userId = formData.get('userId') as string; - const role = formData.get('role'); + const role = UserRoleSchema.safeParse(formData.get('role')); - if (typeof role !== 'string' || (role !== 'board' && role !== 'normal')) { + if (!role.success) { return fail(400, { error: 'Invalid role specified' }); } @@ -57,7 +58,7 @@ export const actions: Actions = { return fail(401, { error: 'Unauthorized' }); } - const success = await locals.userService.updateUserRole(userId, role); + const success = await locals.userService.updateUserRole(userId, role.data); if (success) { return { success: true }; diff --git a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts index 3cf7c065..cc7bcf51 100644 --- a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts +++ b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.server.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import { UserRoleSchema } from '$lib/validators'; import { error, fail, redirect } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; @@ -31,19 +31,15 @@ export const actions: Actions = { const userId = params.id; const formData = await request.formData(); - const activeStatus = z.enum(['true', 'false']).safeParse(formData.get('isActive')); - if (!activeStatus.success) return fail(400, { message: 'Ugyldig frivilligstatus' }); - const role = formData.get('role')?.toString() as 'board' | 'normal'; + const role = UserRoleSchema.safeParse(formData.get('role')); + if (!role.success) return fail(400, { message: 'Ugyldig rolle' }); const phone = formData.get('phone')?.toString(); const canRefer = formData.get('canRefer') === 'true'; if (phone) { await locals.userService.updatePhone(userId, phone); } - if (role) { - await locals.userService.updateUserRole(userId, role); - } - await locals.userService.updateActiveStatus(userId, activeStatus.data === 'true'); + await locals.userService.updateUserRole(userId, role.data); await locals.userService.updateCanRefer(userId, canRefer); throw redirect(303, `/portal/admin/bruker/${userId}`); diff --git a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte index ed8dfc8a..e103a932 100644 --- a/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/admin/bruker/[id]/rediger/+page.svelte @@ -1,5 +1,5 @@