diff --git a/README.md b/README.md index 6510851..9d446f2 100644 --- a/README.md +++ b/README.md @@ -76,13 +76,14 @@ Megjegyzések: Jelszavak: `oob/local-secrets.json` (gitignore-olt). -| Felhasználó | Szerep | -| --------------------- | --------------------------------------------------------------------- | -| (nincs bejelentkezés) | névtelen látogató — csak publikus videók | -| schonherz-dev | bejelentkezett schönherzes — publikus + schönherz videók, admin nincs | -| tag-dev | BSS-tag — minden láthatóság, adminjogok | -| vezetoseg-dev | vezetőség — kibővített adminjogok | -| további `-dev` userek | szintetikus tagprofilok minden tagsági státuszhoz | +| Felhasználó | Szerep | +| --------------------- | --------------------------------------------------------------- | +| (nincs bejelentkezés) | névtelen látogató — csak publikus videók | +| schonherz-dev | csoport nélküli felhasználó — csak publikus videók, admin nincs | +| tag-dev | BSS-tag — minden láthatóság, adminjogok | +| vezetoseg-dev | vezetőség — kibővített adminjogok | +| admin-dev | admin — kibővített adminjogok | +| további `-dev` userek | szintetikus tagprofilok minden tagsági státuszhoz | ## Demo forgatókönyvek szereplőnként @@ -95,10 +96,9 @@ Jelszavak: `oob/local-secrets.json` (gitignore-olt). szűrős videólista. 4. A navbar Belépés gombja belépésre visz, megtartva a kért oldalt. -### Schönherzes (schonherz-dev) +### Csoport nélküli felhasználó (schonherz-dev) -1. Jelentkezz be; a `/videos` listában már a schönherz láthatóságú videók is - megjelennek. +1. Jelentkezz be; továbbra is csak a publikus videók jelennek meg. 2. `/admin` közvetlenül: magyar tiltóoldal (403), sidebar sem látszik. ### BSS-tag (tag-dev) diff --git a/docs/api/members-webhook.openapi.yaml b/docs/api/members-webhook.openapi.yaml index 0033d37..15ed0c3 100644 --- a/docs/api/members-webhook.openapi.yaml +++ b/docs/api/members-webhook.openapi.yaml @@ -23,7 +23,7 @@ paths: one transaction, so either every one of them takes effect or none does. The same `sub` may appear only once per request. - `delete` is a soft delete: the member disappears from the public + `archive` hides the member from the public surfaces, but their row and the roles recorded in credit lists are kept, and a later `upsert` restores them. security: @@ -52,7 +52,7 @@ paths: - "$ref": "#/components/schemas/ReplacePayload" examples: operations: - summary: "Operations (create and delete in one request)" + summary: "Operations (create and archive in one request)" value: operations: - op: "upsert" @@ -62,10 +62,10 @@ paths: fullName: "Gipsz Jakab" nickname: "Pitypang" avatarUrl: null - membershipStatus: "studio_member" + membershipStatus: "MEMBER" isLeadership: false joinedSemester: "2021/2022/1" - - op: "delete" + - op: "archive" sub: "57" replace: summary: "Full roster replacement" @@ -77,7 +77,7 @@ paths: fullName: "Gipsz Jakab" nickname: "Pitypang" avatarUrl: null - membershipStatus: "studio_member" + membershipStatus: "MEMBER" isLeadership: false joinedSemester: "2021/2022/1" responses: @@ -178,15 +178,14 @@ components: membershipStatus: type: "string" enum: - - "studio_member" - - "studio_candidate" - - "studio_applicant" - - "senior_active" - - "senior_archived" - - "contributor" + - "MEMBER_CANDIDATE_CANDIDATE" + - "MEMBER_CANDIDATE" + - "MEMBER" + - "ACTIVE_ALUMNI" + - "ALUMNI" description: "Membership status. It decides which public list the member appears on; it grants NO permissions." examples: - - "studio_member" + - "MEMBER" isLeadership: type: "boolean" default: false @@ -207,7 +206,7 @@ components: fullName: "Gipsz Jakab" nickname: "Pitypang" avatarUrl: null - membershipStatus: "studio_member" + membershipStatus: "MEMBER" isLeadership: false joinedSemester: "2021/2022/1" UpsertOperation: @@ -221,7 +220,7 @@ components: const: "upsert" member: "$ref": "#/components/schemas/Member" - DeleteOperation: + ArchiveOperation: type: "object" required: - "op" @@ -229,11 +228,11 @@ components: properties: op: type: "string" - const: "delete" + const: "archive" sub: type: "string" maxLength: 255 - description: "The `sub` of the member to delete. An unknown `sub` is not an error, it is simply ignored." + description: "The `sub` of the member to archive. An unknown `sub` is not an error, it is simply ignored." OperationsPayload: type: "object" required: @@ -250,14 +249,14 @@ components: items: oneOf: - "$ref": "#/components/schemas/UpsertOperation" - - "$ref": "#/components/schemas/DeleteOperation" + - "$ref": "#/components/schemas/ArchiveOperation" ReplacePayload: type: "object" required: - "mode" - "members" description: |- - The full member roster. Anything left out of the list gets deleted — + The full member roster. Anything left out of the list gets archived — a faulty client can empty the entire roster this way. properties: mode: @@ -274,7 +273,7 @@ components: - "operationCount" - "created" - "updated" - - "deleted" + - "archived" - "restored" - "unchanged" - "ignored" @@ -290,18 +289,18 @@ components: type: "integer" updated: type: "integer" - deleted: + archived: type: "integer" - description: "Soft-deleted members." + description: "Archived members." restored: type: "integer" - description: "Members previously deleted and now restored." + description: "Members previously archived and now restored." unchanged: type: "integer" description: "Unchanged members; no audit row is written for these." ignored: type: "integer" - description: "Deletes targeting an unknown `sub`." + description: "Archives targeting an unknown `sub`." PushResponse: type: "object" required: diff --git a/docs/examples/oob-config.example.json b/docs/examples/oob-config.example.json index 8c10199..979944d 100644 --- a/docs/examples/oob-config.example.json +++ b/docs/examples/oob-config.example.json @@ -5,16 +5,15 @@ "clientSecret": "TÖLTSD-KI-titok-nem-kerülhet-gitbe", "scopes": ["openid", "profile", "email"], "claims": { - "sub": "sub", - "username": "preferred_username", - "fullName": "name", - "nickname": "nickname", - "avatarUrl": "picture" + "sub": "sub" }, "groups": { - "schonherz": "TÖLTSD-KI-schönherzes-csoport-neve", - "tag": "TÖLTSD-KI-tag-csoport-neve", - "vezetoseg": "TÖLTSD-KI-vezetőség-csoport-neve" + "admin": "Admin", + "studio": "Stúdiós", + "studioCandidate": "Stúdiós jelölt", + "studioCandidateCandidate": "Stúdiós jelölt-jelölt", + "leadership": "Vezetőség", + "alumni": "Öregtag" } }, "youtube": { diff --git a/docs/oob-inputs.md b/docs/oob-inputs.md index 54d8af3..9085657 100644 --- a/docs/oob-inputs.md +++ b/docs/oob-inputs.md @@ -9,8 +9,8 @@ Az alkalmazás három bemenete out-of-band, azaz nem a git repóban érkezik. Hi - Formája: JSON, a szerkezetét a [`docs/examples/oob-config.example.json`](./examples/oob-config.example.json) mutatja. - Tartalma: - Authentik OIDC kapcsolat: issuer URL, client id, client secret, scope-ok; - - claim-leképezés: `sub`, felhasználónév, teljes név, becenév, profilkép URL; - - csoportleképezés: schönherzes, tag és vezetőség csoport neve; + - claim-leképezés: az Authentik felhasználót a taggal összekötő `sub`; + - csoportleképezés: Admin, Stúdiós, Stúdiós jelölt, Stúdiós jelölt-jelölt, Vezetőség és Öregtag; - médiahost (`media.host`, jelenleg `https://v.bsstudio.hu`); - YouTube oEmbed végpont; - seed JSON helye. diff --git a/drizzle/20260831195115_cool_violations/migration.sql b/drizzle/20260831195115_cool_violations/migration.sql new file mode 100644 index 0000000..5516a37 --- /dev/null +++ b/drizzle/20260831195115_cool_violations/migration.sql @@ -0,0 +1,14 @@ +ALTER TABLE "member_cache" ALTER COLUMN "membership_status" SET DATA TYPE text;--> statement-breakpoint +UPDATE "member_cache" +SET "membership_status" = CASE "membership_status" + WHEN 'studio_applicant' THEN 'MEMBER_CANDIDATE_CANDIDATE' + WHEN 'studio_candidate' THEN 'MEMBER_CANDIDATE' + WHEN 'studio_member' THEN 'MEMBER' + WHEN 'senior_active' THEN 'ACTIVE_ALUMNI' + WHEN 'senior_archived' THEN 'ALUMNI' + WHEN 'contributor' THEN 'ALUMNI' + ELSE "membership_status" +END;--> statement-breakpoint +DROP TYPE "membership_status";--> statement-breakpoint +CREATE TYPE "membership_status" AS ENUM('MEMBER_CANDIDATE_CANDIDATE', 'MEMBER_CANDIDATE', 'MEMBER', 'ACTIVE_ALUMNI', 'ALUMNI');--> statement-breakpoint +ALTER TABLE "member_cache" ALTER COLUMN "membership_status" SET DATA TYPE "membership_status" USING "membership_status"::"membership_status"; diff --git a/drizzle/20260831195115_cool_violations/snapshot.json b/drizzle/20260831195115_cool_violations/snapshot.json new file mode 100644 index 0000000..089e179 --- /dev/null +++ b/drizzle/20260831195115_cool_violations/snapshot.json @@ -0,0 +1,2832 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "ba614b12-0c00-43b8-97ff-e62f8a34231f", + "prevIds": ["6bf6ee70-6c6a-4fd6-8205-d183a8c3a47e"], + "ddl": [ + { + "values": ["draft", "published", "archived", "trash"], + "name": "content_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["draft", "published", "archived"], + "name": "event_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["scheduled", "active", "ended"], + "name": "live_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "MEMBER_CANDIDATE_CANDIDATE", + "MEMBER_CANDIDATE", + "MEMBER", + "ACTIVE_ALUMNI", + "ALUMNI" + ], + "name": "membership_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["spring", "autumn"], + "name": "semester", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["video", "event"], + "name": "slug_entity_type", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["4a3_SD", "16a9_SD", "16a9_HD"], + "name": "video_encoding_group", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["public", "schonherz", "bss"], + "name": "visibility", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["operations", "replace"], + "name": "webhook_delivery_mode", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["ok", "rejected", "duplicate"], + "name": "webhook_delivery_status", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "about_page_videos", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "audit_log", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "auth_sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "events", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "live_streams", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "member_cache", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "related_videos", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "site_settings", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "slug_history", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "staff_roles", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tags", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "video_staff", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "video_tags", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "videos", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "view_sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "webhook_clients", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "webhook_deliveries", + "entityType": "tables", + "schema": "public" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "position", + "entityType": "columns", + "schema": "public", + "table": "about_page_videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "about_page_videos" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "actor", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "before_value", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "after_value", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "occurred_at", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_sub", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "groups", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(10000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "date", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "start_date", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "date", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "end_date", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "event_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'draft'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "version", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "youtube_video_id", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "starts_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ends_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "live_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'scheduled'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "activation_error", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "activated_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ended_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "sub", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "full_name", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nickname", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatar_url", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "membership_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "membership_status", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_leadership", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "joined_year", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "semester", + "typeSchema": "public", + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "joined_semester", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(10000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "introduction", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_at", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "related_videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "related_video_id", + "entityType": "columns", + "schema": "public", + "table": "related_videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "position", + "entityType": "columns", + "schema": "public", + "table": "related_videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "site_settings" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "highlighted_video_id", + "entityType": "columns", + "schema": "public", + "table": "site_settings" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "site_settings" + }, + { + "type": "slug_entity_type", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "normalized_name", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "display_order", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "normalized_name", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "video_staff" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role_id", + "entityType": "columns", + "schema": "public", + "table": "video_staff" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_sub", + "entityType": "columns", + "schema": "public", + "table": "video_staff" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "video_tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tag_id", + "entityType": "columns", + "schema": "public", + "table": "video_tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(10000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(5000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "guests", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(5000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "songs", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "video_encoding_group", + "typeSchema": "public", + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encoding_group", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "has_hq", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "has_lq", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "base_filename", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "visibility", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'public'", + "generated": null, + "identity": null, + "name": "visibility", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "content_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'draft'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "event_id", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "date", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recorded_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "published_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "view_count", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "version", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trashed_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trashed_by", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "view_sessions" + }, + { + "type": "varchar(128)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "session_id", + "entityType": "columns", + "schema": "public", + "table": "view_sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "viewed_at", + "entityType": "columns", + "schema": "public", + "table": "view_sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret_hash", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_used_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "revoked_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "client_id", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "delivery_id", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "webhook_delivery_mode", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'operations'", + "generated": null, + "identity": null, + "name": "mode", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "webhook_delivery_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "operation_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "created_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "updated_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "deleted_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "restored_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "received_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "video_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "about_page_videos_video_key", + "entityType": "indexes", + "schema": "public", + "table": "about_page_videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "occurred_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_occurred_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "entity_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_entity_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "actor", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_actor_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "action", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_action_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "auth_sessions_expires_idx", + "entityType": "indexes", + "schema": "public", + "table": "auth_sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "events_slug_key", + "entityType": "indexes", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "start_date", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "events_status_start_idx", + "entityType": "indexes", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "starts_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "live_streams_status_starts_idx", + "entityType": "indexes", + "schema": "public", + "table": "live_streams" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "username", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_cache_username_key", + "entityType": "indexes", + "schema": "public", + "table": "member_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "membership_status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_cache_status_idx", + "entityType": "indexes", + "schema": "public", + "table": "member_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "deleted_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_cache_deleted_idx", + "entityType": "indexes", + "schema": "public", + "table": "member_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "entity_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "slug_history_entity_idx", + "entityType": "indexes", + "schema": "public", + "table": "slug_history" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "normalized_name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "staff_roles_normalized_name_key", + "entityType": "indexes", + "schema": "public", + "table": "staff_roles" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "display_order", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "staff_roles_display_order_idx", + "entityType": "indexes", + "schema": "public", + "table": "staff_roles" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "normalized_name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tags_normalized_name_key", + "entityType": "indexes", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "member_sub", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "video_staff_member_idx", + "entityType": "indexes", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "role_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "video_staff_role_idx", + "entityType": "indexes", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tag_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "video_tags_tag_idx", + "entityType": "indexes", + "schema": "public", + "table": "video_tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_slug_key", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "visibility", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "published_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_visibility_status_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "event_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_event_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "recorded_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_recorded_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "trashed_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_trash_purge_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "viewed_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "view_sessions_viewed_idx", + "entityType": "indexes", + "schema": "public", + "table": "view_sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "webhook_clients_name_key", + "entityType": "indexes", + "schema": "public", + "table": "webhook_clients" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "received_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "webhook_deliveries_received_idx", + "entityType": "indexes", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "client_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "delivery_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "webhook_deliveries_delivery_key", + "entityType": "indexes", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "about_page_videos_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "about_page_videos" + }, + { + "nameExplicit": false, + "columns": ["created_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "events_created_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": false, + "columns": ["updated_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "events_updated_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": false, + "columns": ["created_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "live_streams_created_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "live_streams" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "related_videos_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "related_videos" + }, + { + "nameExplicit": false, + "columns": ["related_video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "related_videos_related_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "related_videos" + }, + { + "nameExplicit": false, + "columns": ["highlighted_video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "site_settings_highlighted_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "site_settings" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "video_staff_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": false, + "columns": ["role_id"], + "schemaTo": "public", + "tableTo": "staff_roles", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "video_staff_role_id_staff_roles_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": false, + "columns": ["member_sub"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "video_staff_member_sub_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "video_tags_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_tags" + }, + { + "nameExplicit": false, + "columns": ["tag_id"], + "schemaTo": "public", + "tableTo": "tags", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "video_tags_tag_id_tags_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_tags" + }, + { + "nameExplicit": false, + "columns": ["event_id"], + "schemaTo": "public", + "tableTo": "events", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "videos_event_id_events_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["created_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "videos_created_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["updated_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "videos_updated_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["trashed_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "videos_trashed_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "view_sessions_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "view_sessions" + }, + { + "nameExplicit": false, + "columns": ["client_id"], + "schemaTo": "public", + "tableTo": "webhook_clients", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "webhook_deliveries_client_id_webhook_clients_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "columns": ["position", "video_id"], + "nameExplicit": false, + "name": "about_page_videos_pkey", + "entityType": "pks", + "schema": "public", + "table": "about_page_videos" + }, + { + "columns": ["video_id", "related_video_id"], + "nameExplicit": false, + "name": "related_videos_pkey", + "entityType": "pks", + "schema": "public", + "table": "related_videos" + }, + { + "columns": ["entity_type", "slug"], + "nameExplicit": false, + "name": "slug_history_pkey", + "entityType": "pks", + "schema": "public", + "table": "slug_history" + }, + { + "columns": ["video_id", "role_id", "member_sub"], + "nameExplicit": false, + "name": "video_staff_pkey", + "entityType": "pks", + "schema": "public", + "table": "video_staff" + }, + { + "columns": ["video_id", "tag_id"], + "nameExplicit": false, + "name": "video_tags_pkey", + "entityType": "pks", + "schema": "public", + "table": "video_tags" + }, + { + "columns": ["video_id", "session_id"], + "nameExplicit": false, + "name": "view_sessions_pkey", + "entityType": "pks", + "schema": "public", + "table": "view_sessions" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "audit_log_pkey", + "schema": "public", + "table": "audit_log", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "auth_sessions_pkey", + "schema": "public", + "table": "auth_sessions", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "events_pkey", + "schema": "public", + "table": "events", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "live_streams_pkey", + "schema": "public", + "table": "live_streams", + "entityType": "pks" + }, + { + "columns": ["sub"], + "nameExplicit": false, + "name": "member_cache_pkey", + "schema": "public", + "table": "member_cache", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "site_settings_pkey", + "schema": "public", + "table": "site_settings", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "staff_roles_pkey", + "schema": "public", + "table": "staff_roles", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "tags_pkey", + "schema": "public", + "table": "tags", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "videos_pkey", + "schema": "public", + "table": "videos", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "webhook_clients_pkey", + "schema": "public", + "table": "webhook_clients", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "webhook_deliveries_pkey", + "schema": "public", + "table": "webhook_deliveries", + "entityType": "pks" + }, + { + "value": "\"position\" >= 1 and \"position\" <= 6", + "name": "about_page_videos_position_range_check", + "entityType": "checks", + "schema": "public", + "table": "about_page_videos" + }, + { + "value": "\"end_date\" is null or \"end_date\" >= \"start_date\"", + "name": "events_end_after_start_check", + "entityType": "checks", + "schema": "public", + "table": "events" + }, + { + "value": "\"ends_at\" > \"starts_at\"", + "name": "live_streams_end_after_start_check", + "entityType": "checks", + "schema": "public", + "table": "live_streams" + }, + { + "value": "\"video_id\" <> \"related_video_id\"", + "name": "related_videos_no_self_reference_check", + "entityType": "checks", + "schema": "public", + "table": "related_videos" + }, + { + "value": "\"status\" <> 'published' or \"published_at\" is not null", + "name": "videos_published_requires_timestamp_check", + "entityType": "checks", + "schema": "public", + "table": "videos" + }, + { + "value": "\"status\" <> 'trash' or \"trashed_at\" is not null", + "name": "videos_trash_needs_timestamp_check", + "entityType": "checks", + "schema": "public", + "table": "videos" + } + ], + "renames": [] +} diff --git a/drizzle/20260831203332_archive_members/migration.sql b/drizzle/20260831203332_archive_members/migration.sql new file mode 100644 index 0000000..def397d --- /dev/null +++ b/drizzle/20260831203332_archive_members/migration.sql @@ -0,0 +1,3 @@ +ALTER TABLE "member_cache" RENAME COLUMN "deleted_at" TO "archived_at";--> statement-breakpoint +ALTER TABLE "webhook_deliveries" RENAME COLUMN "deleted_count" TO "archived_count";--> statement-breakpoint +ALTER INDEX "member_cache_deleted_idx" RENAME TO "member_cache_archived_idx"; \ No newline at end of file diff --git a/drizzle/20260831203332_archive_members/snapshot.json b/drizzle/20260831203332_archive_members/snapshot.json new file mode 100644 index 0000000..5771f4a --- /dev/null +++ b/drizzle/20260831203332_archive_members/snapshot.json @@ -0,0 +1,2836 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "b3e0e537-5cc9-4a64-b0ce-98b31f9b0e47", + "prevIds": ["ba614b12-0c00-43b8-97ff-e62f8a34231f"], + "ddl": [ + { + "values": ["draft", "published", "archived", "trash"], + "name": "content_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["draft", "published", "archived"], + "name": "event_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["scheduled", "active", "ended"], + "name": "live_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "MEMBER_CANDIDATE_CANDIDATE", + "MEMBER_CANDIDATE", + "MEMBER", + "ACTIVE_ALUMNI", + "ALUMNI" + ], + "name": "membership_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["spring", "autumn"], + "name": "semester", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["video", "event"], + "name": "slug_entity_type", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["4a3_SD", "16a9_SD", "16a9_HD"], + "name": "video_encoding_group", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["public", "schonherz", "bss"], + "name": "visibility", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["operations", "replace"], + "name": "webhook_delivery_mode", + "entityType": "enums", + "schema": "public" + }, + { + "values": ["ok", "rejected", "duplicate"], + "name": "webhook_delivery_status", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "about_page_videos", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "audit_log", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "auth_sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "events", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "live_streams", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "member_cache", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "related_videos", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "site_settings", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "slug_history", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "staff_roles", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tags", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "video_staff", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "video_tags", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "videos", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "view_sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "webhook_clients", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "webhook_deliveries", + "entityType": "tables", + "schema": "public" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "position", + "entityType": "columns", + "schema": "public", + "table": "about_page_videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "about_page_videos" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "actor", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "before_value", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "after_value", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "occurred_at", + "entityType": "columns", + "schema": "public", + "table": "audit_log" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_sub", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "groups", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "auth_sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(10000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "date", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "start_date", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "date", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "end_date", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "event_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'draft'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "version", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "events" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "youtube_video_id", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "starts_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ends_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "live_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'scheduled'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "activation_error", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "activated_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ended_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "live_streams" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "sub", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "full_name", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nickname", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatar_url", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "membership_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "membership_status", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_leadership", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "joined_year", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "semester", + "typeSchema": "public", + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "joined_semester", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "varchar(10000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "introduction", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "archived_at", + "entityType": "columns", + "schema": "public", + "table": "member_cache" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "related_videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "related_video_id", + "entityType": "columns", + "schema": "public", + "table": "related_videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "position", + "entityType": "columns", + "schema": "public", + "table": "related_videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "site_settings" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "highlighted_video_id", + "entityType": "columns", + "schema": "public", + "table": "site_settings" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "site_settings" + }, + { + "type": "slug_entity_type", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "slug_history" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "normalized_name", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "display_order", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "staff_roles" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "normalized_name", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "video_staff" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role_id", + "entityType": "columns", + "schema": "public", + "table": "video_staff" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "member_sub", + "entityType": "columns", + "schema": "public", + "table": "video_staff" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "video_tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tag_id", + "entityType": "columns", + "schema": "public", + "table": "video_tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(10000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(5000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "guests", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(5000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "songs", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "video_encoding_group", + "typeSchema": "public", + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "encoding_group", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "has_hq", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "has_lq", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "base_filename", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "visibility", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'public'", + "generated": null, + "identity": null, + "name": "visibility", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "content_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'draft'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "event_id", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "date", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recorded_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "published_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "view_count", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "version", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trashed_at", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trashed_by", + "entityType": "columns", + "schema": "public", + "table": "videos" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "video_id", + "entityType": "columns", + "schema": "public", + "table": "view_sessions" + }, + { + "type": "varchar(128)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "session_id", + "entityType": "columns", + "schema": "public", + "table": "view_sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "viewed_at", + "entityType": "columns", + "schema": "public", + "table": "view_sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret_hash", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_used_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "revoked_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_clients" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "client_id", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "varchar(200)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "delivery_id", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "webhook_delivery_mode", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'operations'", + "generated": null, + "identity": null, + "name": "mode", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "webhook_delivery_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "operation_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "created_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "updated_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "archived_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "restored_count", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "received_at", + "entityType": "columns", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "video_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "about_page_videos_video_key", + "entityType": "indexes", + "schema": "public", + "table": "about_page_videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "occurred_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_occurred_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "entity_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_entity_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "actor", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_actor_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "action", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "audit_log_action_idx", + "entityType": "indexes", + "schema": "public", + "table": "audit_log" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "auth_sessions_expires_idx", + "entityType": "indexes", + "schema": "public", + "table": "auth_sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "events_slug_key", + "entityType": "indexes", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "start_date", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "events_status_start_idx", + "entityType": "indexes", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "starts_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "live_streams_status_starts_idx", + "entityType": "indexes", + "schema": "public", + "table": "live_streams" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "username", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_cache_username_key", + "entityType": "indexes", + "schema": "public", + "table": "member_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "membership_status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_cache_status_idx", + "entityType": "indexes", + "schema": "public", + "table": "member_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "archived_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "member_cache_archived_idx", + "entityType": "indexes", + "schema": "public", + "table": "member_cache" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "entity_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "slug_history_entity_idx", + "entityType": "indexes", + "schema": "public", + "table": "slug_history" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "normalized_name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "staff_roles_normalized_name_key", + "entityType": "indexes", + "schema": "public", + "table": "staff_roles" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "display_order", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "staff_roles_display_order_idx", + "entityType": "indexes", + "schema": "public", + "table": "staff_roles" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "normalized_name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tags_normalized_name_key", + "entityType": "indexes", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "member_sub", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "video_staff_member_idx", + "entityType": "indexes", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "role_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "video_staff_role_idx", + "entityType": "indexes", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tag_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "video_tags_tag_idx", + "entityType": "indexes", + "schema": "public", + "table": "video_tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_slug_key", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "visibility", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "published_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_visibility_status_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "event_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_event_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "recorded_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_recorded_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "trashed_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "videos_trash_purge_idx", + "entityType": "indexes", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "viewed_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "view_sessions_viewed_idx", + "entityType": "indexes", + "schema": "public", + "table": "view_sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "webhook_clients_name_key", + "entityType": "indexes", + "schema": "public", + "table": "webhook_clients" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "received_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "webhook_deliveries_received_idx", + "entityType": "indexes", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "client_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "delivery_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "webhook_deliveries_delivery_key", + "entityType": "indexes", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "about_page_videos_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "about_page_videos" + }, + { + "nameExplicit": false, + "columns": ["created_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "events_created_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": false, + "columns": ["updated_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "events_updated_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "events" + }, + { + "nameExplicit": false, + "columns": ["created_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "live_streams_created_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "live_streams" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "related_videos_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "related_videos" + }, + { + "nameExplicit": false, + "columns": ["related_video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "related_videos_related_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "related_videos" + }, + { + "nameExplicit": false, + "columns": ["highlighted_video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "site_settings_highlighted_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "site_settings" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "video_staff_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": false, + "columns": ["role_id"], + "schemaTo": "public", + "tableTo": "staff_roles", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "video_staff_role_id_staff_roles_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": false, + "columns": ["member_sub"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "video_staff_member_sub_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_staff" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "video_tags_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_tags" + }, + { + "nameExplicit": false, + "columns": ["tag_id"], + "schemaTo": "public", + "tableTo": "tags", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "video_tags_tag_id_tags_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "video_tags" + }, + { + "nameExplicit": false, + "columns": ["event_id"], + "schemaTo": "public", + "tableTo": "events", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "videos_event_id_events_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["created_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "videos_created_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["updated_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "videos_updated_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["trashed_by"], + "schemaTo": "public", + "tableTo": "member_cache", + "columnsTo": ["sub"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "videos_trashed_by_member_cache_sub_fkey", + "entityType": "fks", + "schema": "public", + "table": "videos" + }, + { + "nameExplicit": false, + "columns": ["video_id"], + "schemaTo": "public", + "tableTo": "videos", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "view_sessions_video_id_videos_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "view_sessions" + }, + { + "nameExplicit": false, + "columns": ["client_id"], + "schemaTo": "public", + "tableTo": "webhook_clients", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "webhook_deliveries_client_id_webhook_clients_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "webhook_deliveries" + }, + { + "columns": ["position", "video_id"], + "nameExplicit": false, + "name": "about_page_videos_pkey", + "entityType": "pks", + "schema": "public", + "table": "about_page_videos" + }, + { + "columns": ["video_id", "related_video_id"], + "nameExplicit": false, + "name": "related_videos_pkey", + "entityType": "pks", + "schema": "public", + "table": "related_videos" + }, + { + "columns": ["entity_type", "slug"], + "nameExplicit": false, + "name": "slug_history_pkey", + "entityType": "pks", + "schema": "public", + "table": "slug_history" + }, + { + "columns": ["video_id", "role_id", "member_sub"], + "nameExplicit": false, + "name": "video_staff_pkey", + "entityType": "pks", + "schema": "public", + "table": "video_staff" + }, + { + "columns": ["video_id", "tag_id"], + "nameExplicit": false, + "name": "video_tags_pkey", + "entityType": "pks", + "schema": "public", + "table": "video_tags" + }, + { + "columns": ["video_id", "session_id"], + "nameExplicit": false, + "name": "view_sessions_pkey", + "entityType": "pks", + "schema": "public", + "table": "view_sessions" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "audit_log_pkey", + "schema": "public", + "table": "audit_log", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "auth_sessions_pkey", + "schema": "public", + "table": "auth_sessions", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "events_pkey", + "schema": "public", + "table": "events", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "live_streams_pkey", + "schema": "public", + "table": "live_streams", + "entityType": "pks" + }, + { + "columns": ["sub"], + "nameExplicit": false, + "name": "member_cache_pkey", + "schema": "public", + "table": "member_cache", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "site_settings_pkey", + "schema": "public", + "table": "site_settings", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "staff_roles_pkey", + "schema": "public", + "table": "staff_roles", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "tags_pkey", + "schema": "public", + "table": "tags", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "videos_pkey", + "schema": "public", + "table": "videos", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "webhook_clients_pkey", + "schema": "public", + "table": "webhook_clients", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "webhook_deliveries_pkey", + "schema": "public", + "table": "webhook_deliveries", + "entityType": "pks" + }, + { + "value": "\"position\" >= 1 and \"position\" <= 6", + "name": "about_page_videos_position_range_check", + "entityType": "checks", + "schema": "public", + "table": "about_page_videos" + }, + { + "value": "\"end_date\" is null or \"end_date\" >= \"start_date\"", + "name": "events_end_after_start_check", + "entityType": "checks", + "schema": "public", + "table": "events" + }, + { + "value": "\"ends_at\" > \"starts_at\"", + "name": "live_streams_end_after_start_check", + "entityType": "checks", + "schema": "public", + "table": "live_streams" + }, + { + "value": "\"video_id\" <> \"related_video_id\"", + "name": "related_videos_no_self_reference_check", + "entityType": "checks", + "schema": "public", + "table": "related_videos" + }, + { + "value": "\"status\" <> 'published' or \"published_at\" is not null", + "name": "videos_published_requires_timestamp_check", + "entityType": "checks", + "schema": "public", + "table": "videos" + }, + { + "value": "\"status\" <> 'trash' or \"trashed_at\" is not null", + "name": "videos_trash_needs_timestamp_check", + "entityType": "checks", + "schema": "public", + "table": "videos" + } + ], + "renames": [ + "public.member_cache.deleted_at->public.member_cache.archived_at", + "public.webhook_deliveries.deleted_count->public.webhook_deliveries.archived_count", + "public.member_cache.member_cache_deleted_idx->public.member_cache.member_cache_archived_idx" + ] +} diff --git a/scripts/check-oob.ts b/scripts/check-oob.ts index 04c9d23..768e012 100644 --- a/scripts/check-oob.ts +++ b/scripts/check-oob.ts @@ -11,7 +11,7 @@ try { ) console.log(` Authentik issuer: ${config.authentik.issuerUrl}`) console.log( - ` Csoportok: schonherz=${config.authentik.groups.schonherz}, tag=${config.authentik.groups.tag}, vezetoseg=${config.authentik.groups.vezetoseg}`, + ` Csoportok: admin=${config.authentik.groups.admin}, stúdiós=${config.authentik.groups.studio}, jelölt=${config.authentik.groups.studioCandidate}, jelölt-jelölt=${config.authentik.groups.studioCandidateCandidate}, vezetőség=${config.authentik.groups.leadership}, öregtag=${config.authentik.groups.alumni}`, ) console.log(` Seed fájl helye: ${config.seed.path}`) } catch (error) { diff --git a/scripts/lib/local-bootstrap.ts b/scripts/lib/local-bootstrap.ts index e7c9c04..e9eeed6 100644 --- a/scripts/lib/local-bootstrap.ts +++ b/scripts/lib/local-bootstrap.ts @@ -36,12 +36,24 @@ interface LocalUserSpec { introduction: string | null } +const LOCAL_GROUPS = [ + { id: 'admin', name: 'Admin' }, + { id: 'studio', name: 'Stúdiós' }, + { id: 'studio-candidate', name: 'Stúdiós jelölt' }, + { + id: 'studio-candidate-candidate', + name: 'Stúdiós jelölt-jelölt', + }, + { id: 'leadership', name: 'Vezetőség' }, + { id: 'alumni', name: 'Öregtag' }, +] as const + export const LOCAL_USERS: LocalUserSpec[] = [ { username: 'schonherz-dev', fullName: 'Schönherzes Teszt Felhasználó', nickname: 'Schi', - groups: ['bss-schonherz'], + groups: [], status: null, joinedSemester: null, introduction: null, @@ -50,7 +62,7 @@ export const LOCAL_USERS: LocalUserSpec[] = [ username: 'tag-dev', fullName: 'Teszt BSS Tag', nickname: 'Tagocska', - groups: ['bss-tag'], + groups: ['Stúdiós'], status: 'stúdiós', joinedSemester: '2023/2024/1', introduction: 'Lokális teszt profil egy stúdiós szerepére.', @@ -59,16 +71,25 @@ export const LOCAL_USERS: LocalUserSpec[] = [ username: 'vezetoseg-dev', fullName: 'Teszt Vezetőségi Tag', nickname: 'Vezér', - groups: ['bss-tag', 'bss-vezetoseg'], + groups: ['Vezetőség'], status: 'stúdiós', joinedSemester: '2020/2021/2', introduction: 'Lokális teszt profil vezetőségi joggal.', }, + { + username: 'admin-dev', + fullName: 'Teszt Admin', + nickname: 'Admin', + groups: ['Admin'], + status: 'stúdiós', + joinedSemester: '2020/2021/2', + introduction: 'Lokális teszt profil admin joggal.', + }, { username: 'jelolt-dev', fullName: 'Teszt Stúdiósjelölt', nickname: 'Jelöltke', - groups: [], + groups: ['Stúdiós jelölt'], status: 'stúdiósjelölt', joinedSemester: '2025/2026/1', introduction: null, @@ -77,7 +98,7 @@ export const LOCAL_USERS: LocalUserSpec[] = [ username: 'jeloltjelolt-dev', fullName: 'Teszt Stúdiósjelölt-jelölt', nickname: 'Jelcsa', - groups: [], + groups: ['Stúdiós jelölt-jelölt'], status: 'stúdiósjelölt-jelölt', joinedSemester: '2026/2027/1', introduction: null, @@ -86,7 +107,7 @@ export const LOCAL_USERS: LocalUserSpec[] = [ username: 'oregtag-dev', fullName: 'Teszt Aktív Öregtag', nickname: 'Öreg', - groups: [], + groups: ['Öregtag'], status: 'aktív öregtag', joinedSemester: '2019/2020/1', introduction: null, @@ -95,20 +116,11 @@ export const LOCAL_USERS: LocalUserSpec[] = [ username: 'archivalt-oregtag-dev', fullName: 'Teszt Archivált Öregtag', nickname: 'Archie', - groups: [], + groups: ['Öregtag'], status: 'archivált öregtag', joinedSemester: '2018/2019/1', introduction: null, }, - { - username: 'kozremukodo-dev', - fullName: 'Teszt Korábbi Közreműködő', - nickname: 'Közreműködő', - groups: [], - status: 'dolgozott még velünk', - joinedSemester: '2019/2020/2', - introduction: null, - }, ] function generateToken(): string { @@ -134,7 +146,11 @@ export function renderBlueprint(secrets: LocalSecrets): string { if (user.groups.length > 0) { lines.push(' groups:') for (const group of user.groups) { - lines.push(` - !KeyOf id-${group}`) + const groupId = LOCAL_GROUPS.find((entry) => entry.name === group)?.id + if (groupId === undefined) { + throw new Error(`Ismeretlen lokális Authentik csoport: ${group}`) + } + lines.push(` - !KeyOf id-bss-${groupId}`) } } if ( @@ -241,27 +257,15 @@ export function renderBlueprint(secrets: LocalSecrets): string { ' order: 2', ' target: !KeyOf id-authn-flow', ' stage: !KeyOf id-stage-login', - ' - model: authentik_core.group', - ' id: id-bss-schonherz', - ' identifiers:', - ' name: bss-schonherz', - ' attrs:', - ' name: bss-schonherz', - ' is_superuser: false', - ' - model: authentik_core.group', - ' id: id-bss-tag', - ' identifiers:', - ' name: bss-tag', - ' attrs:', - ' name: bss-tag', - ' is_superuser: false', - ' - model: authentik_core.group', - ' id: id-bss-vezetoseg', - ' identifiers:', - ' name: bss-vezetoseg', - ' attrs:', - ' name: bss-vezetoseg', - ' is_superuser: false', + ...LOCAL_GROUPS.flatMap((group) => [ + ' - model: authentik_core.group', + ` id: id-bss-${group.id}`, + ' identifiers:', + ` name: ${group.name}`, + ' attrs:', + ` name: ${group.name}`, + ' is_superuser: false', + ]), userEntries, ' - model: authentik_providers_oauth2.scopemapping', ' id: id-map-profile', @@ -358,15 +362,14 @@ export function renderOobConfig(secrets: LocalSecrets): unknown { scopes: ['openid', 'profile', 'email', 'bss'], claims: { sub: 'sub', - username: 'preferred_username', - fullName: 'name', - nickname: 'nickname', - avatarUrl: 'picture', }, groups: { - schonherz: 'bss-schonherz', - tag: 'bss-tag', - vezetoseg: 'bss-vezetoseg', + admin: 'Admin', + studio: 'Stúdiós', + studioCandidate: 'Stúdiós jelölt', + studioCandidateCandidate: 'Stúdiós jelölt-jelölt', + leadership: 'Vezetőség', + alumni: 'Öregtag', }, }, youtube: { diff --git a/src/db/schema.ts b/src/db/schema.ts index e3bd56f..c0ec7ee 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -43,12 +43,11 @@ export const eventStatusEnum = pgEnum('event_status', [ ]) export const membershipStatusEnum = pgEnum('membership_status', [ - 'studio_member', - 'studio_candidate', - 'studio_applicant', - 'senior_active', - 'senior_archived', - 'contributor', + 'MEMBER_CANDIDATE_CANDIDATE', + 'MEMBER_CANDIDATE', + 'MEMBER', + 'ACTIVE_ALUMNI', + 'ALUMNI', ]) export const semesterEnum = pgEnum('semester', ['spring', 'autumn']) @@ -107,13 +106,13 @@ export const memberCache = pgTable( updatedAt: timestamp('updated_at', { withTimezone: true }) .notNull() .defaultNow(), - /** Soft delete: retired members keep their staff credits but leave every listing. */ - deletedAt: timestamp('deleted_at', { withTimezone: true }), + /** Archived members keep their staff credits but leave every listing. */ + archivedAt: timestamp('archived_at', { withTimezone: true }), }, (table) => [ uniqueIndex('member_cache_username_key').on(table.username), index('member_cache_status_idx').on(table.membershipStatus), - index('member_cache_deleted_idx').on(table.deletedAt), + index('member_cache_archived_idx').on(table.archivedAt), ], ) @@ -156,7 +155,7 @@ export const webhookDeliveries = pgTable( operationCount: integer('operation_count').notNull().default(0), createdCount: integer('created_count').notNull().default(0), updatedCount: integer('updated_count').notNull().default(0), - deletedCount: integer('deleted_count').notNull().default(0), + archivedCount: integer('archived_count').notNull().default(0), restoredCount: integer('restored_count').notNull().default(0), message: text('message'), receivedAt: timestamp('received_at', { withTimezone: true }) diff --git a/src/lib/admin-labels.ts b/src/lib/admin-labels.ts index cf4328a..bf4973a 100644 --- a/src/lib/admin-labels.ts +++ b/src/lib/admin-labels.ts @@ -21,12 +21,11 @@ export const VISIBILITY_LABELS: Record = { } export const MEMBERSHIP_STATUS_LABELS: Record = { - studio_member: 'Stúdiós', - studio_candidate: 'Stúdiósjelölt', - studio_applicant: 'Stúdiósjelölt-jelölt', - senior_active: 'Aktív öregtag', - senior_archived: 'Archivált öregtag', - contributor: 'Dolgozott még velünk', + MEMBER_CANDIDATE_CANDIDATE: 'Stúdiósjelölt-jelölt', + MEMBER_CANDIDATE: 'Stúdiósjelölt', + MEMBER: 'Stúdiós', + ACTIVE_ALUMNI: 'Aktív öregtag', + ALUMNI: 'Öregtag', } /** diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 9e2c33d..1a3413b 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -24,7 +24,6 @@ import { Route as EventsSlugRouteImport } from './routes/events/$slug' import { Route as MembersIndexRouteImport } from './routes/members/index' import { Route as MembersSlugRouteImport } from './routes/members/$slug' import { Route as MembersArchivedRouteImport } from './routes/members/archived' -import { Route as MembersContributorsRouteImport } from './routes/members/contributors' import { Route as VideosIndexRouteImport } from './routes/videos/index' import { Route as VideosSlugRouteImport } from './routes/videos/$slug' import { Route as AdminCatalogStaffRolesRouteImport } from './routes/admin/catalog/staff-roles' @@ -111,11 +110,6 @@ const MembersArchivedRoute = MembersArchivedRouteImport.update({ path: '/members/archived', getParentRoute: () => rootRouteImport, } as any) -const MembersContributorsRoute = MembersContributorsRouteImport.update({ - id: '/members/contributors', - path: '/members/contributors', - getParentRoute: () => rootRouteImport, -} as any) const VideosIndexRoute = VideosIndexRouteImport.update({ id: '/videos/', path: '/videos/', @@ -180,7 +174,6 @@ export interface FileRoutesByFullPath { '/events/$slug': typeof EventsSlugRoute '/members/$slug': typeof MembersSlugRoute '/members/archived': typeof MembersArchivedRoute - '/members/contributors': typeof MembersContributorsRoute '/videos/$slug': typeof VideosSlugRoute '/admin/': typeof AdminIndexRoute '/events/': typeof EventsIndexRoute @@ -207,7 +200,6 @@ export interface FileRoutesByTo { '/events/$slug': typeof EventsSlugRoute '/members/$slug': typeof MembersSlugRoute '/members/archived': typeof MembersArchivedRoute - '/members/contributors': typeof MembersContributorsRoute '/videos/$slug': typeof VideosSlugRoute '/admin': typeof AdminIndexRoute '/events': typeof EventsIndexRoute @@ -236,7 +228,6 @@ export interface FileRoutesById { '/events/$slug': typeof EventsSlugRoute '/members/$slug': typeof MembersSlugRoute '/members/archived': typeof MembersArchivedRoute - '/members/contributors': typeof MembersContributorsRoute '/videos/$slug': typeof VideosSlugRoute '/admin/': typeof AdminIndexRoute '/events/': typeof EventsIndexRoute @@ -266,7 +257,6 @@ export interface FileRouteTypes { | '/events/$slug' | '/members/$slug' | '/members/archived' - | '/members/contributors' | '/videos/$slug' | '/admin/' | '/events/' @@ -293,7 +283,6 @@ export interface FileRouteTypes { | '/events/$slug' | '/members/$slug' | '/members/archived' - | '/members/contributors' | '/videos/$slug' | '/admin' | '/events' @@ -321,7 +310,6 @@ export interface FileRouteTypes { | '/events/$slug' | '/members/$slug' | '/members/archived' - | '/members/contributors' | '/videos/$slug' | '/admin/' | '/events/' @@ -346,7 +334,6 @@ export interface RootRouteChildren { EventsSlugRoute: typeof EventsSlugRoute MembersSlugRoute: typeof MembersSlugRoute MembersArchivedRoute: typeof MembersArchivedRoute - MembersContributorsRoute: typeof MembersContributorsRoute VideosSlugRoute: typeof VideosSlugRoute EventsIndexRoute: typeof EventsIndexRoute MembersIndexRoute: typeof MembersIndexRoute @@ -460,13 +447,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MembersArchivedRouteImport parentRoute: typeof rootRouteImport } - '/members/contributors': { - id: '/members/contributors' - path: '/members/contributors' - fullPath: '/members/contributors' - preLoaderRoute: typeof MembersContributorsRouteImport - parentRoute: typeof rootRouteImport - } '/videos/': { id: '/videos/' path: '/videos' @@ -583,7 +563,6 @@ const rootRouteChildren: RootRouteChildren = { EventsSlugRoute: EventsSlugRoute, MembersSlugRoute: MembersSlugRoute, MembersArchivedRoute: MembersArchivedRoute, - MembersContributorsRoute: MembersContributorsRoute, VideosSlugRoute: VideosSlugRoute, EventsIndexRoute: EventsIndexRoute, MembersIndexRoute: MembersIndexRoute, diff --git a/src/routes/admin/members.tsx b/src/routes/admin/members.tsx index 3a98ff7..0d34bd1 100644 --- a/src/routes/admin/members.tsx +++ b/src/routes/admin/members.tsx @@ -104,8 +104,8 @@ function MemberAdminPage() {

