diff --git a/programmerbar-web/migrations/0030_groovy_paper_doll.sql b/programmerbar-web/migrations/0030_groovy_paper_doll.sql new file mode 100644 index 00000000..e3ca55cd --- /dev/null +++ b/programmerbar-web/migrations/0030_groovy_paper_doll.sql @@ -0,0 +1,10 @@ +CREATE TABLE `unavailability` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text NOT NULL, + `start_at` integer NOT NULL, + `end_at` integer NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade, + CONSTRAINT "unavailability_valid_interval" CHECK("unavailability"."end_at" > "unavailability"."start_at") +); +--> statement-breakpoint +CREATE INDEX `unavailability_user_id_idx` ON `unavailability` (`user_id`); \ No newline at end of file diff --git a/programmerbar-web/migrations/0031_spicy_gauntlet.sql b/programmerbar-web/migrations/0031_spicy_gauntlet.sql new file mode 100644 index 00000000..1bd4066f --- /dev/null +++ b/programmerbar-web/migrations/0031_spicy_gauntlet.sql @@ -0,0 +1,2 @@ +ALTER TABLE `unavailability` ADD `group_id` text;--> statement-breakpoint +ALTER TABLE `unavailability` ADD `group_label` text; \ No newline at end of file diff --git a/programmerbar-web/migrations/meta/0029_snapshot.json b/programmerbar-web/migrations/meta/0029_snapshot.json index 11930225..c42fe16a 100644 --- a/programmerbar-web/migrations/meta/0029_snapshot.json +++ b/programmerbar-web/migrations/meta/0029_snapshot.json @@ -2,7 +2,7 @@ "version": "6", "dialect": "sqlite", "id": "7cf34021-def4-4271-a8d3-ee7ef8f93136", - "prevId": "e982dbc1-a960-4bbf-ad49-b48bdc24d6d8", + "prevId": "b72667c7-1e6f-4cd5-9ec8-32b41ec5a89b", "tables": { "claimed_credits": { "name": "claimed_credits", @@ -1205,4 +1205,4 @@ "internal": { "indexes": {} } -} \ No newline at end of file +} diff --git a/programmerbar-web/migrations/meta/0030_snapshot.json b/programmerbar-web/migrations/meta/0030_snapshot.json new file mode 100644 index 00000000..4f3b5815 --- /dev/null +++ b/programmerbar-web/migrations/meta/0030_snapshot.json @@ -0,0 +1,1273 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "dd124260-ab89-4465-837c-ce3db2fd07ba", + "prevId": "7cf34021-def4-4271-a8d3-ee7ef8f93136", + "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": {} + }, + "board_member": { + "name": "board_member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "membership": { + "name": "membership", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "roles": { + "name": "roles", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": {}, + "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": { + "user_shift_user_id_shift_id_unique": { + "name": "user_shift_user_id_shift_id_unique", + "columns": [ + "user_id", + "shift_id" + ], + "isUnique": true + } + }, + "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": {} + }, + "unavailability": { + "name": "unavailability", + "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 + }, + "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": { + "unavailability_user_id_idx": { + "name": "unavailability_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "unavailability_user_id_user_id_fk": { + "name": "unavailability_user_id_user_id_fk", + "tableFrom": "unavailability", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "unavailability_valid_interval": { + "name": "unavailability_valid_interval", + "value": "\"unavailability\".\"end_at\" > \"unavailability\".\"start_at\"" + } + } + }, + "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_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/0031_snapshot.json b/programmerbar-web/migrations/meta/0031_snapshot.json new file mode 100644 index 00000000..2dd7aa80 --- /dev/null +++ b/programmerbar-web/migrations/meta/0031_snapshot.json @@ -0,0 +1,1287 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "81478e77-e4c8-48bb-9da0-ba00db7ddf81", + "prevId": "dd124260-ab89-4465-837c-ce3db2fd07ba", + "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": {} + }, + "board_member": { + "name": "board_member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "membership": { + "name": "membership", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "roles": { + "name": "roles", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": {}, + "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": { + "user_shift_user_id_shift_id_unique": { + "name": "user_shift_user_id_shift_id_unique", + "columns": [ + "user_id", + "shift_id" + ], + "isUnique": true + } + }, + "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": {} + }, + "unavailability": { + "name": "unavailability", + "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 + }, + "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 + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_label": { + "name": "group_label", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "unavailability_user_id_idx": { + "name": "unavailability_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "unavailability_user_id_user_id_fk": { + "name": "unavailability_user_id_user_id_fk", + "tableFrom": "unavailability", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "unavailability_valid_interval": { + "name": "unavailability_valid_interval", + "value": "\"unavailability\".\"end_at\" > \"unavailability\".\"start_at\"" + } + } + }, + "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_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 f146bdab..ea334b5a 100644 --- a/programmerbar-web/migrations/meta/_journal.json +++ b/programmerbar-web/migrations/meta/_journal.json @@ -203,6 +203,7 @@ "version": "6", "when": 1789667294738, "tag": "0028_tiresome_mister_fear", + "breakpoints": true }, { "idx": 29, @@ -210,6 +211,20 @@ "when": 1789753789061, "tag": "0029_dear_mattie_franklin", "breakpoints": true + }, + { + "idx": 30, + "version": "6", + "when": 1790965848085, + "tag": "0030_groovy_paper_doll", + "breakpoints": true + }, + { + "idx": 31, + "version": "6", + "when": 1791060339063, + "tag": "0031_spicy_gauntlet", + "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 b8970d20..96816fe3 100644 --- a/programmerbar-web/src/lib/api/remotes/events.remote.ts +++ b/programmerbar-web/src/lib/api/remotes/events.remote.ts @@ -3,6 +3,7 @@ import type { ShiftEmailProps } from '$lib/server/services/email.service'; import { normalDate } from '$lib/utils/date'; import { z } from 'zod'; import { command, getRequestEvent } from '$app/server'; +import { isHttpError } from '@sveltejs/kit'; export const createEvent = command(CreateEventSchema, async (event) => { const { locals, platform } = getRequestEvent(); @@ -17,6 +18,18 @@ export const createEvent = command(CreateEventSchema, async (event) => { const { name, date, slug, description, shifts } = event; await locals.eventService.assertActiveVolunteers(shifts.flatMap((shift) => shift.users)); + try { + await locals.shiftService.assertAvailable( + shifts.map((shift) => ({ + startAt: new Date(shift.startAt), + endAt: new Date(shift.endAt), + users: shift.users + })) + ); + } catch (cause) { + if (isHttpError(cause)) return { success: false, message: cause.body.message }; + throw cause; + } const createdEvent = await locals.eventService.create(name, date, slug, description); if (!createdEvent) { diff --git a/programmerbar-web/src/lib/availability-periods.test.ts b/programmerbar-web/src/lib/availability-periods.test.ts new file mode 100644 index 00000000..615ec107 --- /dev/null +++ b/programmerbar-web/src/lib/availability-periods.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, it } from 'vitest'; +import { + recurringAvailabilitySchema, + recurringPeriods, + dateRangeSchema, + wholeDayPeriod +} from './availability-periods'; +import { toLocalDateTimeString } from './utils/date'; + +describe('repeating availability', () => { + it('includes both date boundaries and permits a single selected day', () => { + const input = { startDate: '2026-09-01', endDate: '2026-09-01', weekdays: [2] }; + expect(recurringAvailabilitySchema.safeParse(input).success).toBe(true); + const periods = recurringPeriods(input, new Date('2026-01-01')); + expect(periods).toHaveLength(1); + expect(toLocalDateTimeString(periods[0].endAt)).toBe('2026-09-02T00:00'); + expect(recurringPeriods({ ...input, weekdays: [3] }, new Date('2026-01-01'))).toEqual([]); + }); + it('creates every Tuesday and Wednesday for one calendar month', () => { + const periods = recurringPeriods( + { startDate: '2026-09-01', endDate: '2026-09-30', weekdays: [2, 3] }, + new Date('2026-09-01T00:00Z') + ); + expect(periods).toHaveLength(10); + expect(toLocalDateTimeString(periods[0].startAt)).toBe('2026-09-01T00:00'); + expect(toLocalDateTimeString(periods.at(-1)!.startAt)).toBe('2026-09-30T00:00'); + }); + it('supports custom date ranges and ignores duplicate weekdays', () => { + const periods = recurringPeriods( + { startDate: '2026-01-31', endDate: '2026-03-30', weekdays: [2, 2] }, + new Date('2026-01-01') + ); + expect(periods).toHaveLength(8); + expect(toLocalDateTimeString(periods.at(-1)!.startAt)).toBe('2026-03-24T00:00'); + const february = recurringPeriods( + { startDate: '2026-01-31', endDate: '2026-02-27', weekdays: [6] }, + new Date('2026-01-01') + ); + expect(toLocalDateTimeString(february.at(-1)!.startAt)).toBe('2026-02-21T00:00'); + }); + it('keeps local midnights across both daylight saving transitions', () => { + for (const [startDate, expectedHours] of [ + ['2026-03-29', 23], + ['2026-10-25', 25] + ] as const) { + const [period] = recurringPeriods( + { startDate, endDate: startDate, weekdays: [0] }, + new Date('2026-01-01') + ); + expect(toLocalDateTimeString(period.startAt)).toBe(`${startDate}T00:00`); + expect((period.endAt.getTime() - period.startAt.getTime()) / 3600000).toBe(expectedHours); + } + }); + it('omits expired occurrences', () => { + const periods = recurringPeriods( + { startDate: '2026-09-01', endDate: '2026-09-30', weekdays: [2] }, + new Date('2026-09-16') + ); + expect(periods).toHaveLength(2); + }); + it('validates dates, weekday selection and bounded duration', () => { + for (const change of [ + { weekdays: [] }, + { weekdays: [7] }, + { endDate: '2026-08-31' }, + { endDate: '2028-01-01' }, + { endDate: 'bad' }, + { startDate: '2026-02-30' } + ]) { + expect( + recurringAvailabilitySchema.safeParse({ + startDate: '2026-09-01', + endDate: '2026-09-30', + weekdays: [2], + ...change + }).success + ).toBe(false); + } + expect(dateRangeSchema.safeParse({ startDate: 'bad', endDate: '2026-09-30' }).success).toBe( + false + ); + }); +}); + +describe('whole day date ranges', () => { + it('includes the end date even across years', () => { + const period = wholeDayPeriod('2026-12-28', '2027-01-03'); + expect(toLocalDateTimeString(period.startAt)).toBe('2026-12-28T00:00'); + expect(toLocalDateTimeString(period.endAt)).toBe('2027-01-04T00:00'); + }); + it('handles a daylight saving week as seven local days', () => { + const period = wholeDayPeriod('2026-03-23', '2026-03-29'); + expect(toLocalDateTimeString(period.startAt)).toBe('2026-03-23T00:00'); + expect(toLocalDateTimeString(period.endAt)).toBe('2026-03-30T00:00'); + expect((period.endAt.getTime() - period.startAt.getTime()) / 3600000).toBe(167); + }); +}); diff --git a/programmerbar-web/src/lib/availability-periods.ts b/programmerbar-web/src/lib/availability-periods.ts new file mode 100644 index 00000000..bdd2195c --- /dev/null +++ b/programmerbar-web/src/lib/availability-periods.ts @@ -0,0 +1,62 @@ +import { z } from 'zod'; +import { parseDateTimeLocal, toLocalDateTimeString } from './utils/date'; +import type { Interval } from './shift-planning'; + +const calendarDate = z + .string() + .regex(/^\d{4}-\d{2}-\d{2}$/) + .refine((value) => { + const date = new Date(`${value}T00:00:00Z`); + return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value; + }, 'Velg en gyldig dato.'); + +export const dateRangeSchema = z + .object({ + startDate: calendarDate, + endDate: calendarDate + }) + .refine( + (value) => value.endDate >= value.startDate, + 'Til-dato må være lik eller etter fra-dato.' + ); + +export const recurringAvailabilitySchema = dateRangeSchema + .safeExtend({ + weekdays: z.array(z.coerce.number().int().min(0).max(6)).min(1, 'Velg minst én ukedag.').max(7) + }) + .refine( + (value) => + new Date(value.endDate).getTime() - new Date(value.startDate).getTime() < 366 * 86400000, + 'Velg maksimalt ett år om gangen.' + ); + +// UTC dates are calendar cursors only. Each boundary is converted separately to Oslo time, +// so a whole day remains midnight-to-midnight across daylight saving changes. +const localMidnight = (date: Date) => + parseDateTimeLocal(`${date.toISOString().slice(0, 10)}T00:00`); +const nextDay = (date: Date) => new Date(date.getTime() + 24 * 60 * 60 * 1000); + +export function recurringPeriods( + input: z.infer, + now: Date +): Interval[] { + let cursor = new Date(`${input.startDate}T00:00:00Z`); + const end = new Date(`${input.endDate}T00:00:00Z`); + const periods: Interval[] = []; + while (cursor <= end) { + const tomorrow = nextDay(cursor); + const period = { startAt: localMidnight(cursor), endAt: localMidnight(tomorrow) }; + if (input.weekdays.includes(cursor.getUTCDay()) && period.endAt > now) periods.push(period); + cursor = tomorrow; + } + return periods; +} + +export function wholeDayPeriod(startDate: string, endDate: string): Interval { + return { + startAt: localMidnight(new Date(`${startDate}T00:00:00Z`)), + endAt: localMidnight(nextDay(new Date(`${endDate}T00:00:00Z`))) + }; +} + +export const todayInOslo = (now: Date) => toLocalDateTimeString(now).slice(0, 10); diff --git a/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte b/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte index 7217cbfb..b1954379 100644 --- a/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte +++ b/programmerbar-web/src/lib/components/portal/PortalSidebar.svelte @@ -45,6 +45,7 @@ // Main portal navigation items const portalRoutes = [ + { name: 'Når jeg ikke kan stå', routeId: '/(portal)/portal/fravaer', icon: Calendar }, { name: 'Hjem', routeId: '/(portal)/portal', @@ -71,6 +72,7 @@ const adminRoutes = $derived( user.current?.role === 'board' ? ([ + { name: 'Vaktoversikt', routeId: '/(portal)/portal/admin/vaktoversikt', icon: Users }, { name: 'Status', routeId: '/(portal)/portal/status', diff --git a/programmerbar-web/src/lib/components/portal/ShiftRecommendations.svelte b/programmerbar-web/src/lib/components/portal/ShiftRecommendations.svelte new file mode 100644 index 00000000..a595ff05 --- /dev/null +++ b/programmerbar-web/src/lib/components/portal/ShiftRecommendations.svelte @@ -0,0 +1,165 @@ + + +
+
+

Anbefalte frivillige

+ + + + + + + Færrest fullførte vakter prioriteres først. Ved likt antall kommer den med lengst + avstand til nærmeste vakt først, også kommende vakter. Er begge deler likt, trekkes + rekkefølgen tilfeldig når siden lastes. Bare vakter i samme semester teller. Styret + velges manuelt. Vår: januar–juni. Høst: juli–desember. + + + + +
+ {#if !valid} +

Velg start og slutt for å se anbefalinger.

+ {:else} +

+ {semesterFor(target.startAt).label} +

+ +
+ {#each recommended as user (user.id)} +
+
+

{user.name}

+

+ {user.completed} fullført · {user.upcoming} kommende · Siste: {user.lastShift + ? formatDate(user.lastShift) + : 'Ingen'} +

+ {#if user.recent}

Har vakt innenfor 14 dager av denne vakten

{/if} +
+ +
+ {:else}

Ingen tilgjengelige frivillige som passer søket.

{/each} +
+
+ Styret og utilgjengelige ({others.length}) + {#each others as user (user.id)} +
+
+

{user.name}{user.role === 'board' ? ' · Styret (valgfritt)' : ''}

+ {#if user.unavailable}

Kan ikke stå i denne perioden

{/if} + {#if user.busy}

Overlappende vakt

{/if} +
+ +
+ {/each} +
+ {#each ranked.filter((user) => (user.unavailable || user.busy) && drafts[index].users.some((selected) => selected.id === user.id)) as user (user.id)} + + {/each} + {/if} +
diff --git a/programmerbar-web/src/lib/components/styles/button-variant.ts b/programmerbar-web/src/lib/components/styles/button-variant.ts index 0dc254dd..186643fe 100644 --- a/programmerbar-web/src/lib/components/styles/button-variant.ts +++ b/programmerbar-web/src/lib/components/styles/button-variant.ts @@ -1,7 +1,7 @@ import { cva, type VariantProps } from 'class-variance-authority'; export const buttonVariant = cva( - 'flex items-center justify-center border rounded-lg font-medium transition-all ', + 'flex cursor-pointer items-center justify-center border rounded-lg font-medium transition-all disabled:cursor-not-allowed aria-disabled:cursor-not-allowed ', { variants: { intent: { diff --git a/programmerbar-web/src/lib/components/styles/checkbox.ts b/programmerbar-web/src/lib/components/styles/checkbox.ts new file mode 100644 index 00000000..87978bea --- /dev/null +++ b/programmerbar-web/src/lib/components/styles/checkbox.ts @@ -0,0 +1,2 @@ +export const checkboxClass = + 'border-input-border bg-input-background text-primary checked:border-primary checked:bg-primary focus-visible:ring-primary size-4 shrink-0 cursor-pointer rounded border-2 transition-colors focus:ring-0 focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-600 dark:bg-slate-800 dark:checked:border-primary dark:checked:bg-primary dark:focus-visible:ring-offset-slate-900'; diff --git a/programmerbar-web/src/lib/components/ui/Checkbox.svelte b/programmerbar-web/src/lib/components/ui/Checkbox.svelte index 56d4ac92..01754747 100644 --- a/programmerbar-web/src/lib/components/ui/Checkbox.svelte +++ b/programmerbar-web/src/lib/components/ui/Checkbox.svelte @@ -1,6 +1,7 @@ + +Vaktoversikt +
+
+
+
+
+
+

+ {data.semester.label} +

+

+ Viser {filtered.length} av {data.users.length} personer +

+
+
+ + +
+
+
+ + +
+
+ + + + + {#each columns as column (column.key)} + + {/each} + + + + {#each filtered as user (user.id)} + + + + + + + + {:else} + + {/each} + +
Vaktfordeling for {data.semester.label}
+ +
+ {user.name} + {#if user.role !== 'normal'}{user.role === 'board' ? 'Styret' : 'Inaktiv'}{/if} + {user.completed}{user.upcoming}{user.lastShift ? normalDate(user.lastShift) : 'Ingen'} + {#if user.absences.length} +
+ {user.absences.length} + {user.absences.length === 1 ? 'periode' : 'perioder'} +
    + {#each user.absences as absence (absence.id)}
  • + {normalDate(absence.startAt)} – {normalDate(absence.endAt)} +
  • {/each} +
+
+ {:else}Ingen{/if} +
Ingen frivillige som passer søket. Prøv et annet navn eller endre filtrene.
+
+
+ Slik telles vaktene +

+ Vårsemesteret er januar–juni, og høstsemesteret er juli–desember. Vaktens start avgjør + semesteret. Fullførte vakter er aksepterte vakter med passert sluttid; oppmøte registreres + ikke separat. Kommende inkluderer pågående vakter. +

+
+
+
diff --git a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.server.ts b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.server.ts index eeab1dba..dd4cf4c7 100644 --- a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.server.ts +++ b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.server.ts @@ -1,4 +1,4 @@ -import { error, fail, redirect } from '@sveltejs/kit'; +import { error, fail, redirect, isHttpError } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; import type { EventService } from '$lib/server/services/event.service'; @@ -85,6 +85,22 @@ export const actions: Actions = { }); } + const targetEvent = await locals.eventService.findFullEventById(params.id); + const targetShift = targetEvent?.shifts.find((shift) => shift.id === shiftId); + if (!targetShift || targetShift.startAt <= new Date()) + return fail(400, { message: 'Vakten finnes ikke eller har allerede startet.' }); + if ( + targetShift.members.some( + (member) => member.userId === locals.user!.id && member.status === 'accepted' + ) + ) + return { success: true }; + try { + await locals.shiftService.assertAvailable([{ ...targetShift, users: [locals.user.id] }]); + } catch (cause) { + if (isHttpError(cause)) return fail(cause.status, { message: cause.body.message }); + throw cause; + } const created = await locals.eventService.createUserShift({ shiftId, userId: locals.user.id, 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 e02df974..9b4ddac9 100644 --- a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/+page.svelte @@ -10,7 +10,7 @@ import ButtonLink from '$lib/components/ui/ButtonLink.svelte'; import { cn } from '$lib/utils/cn'; - let { data } = $props(); + let { data, form } = $props(); let user = getUser(); let activeTab = $state('details'); let isPastEvent = $derived(new Date() > new Date(data.event.date)); @@ -24,6 +24,7 @@
+ {#if form?.message}{/if}
{ + if (locals.user?.role !== 'board') error(403, 'Du har ikke tilgang.'); const event = await locals.eventService.findFullEventById(params.id); if (!event) { throw error(404, 'Event not found'); @@ -16,6 +19,7 @@ export const load: PageServerLoad = async ({ locals, params }) => { ); return { + planning: await locals.shiftService.planningData(params.id), event, users }; @@ -43,18 +47,49 @@ export const actions: Actions = { if (!originalEvent) return fail(404, { message: 'Event not found' }); const addedUserIds: string[] = []; const count = Number(formData.get('shiftsCount') || 0); + if (!Number.isInteger(count) || count < 0 || count > 100) + return fail(400, { message: 'Ugyldig antall vakter.' }); + const planned: PlannedShift[] = []; for (let i = 0; i < count; i++) { const shiftId = formData.get(`shift[${i}].id`)?.toString(); const existing = originalEvent.shifts.find((shift) => shift.id === shiftId); + if (shiftId && !existing) + return fail(400, { message: 'Vakten tilhører ikke arrangementet.' }); + const period = availabilitySchema.safeParse({ + startAt: formData.get(`shift[${i}].startAt`), + endAt: formData.get(`shift[${i}].endAt`) + }); + if (!period.success) + return fail(400, { message: 'Velg gyldig start og slutt for alle vaktene.' }); + const plannedShift: PlannedShift = { ...period.data, users: [] }; const userCount = Number(formData.get(`shift[${i}].userCount`) || 0); + if (!Number.isInteger(userCount) || userCount < 0 || userCount > 100) + return fail(400, { message: 'Ugyldig antall frivillige.' }); for (let j = 0; j < userCount; j++) { const userId = formData.get(`shift[${i}].user[${j}].id`)?.toString(); + if (userId?.trim()) plannedShift.users.push(userId); if (userId?.trim() && !existing?.members.some((member) => member.user.id === userId)) { addedUserIds.push(userId); } } + planned.push(plannedShift); } await locals.eventService.assertActiveVolunteers(addedUserIds); + try { + await locals.shiftService.assertAvailable(planned, eventId); + } catch (cause) { + if (isHttpError(cause)) return fail(cause.status, { message: cause.body.message }); + throw cause; + } + const ownedShiftIds = new Set(originalEvent.shifts.map((shift) => shift.id)); + if ( + formData.getAll('deletedShiftIds').some((id) => !ownedShiftIds.has(String(id))) || + formData + .getAll('removedUserShifts') + .some((value) => !ownedShiftIds.has(String(value).split('|')[0])) + ) { + return fail(400, { message: 'Vakten tilhører ikke arrangementet.' }); + } const shouldBePublic = formData.get('shouldBePublic') === 'true'; diff --git a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.svelte b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.svelte index f3b495ca..03d99dac 100644 --- a/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.svelte +++ b/programmerbar-web/src/routes/(portal)/portal/arrangementer/[id]/rediger/+page.svelte @@ -1,4 +1,5 @@ + +Når jeg ikke kan stå +
+ Når jeg ikke kan stå +

+ Velg datoene du ikke kan stå. Har du allerede en vakt, må du kontakte frivilligansvarlig for å + bytte. +

+ {#if form?.message}

{form.message}

{/if} +
{ + saving = true; + return async ({ update }) => { + try { + await update({ reset: false }); + } finally { + saving = false; + } + }; + }} + class="bg-portal-card space-y-5 rounded-lg border p-5" + > +
+ Registrer når du ikke kan stå +
+ + +
+
+ + +
+ {#if mode === 'recurring'} +
+ Hvilke ukedager? +
+ {#each days as day (day.value)} + + {/each} +
+
+

Gjelder hele dagen på valgte ukedager, til og med sluttdatoen.

+ {#if startDate && endDate && weekdays.length && !selection.success} +

{selection.error.issues[0]?.message}

+ {:else if selection.success && !preview.length} +

Ingen av de valgte ukedagene finnes i perioden.

+ {/if} + {#if preview.length} +
+ {preview.length} dager valgt – se datoene +
    + {#each preview as period (period.startAt.getTime())}
  • + {formatDate(period.startAt)} +
  • {/each} +
+
+ {/if} + {:else} + + + {#if !allDay} +
+ + +
+

+ Fra klokkeslettet på startdatoen til klokkeslettet på sluttdatoen. Norsk tid. +

+ {:else} +

Alle dager i perioden tas med, også sluttdatoen.

+ {/if} + {/if} + +
+
+ Registrerte perioder + {#if selected.length} +
+ {#each selected as id (id)}{/each} + + +
+ {/if} + {#each data.groups as group (group.key)} + {#if group.groupId} +
+
+
+

{group.label ?? 'Faste ukedager'}

+

+ {group.periods.length} dager registrert +

+
+
+ + +
+
+
+ Vis dager / fjern enkeltdager +
    + {#each group.periods as absence (absence.id)} +
  • + {formatDate(absence.startAt)} +
    + + +
    +
  • + {/each} +
+
+
+ {:else} + {@const absence = group.periods[0]} +
+ +
+ + +
+
+ {/if} + {:else}

Du har ikke registrert noen perioder du ikke kan stå.

{/each} +
diff --git a/programmerbar-web/src/tailwind.css b/programmerbar-web/src/tailwind.css index 6de203cd..d0f71fc9 100644 --- a/programmerbar-web/src/tailwind.css +++ b/programmerbar-web/src/tailwind.css @@ -7,6 +7,24 @@ @custom-variant dark (&:where(.dark, .dark *)); +@layer base { + #portal-layout + :is( + button, + select, + summary, + [role='button'], + input[type='checkbox'], + input[type='radio'], + input[type='submit'] + ) { + cursor: pointer; + } + #portal-layout :is(:disabled, [aria-disabled='true']) { + cursor: not-allowed; + } +} + :root { --background: #f5f5f5; --foreground: #262626;