From dcb4db3d191bdacf583a7ce7b15eb4ad565400ed Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Fri, 10 Jul 2026 11:23:30 +0200 Subject: [PATCH 01/15] fix(shared): make toTableName snake_case-aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit camelCase entity types (e.g. a fork's courseSection) derived a wrong physical table name (courseSections instead of course_sections), breaking the yjs relay's derived SQL. Snake_case first via toColumnName — identical output for every existing single-word entity type. Locked by an explicit convention test. From projectcampus (CELLA-CHANGES #09). Co-Authored-By: Claude Fable 5 --- backend/src/schemas/schema-naming-conventions.test.ts | 7 +++++++ shared/src/permissions/schema-naming.ts | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/src/schemas/schema-naming-conventions.test.ts b/backend/src/schemas/schema-naming-conventions.test.ts index 2fc10bd5d..25c86a6b5 100644 --- a/backend/src/schemas/schema-naming-conventions.test.ts +++ b/backend/src/schemas/schema-naming-conventions.test.ts @@ -22,6 +22,13 @@ describe('yjs schema-naming conventions match the drizzle schema', () => { } }); + it('snake_cases camelCase entity types (multi-word forks)', () => { + // No cella entity type is camelCase, but forks add them (e.g. courseSection); + // the yjs relay would derive a wrong physical name without snake_casing here. + expect(toTableName('courseSection')).toBe('course_sections'); + expect(toTableName('task')).toBe('tasks'); + }); + it('each entity table name and read-columns follow the convention', () => { for (const [entityType, table] of Object.entries(entityTables)) { expect(toTableName(entityType), `table name for "${entityType}"`).toBe(getTableName(table)); diff --git a/shared/src/permissions/schema-naming.ts b/shared/src/permissions/schema-naming.ts index d72c2ac56..e785c6e70 100644 --- a/shared/src/permissions/schema-naming.ts +++ b/shared/src/permissions/schema-naming.ts @@ -10,5 +10,8 @@ import type { EntityType } from '../../types'; */ export const toColumnName = (key: string): string => key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`); -/** Physical table name for an entity/resource type (drizzle convention: `type + 's'`). e.g. `task` → `tasks`. */ -export const toTableName = (entityType: EntityType | string): string => `${entityType}s`; +/** + * Physical table name for an entity/resource type (drizzle convention: snake_cased `type + 's'`). + * e.g. `task` → `tasks`, `courseSection` → `course_sections`. + */ +export const toTableName = (entityType: EntityType | string): string => `${toColumnName(entityType)}s`; From fff14fcb785e665aafea92c3325d8f186f1378b6 Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Fri, 10 Jul 2026 11:24:56 +0200 Subject: [PATCH 02/15] fix(backend): annotate tuple returns in rls-security flatMaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two ImmutableEntityCase[] assignments infer string[][] from their flatMap callbacks — an error under plain tsc while tsgo (repo default) accepts it, so CI stays green while 'npx tsc' fails. Explicit return annotations on the map callbacks fix the inference. From projectcampus (CELLA-CHANGES #08). Co-Authored-By: Claude Fable 5 --- backend/tests/integration/rls-security.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/tests/integration/rls-security.test.ts b/backend/tests/integration/rls-security.test.ts index 2800e162c..b24e7a32d 100644 --- a/backend/tests/integration/rls-security.test.ts +++ b/backend/tests/integration/rls-security.test.ts @@ -896,7 +896,7 @@ const rlsSuiteReady = await (async () => { const tableName = getTableName(entityTables[entityType as keyof typeof entityTables]); const rowId = seededContextRowIdsByTable.get(tableName); if (!rowId) return []; - return baseImmutableColumns.map((col) => [tableName, col, entityType, rowId]); + return baseImmutableColumns.map((col): ImmutableEntityCase => [tableName, col, entityType, rowId]); }); const seededProductRowIdsByTable = new Map( @@ -908,7 +908,9 @@ const rlsSuiteReady = await (async () => { const tableName = getTableName(entityTables[entityType as keyof typeof entityTables]); const rowId = seededProductRowIdsByTable.get(tableName); if (!rowId) return []; - return [...baseImmutableColumns, 'organization_id'].map((col) => [tableName, col, entityType, rowId]); + return [...baseImmutableColumns, 'organization_id'].map( + (col): ImmutableEntityCase => [tableName, col, entityType, rowId], + ); }); const membershipCases: [string, string][] = membershipImmutableColumns.map((col) => ['memberships', col]); From 2b06a1f7359fa72a22f1eecb1512f27926e7200b Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Fri, 10 Jul 2026 11:29:11 +0200 Subject: [PATCH 03/15] chore(backend): generate pending attachments index-drop migration The attachments organization_id/created_at index was removed from the drizzle schema (via #862/#863) without regenerating migrations; any fresh 'pnpm generate' emits this diff as a side effect. Landing it standalone so it stops photobombing unrelated migration work. Co-Authored-By: Claude Fable 5 --- .../migration.sql | 1 + .../snapshot.json | 5325 +++++++++++++++++ 2 files changed, 5326 insertions(+) create mode 100644 backend/drizzle/20260710092603_uneven_dakota_north/migration.sql create mode 100644 backend/drizzle/20260710092603_uneven_dakota_north/snapshot.json diff --git a/backend/drizzle/20260710092603_uneven_dakota_north/migration.sql b/backend/drizzle/20260710092603_uneven_dakota_north/migration.sql new file mode 100644 index 000000000..3fb8a4783 --- /dev/null +++ b/backend/drizzle/20260710092603_uneven_dakota_north/migration.sql @@ -0,0 +1 @@ +DROP INDEX "attachments_organization_id_created_at_index"; \ No newline at end of file diff --git a/backend/drizzle/20260710092603_uneven_dakota_north/snapshot.json b/backend/drizzle/20260710092603_uneven_dakota_north/snapshot.json new file mode 100644 index 000000000..add298d6d --- /dev/null +++ b/backend/drizzle/20260710092603_uneven_dakota_north/snapshot.json @@ -0,0 +1,5325 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "2be899a2-c3e4-455d-adb6-eea7232d159d", + "prevIds": [ + "2612cce1-6377-4fe5-8ced-d528b8b0a1e5" + ], + "ddl": [ + { + "isRlsEnabled": false, + "name": "activities", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": true, + "name": "attachments", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "oauth_accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "passkeys", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "rate_limits", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "totps", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "domains", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "context_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "product_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "inactive_memberships", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "memberships", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organizations", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "requests", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "seen_by", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "system_roles", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tenants", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "emails", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "unsubscribe_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "users", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": true, + "name": "yjs_documents", + "entityType": "tables", + "schema": "public" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "table_name", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subject_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "changed_fields", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "stx", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'attachment'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'New attachment'", + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "stx", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "keywords", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "seq", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "public", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bucket_name", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "group_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "filename", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "converted_content_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "size", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "original_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "converted_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credential_id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "public_key", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_name", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'desktop'", + "generated": null, + "identity": null, + "name": "device_type", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_os", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "browser", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name_on_device", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "points", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expire", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'regular'", + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_name", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'desktop'", + "generated": null, + "identity": null, + "name": "device_type", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_os", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "browser", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "auth_strategy", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_hash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_subnet_hash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(2)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_country", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_asn", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "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": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "single_use_token", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "oauth_account_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inactive_membership_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "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": "tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "invoked_at", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "domain", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verification_token", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_checked_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_key", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "counts", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "view_count", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_viewed_at", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_type", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rejected_at", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_type", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "archived", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "muted", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "double precision", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "display_order", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'organization'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "banner_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "short_name", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "country", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "timezone", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'en'", + "generated": null, + "identity": null, + "name": "default_language", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[\"en\"]'", + "generated": null, + "identity": null, + "name": "languages", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "notification_email", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "color", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "logo_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "website_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "welcome_text", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "auth_strategies", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "chat_support", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'active'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{\"quotas\":{\"user\":1000,\"organization\":5,\"attachment\":100},\"rateLimits\":{\"apiPointsPerHour\":1000}}'", + "generated": null, + "identity": null, + "name": "restrictions", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_id", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'none'", + "generated": null, + "identity": null, + "name": "subscription_status", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_plan", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "json", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_data", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_seen_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_started_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_sign_in_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'user'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "banner_url", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "mfa_required", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "first_name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'en'", + "generated": null, + "identity": null, + "name": "language", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "newsletter", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "user_flags", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_edited_by", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_org_id_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_entity_type_subject_id_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_organization_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_updated_by_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "group_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_group_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "oauth_accounts_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "passkeys_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "credential_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "passkeys_credential_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_secret_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "ip_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_user_id_ip_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "ip_subnet_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_ip_subnet_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_secret_type_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "single_use_token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_single_use_token_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "totps_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "totps" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "domains_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "domain", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "domains_domain_idx", + "entityType": "indexes", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_email_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "rejected_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_org_pending_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_updated_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "context_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "role", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_context_org_role_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_org_user_tenant_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_name_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_updated_by_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "requests_emails", + "entityType": "indexes", + "schema": "public", + "table": "requests" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "requests_created_at", + "entityType": "indexes", + "schema": "public", + "table": "requests" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "context_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_user_context_type_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_entity_id_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_status_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "subscription_status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_subscription_status_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "emails_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "emails" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "unsubscribe_tokens_secret_idx", + "entityType": "indexes", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "unsubscribe_tokens_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_name_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_email_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_yjs_docs_tenant", + "entityType": "indexes", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_yjs_docs_org", + "entityType": "indexes", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "attachments_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "deleted_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_deleted_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "attachments_Ytb3N4m0pWsH_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_accounts_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "passkeys_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "oauth_account_id" + ], + "schemaTo": "public", + "tableTo": "oauth_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_oauth_account_id_oauth_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "totps_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "totps" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "domains_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "inactive_memberships_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_gmmCA2ACknk4_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "memberships_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "memberships_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "memberships_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "memberships_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "memberships_Yta3VHtyCTj4_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "organizations_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "organizations_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "organizations_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "seen_by_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "system_roles_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "system_roles" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "tenants_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "emails_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "emails" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "unsubscribe_tokens_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "users_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "yjs_documents_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "yjs_documents_HY8MgE0sbYNM_fkey", + "entityType": "fks", + "schema": "public", + "table": "yjs_documents" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "activities_pkey", + "entityType": "pks", + "schema": "public", + "table": "activities" + }, + { + "columns": [ + "id", + "expires_at" + ], + "nameExplicit": false, + "name": "sessions_pkey", + "entityType": "pks", + "schema": "public", + "table": "sessions" + }, + { + "columns": [ + "id", + "expires_at" + ], + "nameExplicit": false, + "name": "tokens_pkey", + "entityType": "pks", + "schema": "public", + "table": "tokens" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "seen_by_pkey", + "entityType": "pks", + "schema": "public", + "table": "seen_by" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "unsubscribe_tokens_pkey", + "entityType": "pks", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "columns": [ + "entity_type", + "entity_id" + ], + "nameExplicit": false, + "name": "yjs_documents_pkey", + "entityType": "pks", + "schema": "public", + "table": "yjs_documents" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "attachments_pkey", + "schema": "public", + "table": "attachments", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "oauth_accounts_pkey", + "schema": "public", + "table": "oauth_accounts", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "passkeys_pkey", + "schema": "public", + "table": "passkeys", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "rate_limits_pkey", + "schema": "public", + "table": "rate_limits", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "totps_pkey", + "schema": "public", + "table": "totps", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "domains_pkey", + "schema": "public", + "table": "domains", + "entityType": "pks" + }, + { + "columns": [ + "context_key" + ], + "nameExplicit": false, + "name": "context_counters_pkey", + "schema": "public", + "table": "context_counters", + "entityType": "pks" + }, + { + "columns": [ + "entity_id" + ], + "nameExplicit": false, + "name": "product_counters_pkey", + "schema": "public", + "table": "product_counters", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "inactive_memberships_pkey", + "schema": "public", + "table": "inactive_memberships", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "memberships_pkey", + "schema": "public", + "table": "memberships", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "organizations_pkey", + "schema": "public", + "table": "organizations", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "requests_pkey", + "schema": "public", + "table": "requests", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "system_roles_pkey", + "schema": "public", + "table": "system_roles", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tenants_pkey", + "schema": "public", + "table": "tenants", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "emails_pkey", + "schema": "public", + "table": "emails", + "entityType": "pks" + }, + { + "columns": [ + "user_id" + ], + "nameExplicit": false, + "name": "user_counters_pkey", + "schema": "public", + "table": "user_counters", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "users_pkey", + "schema": "public", + "table": "users", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": [ + "provider", + "provider_user_id", + "email" + ], + "nullsNotDistinct": false, + "name": "oauth_accounts_provider_provider_user_id_email_unique", + "entityType": "uniques", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "email", + "context_id" + ], + "nullsNotDistinct": false, + "name": "inactive_memberships_tenant_email_ctx", + "entityType": "uniques", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "user_id", + "context_id" + ], + "nullsNotDistinct": false, + "name": "memberships_unique_context", + "entityType": "uniques", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "id" + ], + "nullsNotDistinct": false, + "name": "organizations_tenant_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + "user_id", + "entity_id" + ], + "nullsNotDistinct": false, + "name": "seen_by_user_entity_unique", + "entityType": "uniques", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": false, + "columns": [ + "domain" + ], + "nullsNotDistinct": false, + "name": "domains_domain_key", + "schema": "public", + "table": "domains", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "organizations_slug_key", + "schema": "public", + "table": "organizations", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "nullsNotDistinct": false, + "name": "system_roles_user_id_key", + "schema": "public", + "table": "system_roles", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "emails_email_key", + "schema": "public", + "table": "emails", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "users_slug_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "users_email_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + }, + { + "as": "PERMISSIVE", + "for": "SELECT", + "roles": [ + "public" + ], + "using": "\n \n COALESCE(current_setting('app.tenant_id', true), '') != ''\n AND \"attachments\".\"tenant_id\" = current_setting('app.tenant_id', true)::text\n\n AND (\"attachments\".\"deleted_at\" IS NULL OR current_setting('app.include_deleted', true) = 'true')\n ", + "withCheck": null, + "name": "attachments_select_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "INSERT", + "roles": [ + "public" + ], + "using": null, + "withCheck": "true", + "name": "attachments_insert_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "UPDATE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "attachments_update_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "DELETE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "attachments_delete_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "SELECT", + "roles": [ + "public" + ], + "using": "\n \n COALESCE(current_setting('app.tenant_id', true), '') != ''\n AND \"yjs_documents\".\"tenant_id\" = current_setting('app.tenant_id', true)::text\n\n \n ", + "withCheck": null, + "name": "yjs_documents_select_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "INSERT", + "roles": [ + "public" + ], + "using": null, + "withCheck": "true", + "name": "yjs_documents_insert_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "UPDATE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "yjs_documents_update_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "DELETE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "yjs_documents_delete_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + } + ], + "renames": [] +} \ No newline at end of file From 06ecb79fe990f0b39b4ac1d36b513fd1d7734b06 Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Fri, 10 Jul 2026 11:29:17 +0200 Subject: [PATCH 04/15] fix(backend): derive RLS no-RLS table list; fail loudly on grant errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two failure modes from projectcampus (CELLA-CHANGES #14): - noRlsProductEntityNames hardcoded 'pages'; a fork without that module emits 'GRANT ... ON pages', which errors. Now filtered by the tables that actually exist. - That error was swallowed by 'EXCEPTION WHEN OTHERS THEN RAISE NOTICE', rolling back ownership, FORCE RLS and EVERY grant in one silent notice — a fresh DB then boots with zero table grants (every request 403s, pg 42501) or without enforced RLS. The handler now re-raises; the legitimate skip (roles not yet created) is already handled before the block. Regenerated rls_setup migration included. Co-Authored-By: Claude Fable 5 --- .../20260710092608_rls_setup/migration.sql | 67 + .../20260710092608_rls_setup/snapshot.json | 5325 +++++++++++++++++ .../scripts/migrations/10-rls.migration.ts | 13 +- 3 files changed, 5402 insertions(+), 3 deletions(-) create mode 100644 backend/drizzle/20260710092608_rls_setup/migration.sql create mode 100644 backend/drizzle/20260710092608_rls_setup/snapshot.json diff --git a/backend/drizzle/20260710092608_rls_setup/migration.sql b/backend/drizzle/20260710092608_rls_setup/migration.sql new file mode 100644 index 000000000..f2a626d33 --- /dev/null +++ b/backend/drizzle/20260710092608_rls_setup/migration.sql @@ -0,0 +1,67 @@ +-- ⚠️ AUTO-GENERATED — DO NOT EDIT THIS FILE DIRECTLY +-- This migration is generated by a script in backend/scripts/migrations/. +-- To make changes, edit the generator script and re-run it. +-- The user will handle migration generation and application. +-- +-- RLS (Row-Level Security) Setup +-- Configures FORCE RLS, table ownership, and grants. +-- Policies are defined in Drizzle schema files using pgPolicy(). +-- RLS enforces tenant-level isolation only; org-level isolation is application-layer (orgGuard). +-- Gracefully skips if required roles are not yet created. + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'runtime_role') THEN + RAISE NOTICE 'Roles not available - skipping RLS setup.'; + RETURN; + END IF; + + BEGIN + -- Table ownership and FORCE RLS + ALTER TABLE attachments OWNER TO admin_role; + ALTER TABLE yjs_documents OWNER TO admin_role; + ALTER TABLE activities OWNER TO admin_role; + + ALTER TABLE attachments FORCE ROW LEVEL SECURITY; + ALTER TABLE yjs_documents FORCE ROW LEVEL SECURITY; + + -- Grants: runtime_role (subject to RLS) + GRANT SELECT, INSERT, UPDATE, DELETE ON attachments TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON yjs_documents TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON organizations TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON memberships TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON inactive_memberships TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON users TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON sessions TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON user_counters TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON tokens TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON passkeys TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON oauth_accounts TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON totps TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON requests TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON unsubscribe_tokens TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON emails TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON rate_limits TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON context_counters TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON seen_by TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON product_counters TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON domains TO runtime_role; + GRANT SELECT, INSERT, UPDATE, DELETE ON tenants TO runtime_role; + GRANT SELECT ON system_roles TO runtime_role; + GRANT SELECT ON activities TO runtime_role; + + -- Grants: admin_role (full access; also used by the CDC worker) + GRANT ALL ON ALL TABLES IN SCHEMA public TO admin_role; + GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO admin_role; + + -- Grants: pg_catalog usage for JSONB operators + GRANT USAGE ON SCHEMA pg_catalog TO runtime_role; + + RAISE NOTICE 'RLS setup complete.'; + EXCEPTION WHEN OTHERS THEN + -- Fail LOUDLY: swallowing this rolled back ownership, FORCE RLS and every grant in + -- one silent NOTICE — the app then boots with no table grants (every request 403s) + -- or, worse, without enforced RLS. + RAISE EXCEPTION 'RLS setup failed: % (SQLSTATE: %)', SQLERRM, SQLSTATE; + END; +END $$; diff --git a/backend/drizzle/20260710092608_rls_setup/snapshot.json b/backend/drizzle/20260710092608_rls_setup/snapshot.json new file mode 100644 index 000000000..add298d6d --- /dev/null +++ b/backend/drizzle/20260710092608_rls_setup/snapshot.json @@ -0,0 +1,5325 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "2be899a2-c3e4-455d-adb6-eea7232d159d", + "prevIds": [ + "2612cce1-6377-4fe5-8ced-d528b8b0a1e5" + ], + "ddl": [ + { + "isRlsEnabled": false, + "name": "activities", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": true, + "name": "attachments", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "oauth_accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "passkeys", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "rate_limits", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "totps", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "domains", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "context_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "product_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "inactive_memberships", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "memberships", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organizations", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "requests", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "seen_by", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "system_roles", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tenants", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "emails", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "unsubscribe_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "users", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": true, + "name": "yjs_documents", + "entityType": "tables", + "schema": "public" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "table_name", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subject_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "changed_fields", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "stx", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'attachment'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'New attachment'", + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "stx", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "keywords", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "seq", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "public", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bucket_name", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "group_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "filename", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "converted_content_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "size", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "original_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "converted_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credential_id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "public_key", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_name", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'desktop'", + "generated": null, + "identity": null, + "name": "device_type", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_os", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "browser", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name_on_device", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "points", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expire", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'regular'", + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_name", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'desktop'", + "generated": null, + "identity": null, + "name": "device_type", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_os", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "browser", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "auth_strategy", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_hash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_subnet_hash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(2)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_country", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_asn", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "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": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "single_use_token", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "oauth_account_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inactive_membership_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "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": "tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "invoked_at", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "domain", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verification_token", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_checked_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_key", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "counts", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "view_count", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_viewed_at", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_type", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rejected_at", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_type", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "archived", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "muted", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "double precision", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "display_order", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'organization'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "banner_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "short_name", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "country", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "timezone", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'en'", + "generated": null, + "identity": null, + "name": "default_language", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[\"en\"]'", + "generated": null, + "identity": null, + "name": "languages", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "notification_email", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "color", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "logo_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "website_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "welcome_text", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "auth_strategies", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "chat_support", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'active'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{\"quotas\":{\"user\":1000,\"organization\":5,\"attachment\":100},\"rateLimits\":{\"apiPointsPerHour\":1000}}'", + "generated": null, + "identity": null, + "name": "restrictions", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_id", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'none'", + "generated": null, + "identity": null, + "name": "subscription_status", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_plan", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "json", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_data", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_seen_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_started_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_sign_in_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'user'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "banner_url", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "mfa_required", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "first_name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'en'", + "generated": null, + "identity": null, + "name": "language", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "newsletter", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "user_flags", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_edited_by", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_org_id_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_entity_type_subject_id_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_organization_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_updated_by_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "group_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_group_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "oauth_accounts_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "passkeys_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "credential_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "passkeys_credential_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_secret_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "ip_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_user_id_ip_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "ip_subnet_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_ip_subnet_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_secret_type_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "single_use_token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_single_use_token_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "totps_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "totps" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "domains_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "domain", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "domains_domain_idx", + "entityType": "indexes", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_email_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "rejected_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_org_pending_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_updated_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "context_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "role", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_context_org_role_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_org_user_tenant_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_name_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_updated_by_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "requests_emails", + "entityType": "indexes", + "schema": "public", + "table": "requests" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "requests_created_at", + "entityType": "indexes", + "schema": "public", + "table": "requests" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "context_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_user_context_type_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_entity_id_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_status_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "subscription_status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_subscription_status_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "emails_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "emails" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "unsubscribe_tokens_secret_idx", + "entityType": "indexes", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "unsubscribe_tokens_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_name_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_email_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_yjs_docs_tenant", + "entityType": "indexes", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_yjs_docs_org", + "entityType": "indexes", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "attachments_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "deleted_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_deleted_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "attachments_Ytb3N4m0pWsH_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_accounts_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "passkeys_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "oauth_account_id" + ], + "schemaTo": "public", + "tableTo": "oauth_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_oauth_account_id_oauth_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "totps_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "totps" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "domains_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "inactive_memberships_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_gmmCA2ACknk4_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "memberships_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "memberships_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "memberships_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "memberships_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "memberships_Yta3VHtyCTj4_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "organizations_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "organizations_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "organizations_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "seen_by_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "system_roles_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "system_roles" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "tenants_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "emails_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "emails" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "unsubscribe_tokens_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "users_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "yjs_documents_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "yjs_documents_HY8MgE0sbYNM_fkey", + "entityType": "fks", + "schema": "public", + "table": "yjs_documents" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "activities_pkey", + "entityType": "pks", + "schema": "public", + "table": "activities" + }, + { + "columns": [ + "id", + "expires_at" + ], + "nameExplicit": false, + "name": "sessions_pkey", + "entityType": "pks", + "schema": "public", + "table": "sessions" + }, + { + "columns": [ + "id", + "expires_at" + ], + "nameExplicit": false, + "name": "tokens_pkey", + "entityType": "pks", + "schema": "public", + "table": "tokens" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "seen_by_pkey", + "entityType": "pks", + "schema": "public", + "table": "seen_by" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "unsubscribe_tokens_pkey", + "entityType": "pks", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "columns": [ + "entity_type", + "entity_id" + ], + "nameExplicit": false, + "name": "yjs_documents_pkey", + "entityType": "pks", + "schema": "public", + "table": "yjs_documents" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "attachments_pkey", + "schema": "public", + "table": "attachments", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "oauth_accounts_pkey", + "schema": "public", + "table": "oauth_accounts", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "passkeys_pkey", + "schema": "public", + "table": "passkeys", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "rate_limits_pkey", + "schema": "public", + "table": "rate_limits", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "totps_pkey", + "schema": "public", + "table": "totps", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "domains_pkey", + "schema": "public", + "table": "domains", + "entityType": "pks" + }, + { + "columns": [ + "context_key" + ], + "nameExplicit": false, + "name": "context_counters_pkey", + "schema": "public", + "table": "context_counters", + "entityType": "pks" + }, + { + "columns": [ + "entity_id" + ], + "nameExplicit": false, + "name": "product_counters_pkey", + "schema": "public", + "table": "product_counters", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "inactive_memberships_pkey", + "schema": "public", + "table": "inactive_memberships", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "memberships_pkey", + "schema": "public", + "table": "memberships", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "organizations_pkey", + "schema": "public", + "table": "organizations", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "requests_pkey", + "schema": "public", + "table": "requests", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "system_roles_pkey", + "schema": "public", + "table": "system_roles", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tenants_pkey", + "schema": "public", + "table": "tenants", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "emails_pkey", + "schema": "public", + "table": "emails", + "entityType": "pks" + }, + { + "columns": [ + "user_id" + ], + "nameExplicit": false, + "name": "user_counters_pkey", + "schema": "public", + "table": "user_counters", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "users_pkey", + "schema": "public", + "table": "users", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": [ + "provider", + "provider_user_id", + "email" + ], + "nullsNotDistinct": false, + "name": "oauth_accounts_provider_provider_user_id_email_unique", + "entityType": "uniques", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "email", + "context_id" + ], + "nullsNotDistinct": false, + "name": "inactive_memberships_tenant_email_ctx", + "entityType": "uniques", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "user_id", + "context_id" + ], + "nullsNotDistinct": false, + "name": "memberships_unique_context", + "entityType": "uniques", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "id" + ], + "nullsNotDistinct": false, + "name": "organizations_tenant_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + "user_id", + "entity_id" + ], + "nullsNotDistinct": false, + "name": "seen_by_user_entity_unique", + "entityType": "uniques", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": false, + "columns": [ + "domain" + ], + "nullsNotDistinct": false, + "name": "domains_domain_key", + "schema": "public", + "table": "domains", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "organizations_slug_key", + "schema": "public", + "table": "organizations", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "nullsNotDistinct": false, + "name": "system_roles_user_id_key", + "schema": "public", + "table": "system_roles", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "emails_email_key", + "schema": "public", + "table": "emails", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "users_slug_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "users_email_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + }, + { + "as": "PERMISSIVE", + "for": "SELECT", + "roles": [ + "public" + ], + "using": "\n \n COALESCE(current_setting('app.tenant_id', true), '') != ''\n AND \"attachments\".\"tenant_id\" = current_setting('app.tenant_id', true)::text\n\n AND (\"attachments\".\"deleted_at\" IS NULL OR current_setting('app.include_deleted', true) = 'true')\n ", + "withCheck": null, + "name": "attachments_select_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "INSERT", + "roles": [ + "public" + ], + "using": null, + "withCheck": "true", + "name": "attachments_insert_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "UPDATE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "attachments_update_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "DELETE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "attachments_delete_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "SELECT", + "roles": [ + "public" + ], + "using": "\n \n COALESCE(current_setting('app.tenant_id', true), '') != ''\n AND \"yjs_documents\".\"tenant_id\" = current_setting('app.tenant_id', true)::text\n\n \n ", + "withCheck": null, + "name": "yjs_documents_select_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "INSERT", + "roles": [ + "public" + ], + "using": null, + "withCheck": "true", + "name": "yjs_documents_insert_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "UPDATE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "yjs_documents_update_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "DELETE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "yjs_documents_delete_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/backend/scripts/migrations/10-rls.migration.ts b/backend/scripts/migrations/10-rls.migration.ts index 95c90b938..5c109ea29 100644 --- a/backend/scripts/migrations/10-rls.migration.ts +++ b/backend/scripts/migrations/10-rls.migration.ts @@ -29,8 +29,12 @@ async function run() { }); const membershipTableNames = [getTableName(membershipsTable), getTableName(inactiveMembershipsTable)]; - // Pages have no RLS: parentless, always public, and protected by sysAdminGuard. - const noRlsProductEntityNames = ['pages']; + // Products excluded from RLS (parentless, always public, protected by sysAdminGuard). + // Filtered by the tables that actually exist: a fork without these modules must not + // emit grants for nonexistent tables — one failed statement rolls back the whole + // grant block (see the exception handler below). + const noRlsCandidates = ['pages']; + const noRlsProductEntityNames = noRlsCandidates.filter((name) => (entityTableNames as string[]).includes(name)); // Only product entity tables + yjs_documents still use RLS (excluding pages) const additionalRlsTables = [getTableName(yjsDocumentsTable)]; @@ -101,7 +105,10 @@ ${readOnlyTables.map((t) => ` GRANT SELECT ON ${t} TO runtime_role;`).join('\ RAISE NOTICE 'RLS setup complete.'; EXCEPTION WHEN OTHERS THEN - RAISE NOTICE 'RLS setup failed: %. Skipping - RLS will not be enforced.', SQLERRM; + -- Fail LOUDLY: swallowing this rolled back ownership, FORCE RLS and every grant in + -- one silent NOTICE — the app then boots with no table grants (every request 403s) + -- or, worse, without enforced RLS. + RAISE EXCEPTION 'RLS setup failed: % (SQLSTATE: %)', SQLERRM, SQLSTATE; END; END $$; `; From 3fb01fe9dbe0e3fe75a2813de905a03687d8ad7c Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Fri, 10 Jul 2026 11:32:38 +0200 Subject: [PATCH 05/15] feat(permissions): validate policy completeness at config time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The engine is strict at runtime: the first membership whose (context, role) has no policy row for the checked subject throws — a request-time 500 that only surfaces when a real user with that role hits that subject. Projectcampus shipped several such gaps that survived until member-session e2e tests ran (CELLA-CHANGES #17). configurePermissions now asserts at config time that every subject with declared rows covers every role of every context in its ancestor chain; all-zero rows (contexts.x.role({})) express 'no access' explicitly. Subjects with no case are skipped (they fail fast with a clear engine error). configureAccessPolicies and configureWidePermissions (deliberately partial test fixtures) opt out. Note for forks: configs with gaps now fail on startup after upgrading — intended; add the missing all-zero rows. Co-Authored-By: Claude Fable 5 --- shared/src/permissions/access-policies.ts | 65 ++++++++++++++++++- .../permissions/policy-completeness.test.ts | 50 ++++++++++++++ shared/src/testing/wide-fixture.ts | 2 + 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 shared/src/permissions/policy-completeness.test.ts diff --git a/shared/src/permissions/access-policies.ts b/shared/src/permissions/access-policies.ts index 8b00f0e72..de0f4fa0d 100644 --- a/shared/src/permissions/access-policies.ts +++ b/shared/src/permissions/access-policies.ts @@ -98,6 +98,15 @@ export const configurePermissions = ( entityTypes: readonly EntityType[], callback: AccessPolicyCallback, topology?: PermissionTopology, + options?: { + /** + * Assert at config time that every subject with declared rows covers EVERY role of + * EVERY context in its ancestor chain (default true — see + * `validatePolicyCompleteness`). Test helpers with deliberately partial fixtures + * opt out. + */ + validateCompleteness?: boolean; + }, ): PermissionsConfigResult => { const policies: AccessPolicies = {}; const publicReadGrants: PublicReadGrants = {}; @@ -106,6 +115,7 @@ export const configurePermissions = ( // Topology defaults to the app's real config; tests pass a synthetic one (wide-fixture.ts). const { entityActions, contextEntityTypes, getRoles, getHostType, getParent } = resolveTopology(topology); + const validateCompleteness = options?.validateCompleteness ?? true; const permissionableTypes = entityTypes.filter( (type): type is ContextEntityType | ProductEntityType => type !== 'user', @@ -154,10 +164,62 @@ export const configurePermissions = ( } validatePublicReadGrants(publicReadGrants, getParent); + if (validateCompleteness) { + validatePolicyCompleteness(policies, { contextEntityTypes, getRoles, getParent }); + } return { accessPolicies: policies, publicReadGrants, rowRestrictions, hostDelegation }; }; +/** + * The engine is strict at runtime: the first membership whose (context, role) has no + * policy row for the checked subject THROWS — a request-time 500 that only surfaces + * when a real user with that role hits that subject. Enforce the same rule at config + * time instead: every subject that declares ANY rows must declare a row for every role + * of every context in its ancestor chain — an all-zero row (`contexts.x.role({})`) + * expresses "no access" explicitly. Subjects with no case at all are skipped (they + * fail fast with a clear engine error on their first check). + */ +const validatePolicyCompleteness = ( + policies: AccessPolicies, + topology: { + contextEntityTypes: readonly ContextEntityType[]; + getRoles: (contextType: string) => readonly string[]; + getParent: (type: string) => string | null; + }, +): void => { + const { contextEntityTypes, getRoles, getParent } = topology; + + /** The subject's context chain: self (for context entities) plus every ancestor. */ + const chainOf = (subject: string): ContextEntityType[] => { + const chain: ContextEntityType[] = []; + let current: string | null = contextEntityTypes.includes(subject as ContextEntityType) + ? subject + : getParent(subject); + while (current) { + chain.push(current as ContextEntityType); + current = getParent(current); + } + return chain; + }; + + const missing: string[] = []; + for (const [subject, entries] of Object.entries(policies)) { + const declared = new Set(entries.map((entry) => `${entry.contextType}:${entry.role}`)); + for (const contextType of chainOf(subject)) { + for (const role of getRoles(contextType)) { + if (!declared.has(`${contextType}:${role}`)) missing.push(`"${subject}": ${contextType}.${role}`); + } + } + } + + if (missing.length > 0) { + throw new Error( + `[Permission] Incomplete policy: every subject with declared rows needs a row for every role of every context in its chain (all-zero rows express "no access"). Missing:\n ${missing.join('\n ')}`, + ); + } +}; + /** * A parent-dependent public grant only works if the parent itself can become public: * its own grant must include self-publication. @@ -189,7 +251,8 @@ export const configureAccessPolicies = ( callback: AccessPolicyCallback, topology?: PermissionTopology, ): AccessPolicies => { - return configurePermissions(entityTypes, callback, topology).accessPolicies; + // Raw/test path: synthetic policy sets are deliberately partial, so no completeness check. + return configurePermissions(entityTypes, callback, topology, { validateCompleteness: false }).accessPolicies; }; /** diff --git a/shared/src/permissions/policy-completeness.test.ts b/shared/src/permissions/policy-completeness.test.ts new file mode 100644 index 000000000..93a4137d6 --- /dev/null +++ b/shared/src/permissions/policy-completeness.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; +import type { EntityType } from '../../types'; +import { wideEntityTypes, wideTopology } from '../testing/wide-fixture'; +import type { AccessPolicyCallback } from './types'; +import { configurePermissions } from './access-policies'; + +/** + * Config-time policy completeness (mirrors the engine's strict runtime rule): a subject + * with ANY declared rows must declare one for every role of every context in its chain, + * or the engine throws a request-time 500 the first time such a membership appears. + */ +describe('configurePermissions completeness validation', () => { + const configure = (callback: AccessPolicyCallback, validateCompleteness = true) => + configurePermissions(wideEntityTypes as unknown as readonly EntityType[], callback, wideTopology, { + validateCompleteness, + }); + + it('rejects a subject missing a role row in its chain', () => { + expect(() => + configure(({ subject, contexts }) => { + if (subject.name !== 'attachment') return; + // organization has admin + member in the wide fixture; member row omitted + contexts.organization.admin({ create: 1, read: 1, update: 1, delete: 1 }); + }), + ).toThrow(/Incomplete policy[\s\S]*attachment[\s\S]*organization\.member/); + }); + + it('accepts all-zero rows as explicit "no access"', () => { + expect(() => + configure(({ subject, contexts }) => { + if (subject.name !== 'organization') return; + contexts.organization.admin({ read: 1, update: 1, delete: 1 }); + contexts.organization.member({}); + }), + ).not.toThrow(); + }); + + it('skips subjects with no declared case at all', () => { + expect(() => configure(() => {})).not.toThrow(); + }); + + it('can be opted out for deliberately partial fixtures', () => { + expect(() => + configure(({ subject, contexts }) => { + if (subject.name !== 'attachment') return; + contexts.organization.admin({ read: 1 }); + }, false), + ).not.toThrow(); + }); +}); diff --git a/shared/src/testing/wide-fixture.ts b/shared/src/testing/wide-fixture.ts index 3bfe446e2..1717b46da 100644 --- a/shared/src/testing/wide-fixture.ts +++ b/shared/src/testing/wide-fixture.ts @@ -100,6 +100,8 @@ export const configureWidePermissions = (callback: WideAccessPolicyCallback): Pe wideEntityTypes as unknown as readonly EntityType[], callback as unknown as AccessPolicyCallback, wideTopology, + // Test fixtures are deliberately partial; completeness is the app config's contract + { validateCompleteness: false }, ); /** Build a membership over a wide context. */ From 2d58c0e1daca6c5f5cc7286d2d930b0bc5581eea Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Fri, 10 Jul 2026 11:39:08 +0200 Subject: [PATCH 06/15] =?UTF-8?q?feat(memberships):=20context=20draft=20mo?= =?UTF-8?q?de=20=E2=80=94=20publishedAt=20+=20deferred=20invites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contexts gain a draft state that defers member invites until publish, dormant for forks that never use it (publishedAt defaults to creation time — the same graceful-degradation pattern as nullableAncestors). Distinct from publicAt (public readability). From projectcampus (CELLA-CHANGES #01–04 / PR-1). - contextEntityColumns: nullable publishedAt timestamp, defaultNow(). - create-memberships: on a draft context, invites for non-top roles are recorded (inactive memberships + tokens) but emails are held and existing users are not direct-added; the most-privileged role (first in the vocabulary) stays live so staff can collaborate in drafts. - inactive_memberships.remindedAt: last email dispatch; reminder emails are throttled to once per 7 days via remindedAt ?? createdAt. - helpers/deferred-invites.ts: dispatchDeferredInvites({contextIds}) — called by a fork's publish operation; rotates invitation tokens (raw secrets are unrecoverable and may have expired), sends held emails, stamps remindedAt, honors the throttle. - me-queries findPendingInvitations: invites whose context is unpublished stay hidden from the invitee (staff-side pending lists unchanged). Known pre-existing behavior made visible by the throttle work: re-inviting a truly-new token invitee (no emails row) re-enters the new-user path where onConflictDoNothing suppresses the duplicate — no email is re-sent, but an orphan token row is minted per attempt. Co-Authored-By: Claude Fable 5 --- .../migration.sql | 2 + .../snapshot.json | 5351 +++++++++++++++++ .../src/db/utils/context-entity-columns.ts | 9 +- backend/src/modules/me/me-queries.ts | 5 +- .../memberships/helpers/deferred-invites.ts | 114 + .../memberships/inactive-memberships-db.ts | 6 + .../modules/memberships/memberships-mocks.ts | 1 + .../memberships/memberships-queries.ts | 37 +- .../operations/create-memberships.ts | 42 +- .../organization/organization-mocks.ts | 1 + .../tests/invitations/draft-invites.test.ts | 189 + 11 files changed, 5749 insertions(+), 8 deletions(-) create mode 100644 backend/drizzle/20260710093820_jazzy_colossus/migration.sql create mode 100644 backend/drizzle/20260710093820_jazzy_colossus/snapshot.json create mode 100644 backend/src/modules/memberships/helpers/deferred-invites.ts create mode 100644 backend/tests/invitations/draft-invites.test.ts diff --git a/backend/drizzle/20260710093820_jazzy_colossus/migration.sql b/backend/drizzle/20260710093820_jazzy_colossus/migration.sql new file mode 100644 index 000000000..47e242db6 --- /dev/null +++ b/backend/drizzle/20260710093820_jazzy_colossus/migration.sql @@ -0,0 +1,2 @@ +ALTER TABLE "inactive_memberships" ADD COLUMN "reminded_at" timestamp;--> statement-breakpoint +ALTER TABLE "organizations" ADD COLUMN "published_at" timestamp DEFAULT now(); \ No newline at end of file diff --git a/backend/drizzle/20260710093820_jazzy_colossus/snapshot.json b/backend/drizzle/20260710093820_jazzy_colossus/snapshot.json new file mode 100644 index 000000000..c949c8949 --- /dev/null +++ b/backend/drizzle/20260710093820_jazzy_colossus/snapshot.json @@ -0,0 +1,5351 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "52bb50e2-6b5a-41e7-899e-42ee4bcf341a", + "prevIds": [ + "2be899a2-c3e4-455d-adb6-eea7232d159d" + ], + "ddl": [ + { + "isRlsEnabled": false, + "name": "activities", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": true, + "name": "attachments", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "oauth_accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "passkeys", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "rate_limits", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "totps", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "domains", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "context_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "product_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "inactive_memberships", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "memberships", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "organizations", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "requests", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "seen_by", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "system_roles", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tenants", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "emails", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "unsubscribe_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user_counters", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "users", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": true, + "name": "yjs_documents", + "entityType": "tables", + "schema": "public" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "resource_type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "table_name", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subject_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "changed_fields", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "stx", + "entityType": "columns", + "schema": "public", + "table": "activities" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'attachment'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'New attachment'", + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "stx", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "keywords", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_at", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_by", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "seq", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "public", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bucket_name", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "group_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "filename", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "converted_content_type", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "size", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "original_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "converted_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_key", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "attachments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "oauth_accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credential_id", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "public_key", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_name", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'desktop'", + "generated": null, + "identity": null, + "name": "device_type", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_os", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "browser", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name_on_device", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "passkeys" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "points", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expire", + "entityType": "columns", + "schema": "public", + "table": "rate_limits" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'regular'", + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_name", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'desktop'", + "generated": null, + "identity": null, + "name": "device_type", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_os", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "browser", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "auth_strategy", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_hash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_subnet_hash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(2)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_country", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_asn", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "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": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "single_use_token", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "oauth_account_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inactive_membership_id", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "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": "tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "invoked_at", + "entityType": "columns", + "schema": "public", + "table": "tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "totps" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "domain", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verification_token", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_checked_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "domains" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_key", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "counts", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "context_counters" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "view_count", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_viewed_at", + "entityType": "columns", + "schema": "public", + "table": "product_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_type", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rejected_at", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reminded_at", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "inactive_memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_type", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "archived", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "muted", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "double precision", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "display_order", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "memberships" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'organization'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "banner_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "published_at", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "short_name", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "country", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "timezone", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'en'", + "generated": null, + "identity": null, + "name": "default_language", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[\"en\"]'", + "generated": null, + "identity": null, + "name": "languages", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "notification_email", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "color", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "logo_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "website_url", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "varchar(1000000)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "welcome_text", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "auth_strategies", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "chat_support", + "entityType": "columns", + "schema": "public", + "table": "organizations" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "requests" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "seen_by" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "system_roles" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'active'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "json", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{\"quotas\":{\"user\":1000,\"organization\":5,\"attachment\":100},\"rateLimits\":{\"apiPointsPerHour\":1000}}'", + "generated": null, + "identity": null, + "name": "restrictions", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_id", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'none'", + "generated": null, + "identity": null, + "name": "subscription_status", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_plan", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "json", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subscription_data", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "tenants" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "verified", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "verified_at", + "entityType": "columns", + "schema": "public", + "table": "emails" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_seen_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_started_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_sign_in_at", + "entityType": "columns", + "schema": "public", + "table": "user_counters" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'user'", + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbnail_url", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "banner_url", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "mfa_required", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "first_name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'en'", + "generated": null, + "identity": null, + "name": "language", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "newsletter", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "user_flags", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_by", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(50)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_type", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "varchar(24)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tenant_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "organization_id", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_edited_by", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_org_id_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "activities_entity_type_subject_id_index", + "entityType": "indexes", + "schema": "public", + "table": "activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_organization_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_updated_by_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "group_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "attachments_group_id_index", + "entityType": "indexes", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "oauth_accounts_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "passkeys_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "credential_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "passkeys_credential_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_secret_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "ip_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_user_id_ip_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "ip_subnet_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_ip_subnet_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_secret_type_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "single_use_token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tokens_single_use_token_idx", + "entityType": "indexes", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "totps_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "totps" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "domains_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "domain", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "domains_domain_idx", + "entityType": "indexes", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_email_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "rejected_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "inactive_memberships_org_pending_idx", + "entityType": "indexes", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_created_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_updated_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_tenant_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "context_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "role", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_context_org_role_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "memberships_org_user_tenant_idx", + "entityType": "indexes", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_name_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "organizations_updated_by_index", + "entityType": "indexes", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "requests_emails", + "entityType": "indexes", + "schema": "public", + "table": "requests" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "requests_created_at", + "entityType": "indexes", + "schema": "public", + "table": "requests" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "context_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "entity_type", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_user_context_type_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "entity_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_entity_id_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "seen_by_tenant_id_index", + "entityType": "indexes", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_status_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_created_by_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "subscription_status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tenants_subscription_status_index", + "entityType": "indexes", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "emails_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "emails" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "secret", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "unsubscribe_tokens_secret_idx", + "entityType": "indexes", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "unsubscribe_tokens_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "name", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_name_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_email_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_created_at_index", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tenant_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_yjs_docs_tenant", + "entityType": "indexes", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "organization_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_yjs_docs_org", + "entityType": "indexes", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "attachments_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "deleted_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "attachments_deleted_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "attachments_Ytb3N4m0pWsH_fkey", + "entityType": "fks", + "schema": "public", + "table": "attachments" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "oauth_accounts_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "passkeys_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "passkeys" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "oauth_account_id" + ], + "schemaTo": "public", + "tableTo": "oauth_accounts", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_oauth_account_id_oauth_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tokens_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tokens" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "totps_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "totps" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "domains_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "domains" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "inactive_memberships_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "inactive_memberships_gmmCA2ACknk4_fkey", + "entityType": "fks", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "memberships_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "memberships_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "memberships_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "memberships_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "memberships_Yta3VHtyCTj4_fkey", + "entityType": "fks", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "organizations_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "organizations_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "organizations_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "seen_by_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "system_roles_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "system_roles" + }, + { + "nameExplicit": false, + "columns": [ + "created_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "tenants_created_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tenants" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "emails_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "emails" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "unsubscribe_tokens_user_id_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "nameExplicit": false, + "columns": [ + "updated_by" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "users_updated_by_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id" + ], + "schemaTo": "public", + "tableTo": "tenants", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "yjs_documents_tenant_id_tenants_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "yjs_documents" + }, + { + "nameExplicit": false, + "columns": [ + "tenant_id", + "organization_id" + ], + "schemaTo": "public", + "tableTo": "organizations", + "columnsTo": [ + "tenant_id", + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "yjs_documents_HY8MgE0sbYNM_fkey", + "entityType": "fks", + "schema": "public", + "table": "yjs_documents" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "activities_pkey", + "entityType": "pks", + "schema": "public", + "table": "activities" + }, + { + "columns": [ + "id", + "expires_at" + ], + "nameExplicit": false, + "name": "sessions_pkey", + "entityType": "pks", + "schema": "public", + "table": "sessions" + }, + { + "columns": [ + "id", + "expires_at" + ], + "nameExplicit": false, + "name": "tokens_pkey", + "entityType": "pks", + "schema": "public", + "table": "tokens" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "seen_by_pkey", + "entityType": "pks", + "schema": "public", + "table": "seen_by" + }, + { + "columns": [ + "id", + "created_at" + ], + "nameExplicit": false, + "name": "unsubscribe_tokens_pkey", + "entityType": "pks", + "schema": "public", + "table": "unsubscribe_tokens" + }, + { + "columns": [ + "entity_type", + "entity_id" + ], + "nameExplicit": false, + "name": "yjs_documents_pkey", + "entityType": "pks", + "schema": "public", + "table": "yjs_documents" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "attachments_pkey", + "schema": "public", + "table": "attachments", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "oauth_accounts_pkey", + "schema": "public", + "table": "oauth_accounts", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "passkeys_pkey", + "schema": "public", + "table": "passkeys", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "rate_limits_pkey", + "schema": "public", + "table": "rate_limits", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "totps_pkey", + "schema": "public", + "table": "totps", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "domains_pkey", + "schema": "public", + "table": "domains", + "entityType": "pks" + }, + { + "columns": [ + "context_key" + ], + "nameExplicit": false, + "name": "context_counters_pkey", + "schema": "public", + "table": "context_counters", + "entityType": "pks" + }, + { + "columns": [ + "entity_id" + ], + "nameExplicit": false, + "name": "product_counters_pkey", + "schema": "public", + "table": "product_counters", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "inactive_memberships_pkey", + "schema": "public", + "table": "inactive_memberships", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "memberships_pkey", + "schema": "public", + "table": "memberships", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "organizations_pkey", + "schema": "public", + "table": "organizations", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "requests_pkey", + "schema": "public", + "table": "requests", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "system_roles_pkey", + "schema": "public", + "table": "system_roles", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tenants_pkey", + "schema": "public", + "table": "tenants", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "emails_pkey", + "schema": "public", + "table": "emails", + "entityType": "pks" + }, + { + "columns": [ + "user_id" + ], + "nameExplicit": false, + "name": "user_counters_pkey", + "schema": "public", + "table": "user_counters", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "users_pkey", + "schema": "public", + "table": "users", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": [ + "provider", + "provider_user_id", + "email" + ], + "nullsNotDistinct": false, + "name": "oauth_accounts_provider_provider_user_id_email_unique", + "entityType": "uniques", + "schema": "public", + "table": "oauth_accounts" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "email", + "context_id" + ], + "nullsNotDistinct": false, + "name": "inactive_memberships_tenant_email_ctx", + "entityType": "uniques", + "schema": "public", + "table": "inactive_memberships" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "user_id", + "context_id" + ], + "nullsNotDistinct": false, + "name": "memberships_unique_context", + "entityType": "uniques", + "schema": "public", + "table": "memberships" + }, + { + "nameExplicit": true, + "columns": [ + "tenant_id", + "id" + ], + "nullsNotDistinct": false, + "name": "organizations_tenant_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "organizations" + }, + { + "nameExplicit": true, + "columns": [ + "user_id", + "entity_id" + ], + "nullsNotDistinct": false, + "name": "seen_by_user_entity_unique", + "entityType": "uniques", + "schema": "public", + "table": "seen_by" + }, + { + "nameExplicit": false, + "columns": [ + "domain" + ], + "nullsNotDistinct": false, + "name": "domains_domain_key", + "schema": "public", + "table": "domains", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "organizations_slug_key", + "schema": "public", + "table": "organizations", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "nullsNotDistinct": false, + "name": "system_roles_user_id_key", + "schema": "public", + "table": "system_roles", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "emails_email_key", + "schema": "public", + "table": "emails", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "slug" + ], + "nullsNotDistinct": false, + "name": "users_slug_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "users_email_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + }, + { + "as": "PERMISSIVE", + "for": "SELECT", + "roles": [ + "public" + ], + "using": "\n \n COALESCE(current_setting('app.tenant_id', true), '') != ''\n AND \"attachments\".\"tenant_id\" = current_setting('app.tenant_id', true)::text\n\n AND (\"attachments\".\"deleted_at\" IS NULL OR current_setting('app.include_deleted', true) = 'true')\n ", + "withCheck": null, + "name": "attachments_select_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "INSERT", + "roles": [ + "public" + ], + "using": null, + "withCheck": "true", + "name": "attachments_insert_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "UPDATE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "attachments_update_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "DELETE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "attachments_delete_policy", + "entityType": "policies", + "schema": "public", + "table": "attachments" + }, + { + "as": "PERMISSIVE", + "for": "SELECT", + "roles": [ + "public" + ], + "using": "\n \n COALESCE(current_setting('app.tenant_id', true), '') != ''\n AND \"yjs_documents\".\"tenant_id\" = current_setting('app.tenant_id', true)::text\n\n \n ", + "withCheck": null, + "name": "yjs_documents_select_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "INSERT", + "roles": [ + "public" + ], + "using": null, + "withCheck": "true", + "name": "yjs_documents_insert_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "UPDATE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "yjs_documents_update_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + }, + { + "as": "PERMISSIVE", + "for": "DELETE", + "roles": [ + "public" + ], + "using": "true", + "withCheck": null, + "name": "yjs_documents_delete_policy", + "entityType": "policies", + "schema": "public", + "table": "yjs_documents" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/backend/src/db/utils/context-entity-columns.ts b/backend/src/db/utils/context-entity-columns.ts index 748100a25..5549b93e4 100644 --- a/backend/src/db/utils/context-entity-columns.ts +++ b/backend/src/db/utils/context-entity-columns.ts @@ -1,4 +1,4 @@ -import { uuid, varchar } from 'drizzle-orm/pg-core'; +import { timestamp, uuid, varchar } from 'drizzle-orm/pg-core'; import type { ContextEntityType } from 'shared'; import { maxLength } from '#/db/utils/constraints'; import { tenantEntityColumns } from '#/db/utils/tenant-entity-columns'; @@ -14,4 +14,11 @@ export const contextEntityColumns = (entityType: T) bannerUrl: varchar({ length: maxLength.url }), createdBy: uuid().references(() => usersTable.id, { onDelete: 'set null' }), updatedBy: uuid().references(() => usersTable.id, { onDelete: 'set null' }), + /** + * When the context went live for its members: null = draft (invites against it are + * deferred, see membership dispatch). Defaults to creation time, so forks without + * draft flows never see null and the mechanism stays dormant. Distinct from + * `publicAt` (public readability). + */ + publishedAt: timestamp({ mode: 'string' }).defaultNow(), }); diff --git a/backend/src/modules/me/me-queries.ts b/backend/src/modules/me/me-queries.ts index e441d9b87..f7c4c761d 100644 --- a/backend/src/modules/me/me-queries.ts +++ b/backend/src/modules/me/me-queries.ts @@ -1,4 +1,4 @@ -import { and, eq, getColumns, inArray, isNull, sql } from 'drizzle-orm'; +import { and, eq, getColumns, inArray, isNotNull, isNull, sql } from 'drizzle-orm'; import { appConfig } from 'shared'; import type { AuthContext, DbContext } from '#/core/context'; import { sessionsTable } from '#/modules/auth/sessions-db'; @@ -142,6 +142,9 @@ export const findPendingInvitations = async (ctx: DbContext, { userId }: FindPen eq(inactiveMembershipsTable.contextType, entityType), eq(inactiveMembershipsTable.userId, userId), isNull(inactiveMembershipsTable.rejectedAt), + // Invites against an unpublished (draft) context are deferred: hidden from the + // invitee until the context is published and the invite is dispatched. + isNotNull(cols.publishedAt), ), ); }), diff --git a/backend/src/modules/memberships/helpers/deferred-invites.ts b/backend/src/modules/memberships/helpers/deferred-invites.ts new file mode 100644 index 000000000..03d25f065 --- /dev/null +++ b/backend/src/modules/memberships/helpers/deferred-invites.ts @@ -0,0 +1,114 @@ +import { appConfig, type ContextEntityType } from 'shared'; +import { nanoid } from 'shared/utils/nanoid'; +import type { AuthContext } from '#/core/context'; +import { mailer } from '#/lib/mailer'; +import { resolveEntity } from '#/modules/entities/entities-queries'; +import { + findPendingInactiveMembershipsByContexts, + insertTokens, + stampInactiveMembershipsReminded, + updateInactiveMembershipToken, +} from '#/modules/memberships/memberships-queries'; +import { log } from '#/utils/logger'; +import { encodeLowerCased } from '#/utils/oslo'; +import { slugFromEmail } from '#/utils/slug-from-email'; +import { createDate, TimeSpan } from '#/utils/time-span'; +import { memberInviteEmail, memberInviteWithTokenEmail } from '../../../../emails'; + +interface DispatchDeferredInvitesOpts { + /** Context entity ids whose pending invites should be dispatched (e.g. a published course + descendants). */ + contextIds: string[]; +} + +/** + * Dispatch invites that were deferred while their context was unpublished: send the held + * emails through the normal invitation flow and stamp `remindedAt`. Invitation tokens are + * rotated (fresh secret + expiry) since raw tokens are unrecoverable and may have expired + * while the context was a draft. Rows emailed within the last 7 days are skipped (legacy + * re-invite throttle). + */ +export async function dispatchDeferredInvites(ctx: AuthContext, { contextIds }: DispatchDeferredInvitesOpts) { + const user = ctx.var.user; + + const pendingRows = await findPendingInactiveMembershipsByContexts(ctx, { contextIds }); + + // 7-day throttle on last dispatch; deferred rows have remindedAt null → always due + const throttleBefore = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); + const dueRows = pendingRows.filter((row) => !row.remindedAt || new Date(row.remindedAt) < throttleBefore); + if (!dueRows.length) return { dispatched: 0 }; + + const lng = appConfig.defaultLanguage; + const senderName = user.name; + const senderThumbnailUrl = user.thumbnailUrl; + + // Group per context+role: each email batch shares entityName + role static props + const groups = new Map(); + for (const row of dueRows) { + const key = `${row.contextType}:${row.contextId}:${row.role}`; + const group = groups.get(key) ?? []; + group.push(row); + groups.set(key, group); + } + + const dispatchedIds: string[] = []; + + for (const group of groups.values()) { + const { contextType, contextId, role } = group[0]; + const entity = await resolveEntity(ctx, contextType as ContextEntityType, contextId); + if (!entity) continue; + + const staticProps = { senderName, senderThumbnailUrl, role, entityName: entity.name }; + const entityLink = `${appConfig.frontendUrl}/${contextType}/${entity.slug}`; + + const withTokenRecipients: Array<{ email: string; lng: string; name: string; inviteLink: string }> = []; + const noTokenRecipients: Array<{ email: string; lng: string; name: string; memberInviteLink: string }> = []; + + for (const row of group) { + if (row.tokenId) { + // Rotate the invitation token: fresh secret + expiry, re-pointed from the invite row + const raw = nanoid(40); + const [token] = await insertTokens(ctx, { + tokens: [ + { + secret: encodeLowerCased(raw), + type: 'invitation' as const, + email: row.email, + createdBy: row.createdBy, + expiresAt: createDate(new TimeSpan(7, 'd')), + inactiveMembershipId: row.id, + }, + ], + }); + await updateInactiveMembershipToken(ctx, { id: row.id, tokenId: token.id }); + + withTokenRecipients.push({ + email: row.email, + lng, + name: slugFromEmail(row.email), + inviteLink: `${appConfig.backendAuthUrl}/invoke-token/invitation/${raw}`, + }); + } else { + noTokenRecipients.push({ + email: row.email, + lng, + name: slugFromEmail(row.email), + memberInviteLink: entityLink, + }); + } + dispatchedIds.push(row.id); + } + + if (withTokenRecipients.length > 0) { + await mailer.prepareEmails(memberInviteWithTokenEmail, staticProps, withTokenRecipients, user.email); + } + if (noTokenRecipients.length > 0) { + await mailer.prepareEmails(memberInviteEmail, staticProps, noTokenRecipients, user.email); + } + } + + await stampInactiveMembershipsReminded(ctx, { ids: dispatchedIds, remindedAt: new Date().toISOString() }); + + log.info('Deferred invites dispatched', { count: dispatchedIds.length, contexts: contextIds.length }); + + return { dispatched: dispatchedIds.length }; +} diff --git a/backend/src/modules/memberships/inactive-memberships-db.ts b/backend/src/modules/memberships/inactive-memberships-db.ts index de59a183e..56c27b3ba 100644 --- a/backend/src/modules/memberships/inactive-memberships-db.ts +++ b/backend/src/modules/memberships/inactive-memberships-db.ts @@ -26,6 +26,12 @@ export const inactiveMembershipsTable = snakeCase.table( tokenId: uuid(), // References tokens.id logically (no FK due to partitioning) role: varchar({ enum: roleEnum }).notNull().default('member'), rejectedAt: timestamp({ mode: 'string' }), + /** + * Last invite/reminder email dispatch for this row; null when never dispatched (e.g. + * deferred while the target context was unpublished). Reminder emails are throttled + * against remindedAt ?? createdAt. + */ + remindedAt: timestamp({ mode: 'string' }), createdBy: uuid() .notNull() .references(() => usersTable.id, { onDelete: 'cascade' }), diff --git a/backend/src/modules/memberships/memberships-mocks.ts b/backend/src/modules/memberships/memberships-mocks.ts index e63ab5973..06eccfacc 100644 --- a/backend/src/modules/memberships/memberships-mocks.ts +++ b/backend/src/modules/memberships/memberships-mocks.ts @@ -157,6 +157,7 @@ export const mockInactiveMembership = (key = 'inactive-membership:default'): Ina tokenId, role: faker.helpers.arrayElement(roles.all), rejectedAt: null, + remindedAt: null, createdAt, createdBy: mockUuid(), ...contextEntityColumns, diff --git a/backend/src/modules/memberships/memberships-queries.ts b/backend/src/modules/memberships/memberships-queries.ts index 27d4fe8db..d89893ebb 100644 --- a/backend/src/modules/memberships/memberships-queries.ts +++ b/backend/src/modules/memberships/memberships-queries.ts @@ -1,4 +1,4 @@ -import { and, count, eq, ilike, inArray, or, type SQL, sql } from 'drizzle-orm'; +import { and, count, eq, ilike, inArray, isNull, or, type SQL, sql } from 'drizzle-orm'; import { alias } from 'drizzle-orm/pg-core'; import type { ContextEntityType, EntityRole } from 'shared'; import type { AuthContext, DbContext } from '#/core/context'; @@ -73,6 +73,9 @@ export const findMembershipAwareRows = async ( language: usersTable.language, membershipId: membershipsTable.id, inactiveMembershipId: inactiveMembershipsTable.id, + // Last dispatch timestamps for the reminder throttle (remindedAt ?? createdAt) + inactiveMembershipCreatedAt: inactiveMembershipsTable.createdAt, + inactiveMembershipRemindedAt: inactiveMembershipsTable.remindedAt, orgMembershipId: orgMemberships.id, tokenId: tokensTable.id, }) @@ -109,6 +112,38 @@ export const findMembershipAwareRows = async ( .where(and(inArray(emailsTable.email, emails))); }; +/** Pending (not rejected) inactive memberships for a set of contexts (deferred-invite dispatch). */ +export const findPendingInactiveMembershipsByContexts = async ( + ctx: DbContext, + { contextIds }: { contextIds: string[] }, +) => { + const { db } = ctx.var; + if (!contextIds.length) return []; + return db + .select() + .from(inactiveMembershipsTable) + .where(and(inArray(inactiveMembershipsTable.contextId, contextIds), isNull(inactiveMembershipsTable.rejectedAt))); +}; + +/** Stamp remindedAt (last email dispatch) on inactive memberships. */ +export const stampInactiveMembershipsReminded = async ( + ctx: DbContext, + { ids, remindedAt }: { ids: string[]; remindedAt: string }, +) => { + const { db } = ctx.var; + if (!ids.length) return; + return db.update(inactiveMembershipsTable).set({ remindedAt }).where(inArray(inactiveMembershipsTable.id, ids)); +}; + +/** Point an inactive membership at a fresh invitation token (rotation at deferred dispatch). */ +export const updateInactiveMembershipToken = async ( + ctx: DbContext, + { id, tokenId }: { id: string; tokenId: string }, +) => { + const { db } = ctx.var; + return db.update(inactiveMembershipsTable).set({ tokenId }).where(eq(inactiveMembershipsTable.id, id)); +}; + interface FindMembershipByIdInOrgOpts { membershipId: string; } diff --git a/backend/src/modules/memberships/operations/create-memberships.ts b/backend/src/modules/memberships/operations/create-memberships.ts index 85509bb5b..d4928ee5e 100644 --- a/backend/src/modules/memberships/operations/create-memberships.ts +++ b/backend/src/modules/memberships/operations/create-memberships.ts @@ -12,6 +12,7 @@ import { findMembershipAwareRows, insertInactiveMemberships, insertTokens, + stampInactiveMembershipsReminded, } from '#/modules/memberships/memberships-queries'; import { getValidContextEntity } from '#/permissions/get-context-entity'; import { log } from '#/utils/logger'; @@ -44,6 +45,15 @@ export async function createMembershipsOp(ctx: AuthContext, input: CreateMembers const { slug: entitySlug, name: entityName } = entity; + /** + * Draft context (publishedAt null): invites are recorded (inactive memberships + tokens) + * but email dispatch is held and existing users are not added directly — everything is + * released when the context is published. The context's most-privileged role (first in + * its vocabulary, e.g. admin/staff/owner) stays live so staff can collaborate in drafts. + */ + const contextIsDraft = entity.publishedAt === null; + const deferDispatch = contextIsDraft && role !== hierarchy.getRoles(entityType)[0]; + const currentOrgMemberships = await countMembershipsByContext(ctx, { contextType: rootContextType, contextId: organization.id, @@ -84,6 +94,10 @@ export async function createMembershipsOp(ctx: AuthContext, input: CreateMembers for (const e of normalizedEmails) rowsByEmail.set(e, []); for (const r of membershipAwareRows) rowsByEmail.get(r.email)?.push(r); + // Reminder throttle: a pending invite is re-emailed at most once per 7 days + const reminderThrottleBefore = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); + const remindedInactiveMembershipIds: string[] = []; + for (const email of normalizedEmails) { const rows = rowsByEmail.get(email)!; @@ -97,7 +111,15 @@ export async function createMembershipsOp(ctx: AuthContext, input: CreateMembers } if (hasUserInactiveMembership || hasTokenInvite) { - reminderEmails.push(email); + // No reminders against a draft context; otherwise throttle on last dispatch + const inactiveRow = rows.find((r) => r.inactiveMembershipId); + const lastDispatch = inactiveRow?.inactiveMembershipRemindedAt ?? inactiveRow?.inactiveMembershipCreatedAt; + const throttled = !!lastDispatch && new Date(lastDispatch) >= reminderThrottleBefore; + + if (!deferDispatch && !throttled) { + reminderEmails.push(email); + if (inactiveRow?.inactiveMembershipId) remindedInactiveMembershipIds.push(inactiveRow.inactiveMembershipId); + } continue; } @@ -110,7 +132,8 @@ export async function createMembershipsOp(ctx: AuthContext, input: CreateMembers } else { const hasActiveOrgMembership = entityType !== rootContextType && !!rows.find((r) => r.orgMembershipId); - if (hasActiveOrgMembership) { + // Draft context: existing users are deferred too — no membership, no nav entry, no email + if (hasActiveOrgMembership && !deferDispatch) { existingUsersToDirectAdd.push({ userId: userRow.userId, email }); } else { existingUsersToActivate.push({ userId: userRow.userId, email }); @@ -230,7 +253,8 @@ export async function createMembershipsOp(ctx: AuthContext, input: CreateMembers const staticProps = { senderName, senderThumbnailUrl, role, entityName }; - if (noTokenRecipients.length > 0) { + // Draft context: hold every email — deferred invites are dispatched at publish + if (!deferDispatch && noTokenRecipients.length > 0) { await mailer.prepareEmails(memberInviteEmail, staticProps, noTokenRecipients, user.email); } @@ -242,14 +266,22 @@ export async function createMembershipsOp(ctx: AuthContext, input: CreateMembers entityLink, })); - if (directAdditionRecipients.length > 0) { + if (!deferDispatch && directAdditionRecipients.length > 0) { await mailer.prepareEmails(memberAddedEmail, staticProps, directAdditionRecipients, user.email); } - if (withTokenRecipients.length > 0) { + if (!deferDispatch && withTokenRecipients.length > 0) { await mailer.prepareEmails(memberInviteWithTokenEmail, staticProps, withTokenRecipients, user.email); } + // Track reminder dispatch for the 7-day throttle + if (!deferDispatch && remindedInactiveMembershipIds.length > 0) { + await stampInactiveMembershipsReminded(ctx, { + ids: remindedInactiveMembershipIds, + remindedAt: new Date().toISOString(), + }); + } + const invitesSentCount = insertedInactiveMemberships.length; log.info('Users invited on entity level', { diff --git a/backend/src/modules/organization/organization-mocks.ts b/backend/src/modules/organization/organization-mocks.ts index 23f11bc2c..090a4079d 100644 --- a/backend/src/modules/organization/organization-mocks.ts +++ b/backend/src/modules/organization/organization-mocks.ts @@ -59,6 +59,7 @@ const generateOrganizationBase = (id: string, tenantId: string, name: string, cr welcomeText: `Welcome to ${name}!`, authStrategies: ['passkey'] as AuthStrategy[], chatSupport: faker.datatype.boolean(), + publishedAt: createdAt, createdAt, createdBy: null, updatedAt: createdAt, diff --git a/backend/tests/invitations/draft-invites.test.ts b/backend/tests/invitations/draft-invites.test.ts new file mode 100644 index 000000000..18959deb4 --- /dev/null +++ b/backend/tests/invitations/draft-invites.test.ts @@ -0,0 +1,189 @@ +import { eq } from 'drizzle-orm'; +import { getMyInvitations, membershipInvite } from 'sdk'; +import type { EntityRole } from 'shared'; +import { afterEach, beforeAll, describe, expect, it } from 'vitest'; +import type { AuthContext } from '#/core/context'; +import { baseDb as db } from '#/db/db'; +import { dispatchDeferredInvites } from '#/modules/memberships/helpers/deferred-invites'; +import { inactiveMembershipsTable } from '#/modules/memberships/inactive-memberships-db'; +import { membershipsTable } from '#/modules/memberships/memberships-db'; +import { organizationsTable } from '#/modules/organization/organization-db'; +import { defaultHeaders } from '../fixtures'; +import { createOrganizationAdminUser, createTestOrganization, createTestSession, createTestUser } from '../helpers'; +import { createAppClient } from '../test-client'; +import { clearDatabase, mockFetchRequest, setTestConfig } from '../test-utils'; + +setTestConfig({ + enabledAuthStrategies: ['passkey'], + selfRegistration: true, +}); + +beforeAll(async () => { + mockFetchRequest(); +}); + +afterEach(async () => await clearDatabase()); + +/** + * Draft-mode invite deferral (context `publishedAt` mechanism): invites against an + * unpublished context are recorded but not dispatched; the context's most-privileged + * role stays live so staff can collaborate in drafts; `dispatchDeferredInvites` + * (called by a fork's publish flow) releases held invites with token rotation and the + * 7-day reminder throttle. Cella's template never nulls publishedAt, so everything + * here is dormant by default — these tests null it explicitly. + */ +describe('Draft context invite deferral', async () => { + const call = await createAppClient(); + + const createDraftOrgWorld = async () => { + const organization = await createTestOrganization(); + const admin = await createOrganizationAdminUser( + 'admin@example.com', + organization.id, + 'admin', + true, + organization.tenantId, + ); + const sessionCookie = await createTestSession(admin); + // The template always publishes at creation; draft state is a fork flow + await db.update(organizationsTable).set({ publishedAt: null }).where(eq(organizationsTable.id, organization.id)); + return { organization, admin, sessionCookie }; + }; + + const invite = async ( + organization: { id: string; tenantId: string }, + emails: string[], + role: EntityRole, + sessionCookie: string, + ) => { + return await call(membershipInvite, { + path: { tenantId: organization.tenantId, organizationId: organization.id }, + body: { emails, role }, + query: { entityId: organization.id, entityType: 'organization' as const }, + headers: { ...defaultHeaders, Cookie: sessionCookie }, + }); + }; + + const getInactiveRows = async (contextId: string) => { + return await db.select().from(inactiveMembershipsTable).where(eq(inactiveMembershipsTable.contextId, contextId)); + }; + + it('defers member invites against a draft context (row created, no dispatch, no membership)', async () => { + const { organization, sessionCookie } = await createDraftOrgWorld(); + + const { response } = await invite(organization, ['new-member@example.com'], 'member', sessionCookie); + expect(response.status).toBe(200); + + const [row] = await getInactiveRows(organization.id); + expect(row).toBeDefined(); + expect(row.role).toBe('member'); + expect(row.tokenId).toBeTruthy(); // token minted for the new user + expect(row.remindedAt).toBeNull(); // but email dispatch was held + + const memberships = await db.select().from(membershipsTable).where(eq(membershipsTable.contextId, organization.id)); + expect(memberships).toHaveLength(1); // only the inviting admin + }); + + it('keeps the most-privileged role live: admin invites dispatch on a draft context', async () => { + const { organization, sessionCookie } = await createDraftOrgWorld(); + + const { response } = await invite(organization, ['co-admin@example.com'], 'admin', sessionCookie); + expect(response.status).toBe(200); + + const [row] = await getInactiveRows(organization.id); + expect(row).toBeDefined(); + expect(row.role).toBe('admin'); + expect(row.remindedAt).toBeNull(); // initial invite email is not a reminder stamp + }); + + it('hides deferred invites from the invitee until dispatch', async () => { + const { organization, admin, sessionCookie } = await createDraftOrgWorld(); + const invitee = await createTestUser('invitee@example.com'); + + await invite(organization, [invitee.email], 'member', sessionCookie); + + const inviteeCookie = await createTestSession(invitee); + const myInvitations = () => call(getMyInvitations, { headers: { ...defaultHeaders, Cookie: inviteeCookie } }); + + const before = await myInvitations(); + expect(before.response.status).toBe(200); + expect((before.data as { items: unknown[] }).items).toHaveLength(0); + + // A fork's publish flow: stamp publishedAt, then release the held invites + await db + .update(organizationsTable) + .set({ publishedAt: new Date().toISOString() }) + .where(eq(organizationsTable.id, organization.id)); + await dispatchDeferredInvites({ var: { db, user: admin } } as unknown as AuthContext, { + contextIds: [organization.id], + }); + + const after = await myInvitations(); + expect((after.data as { items: unknown[] }).items).toHaveLength(1); + }); + + it('dispatch rotates tokens, stamps remindedAt, and honors the 7-day throttle', async () => { + const { organization, admin, sessionCookie } = await createDraftOrgWorld(); + + await invite(organization, ['deferred@example.com'], 'member', sessionCookie); + const [beforeRow] = await getInactiveRows(organization.id); + const originalTokenId = beforeRow.tokenId; + expect(beforeRow.remindedAt).toBeNull(); + + const ctx = { var: { db, user: admin } } as unknown as AuthContext; + const first = await dispatchDeferredInvites(ctx, { contextIds: [organization.id] }); + expect(first.dispatched).toBe(1); + + const [afterRow] = await getInactiveRows(organization.id); + expect(afterRow.remindedAt).not.toBeNull(); + expect(afterRow.tokenId).toBeTruthy(); + expect(afterRow.tokenId).not.toBe(originalTokenId); // raw secrets are unrecoverable → rotate + + // Second dispatch inside the throttle window: no re-send, no token churn + const second = await dispatchDeferredInvites(ctx, { contextIds: [organization.id] }); + expect(second.dispatched).toBe(0); + const [afterSecond] = await getInactiveRows(organization.id); + expect(afterSecond.remindedAt).toBe(afterRow.remindedAt); + expect(afterSecond.tokenId).toBe(afterRow.tokenId); + }); + + it('throttles reminder emails to once per 7 days on published contexts', async () => { + const organization = await createTestOrganization(); + const admin = await createOrganizationAdminUser( + 'admin@example.com', + organization.id, + 'admin', + true, + organization.tenantId, + ); + const sessionCookie = await createTestSession(admin); + // Reminders apply to invitees with a known email (existing users). Truly-new token + // invitees re-enter the new-user path, where duplicate invites are already + // conflict-suppressed without email. + const invitee = await createTestUser('pending@example.com'); + + await invite(organization, [invitee.email], 'member', sessionCookie); + const [initial] = await getInactiveRows(organization.id); + expect(initial.remindedAt).toBeNull(); // initial invite email is not a reminder + + // Re-invite immediately: pending invite was dispatched at creation → reminder suppressed + await invite(organization, [invitee.email], 'member', sessionCookie); + const [afterEarlyReinvite] = await getInactiveRows(organization.id); + expect(afterEarlyReinvite.remindedAt).toBeNull(); + + // Age the invite past the throttle window (remindedAt takes precedence over the + // immutable createdAt in the throttle check) → re-invite sends and re-stamps + const eightDaysAgo = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString(); + await db + .update(inactiveMembershipsTable) + .set({ remindedAt: eightDaysAgo }) + .where(eq(inactiveMembershipsTable.id, initial.id)); + const [aged] = await getInactiveRows(organization.id); + + await invite(organization, [invitee.email], 'member', sessionCookie); + const [afterDueReinvite] = await getInactiveRows(organization.id); + expect(afterDueReinvite.remindedAt).not.toBeNull(); + expect(afterDueReinvite.remindedAt).not.toBe(aged.remindedAt); + expect(new Date(afterDueReinvite.remindedAt!).getTime()).toBeGreaterThan(Date.now() - 24 * 60 * 60 * 1000); + }); +}); From 89f4f38753251c68032cbf42cd347825a3ec7816 Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Fri, 10 Jul 2026 11:46:15 +0200 Subject: [PATCH 07/15] feat(memberships): multi-vocabulary role support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cella lets each context pick a role subset, but several code paths still assumed one flat vocabulary. All changes are no-ops for single-vocabulary forks. From projectcampus (CELLA-CHANGES #05/06/10/12 / PR-2). - MenuStructureItem.carryRole + resolveParentMembershipRole: auto-created parent/associated memberships get the least-privileged fitting role ('member' when the vocabulary has it — identical to the previous hardcode), or carry the invited role when carryRole is set and valid in the parent vocabulary (e.g. courseSection student → course student). - create-memberships + update-membership: reject roles outside the target context's vocabulary with 400 invalid_role. - SelectRoleRadio: optional entityType prop restricts options to the context's vocabulary; invite-email-form passes it. - mockContextMembership picks its random role from the context's vocabulary (roles.all yields invalid combos for multi-vocabulary forks). Co-Authored-By: Claude Fable 5 --- .../helpers/membership-helpers.test.ts | 24 +++++++++++++++ .../memberships/helpers/membership-helpers.ts | 29 +++++++++++++++++-- .../modules/memberships/memberships-mocks.ts | 5 ++-- .../operations/create-memberships.ts | 5 ++++ .../operations/update-membership.ts | 7 ++++- .../common/form-fields/select-role-radio.tsx | 10 +++++-- .../src/modules/user/invite-email-form.tsx | 2 +- sdk/gen/docs.gen/details.gen/me.gen.json | 6 ++++ .../docs.gen/details.gen/memberships.gen.json | 2 ++ .../details.gen/organizations.gen.json | 22 ++++++++++++++ sdk/gen/docs.gen/schemas.gen.json | 10 +++++++ sdk/gen/openapi.json | 21 ++++++++++++++ sdk/gen/types.gen.ts | 2 ++ sdk/gen/zod.gen.ts | 2 ++ shared/src/config-builder/types.ts | 7 +++++ 15 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 backend/src/modules/memberships/helpers/membership-helpers.test.ts diff --git a/backend/src/modules/memberships/helpers/membership-helpers.test.ts b/backend/src/modules/memberships/helpers/membership-helpers.test.ts new file mode 100644 index 000000000..4a67659a6 --- /dev/null +++ b/backend/src/modules/memberships/helpers/membership-helpers.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; +import { resolveParentMembershipRole } from './membership-helpers'; + +/** + * Auto-created parent/associated membership roles (multi-vocabulary support): the + * least-privileged fitting role by default — 'member' when the vocabulary has it, + * identical to the previous hardcoded behavior — or the invited role carried over + * when carryRole is set and valid. Multi-vocabulary branches (no 'member' in the + * vocabulary, invalid carried roles) are exercised end-to-end by forks with nested + * contexts; cella's single vocabulary covers the default paths here. + */ +describe('resolveParentMembershipRole', () => { + it("defaults to 'member' when the vocabulary has it (previous hardcoded behavior)", () => { + expect(resolveParentMembershipRole('organization', 'admin')).toBe('member'); + }); + + it('carries the invited role over when carryRole is set and valid', () => { + expect(resolveParentMembershipRole('organization', 'admin', true)).toBe('admin'); + }); + + it('ignores carryRole for the default path when the invited role equals member', () => { + expect(resolveParentMembershipRole('organization', 'member', true)).toBe('member'); + }); +}); diff --git a/backend/src/modules/memberships/helpers/membership-helpers.ts b/backend/src/modules/memberships/helpers/membership-helpers.ts index e37691d66..022709478 100644 --- a/backend/src/modules/memberships/helpers/membership-helpers.ts +++ b/backend/src/modules/memberships/helpers/membership-helpers.ts @@ -1,5 +1,6 @@ import { inArray, max } from 'drizzle-orm'; import { appConfig, type ContextEntityType, hierarchy } from 'shared'; +import type { MenuStructureItem } from 'shared/config-builder'; import { defaultOrder, orderGap } from 'shared/utils/display-order'; import type { DbContext } from '#/core/context'; import type { EntityModel } from '#/modules/entities/entities-queries'; @@ -19,6 +20,23 @@ import { log } from '#/utils/logger'; const rootContextType = hierarchy.contextTypes.find((t) => hierarchy.getParent(t) === null)!; const rootIdColumnKey = appConfig.entityIdColumnKeys[rootContextType]; +/** + * Role for an auto-created parent/associated membership. Defaults to the least-privileged + * fitting role: `member` when the target vocabulary has it, else the vocabulary's last role + * (roles are declared most → least privileged). With `carryRole` (menuStructure), the invited + * role carries over when valid in the target vocabulary. + */ +export const resolveParentMembershipRole = ( + contextType: ContextEntityType, + invitedRole: MembershipModel['role'], + carryRole = false, +): MembershipModel['role'] => { + const contextRoles = hierarchy.getRoles(contextType) as readonly MembershipModel['role'][]; + if (carryRole && contextRoles.includes(invitedRole)) return invitedRole; + if (contextRoles.includes('member' as MembershipModel['role'])) return 'member' as MembershipModel['role']; + return contextRoles[contextRoles.length - 1]; +}; + type BaseEntityModel = EntityModel & { [key: string]: unknown; tenantId: string; // Required for RLS @@ -142,7 +160,8 @@ export const insertMemberships = async ( return { ...baseMembership, tenantId: entity.tenantId, - role: 'member', // parent membership is always 'member' + // parent membership defaults to the least-privileged fitting role ('member' in cella) + role: resolveParentMembershipRole(rootContextType, baseMembership.role), [rootIdColumnKey]: targetEntitiesIdColumnKeys[rootIdColumnKey], contextType: rootContextType, contextId: targetEntitiesIdColumnKeys[rootIdColumnKey], @@ -170,7 +189,13 @@ export const insertMemberships = async ( return { ...baseMembership, tenantId: entity.tenantId, - role: 'member', // parent/associated membership is always 'member' + // associated membership role: least-privileged fit, or carried over when carryRole is set + role: resolveParentMembershipRole( + associatedType as ContextEntityType, + baseMembership.role, + // Config literals only carry the property when a fork sets it + 'carryRole' in relation ? (relation as MenuStructureItem).carryRole : undefined, + ), ...remainingIdColumnKeys, contextType: associatedType, contextId: associatedField, diff --git a/backend/src/modules/memberships/memberships-mocks.ts b/backend/src/modules/memberships/memberships-mocks.ts index 06eccfacc..76780a8d6 100644 --- a/backend/src/modules/memberships/memberships-mocks.ts +++ b/backend/src/modules/memberships/memberships-mocks.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker'; -import { appConfig, type ContextEntityType, type EntityRole, roles } from 'shared'; +import { appConfig, type ContextEntityType, type EntityRole, hierarchy, roles } from 'shared'; import { generateMockContextIdColumns, MOCK_REF_DATE, @@ -76,7 +76,8 @@ export const mockContextMembership = ( ...contextEntityColumns, [appConfig.entityIdColumnKeys[contextType]]: contextEntity.id, // Set the correct context entity ID ...overrideIds, - role: faker.helpers.arrayElement(roles.all), + // Pick from the context's own role vocabulary (e.g. course → staff/student/guest) + role: faker.helpers.arrayElement(hierarchy.getRoles(contextType)), displayOrder: getMembershipOrderOffset(contextEntity.id) * 10, createdAt: mockPastIsoDate(), createdBy: userId, diff --git a/backend/src/modules/memberships/operations/create-memberships.ts b/backend/src/modules/memberships/operations/create-memberships.ts index d4928ee5e..f8ae0a4b5 100644 --- a/backend/src/modules/memberships/operations/create-memberships.ts +++ b/backend/src/modules/memberships/operations/create-memberships.ts @@ -41,6 +41,11 @@ export async function createMembershipsOp(ctx: AuthContext, input: CreateMembers const normalizedEmails = [...new Set(emails.map((e: string) => e.toLowerCase().trim()))]; if (!normalizedEmails.length) throw new AppError(400, 'no_recipients', 'warn'); + // The invited role must exist in the target context's vocabulary (e.g. no org 'member' on a course) + if (!hierarchy.getRoles(entityType).includes(role)) { + throw new AppError(400, 'invalid_role', 'warn', { entityType }); + } + const { entity } = await getValidContextEntity(ctx, entityId, entityType, 'update'); const { slug: entitySlug, name: entityName } = entity; diff --git a/backend/src/modules/memberships/operations/update-membership.ts b/backend/src/modules/memberships/operations/update-membership.ts index e636ceefd..092775d82 100644 --- a/backend/src/modules/memberships/operations/update-membership.ts +++ b/backend/src/modules/memberships/operations/update-membership.ts @@ -1,4 +1,4 @@ -import type { EntityRole } from 'shared'; +import { type EntityRole, hierarchy } from 'shared'; import { getEdgeOrder } from 'shared/utils/display-order'; import type { AuthContext } from '#/core/context'; import { AppError } from '#/core/error'; @@ -31,6 +31,11 @@ export async function updateMembershipOp(ctx: AuthContext, membershipId: string, const updatedType = membershipToUpdate.contextType; + // The new role must exist in the context's vocabulary (e.g. no org 'member' on a course) + if (role !== undefined && !hierarchy.getRoles(updatedType).includes(role)) { + throw new AppError(400, 'invalid_role', 'warn', { entityType: updatedType }); + } + await getValidContextEntity(ctx, membershipToUpdate.contextId, updatedType, role ? 'update' : 'read'); if (archived !== undefined && archived !== membershipToUpdate.archived) { diff --git a/frontend/src/modules/common/form-fields/select-role-radio.tsx b/frontend/src/modules/common/form-fields/select-role-radio.tsx index c755c1b92..a86ea2656 100644 --- a/frontend/src/modules/common/form-fields/select-role-radio.tsx +++ b/frontend/src/modules/common/form-fields/select-role-radio.tsx @@ -1,27 +1,31 @@ import { useTranslation } from 'react-i18next'; -import { type EntityRole, roles } from 'shared'; +import { type ContextEntityType, type EntityRole, hierarchy, roles } from 'shared'; import { RadioGroup, RadioGroupItem } from '~/modules/ui/radio-group'; import { cn } from '~/utils/cn'; interface Props { onValueChange: (value?: string) => void; value?: EntityRole; + /** Restrict options to this context entity's role vocabulary (e.g. course → staff/student/guest). */ + entityType?: ContextEntityType; className?: string; } /** * Radio group for selecting a single entity role. */ -export function SelectRoleRadio({ onValueChange, value, className }: Props) { +export function SelectRoleRadio({ onValueChange, value, entityType, className }: Props) { const { t } = useTranslation(); + const roleOptions = entityType ? hierarchy.getRoles(entityType) : roles.all; + return ( onValueChange(v as string)} className={cn('inline-flex items-center gap-4', className)} > - {roles.all.map((role) => ( + {roleOptions.map((role) => ( // biome-ignore lint/a11y/noLabelWithoutControl: label is for visual grouping only, no input needed