Profilok ({data.summary.active} aktív - {data.summary.deleted > 0 && - `, ${data.summary.deleted} törölt`} + {data.summary.archived > 0 && + `, ${data.summary.archived} archivált`} ) — csak olvashatóan

@@ -170,11 +170,11 @@ function EndpointSection() { "fullName": "Gipsz Jakab", "nickname": "Pitypang", "avatarUrl": null, - "membershipStatus": "studio_member", + "membershipStatus": "MEMBER", "isLeadership": false, "joinedSemester": "2021/2022/1" }}, - { "op": "delete", "sub": "57" } + { "op": "archive", "sub": "57" } ] }'`} @@ -183,8 +183,8 @@ function EndpointSection() { ezen keresztül kapcsolódik a tag a bejelentkezéséhez és a stáblistákhoz. A mode: "replace" +{' '} members alakkal a teljes névsor cserélhető: ami kimarad, - az törlésre kerül. A x-bss-delivery-id fejléc opcionális; - ha megadod, ugyanaz a kérés kétszer nem fut le. A{' '} + az archiválásra kerül. A x-bss-delivery-id fejléc + opcionális; ha megadod, ugyanaz a kérés kétszer nem fut le. A{' '} joinedSemester alakja ÉÉÉÉ/ÉÉÉÉ/N, ahol{' '} N 1 (őszi) vagy 2 (tavaszi) félév.

@@ -438,7 +438,7 @@ const deliveryColumns: Array> = [ header: 'Eredmény', render: (row) => row.status === 'ok' - ? `${row.operationCount} művelet — ${row.createdCount} új, ${row.updatedCount} módosítás, ${row.deletedCount} törlés, ${row.restoredCount} visszaállítás` + ? `${row.operationCount} művelet — ${row.createdCount} új, ${row.updatedCount} módosítás, ${row.archivedCount} archiválás, ${row.restoredCount} visszaállítás` : '—', }, { @@ -483,11 +483,11 @@ const profileColumns: Array> = [ key: 'state', header: 'Állapot', render: (row) => - row.deletedAt === null ? ( + row.archivedAt === null ? ( 'Aktív' ) : ( - Törölve ({formatAdminDateTimeHu(row.deletedAt)}) + Archiválva ({formatAdminDateTimeHu(row.archivedAt)}) ), }, diff --git a/src/routes/members/$slug.tsx b/src/routes/members/$slug.tsx index 840dd9a..28150cf 100644 --- a/src/routes/members/$slug.tsx +++ b/src/routes/members/$slug.tsx @@ -155,7 +155,7 @@ function MemberProfilePage() { return (
- {profile.fullName} | BSS + {`{profile.fullName} | BSS`} @@ -193,6 +193,7 @@ function MemberProfilePage() { {profile.statusLabel} {profile.isLeadership ? ', Vezetőség' : ''} + {profile.archived ? ', Archivált' : ''}

{profile.joinedSemester !== null && ( diff --git a/src/routes/members/contributors.tsx b/src/routes/members/contributors.tsx deleted file mode 100644 index 866c994..0000000 --- a/src/routes/members/contributors.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { createFileRoute, Link } from '@tanstack/react-router' -import { useQuery } from '@tanstack/react-query' -import { loadArchiveMembersServer } from '#/server/pages/member-archive-fn.ts' -import { parseSearchPage } from '#/server/shared/pagination.ts' -import { EmptyState } from '#/components/PageStates.tsx' - -export const Route = createFileRoute('/members/contributors')({ - validateSearch: (search: Record) => ({ - page: parseSearchPage(search['page']), - }), - loaderDeps: ({ search }) => ({ page: search.page }), - loader: ({ deps, context }) => - context.queryClient.ensureQueryData({ - queryKey: ['members-archive', 'contributors', deps.page ?? 1], - queryFn: () => - loadArchiveMembersServer({ - data: { kind: 'contributors', page: deps.page }, - }), - }), - component: ArchivedMembersPage, -}) - -function ArchivedMembersPage() { - const search = Route.useSearch() - const listQuery = useQuery({ - queryKey: ['members-archive', 'contributors', search.page ?? 1], - queryFn: () => - loadArchiveMembersServer({ - data: { kind: 'contributors', page: search.page }, - }), - }) - - return ( -
- {listQuery.isPending && ( -

- Betöltés… -

- )} - {listQuery.isError && ( -

- Hiba történt az adatok betöltése közben. Próbáld újra később. -

- )} - {listQuery.isSuccess && ( - <> -

- {listQuery.data.title} -

- {listQuery.data.items.length === 0 ? ( - - ) : ( - <> -
- {listQuery.data.items.map((member) => ( - - {member.fullName} -

- {member.fullName} -

- {member.nickname !== null && ( -

- „{member.nickname}” -

- )} - - ))} -
- {listQuery.data.totalPages > 1 && ( - - )} - - )} - - )} -
- ) -} diff --git a/src/routes/members/index.tsx b/src/routes/members/index.tsx index 41d83fa..7320955 100644 --- a/src/routes/members/index.tsx +++ b/src/routes/members/index.tsx @@ -58,15 +58,15 @@ function MembersPage() { /> ({ page: undefined })} className="font-bold text-(--orange) underline" > - Archivált öregtagok - - ({ page: undefined })} - className="font-bold text-(--orange) underline" - > - Dolgozott még velünk + Dolgoztak még velünk diff --git a/src/routes/search.tsx b/src/routes/search.tsx index b012668..25fb12c 100644 --- a/src/routes/search.tsx +++ b/src/routes/search.tsx @@ -1,7 +1,11 @@ import { createFileRoute, Link } from '@tanstack/react-router' import { createServerFn } from '@tanstack/react-start' import { getRequest } from '@tanstack/react-start/server' -import { MIN_QUERY_LENGTH, search } from '#/server/search/service.ts' +import { + MIN_QUERY_LENGTH, + search, + type MemberHit, +} from '#/server/search/service.ts' import { getVideoListPage } from '#/server/pages/video-list.ts' import { resolveViewerStateFromRequest } from '#/server/pages/viewer.ts' import { getDefaultDb } from '#/server/auth/session-store.ts' @@ -185,25 +189,15 @@ function SearchPage() {
    - {data.results.events.map(({ item }) => ( -
  • - -
  • - ))} + {data.results.events.map(({ item }) => + EventRow({ event: item }), + )}
    {data.results.members.map(({ item }) => ( -
  • - -
  • + ))}
@@ -263,12 +257,7 @@ function SearchPage() { ) : (
    {data.results.events.map(({ item }) => ( -
  • - -
  • + ))}
)} @@ -282,12 +271,7 @@ function SearchPage() { ) : (
    {data.results.members.map(({ item }) => ( -
  • - -
  • + ))}
)} @@ -299,6 +283,27 @@ function SearchPage() { ) } +function EventRow(props: { event: { slug: string; title: string } }) { + const { event } = props + return ( +
  • + +
  • + ) +} + +function MemberRow(props: { member: MemberHit }) { + const { member } = props + return ( +
  • + +
  • + ) +} + function countAll(results: { videos: unknown[] events: unknown[] diff --git a/src/server/admin/member-diagnostics.ts b/src/server/admin/member-diagnostics.ts index b04910f..2f169d4 100644 --- a/src/server/admin/member-diagnostics.ts +++ b/src/server/admin/member-diagnostics.ts @@ -14,8 +14,8 @@ export interface DiagnosticsProfile { isLeadership: boolean joinedSemester: string | null updatedAt: Date - /** Soft-deleted: hidden from every public listing, credits preserved. */ - deletedAt: Date | null + /** Archived: hidden from every public listing, credits preserved. */ + archivedAt: Date | null } export interface DiagnosticsDelivery { @@ -28,7 +28,7 @@ export interface DiagnosticsDelivery { operationCount: number createdCount: number updatedCount: number - deletedCount: number + archivedCount: number restoredCount: number message: string | null receivedAt: Date @@ -41,7 +41,7 @@ export interface MemberDiagnostics { summary: { total: number active: number - deleted: number + archived: number activeClients: number lastDeliveryStatus: string | null lastDeliveryMessage: string | null @@ -73,7 +73,7 @@ export async function getMemberDiagnostics( operationCount: webhookDeliveries.operationCount, createdCount: webhookDeliveries.createdCount, updatedCount: webhookDeliveries.updatedCount, - deletedCount: webhookDeliveries.deletedCount, + archivedCount: webhookDeliveries.archivedCount, restoredCount: webhookDeliveries.restoredCount, message: webhookDeliveries.message, receivedAt: webhookDeliveries.receivedAt, @@ -89,11 +89,11 @@ export async function getMemberDiagnostics( const deliveries: DiagnosticsDelivery[] = deliveryRows - const deletedRows = await executor + const archivedRows = await executor .select({ count: sql`count(*)::int` }) .from(memberCache) - .where(isNotNull(memberCache.deletedAt)) - const deleted = deletedRows.at(0)?.count ?? 0 + .where(isNotNull(memberCache.archivedAt)) + const archived = archivedRows.at(0)?.count ?? 0 const last = deliveries.at(0) return { @@ -109,14 +109,14 @@ export async function getMemberDiagnostics( profile.joinedSemester, ), updatedAt: profile.updatedAt, - deletedAt: profile.deletedAt, + archivedAt: profile.archivedAt, })), clients, deliveries, summary: { total: profileRows.length, - active: profileRows.length - deleted, - deleted, + active: profileRows.length - archived, + archived, activeClients: clients.filter((client) => client.revokedAt === null) .length, lastDeliveryStatus: last?.status ?? null, diff --git a/src/server/admin/video-detail.ts b/src/server/admin/video-detail.ts index 02128de..cd12c64 100644 --- a/src/server/admin/video-detail.ts +++ b/src/server/admin/video-detail.ts @@ -137,7 +137,7 @@ export async function getAdminVideoEditorOptions( executor .select({ sub: memberCache.sub, fullName: memberCache.fullName }) .from(memberCache) - .where(isNull(memberCache.deletedAt)) + .where(isNull(memberCache.archivedAt)) .orderBy(asc(memberCache.fullName)) .limit(2000), executor diff --git a/src/server/api/openapi.ts b/src/server/api/openapi.ts index 251ffdf..b33738b 100644 --- a/src/server/api/openapi.ts +++ b/src/server/api/openapi.ts @@ -115,7 +115,7 @@ export function buildMemberWebhookOpenApi(): JsonValue { 'one transaction, so either every one of them takes effect or none does.', 'The same `sub` may appear only once per request.', '', - '`delete` is a soft delete: the member disappears from the public', + '`archive` hides the member from the public', 'surfaces, but their row and the roles recorded in credit lists are kept,', 'and a later `upsert` restores them.', ].join('\n'), @@ -148,11 +148,11 @@ export function buildMemberWebhookOpenApi(): JsonValue { }, examples: { operations: { - summary: 'Operations (create and delete in one request)', + summary: 'Operations (create and archive in one request)', value: { operations: [ { op: 'upsert', member: example }, - { op: 'delete', sub: '57' }, + { op: 'archive', sub: '57' }, ], }, }, @@ -213,16 +213,16 @@ export function buildMemberWebhookOpenApi(): JsonValue { member: { $ref: '#/components/schemas/Member' }, }, }, - DeleteOperation: { + ArchiveOperation: { type: 'object', required: ['op', 'sub'], properties: { - op: { type: 'string', const: 'delete' }, + op: { type: 'string', const: 'archive' }, sub: { type: 'string', maxLength: 255, description: - 'The `sub` of the member to delete. An unknown `sub` is not an error, it is simply ignored.', + 'The `sub` of the member to archive. An unknown `sub` is not an error, it is simply ignored.', }, }, }, @@ -242,7 +242,7 @@ export function buildMemberWebhookOpenApi(): JsonValue { items: { oneOf: [ { $ref: '#/components/schemas/UpsertOperation' }, - { $ref: '#/components/schemas/DeleteOperation' }, + { $ref: '#/components/schemas/ArchiveOperation' }, ], }, }, @@ -252,7 +252,7 @@ export function buildMemberWebhookOpenApi(): JsonValue { type: 'object', required: ['mode', 'members'], description: [ - 'The full member roster. Anything left out of the list gets deleted —', + 'The full member roster. Anything left out of the list gets archived —', 'a faulty client can empty the entire roster this way.', ].join('\n'), properties: { @@ -270,7 +270,7 @@ export function buildMemberWebhookOpenApi(): JsonValue { 'operationCount', 'created', 'updated', - 'deleted', + 'archived', 'restored', 'unchanged', 'ignored', @@ -280,18 +280,19 @@ export function buildMemberWebhookOpenApi(): JsonValue { operationCount: { type: 'integer' }, created: { type: 'integer' }, updated: { type: 'integer' }, - deleted: { type: 'integer', description: 'Soft-deleted members.' }, + archived: { type: 'integer', description: 'Archived members.' }, restored: { type: 'integer', - description: 'Members previously deleted and now restored.', + description: 'Members previously archived and now restored.', }, unchanged: { type: 'integer', - description: 'Unchanged members; no audit row is written for these.', + description: + 'Unchanged members; no audit row is written for these.', }, ignored: { type: 'integer', - description: 'Deletes targeting an unknown `sub`.', + description: 'Archives targeting an unknown `sub`.', }, }, }, diff --git a/src/server/api/webhook-routes.ts b/src/server/api/webhook-routes.ts index 9aaa1be..a8ba665 100644 --- a/src/server/api/webhook-routes.ts +++ b/src/server/api/webhook-routes.ts @@ -229,7 +229,7 @@ export async function handleMemberWebhook( .set({ createdCount: applied.created, updatedCount: applied.updated, - deletedCount: applied.deleted, + archivedCount: applied.archived, restoredCount: applied.restored, }) .where(eq(webhookDeliveries.id, deliveryRowId)) diff --git a/src/server/auth/oidc.ts b/src/server/auth/oidc.ts index c811ae1..2c7cc1e 100644 --- a/src/server/auth/oidc.ts +++ b/src/server/auth/oidc.ts @@ -50,9 +50,6 @@ export interface TokenSet { export interface AuthenticatedIdentity { sub: string username: string - fullName: string | null - nickname: string | null - avatarUrl: string | null groups: string[] } @@ -372,12 +369,7 @@ export function extractIdentityFromClaims( `Az id_tokenben hiányzik a kötelező "${config.claims.sub}" (sub) claim.`, ) } - const username = claimString(claims, config.claims.username) - if (!username) { - throw new OidcProtocolError( - `Az id_tokenben hiányzik a kötelező "${config.claims.username}" (felhasználónév) claim.`, - ) - } + const username = claimString(claims, 'preferred_username') ?? sub const rawGroups = claims['groups'] const groups = Array.isArray(rawGroups) @@ -390,9 +382,6 @@ export function extractIdentityFromClaims( return { sub, username, - fullName: claimString(claims, config.claims.fullName), - nickname: claimString(claims, config.claims.nickname), - avatarUrl: claimString(claims, config.claims.avatarUrl), groups, } } diff --git a/src/server/auth/viewer.ts b/src/server/auth/viewer.ts index 5f6ddcf..99d67fd 100644 --- a/src/server/auth/viewer.ts +++ b/src/server/auth/viewer.ts @@ -21,24 +21,22 @@ export function anonymousViewer(): Viewer { return { level: 'anonymous', sub: null, username: null } } -/** - * The leadership group complements membership, it does not replace it: - * leadership only grants full rights together with the member group. - */ export function resolveViewerLevel( groups: ReadonlyArray, config: OobConfig['authentik'], ): ViewerLevel { const has = (groupName: string): boolean => groups.includes(groupName) - if (has(config.groups.tag) && has(config.groups.vezetoseg)) { + if (has(config.groups.admin) || has(config.groups.leadership)) { return 'leadership' } - if (has(config.groups.tag)) { + if ( + has(config.groups.studio) || + has(config.groups.studioCandidate) || + has(config.groups.studioCandidateCandidate) || + has(config.groups.alumni) + ) { return 'member' } - if (has(config.groups.schonherz)) { - return 'schonherz' - } return 'anonymous' } diff --git a/src/server/config/load.ts b/src/server/config/load.ts index 923b740..ac68821 100644 --- a/src/server/config/load.ts +++ b/src/server/config/load.ts @@ -1,4 +1,3 @@ -import { readFileSync } from 'node:fs' import { validateOobConfig } from './oob-schema.ts' import type { OobConfig } from './oob-schema.ts' @@ -22,7 +21,8 @@ export function loadOobConfig(explicitPath?: string): OobConfig { let content: string try { - content = readFileSync(path, 'utf-8') + const fs = process.getBuiltinModule('node:fs') + content = fs.readFileSync(path, 'utf-8') } catch (error) { throw new OobConfigFileError( `Nem olvasható a BSS OOB config fájl: ${path}. ` + diff --git a/src/server/config/oob-schema.ts b/src/server/config/oob-schema.ts index c7ac6e5..dabb9cb 100644 --- a/src/server/config/oob-schema.ts +++ b/src/server/config/oob-schema.ts @@ -1,10 +1,9 @@ export const MEMBERSHIP_STATUS_KEYS = [ - 'studio_member', - 'studio_candidate', - 'studio_applicant', - 'senior_active', - 'senior_archived', - 'contributor', + 'MEMBER_CANDIDATE_CANDIDATE', + 'MEMBER_CANDIDATE', + 'MEMBER', + 'ACTIVE_ALUMNI', + 'ALUMNI', ] as const export type MembershipStatusKey = (typeof MEMBERSHIP_STATUS_KEYS)[number] @@ -19,15 +18,14 @@ export interface OobConfig { scopes: string[] claims: { sub: string - username: string - fullName: string - nickname: string - avatarUrl: string } groups: { - schonherz: string - tag: string - vezetoseg: string + admin: string + studio: string + studioCandidate: string + studioCandidateCandidate: string + leadership: string + alumni: string } } youtube: { @@ -141,29 +139,26 @@ export function validateOobConfig(raw: unknown): OobConfig { problems.push('authentik.claims: kötelező szekció hiányzik.') } else { const claims = authentik['claims'] - for (const key of [ - 'sub', - 'username', - 'fullName', - 'nickname', - 'avatarUrl', - ]) { - requireString(claims, `authentik.claims.${key}`, problems) - } + requireString(claims, 'authentik.claims.sub', problems) } if (!isRecord(authentik['groups'])) { problems.push('authentik.groups: kötelező szekció hiányzik.') } else { const groups = authentik['groups'] - for (const key of ['schonherz', 'tag', 'vezetoseg']) { + for (const key of [ + 'admin', + 'studio', + 'studioCandidate', + 'studioCandidateCandidate', + 'leadership', + 'alumni', + ]) { requireString(groups, `authentik.groups.${key}`, problems) } const groupValues = Object.values(groups) if (new Set(groupValues).size !== groupValues.length) { - problems.push( - 'authentik.groups: minden csoportnévnek különböznie kell (a vezetőség tagságot nem helyettesítheti).', - ) + problems.push('authentik.groups: minden csoportnévnek különböznie kell.') } } diff --git a/src/server/members/ingest.ts b/src/server/members/ingest.ts index 3d94df8..0d788ad 100644 --- a/src/server/members/ingest.ts +++ b/src/server/members/ingest.ts @@ -30,7 +30,7 @@ export interface MemberInput { } export type MemberOperation = - { op: 'upsert'; member: MemberInput } | { op: 'delete'; sub: string } + { op: 'upsert'; member: MemberInput } | { op: 'archive'; sub: string } export interface MemberIngestPayload { mode: IngestMode @@ -42,10 +42,10 @@ export interface IngestResult { operationCount: number created: number updated: number - deleted: number + archived: number restored: number unchanged: number - /** Deletes naming a member the app has never seen; not an error. */ + /** Archives naming a member the app has never seen; not an error. */ ignored: number } @@ -186,7 +186,7 @@ function parseMember( typeof values['avatarUrl'] === 'string' ? values['avatarUrl'] : null, membershipStatus: (typeof values['membershipStatus'] === 'string' ? values['membershipStatus'] - : 'studio_member') as MembershipStatusKey, + : 'MEMBER') as MembershipStatusKey, isLeadership: values['isLeadership'] === true, joinedYear, joinedSemester, @@ -249,15 +249,15 @@ export function parseMemberIngestPayload(raw: unknown): MemberIngestPayload { op: 'upsert', member: parseMember(entry['member'], `${path}.member`, problems), }) - } else if (op === 'delete') { + } else if (op === 'archive') { const sub = parseField(SUB_SPEC, entry, path, problems) operations.push({ - op: 'delete', + op: 'archive', sub: typeof sub === 'string' ? sub : '', }) } else { problems.push( - `${path}.op: csak "upsert" vagy "delete" lehet, nem "${String(op)}".`, + `${path}.op: csak "upsert" vagy "archive" lehet, nem "${String(op)}".`, ) } }) @@ -319,7 +319,7 @@ export async function applyMemberIngest( operationCount: payload.operations.length, created: 0, updated: 0, - deleted: 0, + archived: 0, restored: 0, unchanged: 0, ignored: 0, @@ -356,22 +356,22 @@ export async function applyMemberIngest( const existingBySub = new Map(existingRows.map((row) => [row.sub, row])) for (const operation of payload.operations) { - if (operation.op === 'delete') { + if (operation.op === 'archive') { const existing = existingBySub.get(operation.sub) if (existing === undefined) { result.ignored += 1 continue } - if (existing.deletedAt !== null) { + if (existing.archivedAt !== null) { result.unchanged += 1 continue } await executor .update(memberCache) - .set({ deletedAt: now, updatedAt: now }) + .set({ archivedAt: now, updatedAt: now }) .where(eq(memberCache.sub, operation.sub)) - addAudit('delete', operation.sub, snapshot(existing), null) - result.deleted += 1 + addAudit('archive', operation.sub, snapshot(existing), null) + result.archived += 1 continue } @@ -391,16 +391,16 @@ export async function applyMemberIngest( joinedSemester: member.joinedSemester, createdAt: now, updatedAt: now, - deletedAt: null, + archivedAt: null, }) addAudit('create', member.sub, null, snapshot(member)) result.created += 1 continue } - const wasDeleted = existing.deletedAt !== null + const wasArchived = existing.archivedAt !== null const fieldsChanged = differs(existing, member) - if (!wasDeleted && !fieldsChanged) { + if (!wasArchived && !fieldsChanged) { result.unchanged += 1 continue } @@ -417,16 +417,16 @@ export async function applyMemberIngest( joinedYear: member.joinedYear, joinedSemester: member.joinedSemester, updatedAt: now, - deletedAt: null, + archivedAt: null, }) .where(eq(memberCache.sub, member.sub)) addAudit( - wasDeleted ? 'restore' : 'update', + wasArchived ? 'restore' : 'update', member.sub, snapshot(existing), snapshot(member), ) - if (wasDeleted) { + if (wasArchived) { result.restored += 1 } else { result.updated += 1 @@ -438,17 +438,17 @@ export async function applyMemberIngest( const liveRows = await executor .select() .from(memberCache) - .where(isNull(memberCache.deletedAt)) + .where(isNull(memberCache.archivedAt)) for (const row of liveRows) { if (keep.has(row.sub)) { continue } await executor .update(memberCache) - .set({ deletedAt: now, updatedAt: now }) + .set({ archivedAt: now, updatedAt: now }) .where(eq(memberCache.sub, row.sub)) - addAudit('delete', row.sub, snapshot(row), null) - result.deleted += 1 + addAudit('archive', row.sub, snapshot(row), null) + result.archived += 1 } } diff --git a/src/server/members/member-fields.ts b/src/server/members/member-fields.ts index 8411514..8836e28 100644 --- a/src/server/members/member-fields.ts +++ b/src/server/members/member-fields.ts @@ -160,7 +160,7 @@ export const MEMBER_FIELD_SPECS: readonly MemberFieldSpec[] = [ enumValues: MEMBERSHIP_STATUS_KEYS, description: 'Membership status. It decides which public list the member appears on; it grants NO permissions.', - example: 'studio_member', + example: 'MEMBER', }, { name: 'isLeadership', diff --git a/src/server/pages/members.ts b/src/server/pages/members.ts index 79d18ab..d7382f6 100644 --- a/src/server/pages/members.ts +++ b/src/server/pages/members.ts @@ -1,4 +1,4 @@ -import { and, asc, eq, isNull, sql } from 'drizzle-orm' +import { and, asc, eq, isNotNull, isNull, sql } from 'drizzle-orm' import type { Viewer } from '#/server/auth/viewer.ts' import { formatAcademicSemester } from '#/server/members/member-fields.ts' import { memberCache, staffRoles, videoStaff, videos } from '#/db/schema.ts' @@ -12,12 +12,11 @@ type MembershipStatus = (typeof membershipStatusEnum.enumValues)[number] export const MEMBER_PAGE_SIZE = 50 export const MEMBERSHIP_STATUS_LABELS: Record = { - studio_member: 'Stúdiós', - studio_candidate: 'Stúdiósjelölt', - studio_applicant: 'Stúdiósjelölt-jelölt', - senior_active: 'Aktív öregtag', - senior_archived: 'Archivált öregtag', - contributor: 'Dolgozott még velünk', + MEMBER_CANDIDATE_CANDIDATE: 'Stúdiósjelölt-jelölt', + MEMBER_CANDIDATE: 'Stúdiósjelölt', + MEMBER: 'Stúdiós', + ACTIVE_ALUMNI: 'Aktív öregtag', + ALUMNI: 'Öregtag', } export { formatAcademicSemester } @@ -32,9 +31,9 @@ export interface PublicMemberCard { export interface ActiveMemberBlocks { leadership: Array - studioMembers: Array - studioCandidates: Array - studioApplicants: Array + members: Array + member_candidates: Array + member_candiate_candidates: Array seniorActive: Array } @@ -52,7 +51,7 @@ export async function getActiveMemberBlocks( status: memberCache.membershipStatus, }) .from(memberCache) - .where(isNull(memberCache.deletedAt)) + .where(isNull(memberCache.archivedAt)) .orderBy(asc(memberCache.fullName), asc(memberCache.sub)) const toCard = (row: (typeof rows)[number]): PublicMemberCard => ({ @@ -65,26 +64,29 @@ export async function getActiveMemberBlocks( return { leadership: rows.filter((row) => row.isLeadership).map(toCard), - studioMembers: rows - .filter((row) => !row.isLeadership && row.status === 'studio_member') + members: rows + .filter((row) => !row.isLeadership && row.status === 'MEMBER') .map(toCard), - studioCandidates: rows - .filter((row) => row.status === 'studio_candidate') + member_candidates: rows + .filter((row) => row.status === 'MEMBER_CANDIDATE') .map(toCard), - studioApplicants: rows - .filter((row) => row.status === 'studio_applicant') + member_candiate_candidates: rows + .filter((row) => row.status === 'MEMBER_CANDIDATE_CANDIDATE') .map(toCard), seniorActive: rows - .filter((row) => row.status === 'senior_active') + .filter((row) => row.status === 'ACTIVE_ALUMNI') .map(toCard), } } -export type ArchiveKind = 'archived' | 'contributors' +export type ArchiveKind = 'archived' const ARCHIVE_STATUS: Record = { - archived: 'senior_archived', - contributors: 'contributor', + archived: 'ALUMNI', +} + +const ARCHIVE_TITLES: Record = { + archived: 'Dolgoztak még velünk', } export interface MemberListPage { @@ -100,7 +102,6 @@ export async function getMemberArchivePage( kind: ArchiveKind, params: { page?: number } = {}, ): Promise { - const status = ARCHIVE_STATUS[kind] const page = params.page !== undefined && Number.isInteger(params.page) && @@ -108,10 +109,7 @@ export async function getMemberArchivePage( ? params.page : 1 - const condition = and( - isNull(memberCache.deletedAt), - eq(memberCache.membershipStatus, status), - ) + const condition = isNotNull(memberCache.archivedAt) const [rows, countRows] = await Promise.all([ executor .select({ @@ -138,7 +136,7 @@ export async function getMemberArchivePage( total, page, totalPages: Math.ceil(total / MEMBER_PAGE_SIZE), - title: MEMBERSHIP_STATUS_LABELS[status], + title: ARCHIVE_TITLES[kind], } } @@ -151,7 +149,8 @@ export interface MemberProfile { statusLabel: string isLeadership: boolean joinedSemester: string | null - introduction: string | null + introduction: string | null, + archived: boolean } export async function getMemberProfile( @@ -161,9 +160,7 @@ export async function getMemberProfile( const rows = await executor .select() .from(memberCache) - .where( - and(eq(memberCache.username, username), isNull(memberCache.deletedAt)), - ) + .where(eq(memberCache.username, username)) .limit(1) const member = rows.at(0) if (member === undefined) { @@ -182,6 +179,7 @@ export async function getMemberProfile( member.joinedSemester, ), introduction: member.introduction, + archived: member.archivedAt !== null, } } @@ -207,7 +205,7 @@ export async function getMemberActivity( const offset = params.offset ?? 0 const rowsResult = await executor.execute(sql` - select v.id, v.slug, v.title, + select v.id as "videoId", v.slug, v.title, to_char(v.recorded_at, 'YYYY-MM-DD') as "recordedAt", extract(year from v.recorded_at)::int as year, coalesce( diff --git a/src/server/pages/sitemap.ts b/src/server/pages/sitemap.ts index a451ed6..d9b6b5d 100644 --- a/src/server/pages/sitemap.ts +++ b/src/server/pages/sitemap.ts @@ -52,7 +52,7 @@ export async function getSitemapEntries( updatedAt: memberCache.updatedAt, }) .from(memberCache) - .where(isNull(memberCache.deletedAt)) + .where(isNull(memberCache.archivedAt)) for (const row of memberRows) { entries.push({ diff --git a/src/server/search/service.ts b/src/server/search/service.ts index c199977..5b7f216 100644 --- a/src/server/search/service.ts +++ b/src/server/search/service.ts @@ -41,6 +41,7 @@ export interface MemberHit { fullName: string nickname: string | null avatarUrl: string | null + archived: boolean | null } export interface TagHit { @@ -203,7 +204,7 @@ async function searchMembersRaw( limit: number, ): Promise>> { const rows = await executor.execute(sql` - select m.sub, m.username, m.full_name as "fullName", m.nickname, m.avatar_url as "avatarUrl", + select m.sub, m.username, m.full_name as "fullName", m.nickname, m.avatar_url as "avatarUrl", m.archived_at as "archived", greatest( case when bss_norm(m.full_name) = bss_norm(${query}) then 100 else 0 end, case when bss_norm(coalesce(m.nickname, '')) = bss_norm(${query}) and m.nickname is not null then 95 else 0 end, @@ -213,8 +214,7 @@ async function searchMembersRaw( case when coalesce(bss_norm(m.introduction), '') like '%' || bss_norm(${query}) || '%' then 20 else 0 end ) as score from member_cache m - where m.deleted_at is null - and ( + where ( bss_norm(m.full_name) like '%' || bss_norm(${query}) || '%' or bss_norm(coalesce(m.nickname, '')) like '%' || bss_norm(${query}) || '%' or coalesce(bss_norm(m.introduction), '') like '%' || bss_norm(${query}) || '%' @@ -231,6 +231,7 @@ async function searchMembersRaw( fullName: row.fullName, nickname: row.nickname, avatarUrl: row.avatarUrl, + archived: row.archived !== null, }, score: Number(row.score), }), diff --git a/tests/helpers/admin-api.ts b/tests/helpers/admin-api.ts index a6813e7..f7abf26 100644 --- a/tests/helpers/admin-api.ts +++ b/tests/helpers/admin-api.ts @@ -63,7 +63,7 @@ export async function setupAdminApiTest( fullName: 'Admin Tag', nickname: null, avatarUrl: null, - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', isLeadership: false, joinedYear: 2023, joinedSemester: 'autumn', @@ -77,7 +77,7 @@ export async function setupAdminApiTest( fullName: 'Admin Vezetőség', nickname: null, avatarUrl: null, - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', isLeadership: true, joinedYear: 2022, joinedSemester: 'autumn', @@ -91,7 +91,7 @@ export async function setupAdminApiTest( fullName: 'Admin Schönherz', nickname: null, avatarUrl: null, - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', isLeadership: false, joinedYear: 2024, joinedSemester: 'spring', @@ -102,12 +102,9 @@ export async function setupAdminApiTest( ]) const [member, leadership, schonherz] = await Promise.all([ - createSession(db, 'sub-admin-member', 'adminmember', [groups.tag]), - createSession(db, 'sub-admin-leader', 'adminleader', [ - groups.tag, - groups.vezetoseg, - ]), - createSession(db, 'sub-admin-schonherz', 'adminsch', [groups.schonherz]), + createSession(db, 'sub-admin-member', 'adminmember', [groups.studio]), + createSession(db, 'sub-admin-leader', 'adminleader', [groups.leadership]), + createSession(db, 'sub-admin-schonherz', 'adminsch', []), ]) return { db, diff --git a/tests/helpers/factories.ts b/tests/helpers/factories.ts index 454a7af..5351f5a 100644 --- a/tests/helpers/factories.ts +++ b/tests/helpers/factories.ts @@ -108,12 +108,11 @@ export function buildTag(overrides: Partial = {}): TagFixture { } export type MembershipStatus = - | 'studio_member' - | 'studio_candidate' - | 'studio_applicant' - | 'senior_active' - | 'senior_archived' - | 'contributor' + | 'MEMBER' + | 'MEMBER_CANDIDATE' + | 'MEMBER_CANDIDATE_CANDIDATE' + | 'ACTIVE_ALUMNI' + | 'ALUMNI' export interface MemberFixture { sub: string @@ -126,7 +125,7 @@ export interface MemberFixture { joinedYear: number | null joinedSemester: 'spring' | 'autumn' | null introduction: string | null - deletedAt: Date | null + archivedAt: Date | null } export function buildMember( @@ -139,12 +138,12 @@ export function buildMember( fullName: overrides.fullName ?? 'Teszt Teljes Név', nickname: overrides.nickname ?? 'Teszti', avatarUrl: null, - membershipStatus: overrides.membershipStatus ?? 'studio_member', + membershipStatus: overrides.membershipStatus ?? 'MEMBER', isLeadership: overrides.isLeadership ?? false, joinedYear: overrides.joinedYear ?? 2023, joinedSemester: overrides.joinedSemester ?? 'autumn', introduction: overrides.introduction ?? null, - deletedAt: overrides.deletedAt ?? null, + archivedAt: overrides.archivedAt ?? null, ...overrides, } } diff --git a/tests/helpers/oob-config.ts b/tests/helpers/oob-config.ts index f210c00..bc1d79c 100644 --- a/tests/helpers/oob-config.ts +++ b/tests/helpers/oob-config.ts @@ -34,15 +34,14 @@ export function buildRawOobConfig( scopes: ['openid', 'profile', 'email'], claims: { sub: 'sub', - username: 'preferred_username', - fullName: 'name', - nickname: 'nickname', - avatarUrl: 'picture', }, groups: { - schonherz: 'schonherz-dev', - tag: 'tag-dev', - vezetoseg: 'vezetoseg-dev', + admin: 'Admin', + studio: 'Stúdiós', + studioCandidate: 'Stúdiós jelölt', + studioCandidateCandidate: 'Stúdiós jelölt-jelölt', + leadership: 'Vezetőség', + alumni: 'Öregtag', }, }, youtube: { diff --git a/tests/integration/about-courses.test.ts b/tests/integration/about-courses.test.ts index a63669e..76f9159 100644 --- a/tests/integration/about-courses.test.ts +++ b/tests/integration/about-courses.test.ts @@ -36,7 +36,7 @@ async function setupDb(): Promise>> { sub: 'leader-sub', username: 'vezetoseg', fullName: 'Vezetőségi Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }) return migrated.db } diff --git a/tests/integration/admin-access.test.ts b/tests/integration/admin-access.test.ts index d64f614..617f615 100644 --- a/tests/integration/admin-access.test.ts +++ b/tests/integration/admin-access.test.ts @@ -90,7 +90,7 @@ describe.skipIf(!hasTestDatabase)('BSS-027: guard valódi sessionnel', () => { { memberSub: 'sub-admin-member', username: 'adminmember', - groups: [config.authentik.groups.tag], + groups: [config.authentik.groups.studio], accessToken: null, }, { db }, @@ -105,7 +105,7 @@ describe.skipIf(!hasTestDatabase)('BSS-027: guard valódi sessionnel', () => { { memberSub: 'sub-admin-sch', username: 'adminsch', - groups: [config.authentik.groups.schonherz], + groups: [], accessToken: null, }, { db }, @@ -125,7 +125,7 @@ describe.skipIf(!hasTestDatabase)('BSS-027: guard valódi sessionnel', () => { { memberSub: 'sub-expired', username: 'expired', - groups: [config.authentik.groups.tag], + groups: [config.authentik.groups.studio], accessToken: null, }, { db, ttlMs: -1000 }, @@ -138,7 +138,7 @@ describe.skipIf(!hasTestDatabase)('BSS-027: guard valódi sessionnel', () => { expect(adminAreaAccess(state.viewer, '/admin/videos').kind).toBe('login') }) - it('cookie nélkül névtelen; vezetőségi csoport tagsággal együtt ad teljes jogot', async () => { + it('cookie nélkül névtelen; a vezetőségi csoport önmagában teljes jogot ad', async () => { const db = await setupDb() const anonymousState = await resolveViewerStateFromRequest( new Request('http://localhost/admin'), @@ -146,12 +146,11 @@ describe.skipIf(!hasTestDatabase)('BSS-027: guard valódi sessionnel', () => { ) expect(anonymousState.viewer.level).toBe('anonymous') - // névtelen marad, így az admin terület belépést sem enged neki. const leadOnly = await createAuthSession( { memberSub: 'sub-lead-only', username: 'leadonly', - groups: [config.authentik.groups.vezetoseg], + groups: [config.authentik.groups.leadership], accessToken: null, }, { db }, @@ -160,22 +159,14 @@ describe.skipIf(!hasTestDatabase)('BSS-027: guard valódi sessionnel', () => { requestWithCookie(leadOnly.token), { db, config }, ) - expect(leadOnlyState.viewer.level).not.toBe('leadership') - expect(leadOnlyState.viewer.level).not.toBe('member') - expect( - ['ok', 'forbidden'].includes( - adminAreaAccess(leadOnlyState.viewer, '/admin').kind, - ), - ).toBe(false) + expect(leadOnlyState.viewer.level).toBe('leadership') + expect(adminAreaAccess(leadOnlyState.viewer, '/admin').kind).toBe('ok') const full = await createAuthSession( { memberSub: 'sub-full-lead', username: 'fulllead', - groups: [ - config.authentik.groups.tag, - config.authentik.groups.vezetoseg, - ], + groups: [config.authentik.groups.admin], accessToken: null, }, { db }, diff --git a/tests/integration/auth-flow.test.ts b/tests/integration/auth-flow.test.ts index e1996d3..4f450f1 100644 --- a/tests/integration/auth-flow.test.ts +++ b/tests/integration/auth-flow.test.ts @@ -401,7 +401,7 @@ describe.skipIf(!hasTestDatabase)( { memberSub: 'sub-tag-1', username: 'tag-dev', - groups: [testConfig.authentik.groups.tag], + groups: [testConfig.authentik.groups.studio], accessToken: null, }, { clock, db }, diff --git a/tests/integration/auth-policy.test.ts b/tests/integration/auth-policy.test.ts index 4e7893b..649684a 100644 --- a/tests/integration/auth-policy.test.ts +++ b/tests/integration/auth-policy.test.ts @@ -30,12 +30,13 @@ afterAll(async () => { const hasTestDatabase = Boolean(process.env.TEST_DATABASE_URL) const anonymous: Viewer = { level: 'anonymous', sub: null, username: null } -const schonherz: Viewer = viewerFromIdentity( - { sub: 's1', username: 'schonherz-dev', groups: ['schonherz-dev'] }, - testConfig.authentik, -) +const schonherz: Viewer = { + level: 'schonherz', + sub: 's1', + username: 'schonherz-dev', +} const member: Viewer = viewerFromIdentity( - { sub: 't1', username: 'tag-dev', groups: ['tag-dev'] }, + { sub: 't1', username: 'tag-dev', groups: ['Stúdiós'] }, testConfig.authentik, ) @@ -104,7 +105,7 @@ describe.skipIf(!hasTestDatabase)( { sub: 'v1', username: 'vezetoseg-dev', - groups: ['tag-dev', 'vezetoseg-dev'], + groups: ['Vezetőség'], }, testConfig.authentik, ) diff --git a/tests/integration/catalog.test.ts b/tests/integration/catalog.test.ts index b322b74..4de0d7d 100644 --- a/tests/integration/catalog.test.ts +++ b/tests/integration/catalog.test.ts @@ -69,13 +69,13 @@ async function setupDb(): Promise>> { sub: 'leader-sub', username: 'vezetoseg', fullName: 'Vezetőségi Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: 'member-sub', username: 'tag', fullName: 'BSS Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) return migrated.db diff --git a/tests/integration/event-pages.test.ts b/tests/integration/event-pages.test.ts index 2e122da..dcfae5d 100644 --- a/tests/integration/event-pages.test.ts +++ b/tests/integration/event-pages.test.ts @@ -203,13 +203,13 @@ describe.skipIf(!hasTestDatabase)('BSS-022: eseményrészlet', () => { sub: 'sub-a', username: 'anna', fullName: 'Anna Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: 'sub-b', username: 'bence', fullName: 'Bence Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) const roleRows = await db diff --git a/tests/integration/events-domain.test.ts b/tests/integration/events-domain.test.ts index 925f07c..fd6e76f 100644 --- a/tests/integration/events-domain.test.ts +++ b/tests/integration/events-domain.test.ts @@ -68,13 +68,13 @@ async function setupDb(): Promise>> { sub: 'member-sub', username: 'tag', fullName: 'BSS Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: 'leader-sub', username: 'vezetoseg', fullName: 'Vezetőségi Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) return migrated.db diff --git a/tests/integration/homepage-page.test.ts b/tests/integration/homepage-page.test.ts index f938430..f676588 100644 --- a/tests/integration/homepage-page.test.ts +++ b/tests/integration/homepage-page.test.ts @@ -37,7 +37,7 @@ async function setupDb(): Promise>> { sub: 'leader-sub', username: 'vezetoseg', fullName: 'Vezetőségi Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }) return migrated.db } diff --git a/tests/integration/homepage.test.ts b/tests/integration/homepage.test.ts index b959a2b..64496ed 100644 --- a/tests/integration/homepage.test.ts +++ b/tests/integration/homepage.test.ts @@ -61,13 +61,13 @@ async function setupDb(): Promise>> { sub: 'leader-sub', username: 'vezetoseg', fullName: 'Vezetőségi Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: 'member-sub', username: 'tag', fullName: 'BSS Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) return migrated.db diff --git a/tests/integration/member-pages.test.ts b/tests/integration/member-pages.test.ts index e21343e..2fac7d9 100644 --- a/tests/integration/member-pages.test.ts +++ b/tests/integration/member-pages.test.ts @@ -44,7 +44,7 @@ async function seedMember( ): Promise { await db.insert(memberCache).values({ fullName: overrides.username, - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', ...overrides, }) } @@ -57,7 +57,7 @@ describe.skipIf(!hasTestDatabase)('BSS-023: aktív tagoldal blokkjai', () => { username: 'vezeto', fullName: 'Vezető Erika', isLeadership: true, - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }) await seedMember(db, { sub: 'm1', @@ -68,20 +68,20 @@ describe.skipIf(!hasTestDatabase)('BSS-023: aktív tagoldal blokkjai', () => { sub: 'm2', username: 'torolt', fullName: 'Törölt Henriett', - membershipStatus: 'studio_member', - deletedAt: new Date('2026-07-01T00:00:00Z'), + membershipStatus: 'MEMBER', + archivedAt: new Date('2026-07-01T00:00:00Z'), }) await seedMember(db, { sub: 'm3', username: 'jelolt', fullName: 'Jelölt Csaba', - membershipStatus: 'studio_candidate', + membershipStatus: 'MEMBER_CANDIDATE', }) await seedMember(db, { sub: 'm4', username: 'oregtag', fullName: 'Öregtag Dénes', - membershipStatus: 'senior_active', + membershipStatus: 'ACTIVE_ALUMNI', }) const blocks = await getActiveMemberBlocks(db) @@ -89,51 +89,35 @@ describe.skipIf(!hasTestDatabase)('BSS-023: aktív tagoldal blokkjai', () => { 'vezeto', ]) // A vezetőségi tag (stúdiós státuszával együtt) nem ismétlődik a stúdiósoknál. - expect(blocks.studioMembers.map((member) => member.username)).toEqual([ + expect(blocks.members.map((member) => member.username)).toEqual([ 'studios', ]) - expect(blocks.studioCandidates.map((member) => member.username)).toEqual([ + expect(blocks.member_candidates.map((member) => member.username)).toEqual([ 'jelolt', ]) + expect(blocks.member_candiate_candidates.map((member) => member.username)).toEqual([ + 'jelölt-jelölt', + ]) expect(blocks.seniorActive.map((member) => member.username)).toEqual([ 'oregtag', ]) }) }) -describe.skipIf(!hasTestDatabase)('BSS-023: archív aloldalak', () => { - it('archivált öregtagok és közreműködők külön, 50-es lapozással', async () => { +describe.skipIf(!hasTestDatabase)('BSS-023: archív aloldal', () => { + it('öregtagok 50-es lapozással', async () => { const db = await setupDb() for (let index = 0; index < 3; index += 1) { await seedMember(db, { sub: `arch-${index}`, username: `archivalt-${index}`, fullName: `Archivált ${index}`, - membershipStatus: 'senior_archived', + membershipStatus: 'ALUMNI', }) } - await seedMember(db, { - sub: 'contr-1', - username: 'kozmukodo', - fullName: 'Közreműködő Elek', - membershipStatus: 'contributor', - }) - await seedMember(db, { - sub: 'contr-2', - username: 'kozmukodo-torolt', - fullName: 'Törölt Közreműködő', - membershipStatus: 'contributor', - deletedAt: new Date('2026-07-01T00:00:00Z'), - }) - const archived = await getMemberArchivePage(db, 'archived') expect(archived.total).toBe(3) - expect(archived.title).toBe('Archivált öregtag') - - const contributors = await getMemberArchivePage(db, 'contributors') - expect(contributors.items.map((member) => member.username)).toEqual([ - 'kozmukodo', - ]) + expect(archived.title).toBe('Öregtag') }) }) @@ -153,7 +137,7 @@ describe.skipIf(!hasTestDatabase)('BSS-023: tagprofil', () => { sub: 'prof-2', username: 'rejtett', fullName: 'Rejtett Gábor', - deletedAt: new Date('2026-07-01T00:00:00Z'), + archivedAt: new Date('2026-07-01T00:00:00Z'), }) const profile = await getMemberProfile(db, 'profilos') @@ -243,6 +227,10 @@ describe.skipIf(!hasTestDatabase)('BSS-023: tevékenység', () => { 'uj-video', 'regi-video', ]) + expect(activity.items.map((row) => row.videoId)).toEqual([ + recent.id, + old.id, + ]) expect(activity.items[0]?.roles.sort()).toEqual(['Rendező', 'Vágó']) // Tag néző már a titkos videót is látja. diff --git a/tests/integration/member-webhook.test.ts b/tests/integration/member-webhook.test.ts index 67fc64d..5e2f975 100644 --- a/tests/integration/member-webhook.test.ts +++ b/tests/integration/member-webhook.test.ts @@ -98,7 +98,7 @@ function member(overrides: Record = {}) { fullName: 'Gipsz Jakab', nickname: 'Pitypang', avatarUrl: null, - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', isLeadership: false, joinedSemester: '2019/2020/1', ...overrides, @@ -324,7 +324,7 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { fullName: 'Nagy Éva', }), }, - { op: 'delete', sub: 'nem-letezik' }, + { op: 'archive', sub: 'nem-letezik' }, ], }) expect(result.payload['result']).toMatchObject({ @@ -334,8 +334,8 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { }) }) - it('a törlés puha: a tag eltűnik a publikus oldalakról, de a sora megmarad', async () => { - const ctx = await setupAdminApiTest('bss whdelete') + it('az archiválás elrejti a tagot, de a sora megmarad', async () => { + const ctx = await setupAdminApiTest('bss wharchive') const clock = new FakeClock('2026-08-24T10:00:00Z') const { token } = await createClient(ctx, clock) @@ -343,18 +343,18 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { operations: [{ op: 'upsert', member: member() }], }) const beforeBlocks = await getActiveMemberBlocks(ctx.db) - expect(beforeBlocks.studioMembers.some((card) => card.sub === '42')).toBe( + expect(beforeBlocks.members.some((card) => card.sub === '42')).toBe( true, ) clock.advanceMinutes(1) - const deleted = await push(ctx, clock, token, { - operations: [{ op: 'delete', sub: '42' }], + const archived = await push(ctx, clock, token, { + operations: [{ op: 'archive', sub: '42' }], }) - expect(deleted.payload['result']).toMatchObject({ deleted: 1 }) + expect(archived.payload['result']).toMatchObject({ archived: 1 }) const afterBlocks = await getActiveMemberBlocks(ctx.db) - expect(afterBlocks.studioMembers.some((card) => card.sub === '42')).toBe( + expect(afterBlocks.members.some((card) => card.sub === '42')).toBe( false, ) expect(await getMemberProfile(ctx.db, 'gipsz.jakab')).toBeNull() @@ -363,17 +363,20 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { const rows = await memberRows(ctx.db) const row = rows.find((entry) => entry.sub === '42') expect(row).toBeDefined() - expect(row?.deletedAt).not.toBeNull() + expect(row?.archivedAt).not.toBeNull() - // A repeated delete is a no-op rather than an error. + // A repeated archive is a no-op rather than an error. clock.advanceMinutes(1) const again = await push(ctx, clock, token, { - operations: [{ op: 'delete', sub: '42' }], + operations: [{ op: 'archive', sub: '42' }], + }) + expect(again.payload['result']).toMatchObject({ + archived: 0, + unchanged: 1, }) - expect(again.payload['result']).toMatchObject({ deleted: 0, unchanged: 1 }) }) - it('törölt tag újbóli beküldése visszaállítja', async () => { + it('archivált tag újbóli beküldése visszaállítja', async () => { const ctx = await setupAdminApiTest('bss whrestore') const clock = new FakeClock('2026-08-24T10:00:00Z') const { token } = await createClient(ctx, clock) @@ -383,7 +386,7 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { }) clock.advanceMinutes(1) await push(ctx, clock, token, { - operations: [{ op: 'delete', sub: '42' }], + operations: [{ op: 'archive', sub: '42' }], }) clock.advanceMinutes(1) const restored = await push(ctx, clock, token, { @@ -395,7 +398,7 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { expect(profile?.fullName).toBe('Gipsz Jakab') }) - it('replace mód törli a névsorból kimaradt tagokat', async () => { + it('replace mód archiválja a névsorból kimaradt tagokat', async () => { const ctx = await setupAdminApiTest('bss whreplace') const clock = new FakeClock('2026-08-24T10:00:00Z') const { token } = await createClient(ctx, clock) @@ -425,11 +428,11 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { // admin fixture profiles. That is the point of the mode. expect(replaced.payload['result']).toMatchObject({ mode: 'replace', - deleted: 4, + archived: 4, }) const blocks = await getActiveMemberBlocks(ctx.db) - expect(blocks.studioMembers.map((card) => card.sub)).toEqual(['43']) + expect(blocks.members.map((card) => card.sub)).toEqual(['43']) expect(blocks.leadership).toHaveLength(0) }) @@ -446,13 +449,13 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook műveletek', () => { operations: [ { op: 'upsert', - member: member({ membershipStatus: 'senior_archived' }), + member: member({ membershipStatus: 'ALUMNI' }), }, ], }) const blocks = await getActiveMemberBlocks(ctx.db) - expect(blocks.studioMembers.some((card) => card.sub === '42')).toBe(false) + expect(blocks.members.some((card) => card.sub === '42')).toBe(false) const archive = await getMemberArchivePage(ctx.db, 'archived') expect(archive.items.map((card) => card.sub)).toEqual(['42']) }) @@ -648,7 +651,7 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook validáció', () => { const response = await push(ctx, clock, token, { operations: [ { op: 'upsert', member: member() }, - { op: 'delete', sub: '42' }, + { op: 'archive', sub: '42' }, ], }) expect(response.status).toBe(400) @@ -678,9 +681,7 @@ describe.skipIf(!hasTestDatabase)('tagfrissítő webhook validáció', () => { // The colliding push left the existing member untouched. const rows = await memberRows(ctx.db) - expect(rows.filter((row) => row.username === 'gipsz.jakab')).toHaveLength( - 1, - ) + expect(rows.filter((row) => row.username === 'gipsz.jakab')).toHaveLength(1) expect(rows.some((row) => row.sub === '99')).toBe(false) }) }) @@ -769,13 +770,13 @@ describe.skipIf(!hasTestDatabase)('tagadminisztráció diagnosztikája', () => { }) clock.advanceMinutes(1) await push(ctx, clock, token, { - operations: [{ op: 'delete', sub: '43' }], + operations: [{ op: 'archive', sub: '43' }], }) const data = await getMemberDiagnostics(ctx.db) // The three admin fixture profiles plus the two pushed members. expect(data.summary.total).toBe(5) - expect(data.summary.deleted).toBe(1) + expect(data.summary.archived).toBe(1) expect(data.summary.active).toBe(4) expect(data.summary.activeClients).toBe(1) expect(data.summary.lastDeliveryStatus).toBe('ok') diff --git a/tests/integration/related-videos.test.ts b/tests/integration/related-videos.test.ts index dd5e410..56a9970 100644 --- a/tests/integration/related-videos.test.ts +++ b/tests/integration/related-videos.test.ts @@ -60,7 +60,7 @@ async function setupDb(): Promise>> { sub: 'member-sub', username: 'tag', fullName: 'BSS Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) return migrated.db diff --git a/tests/integration/schema.migration.test.ts b/tests/integration/schema.migration.test.ts index f11d000..09fb7b7 100644 --- a/tests/integration/schema.migration.test.ts +++ b/tests/integration/schema.migration.test.ts @@ -67,7 +67,7 @@ describe.skipIf(!hasTestDatabase)('új adatbázisséma és migrációs alap', () const { client } = await createMigratedDatabase() try { await client.query( - "INSERT INTO member_cache (sub, username, full_name, membership_status) VALUES ('sub1','user1','Egy Tag','studio_member')", + "INSERT INTO member_cache (sub, username, full_name, membership_status) VALUES ('sub1','user1','Egy Tag','MEMBER')", ) await expect( diff --git a/tests/integration/search-api.test.ts b/tests/integration/search-api.test.ts index 3166084..05f21ec 100644 --- a/tests/integration/search-api.test.ts +++ b/tests/integration/search-api.test.ts @@ -165,6 +165,6 @@ async function seedMember(db: Database): Promise { sub: 'member-sub', username: 'tag', fullName: 'BSS Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }) } diff --git a/tests/integration/search.test.ts b/tests/integration/search.test.ts index 39a4023..1cf5d1d 100644 --- a/tests/integration/search.test.ts +++ b/tests/integration/search.test.ts @@ -55,14 +55,14 @@ async function setupDb(): Promise>> { username: 'tag', fullName: 'Teszt Béla', nickname: 'Bélus', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: 'error-sub', username: 'torolt', fullName: 'Törölt Profil', - membershipStatus: 'studio_member', - deletedAt: new Date('2026-07-01T00:00:00Z'), + membershipStatus: 'MEMBER', + archivedAt: new Date('2026-07-01T00:00:00Z'), }, ]) return migrated.db diff --git a/tests/integration/seed-importer.test.ts b/tests/integration/seed-importer.test.ts index 9e9372b..a56f8cf 100644 --- a/tests/integration/seed-importer.test.ts +++ b/tests/integration/seed-importer.test.ts @@ -43,7 +43,7 @@ async function seedMember(db: Database, username: string): Promise { username, fullName: `${username} Teljes Neve`, nickname: username, - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', isLeadership: false, }) return sub diff --git a/tests/integration/shared-write.test.ts b/tests/integration/shared-write.test.ts index d8ec7a2..8d7c85b 100644 --- a/tests/integration/shared-write.test.ts +++ b/tests/integration/shared-write.test.ts @@ -51,13 +51,13 @@ async function setupDb(): Promise>> { sub: '36', username: 'tag-dev', fullName: 'Teszt BSS Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: '37', username: 'vezetoseg-dev', fullName: 'Teszt Vezetőségi Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) diff --git a/tests/integration/video-detail-page.test.ts b/tests/integration/video-detail-page.test.ts index 8142122..67aa132 100644 --- a/tests/integration/video-detail-page.test.ts +++ b/tests/integration/video-detail-page.test.ts @@ -126,13 +126,13 @@ describe.skipIf(!hasTestDatabase)('BSS-021: videórészlet', () => { sub: 'sub-a', username: 'tag-a', fullName: 'Anna Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: 'sub-b', username: 'tag-b', fullName: 'Bence Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) const video = await seedVideo(db, { @@ -203,7 +203,7 @@ describe.skipIf(!hasTestDatabase)( sub: 'sub-x', username: 'tag-x', fullName: 'X Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }) const video = await seedVideo(db, { slug: 'szamlalt-video' }) diff --git a/tests/integration/video-list-page.test.ts b/tests/integration/video-list-page.test.ts index 0cdb785..f5339b3 100644 --- a/tests/integration/video-list-page.test.ts +++ b/tests/integration/video-list-page.test.ts @@ -214,7 +214,7 @@ describe.skipIf(!hasTestDatabase)('BSS-020: videólista lekérdezés', () => { sub: 'staff-sub', username: 'stábtag', fullName: 'Stáb Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }) await db.insert(videoStaff).values({ videoId: video.id, diff --git a/tests/integration/videos-domain.test.ts b/tests/integration/videos-domain.test.ts index c671a2d..b7aace2 100644 --- a/tests/integration/videos-domain.test.ts +++ b/tests/integration/videos-domain.test.ts @@ -76,13 +76,13 @@ async function setupDb(): Promise>> { sub: 'member-sub', username: 'tag', fullName: 'BSS Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, { sub: 'leader-sub', username: 'vezetoseg', fullName: 'Vezetőségi Tag', - membershipStatus: 'studio_member', + membershipStatus: 'MEMBER', }, ]) return migrated.db diff --git a/tests/unit/auth-oidc.test.ts b/tests/unit/auth-oidc.test.ts index 0f14d5b..b93b685 100644 --- a/tests/unit/auth-oidc.test.ts +++ b/tests/unit/auth-oidc.test.ts @@ -343,17 +343,17 @@ describe('identity kinyerés', () => { expect(identity.sub).toBe('sub-1') expect(identity.username).toBe('tag-dev') - expect(identity.fullName).toBe('Teszt BSS Tag') - expect(identity.nickname).toBe('Tagocska') - expect(identity.avatarUrl).toBeNull() expect(identity.groups).toEqual(['bss-tag', 'bss-vezetoseg']) }) - it('hiányzó sub vagy felhasználónév esetén nem talál ki identitást', () => { + it('hiányzó sub esetén nem talál ki identitást', () => { expect(() => extractIdentityFromClaims({}, config.authentik)).toThrow(/sub/) - expect(() => + }) + + it('a sub elég, ha nincs felhasználónév claim', () => { + expect( extractIdentityFromClaims({ sub: 'sub-1' }, config.authentik), - ).toThrow(/felhasználónév/) + ).toMatchObject({ sub: 'sub-1', username: 'sub-1' }) }) }) diff --git a/tests/unit/auth-policy.test.ts b/tests/unit/auth-policy.test.ts index 7c03b64..2509cb3 100644 --- a/tests/unit/auth-policy.test.ts +++ b/tests/unit/auth-policy.test.ts @@ -18,12 +18,13 @@ import { validateOobConfig } from '#/server/config/oob-schema.ts' const config = validateOobConfig(buildRawOobConfig()) const anonymous: Viewer = anonymousViewer() -const schonherz: Viewer = viewerFromIdentity( - { sub: 's1', username: 'schonherz-dev', groups: ['schonherz-dev'] }, - config.authentik, -) +const schonherz: Viewer = { + level: 'schonherz', + sub: 's1', + username: 'schonherz-dev', +} const member: Viewer = viewerFromIdentity( - { sub: 't1', username: 'tag-dev', groups: ['tag-dev'] }, + { sub: 't1', username: 'tag-dev', groups: ['Stúdiós'] }, config.authentik, ) @@ -31,25 +32,41 @@ const leadership: Viewer = viewerFromIdentity( { sub: 'v1', username: 'vezetoseg-dev', - groups: ['tag-dev', 'vezetoseg-dev'], + groups: ['Vezetőség'], }, config.authentik, ) -// Csak vezetőségi csoport, tag nélkül: NINCS adminjog. -const leadershipWithoutMember: Viewer = viewerFromIdentity( - { sub: 'v2', username: 'elokuldott', groups: ['vezetoseg-dev'] }, +const admin: Viewer = viewerFromIdentity( + { sub: 'a1', username: 'admin', groups: ['Admin'] }, config.authentik, ) describe('nézői szint felismerése csoportokból', () => { - it('a négy szint helyesen feloldható', () => { + it('az Authentik csoportok helyesen oldódnak fel', () => { expect(anonymous.level).toBe('anonymous') - expect(schonherz.level).toBe('schonherz') + expect( + viewerFromIdentity( + { sub: 's1', username: 'külsős', groups: ['más-csoport'] }, + config.authentik, + ).level, + ).toBe('anonymous') expect(member.level).toBe('member') expect(leadership.level).toBe('leadership') - expect(leadershipWithoutMember.level).toBe('anonymous') + expect(admin.level).toBe('leadership') }) + it.each(['Stúdiós', 'Stúdiós jelölt', 'Stúdiós jelölt-jelölt', 'Öregtag'])( + '%s csoport BSS-tagságot ad', + (group) => { + expect( + viewerFromIdentity( + { sub: group, username: group, groups: [group] }, + config.authentik, + ).level, + ).toBe('member') + }, + ) + it('a vezetőségi jog magában foglalja a tagjogot', () => { for (const capability of Object.values(can)) { if (capability(member)) { @@ -142,6 +159,6 @@ describe('szintrendezés', () => { expect(atLeast(schonherz, 'member')).toBe(false) expect(atLeast(member, 'leadership')).toBe(false) expect(atLeast(leadership, 'member')).toBe(true) - expect(isLeadership(leadershipWithoutMember)).toBe(false) + expect(isLeadership(admin)).toBe(true) }) }) diff --git a/tests/unit/local-bootstrap.test.ts b/tests/unit/local-bootstrap.test.ts index b3076ff..7cc1d32 100644 --- a/tests/unit/local-bootstrap.test.ts +++ b/tests/unit/local-bootstrap.test.ts @@ -36,20 +36,20 @@ describe('lokális Authentik bootstrap', () => { expect(usernames).toContain('schonherz-dev') expect(usernames).toContain('tag-dev') expect(usernames).toContain('vezetoseg-dev') + expect(usernames).toContain('admin-dev') const tag = LOCAL_USERS.find((user) => user.username === 'tag-dev')! - expect(tag.groups).toContain('bss-tag') + expect(tag.groups).toContain('Stúdiós') const leadership = LOCAL_USERS.find( (user) => user.username === 'vezetoseg-dev', )! - expect(leadership.groups).toContain('bss-tag') - expect(leadership.groups).toContain('bss-vezetoseg') + expect(leadership.groups).toEqual(['Vezetőség']) const schonherz = LOCAL_USERS.find( (user) => user.username === 'schonherz-dev', )! - expect(schonherz.groups).not.toContain('bss-tag') + expect(schonherz.groups).toEqual([]) const statuses = LOCAL_USERS.map((user) => user.status) expect(statuses).toEqual( @@ -59,7 +59,6 @@ describe('lokális Authentik bootstrap', () => { 'stúdiósjelölt-jelölt', 'aktív öregtag', 'archivált öregtag', - 'dolgozott még velünk', ]), ) }) @@ -72,8 +71,8 @@ describe('lokális Authentik bootstrap', () => { } const validated = validateOobConfig(renderOobConfig(secrets)) expect(validated.authentik.clientId).toBe('bss-stack-local') - expect(validated.authentik.groups.tag).toBe('bss-tag') - expect(validated.authentik.claims.username).toBe('preferred_username') + expect(validated.authentik.groups.studio).toBe('Stúdiós') + expect(validated.authentik.claims.sub).toBe('sub') }) it('a blueprint tartalmazza a csoportokat, felhasználókat és a providert', () => { @@ -84,7 +83,14 @@ describe('lokális Authentik bootstrap', () => { } const yaml = renderBlueprint(secrets) - for (const group of ['bss-schonherz', 'bss-tag', 'bss-vezetoseg']) { + for (const group of [ + 'Admin', + 'Stúdiós', + 'Stúdiós jelölt', + 'Stúdiós jelölt-jelölt', + 'Vezetőség', + 'Öregtag', + ]) { expect(yaml).toContain(`name: ${group}`) } for (const user of LOCAL_USERS) { diff --git a/tests/unit/member-ingest.test.ts b/tests/unit/member-ingest.test.ts new file mode 100644 index 0000000..bfd88a8 --- /dev/null +++ b/tests/unit/member-ingest.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest' +import { parseMemberIngestPayload } from '#/server/members/ingest.ts' + +const statuses = [ + 'MEMBER_CANDIDATE_CANDIDATE', + 'MEMBER_CANDIDATE', + 'MEMBER', + 'ACTIVE_ALUMNI', + 'ALUMNI', +] as const + +function payload(membershipStatus: string) { + return { + operations: [ + { + op: 'upsert', + member: { + sub: 'sub-1', + username: 'tag', + fullName: 'Teszt Tag', + membershipStatus, + }, + }, + ], + } +} + +describe('membership webhook tagsági státusz', () => { + it.each(statuses)('%s elfogadott', (status) => { + const parsed = parseMemberIngestPayload(payload(status)) + const operation = parsed.operations[0] + expect(operation.op).toBe('upsert') + if (operation.op === 'upsert') { + expect(operation.member.membershipStatus).toBe(status) + } + }) + + it('a korábbi kisbetűs értékeket elutasítja', () => { + expect(() => parseMemberIngestPayload(payload('studio_member'))).toThrow( + /membershipStatus/, + ) + }) +}) diff --git a/tests/unit/oob-config.test.ts b/tests/unit/oob-config.test.ts index f3c7ee6..efbb4b3 100644 --- a/tests/unit/oob-config.test.ts +++ b/tests/unit/oob-config.test.ts @@ -10,7 +10,7 @@ describe('OOB config validáció', () => { const config = validateOobConfig(buildRawOobConfig()) expect(config.authentik.clientId).toBe('bss-stack-local') expect(config.authentik.claims.sub).toBe('sub') - expect(config.authentik.groups.tag).toBe('tag-dev') + expect(config.authentik.groups.studio).toBe('Stúdiós') expect(config.media.host).toBe('https://v.bsstudio.hu') expect(config.media.allowedHosts).toEqual(['v.bsstudio.hu']) }) @@ -40,9 +40,8 @@ describe('OOB config validáció', () => { const raw = buildRawOobConfig({ authentik: { groups: { - schonherz: 'ugyanaz', - tag: 'ugyanaz', - vezetoseg: 'vezetoseg-dev', + admin: 'ugyanaz', + studio: 'ugyanaz', }, }, })