From a27cb4e7250c0e3aa8ab944743d20e3edc18ad48 Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 22 Oct 2026 17:28:06 +0200 Subject: [PATCH] feat(auth)!: drop better-auth and the legacy permission tables (RFC v3 Phase 6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roles, group admins and Telegram links live in the IdP since Phase 5, so the backend's own login and the tables behind it go: - better-auth (email OTP, passkeys, Telegram link plugin), its /api/auth routes and CORS, the OTP email, and the BETTER_AUTH_SECRET, PUBLIC_URL and TRUSTED_ORIGINS settings - tg_permissions, tg_group_admins, tg_link and auth_* (migration 0028) - the legacy tg_grants table, not migrated (decision 6); tg_grants_v2 takes its name - the AUTH_PATH, USER_ROLE and TelegramPlugin exports - the idp:export / idp:reconcile migration tooling and seed:user - the cleanSessions and cleanLinkCodes cron jobs, whose tables are gone The author columns that referenced tg_permissions keep the old Telegram IDs as legacy_created_by_tg_id / legacy_modified_by_tg_id, without the foreign keys (§9.4). Freshman writes no longer accept `modifiedBy`. Grants still return `key`, `source` and `grantedBy`, now constant, so dashboards built against older SDKs keep working. People no longer reads tg_group_admins. Group admins are native Telegram status (decision 2), which the backend does not store, so Telegram groups now report `admins: null` (unknown). While unknown, People keeps the groups it last knew and leaves pending admin removals pending instead of confirming them; the Capo area reports `admins: null` per group. BREAKING CHANGE: the SDK no longer exports AUTH_PATH, USER_ROLE and TelegramPlugin, the backend serves no /api/auth, the Capo area's group `admins` count is nullable, and migration 0028 drops the legacy tables on startup. Back them up before deploying. --- .env.example | 5 - README.md | 13 +- bun.lock | 160 +- drizzle/0028_idp_phase6_cleanup.sql | 69 + drizzle/meta/0028_snapshot.json | 2944 ++++++++++++++++++++++++ drizzle/meta/_journal.json | 9 +- package.json | 10 +- scripts/idp-migration-export.ts | 79 - scripts/idp-migration-reconcile.ts | 53 - scripts/lib/legacy-access.ts | 351 --- scripts/seed-db.ts | 52 +- scripts/seed-user.ts | 69 - src/auth/index.ts | 48 - src/auth/plugins/email-otp.ts | 23 - src/auth/plugins/telegram/index.ts | 111 - src/constants.ts | 22 - src/cron.ts | 33 +- src/db/columns.ts | 11 +- src/db/index.ts | 2 - src/db/schema/auth/auth.ts | 130 -- src/db/schema/auth/index.ts | 1 - src/db/schema/common/group-labels.ts | 9 +- src/db/schema/create-table.ts | 1 - src/db/schema/index.ts | 2 - src/db/schema/tg/grants.ts | 33 +- src/db/schema/tg/index.ts | 4 - src/db/schema/tg/link.ts | 16 - src/db/schema/tg/permissions.ts | 36 - src/db/schema/web/associations.ts | 5 +- src/db/schema/web/faqs.ts | 9 +- src/db/schema/web/freshman.ts | 9 +- src/db/schema/web/guides_matricole.ts | 5 +- src/db/schema/web/projects.ts | 5 +- src/emails/mailer.tsx | 6 - src/emails/templates/otp.tsx | 74 - src/env.ts | 8 - src/idp/author.ts | 14 +- src/index.ts | 5 +- src/people/contract.ts | 2 +- src/people/index.ts | 19 +- src/people/service.ts | 12 +- src/people/types.ts | 3 +- src/routers/groups/labels.ts | 3 +- src/routers/tg/grants.ts | 55 +- src/routers/web/freshman.ts | 2 +- src/routers/web/freshman/validation.ts | 3 - src/server.ts | 18 +- src/utils/grants.ts | 108 +- tests/azure-router.test.ts | 1 - tests/cipher.test.ts | 1 - tests/cron-link-cleanup.test.ts | 40 - tests/cron-retention.test.ts | 26 +- tests/fixtures/freshman-integration.ts | 13 +- tests/freshman.test.ts | 17 +- tests/idp-author.test.ts | 1 - tests/idp-migration-reconcile.test.ts | 192 -- tests/idp-policy.test.ts | 1 - tests/idp-router.test.ts | 9 +- tests/people.test.ts | 24 + tests/seed-db.test.ts | 23 +- 60 files changed, 3192 insertions(+), 1817 deletions(-) create mode 100644 drizzle/0028_idp_phase6_cleanup.sql create mode 100644 drizzle/meta/0028_snapshot.json delete mode 100644 scripts/idp-migration-export.ts delete mode 100644 scripts/idp-migration-reconcile.ts delete mode 100644 scripts/lib/legacy-access.ts delete mode 100644 scripts/seed-user.ts delete mode 100644 src/auth/index.ts delete mode 100644 src/auth/plugins/email-otp.ts delete mode 100644 src/auth/plugins/telegram/index.ts delete mode 100644 src/db/schema/auth/auth.ts delete mode 100644 src/db/schema/auth/index.ts delete mode 100644 src/db/schema/tg/link.ts delete mode 100644 src/db/schema/tg/permissions.ts delete mode 100644 src/emails/templates/otp.tsx delete mode 100644 tests/cron-link-cleanup.test.ts delete mode 100644 tests/idp-migration-reconcile.test.ts diff --git a/.env.example b/.env.example index 36b8d7f..4b84c44 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,7 @@ # WARNING: This file may be outdated # See ./src/env.ts for the up-to-date required environment variables -# PUBLIC_URL="" # set up this value when deploying on a domain (without trailing slash). default to http://localhost:PORT # PORT="" # you can change the server port if you want. default to 3000 -# TRUSTED_ORIGINS="" # comma-separated list of allowed origins. default to http://localhost:3000,3001,3002,5173,5174,5175 DB_HOST="" DB_PORT="5432" @@ -19,9 +17,6 @@ REDIS_PASSWORD="" # you generate it with `openssl rand -hex 32` !!! MINIMUM LENGTH = 32 bytes !!! ENCRYPTION_KEY="" -# you generate it with `openssl rand -hex 20` -BETTER_AUTH_SECRET="" - # Optional. Omit all three to use the seeded in-memory Azure directory. # Set all three to connect to Microsoft Graph. AZURE_TENANT_ID="" diff --git a/README.md b/README.md index f054ed6..b0a9cf9 100644 --- a/README.md +++ b/README.md @@ -34,18 +34,13 @@ Requirements: ```sh bun dev ``` -5. (Optional) Fill the database with sample data: Telegram users, groups and messages, course groups and labels, projects, associations, FAQs, freshman guides, group link reports, audit log entries, grants and staff roles: +5. (Optional) Fill the database with sample data: Telegram users, groups and messages, course groups and labels, projects, associations, FAQs, freshman guides, group link reports, audit log entries and grants: ```sh bun run seed:db ``` - Each table is seeded only while it is empty. New Telegram messages reference retained users and groups, and missing sample FAQ categories are added when the FAQ table is empty. Staff roles, group-admin assignments, and grants are only added for Telegram users created by the current run; group-admin assignments also require groups created by the run. + Each table is seeded only while it is empty. New Telegram messages reference retained users and groups, and missing sample FAQ categories are added when the FAQ table is empty. Grants are only added for Telegram users created by the current run. - `--force` replaces the Telegram activity tables, sample web content, audit logs, and `tg_grants_v2` grants. Existing staff roles and group-admin assignments are preserved. -6. (Optional, for logging into the admin panel locally) Seed a test user with Telegram already linked and the `owner` role: - ```sh - bun run seed:user - ``` - The script sends a sign-in OTP to `test@example.com` and prints a link to a temporary inbox where you can read the code — paste it when prompted. Without this step, logging into admin with `test@example.com` lands on the "Link your Telegram account" onboarding page, since that account has no `telegramId` associated yet. Re-run with `--force` to recreate the test user from scratch. + `--force` replaces the Telegram activity tables, sample web content, audit logs, and grants. ## Authentication @@ -56,7 +51,7 @@ Every caller authenticates with a PoliNetwork IdP token ([RFC v3](https://github Procedures are built with `policy(...)` from `src/trpc.ts`; the bare tRPC procedure is not exported, and `tests/idp-router.test.ts` fails if any procedure lacks a policy. `policy(rules, { tokenOnly: true })` refuses anonymous calls and gives the handler a non-null `ctx.actor`. -Who acted always comes from the token, never from the input: procedures take no actor fields (`createdBy`, `modifiedBy`, `updatedBy`, `adderId`, `interruptedById`, `performerId`, `adminId`). Inputs are plain zod objects, so a key an older client still sends is stripped, not rejected. Content tables record the author in `*_by_sub` columns, and grants go to `tg_grants_v2`. Until Phase 6 a grant counts as active if it is active in either `tg_grants` or `tg_grants_v2`. The bot records moderation actions with `tg.auditLog.record`, which takes an idempotency key and flags `idp_permission` actions the snapshot does not back. +Who acted always comes from the token, never from the input: procedures take no actor fields (`createdBy`, `modifiedBy`, `updatedBy`, `adderId`, `interruptedById`, `performerId`, `adminId`). Inputs are plain zod objects, so a key an older client still sends is stripped, not rejected. Content tables record the author in `*_by_sub` columns; rows written before the IdP migration keep their author's Telegram ID in `legacy_*_by_tg_id`. Grants are in `tg_grants` and record the grantor's IdP subject. People sign in only through the IdP: the backend has no login of its own. The bot records moderation actions with `tg.auditLog.record`, which takes an idempotency key and flags `idp_permission` actions the snapshot does not back. The bot's socket.io client authenticates with its service token in `auth.token` (scope `backend:tg:events`) and is disconnected when the token expires; a socket without a token is refused. Set `OAUTH_BOT_CLIENT_ID` on the backend to the same registered OAuth client ID used by the bot. This is the actual client ID, not its display name. Sockets are rejected if this setting is absent, the client ID differs, the token is not a service token or the scope is missing. diff --git a/bun.lock b/bun.lock index 73816be..1563cf0 100644 --- a/bun.lock +++ b/bun.lock @@ -1,13 +1,12 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "backend", "dependencies": { "@azure/identity": "^4.13.0", "@azure/storage-blob": "^12.31.0", - "@better-auth/drizzle-adapter": "1.5.5", - "@better-auth/passkey": "1.5.5", "@hono/trpc-server": "^0.4.2", "@microsoft/microsoft-graph-client": "^3.0.7", "@polinetwork/auth-kit": "^1.2.0", @@ -16,7 +15,6 @@ "@socket.io/devalue-parser": "^0.1.0", "@t3-oss/env-core": "^0.13.8", "@trpc/server": "11.5.1", - "better-auth": "1.5.5", "croner": "^9.1.0", "drizzle-orm": "^0.44.7", "fast-xml-parser": "^5.11.2", @@ -172,26 +170,6 @@ "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - "@better-auth/core": ["@better-auth/core@1.5.5", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "@cloudflare/workers-types": ">=4", "better-call": "1.3.2", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types"] }, "sha512-1oR/2jAp821Dcf67kQYHUoyNcdc1TcShfw4QMK0YTVntuRES5mUOyvEJql5T6eIuLfaqaN4LOF78l0FtF66HXA=="], - - "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.5.5", "", { "peerDependencies": { "@better-auth/core": "1.5.5", "@better-auth/utils": "^0.3.0", "drizzle-orm": ">=0.41.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-HAi9xAP40oDt48QZeYBFTcmg3vt1Jik90GwoRIfangd7VGbxesIIDBJSnvwMbZ52GBIc6+V4FRw9lasNiNrPfw=="], - - "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.5.5", "", { "peerDependencies": { "@better-auth/core": "1.5.5", "@better-auth/utils": "^0.3.0", "kysely": "^0.27.0 || ^0.28.0" } }, "sha512-LmHffIVnqbfsxcxckMOoE8MwibWrbVFch+kwPKJ5OFDFv6lin75ufN7ZZ7twH0IMPLT/FcgzaRjP8jRrXRef9g=="], - - "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.5.5", "", { "peerDependencies": { "@better-auth/core": "1.5.5", "@better-auth/utils": "^0.3.0" } }, "sha512-4X0j1/2L+nsgmObjmy9xEGUFWUv38Qjthp558fwS3DAp6ueWWyCaxaD6VJZ7m5qPNMrsBStO5WGP8CmJTEWm7g=="], - - "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.5.5", "", { "peerDependencies": { "@better-auth/core": "1.5.5", "@better-auth/utils": "^0.3.0", "mongodb": "^6.0.0 || ^7.0.0" } }, "sha512-P1J9ljL5X5k740I8Rx1esPWNgWYPdJR5hf2CY7BwDSrQFPUHuzeCg0YhtEEP55niNateTXhBqGAcy0fVOeamZg=="], - - "@better-auth/passkey": ["@better-auth/passkey@1.5.5", "", { "dependencies": { "@simplewebauthn/browser": "^13.2.2", "@simplewebauthn/server": "^13.2.3", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "1.5.5", "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "better-auth": "1.5.5", "better-call": "1.3.2", "nanostores": "^1.0.1" } }, "sha512-waGngvVophgoi/yqyU8fPZS04ZRMfjPBlxRlbV49nOgqFXcA9+914cGUedmaePXlTH6q6Z9K3dXUlg8H4g5tTQ=="], - - "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.5.5", "", { "peerDependencies": { "@better-auth/core": "1.5.5", "@better-auth/utils": "^0.3.0", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-CliDd78CXHzzwQIXhCdwGr5Ml53i6JdCHWV7PYwTIJz9EAm6qb2RVBdpP3nqEfNjINGM22A6gfleCgCdZkTIZg=="], - - "@better-auth/telemetry": ["@better-auth/telemetry@1.5.5", "", { "dependencies": { "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.5.5" } }, "sha512-1+lklxArn4IMHuU503RcPdXrSG2tlXt4jnGG3omolmspQ7tktg/Y9XO/yAkYDurtvMn1xJ8X1Ov01Ji/r5s9BQ=="], - - "@better-auth/utils": ["@better-auth/utils@0.3.1", "", {}, "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg=="], - - "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], - "@biomejs/biome": ["@biomejs/biome@2.3.13", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.13", "@biomejs/cli-darwin-x64": "2.3.13", "@biomejs/cli-linux-arm64": "2.3.13", "@biomejs/cli-linux-arm64-musl": "2.3.13", "@biomejs/cli-linux-x64": "2.3.13", "@biomejs/cli-linux-x64-musl": "2.3.13", "@biomejs/cli-win32-arm64": "2.3.13", "@biomejs/cli-win32-x64": "2.3.13" }, "bin": { "biome": "bin/biome" } }, "sha512-Fw7UsV0UAtWIBIm0M7g5CRerpu1eKyKAXIazzxhbXYUyMkwNrkX/KLkGI7b+uVDQ5cLUMfOC9vR60q9IDYDstA=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0OCwP0/BoKzyJHnFdaTk/i7hIP9JHH9oJJq6hrSCPmJPo8JWcJhprK4gQlhFzrwdTBAW4Bjt/RmCf3ZZe59gwQ=="], @@ -274,8 +252,6 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], - "@hexagon/base64": ["@hexagon/base64@1.1.28", "", {}, "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw=="], - "@hono/trpc-server": ["@hono/trpc-server@0.4.2", "", { "peerDependencies": { "@trpc/server": "^10.10.0 || >11.0.0-rc", "hono": ">=4.0.0" } }, "sha512-3TDrc42CZLgcTFkXQba+y7JlRWRiyw1AqhLqztWyNS2IFT+3bHld0lxKdGBttCtGKHYx0505dM67RMazjhdZqw=="], "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], @@ -338,14 +314,10 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.11", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="], - "@microsoft/microsoft-graph-client": ["@microsoft/microsoft-graph-client@3.0.7", "", { "dependencies": { "@babel/runtime": "7.28.4", "tslib": "2.8.1" }, "optionalDependencies": { "@azure/identity": "4.13.0" } }, "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw=="], "@microsoft/microsoft-graph-types": ["@microsoft/microsoft-graph-types@2.43.1", "", {}, "sha512-7r3FiJYW2qTWnl+Li8GV5MzJqPiJp27hvY98kH5V/ZMzGuIOkcJqOfIpusoIQrskLDfYk5kFT8AjpeW713qcIg=="], - "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.6", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="], "@next/env": ["@next/env@16.0.10", "", {}, "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang=="], @@ -366,10 +338,6 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.10", "", { "os": "win32", "cpu": "x64" }, "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q=="], - "@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="], - - "@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="], - "@nodable/entities": ["@nodable/entities@3.1.0", "", {}, "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -384,8 +352,6 @@ "@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="], - "@peculiar/asn1-android": ["@peculiar/asn1-android@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ=="], - "@peculiar/asn1-asym-key": ["@peculiar/asn1-asym-key@2.11.0", "", { "dependencies": { "@peculiar/asn1-pkcs8": "^2.11.0", "@peculiar/asn1-schema": "^2.11.0", "@peculiar/asn1-x509": "^2.11.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-r2957WdSZle/Zt4t9og7OMQYXNJ9/3ANXVtFwLCDJuHORlBZ4AFmDt1Qee0CzyTbHQg4ZZApBGEz5/6cFsyCRg=="], "@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.11.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.11.0", "@peculiar/asn1-x509": "^2.11.0", "@peculiar/asn1-x509-attr": "^2.11.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-SlAiDoT/Cd8qaNmbTt78gqyaEvu2fQd6ML6KWJkdjXGrgDKnHHrusVkfQUelkfinL2p665IIADb0JBixtT8W1g=="], @@ -552,10 +518,6 @@ "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "5.0.3", "selderee": "0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], - "@simplewebauthn/browser": ["@simplewebauthn/browser@13.2.2", "", {}, "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA=="], - - "@simplewebauthn/server": ["@simplewebauthn/server@13.3.0", "", { "dependencies": { "@hexagon/base64": "^1.1.27", "@levischuck/tiny-cbor": "^0.2.2", "@peculiar/asn1-android": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.1", "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/x509": "^1.14.3" } }, "sha512-MLHYFrYG8/wK2i+86XMhiecK72nMaHKKt4bo+7Q1TbuG9iGjlSdfkPWKO5ZFE/BX+ygCJ7pr8H/AJeyAj1EaTQ=="], - "@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "4.9.0", "tslib": "2.8.1" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], "@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "4.3.5", "@smithy/types": "4.9.0", "@smithy/util-config-provider": "4.2.0", "@smithy/util-endpoints": "3.2.5", "@smithy/util-middleware": "4.2.5", "tslib": "2.8.1" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="], @@ -668,10 +630,6 @@ "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], - "@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="], - - "@types/whatwg-url": ["@types/whatwg-url@13.0.0", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q=="], - "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.2", "", { "dependencies": { "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "tslib": "2.8.1" } }, "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg=="], "@vitest/expect": ["@vitest/expect@4.1.4", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww=="], @@ -720,10 +678,6 @@ "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], - "better-auth": ["better-auth@1.5.5", "", { "dependencies": { "@better-auth/core": "1.5.5", "@better-auth/drizzle-adapter": "1.5.5", "@better-auth/kysely-adapter": "1.5.5", "@better-auth/memory-adapter": "1.5.5", "@better-auth/mongo-adapter": "1.5.5", "@better-auth/prisma-adapter": "1.5.5", "@better-auth/telemetry": "1.5.5", "@better-auth/utils": "0.3.1", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.2", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.11", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-GpVPaV1eqr3mOovKfghJXXk6QvlcVeFbS3z+n+FPDid5rK/2PchnDtiaVCzWyXA9jH2KkirOfl+JhAUvnja0Eg=="], - - "better-call": ["better-call@1.3.2", "", { "dependencies": { "@better-auth/utils": "^0.3.1", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-4cZIfrerDsNTn3cm+MhLbUePN0gdwkhSXEuG7r/zuQ8c/H7iU0/jSK5TD3FW7U0MgKHce/8jGpPYNO4Ve+4NBw=="], - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], "bowser": ["bowser@2.12.1", "", {}, "sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw=="], @@ -732,8 +686,6 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "bson": ["bson@7.2.0", "", {}, "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ=="], - "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -816,8 +768,6 @@ "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], - "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "devalue": ["devalue@4.3.3", "", {}, "sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg=="], @@ -984,8 +934,6 @@ "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], - "kysely": ["kysely@0.28.12", "", {}, "sha512-kWiueDWXhbCchgiotwXkwdxZE/6h56IHAeFWg4euUfW0YsmO9sxbAxzx1KLLv2lox15EfuuxHQvgJ1qIfZuHGw=="], - "leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="], "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -1040,8 +988,6 @@ "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], - "memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="], - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], @@ -1060,18 +1006,12 @@ "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "8.15.0", "pathe": "2.0.3", "pkg-types": "1.3.1", "ufo": "1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], - "mongodb": ["mongodb@7.1.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.3.0", "bson": "^7.1.1", "mongodb-connection-string-url": "^7.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.806.0", "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", "mongodb-client-encryption": ">=7.0.0 <7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-kMfnKunbolQYwCIyrkxNJFB4Ypy91pYqua5NargS/f8ODNSJxT03ZU3n1JqL4mCzbSih8tvmMEMLpKTT7x5gCg=="], - - "mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.1", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "1.3.0", "object-assign": "4.1.1", "thenify-all": "1.6.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - "nanostores": ["nanostores@1.2.0", "", {}, "sha512-F0wCzbsH80G7XXo0Jd9/AVQC7ouWY6idUCTnMwW5t/Rv9W8qmO6endavDwg7TNp5GbugwSukFMVZqzPSrSMndg=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "next": ["next@16.0.10", "", { "dependencies": { "@next/env": "16.0.10", "@swc/helpers": "0.5.15", "caniuse-lite": "1.0.30001752", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.10", "@next/swc-darwin-x64": "16.0.10", "@next/swc-linux-arm64-gnu": "16.0.10", "@next/swc-linux-arm64-musl": "16.0.10", "@next/swc-linux-x64-gnu": "16.0.10", "@next/swc-linux-x64-musl": "16.0.10", "@next/swc-win32-arm64-msvc": "16.0.10", "@next/swc-win32-x64-msvc": "16.0.10", "sharp": "0.34.4" }, "peerDependencies": { "react": "19.2.0", "react-dom": "19.2.0" }, "bin": { "next": "dist/bin/next" } }, "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA=="], @@ -1156,8 +1096,6 @@ "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "1.4.5", "once": "1.4.0" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], "pvutils": ["pvutils@1.2.0", "", {}, "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg=="], @@ -1198,8 +1136,6 @@ "rollup": ["rollup@4.52.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.2", "@rollup/rollup-android-arm64": "4.52.2", "@rollup/rollup-darwin-arm64": "4.52.2", "@rollup/rollup-darwin-x64": "4.52.2", "@rollup/rollup-freebsd-arm64": "4.52.2", "@rollup/rollup-freebsd-x64": "4.52.2", "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", "@rollup/rollup-linux-arm-musleabihf": "4.52.2", "@rollup/rollup-linux-arm64-gnu": "4.52.2", "@rollup/rollup-linux-arm64-musl": "4.52.2", "@rollup/rollup-linux-loong64-gnu": "4.52.2", "@rollup/rollup-linux-ppc64-gnu": "4.52.2", "@rollup/rollup-linux-riscv64-gnu": "4.52.2", "@rollup/rollup-linux-riscv64-musl": "4.52.2", "@rollup/rollup-linux-s390x-gnu": "4.52.2", "@rollup/rollup-linux-x64-gnu": "4.52.2", "@rollup/rollup-linux-x64-musl": "4.52.2", "@rollup/rollup-openharmony-arm64": "4.52.2", "@rollup/rollup-win32-arm64-msvc": "4.52.2", "@rollup/rollup-win32-ia32-msvc": "4.52.2", "@rollup/rollup-win32-x64-gnu": "4.52.2", "@rollup/rollup-win32-x64-msvc": "4.52.2", "fsevents": "2.3.3" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA=="], - "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], - "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -1216,8 +1152,6 @@ "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="], - "sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "1.0.0", "detect-libc": "2.1.2", "semver": "7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -1250,8 +1184,6 @@ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "1.1.2", "source-map": "0.6.1" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], - "sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="], - "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], @@ -1308,8 +1240,6 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="], - "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], @@ -1342,10 +1272,6 @@ "vitest": ["vitest@4.1.4", "", { "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", "@vitest/pretty-format": "4.1.4", "@vitest/runner": "4.1.4", "@vitest/snapshot": "4.1.4", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.4", "@vitest/browser-preview": "4.1.4", "@vitest/browser-webdriverio": "4.1.4", "@vitest/coverage-istanbul": "4.1.4", "@vitest/coverage-v8": "4.1.4", "@vitest/ui": "4.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg=="], - "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], - - "whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], - "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -1380,44 +1306,22 @@ "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.8.0", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.2.0", "path-expression-matcher": "^1.5.0", "strnum": "^2.3.0", "xml-naming": "^0.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg=="], - "@better-auth/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@better-auth/passkey/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "0.2.0", "emoji-regex": "9.2.2", "strip-ansi": "7.1.2" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], - "@peculiar/asn1-android/@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.6.0", "", { "dependencies": { "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg=="], - - "@peculiar/asn1-android/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - "@polinetwork/auth-kit/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], - "@simplewebauthn/server/@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g=="], - - "@simplewebauthn/server/@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA=="], - - "@simplewebauthn/server/@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.6.0", "", { "dependencies": { "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg=="], - - "@simplewebauthn/server/@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.6.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA=="], - - "@simplewebauthn/server/@peculiar/x509": ["@peculiar/x509@1.14.3", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA=="], - "@socket.io/devalue-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "@t3-oss/env-core/typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "better-auth/jose": ["jose@6.2.1", "", {}, "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw=="], - - "better-auth/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "conf/dot-prop": ["dot-prop@10.1.0", "", { "dependencies": { "type-fest": "5.3.1" } }, "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q=="], "engine.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], @@ -1546,28 +1450,6 @@ "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "@peculiar/asn1-android/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/asn1-ecc/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/asn1-rsa/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/asn1-schema/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/asn1-x509/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "@peculiar/asn1-x509-attr": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-csr": ["@peculiar/asn1-csr@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-pkcs9": ["@peculiar/asn1-pkcs9@2.6.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-pfx": "^2.6.0", "@peculiar/asn1-pkcs8": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "@peculiar/asn1-x509-attr": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA=="], - "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "execa/cross-spawn/path-key": ["path-key@2.0.1", "", {}, "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw=="], @@ -1658,48 +1540,8 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@simplewebauthn/server/@peculiar/asn1-ecc/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/asn1-rsa/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/asn1-schema/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/asn1-x509/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-cms/@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-cms/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-csr/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-ecc/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-pkcs9/@peculiar/asn1-pfx": ["@peculiar/asn1-pfx@2.6.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-pkcs8": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-pkcs9/@peculiar/asn1-pkcs8": ["@peculiar/asn1-pkcs8@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-pkcs9/@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.6.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-pkcs9/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-rsa/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-x509/asn1js": ["asn1js@3.0.7", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ=="], - "execa/cross-spawn/shebang-command/shebang-regex": ["shebang-regex@1.0.0", "", {}, "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ=="], "sucrase/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-cms/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-csr/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-ecc/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-pkcs9/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-rsa/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], - - "@simplewebauthn/server/@peculiar/x509/@peculiar/asn1-x509/asn1js/pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], } } diff --git a/drizzle/0028_idp_phase6_cleanup.sql b/drizzle/0028_idp_phase6_cleanup.sql new file mode 100644 index 0000000..c520511 --- /dev/null +++ b/drizzle/0028_idp_phase6_cleanup.sql @@ -0,0 +1,69 @@ +-- RFC v3 Phase 6 (§9.3, §9.4, §13). Take the backup in the PR description before deploying: this drops data. + +-- Author columns: keep the old Telegram IDs for history, without the foreign keys to tg_permissions. +ALTER TABLE "common_group_labels" DROP CONSTRAINT "common_group_labels_granted_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "common_group_labels" DROP CONSTRAINT "common_group_labels_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "common_group_labels" DROP CONSTRAINT "common_group_labels_creator_check";--> statement-breakpoint +ALTER TABLE "common_group_labels" RENAME COLUMN "granted_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "common_group_labels" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "common_group_labels" ADD CONSTRAINT "common_group_labels_creator_check" CHECK ("common_group_labels"."legacy_created_by_tg_id" IS NOT NULL OR "common_group_labels"."created_by_sub" IS NOT NULL);--> statement-breakpoint +ALTER TABLE "web_associations" DROP CONSTRAINT "web_associations_created_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_associations" DROP CONSTRAINT "web_associations_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_associations" DROP CONSTRAINT "web_associations_creator_check";--> statement-breakpoint +ALTER TABLE "web_associations" RENAME COLUMN "created_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_associations" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_associations" ADD CONSTRAINT "web_associations_creator_check" CHECK ("web_associations"."legacy_created_by_tg_id" IS NOT NULL OR "web_associations"."created_by_sub" IS NOT NULL);--> statement-breakpoint +ALTER TABLE "web_faq_categories" DROP CONSTRAINT "web_faq_categories_created_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_faq_categories" DROP CONSTRAINT "web_faq_categories_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_faq_categories" DROP CONSTRAINT "web_faq_categories_creator_check";--> statement-breakpoint +ALTER TABLE "web_faq_categories" RENAME COLUMN "created_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_faq_categories" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_faq_categories" ADD CONSTRAINT "web_faq_categories_creator_check" CHECK ("web_faq_categories"."legacy_created_by_tg_id" IS NOT NULL OR "web_faq_categories"."created_by_sub" IS NOT NULL);--> statement-breakpoint +ALTER TABLE "web_faqs" DROP CONSTRAINT "web_faqs_created_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_faqs" DROP CONSTRAINT "web_faqs_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_faqs" DROP CONSTRAINT "web_faqs_creator_check";--> statement-breakpoint +ALTER TABLE "web_faqs" RENAME COLUMN "created_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_faqs" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_faqs" ADD CONSTRAINT "web_faqs_creator_check" CHECK ("web_faqs"."legacy_created_by_tg_id" IS NOT NULL OR "web_faqs"."created_by_sub" IS NOT NULL);--> statement-breakpoint +ALTER TABLE "web_freshman_cards" DROP CONSTRAINT "web_freshman_cards_created_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_freshman_cards" DROP CONSTRAINT "web_freshman_cards_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_freshman_cards" DROP CONSTRAINT "web_freshman_cards_creator_check";--> statement-breakpoint +ALTER TABLE "web_freshman_cards" RENAME COLUMN "created_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_freshman_cards" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_freshman_cards" ADD CONSTRAINT "web_freshman_cards_creator_check" CHECK ("web_freshman_cards"."legacy_created_by_tg_id" IS NOT NULL OR "web_freshman_cards"."created_by_sub" IS NOT NULL);--> statement-breakpoint +ALTER TABLE "web_freshman_faqs" DROP CONSTRAINT "web_freshman_faqs_created_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_freshman_faqs" DROP CONSTRAINT "web_freshman_faqs_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_freshman_faqs" DROP CONSTRAINT "web_freshman_faqs_creator_check";--> statement-breakpoint +ALTER TABLE "web_freshman_faqs" RENAME COLUMN "created_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_freshman_faqs" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_freshman_faqs" ADD CONSTRAINT "web_freshman_faqs_creator_check" CHECK ("web_freshman_faqs"."legacy_created_by_tg_id" IS NOT NULL OR "web_freshman_faqs"."created_by_sub" IS NOT NULL);--> statement-breakpoint +ALTER TABLE "web_guides_matricole" DROP CONSTRAINT "web_guides_matricole_created_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_guides_matricole" DROP CONSTRAINT "web_guides_matricole_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_guides_matricole" DROP CONSTRAINT "web_guides_matricole_creator_check";--> statement-breakpoint +ALTER TABLE "web_guides_matricole" RENAME COLUMN "created_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_guides_matricole" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_guides_matricole" ADD CONSTRAINT "web_guides_matricole_creator_check" CHECK ("web_guides_matricole"."legacy_created_by_tg_id" IS NOT NULL OR "web_guides_matricole"."created_by_sub" IS NOT NULL);--> statement-breakpoint +ALTER TABLE "web_projects" DROP CONSTRAINT "web_projects_created_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_projects" DROP CONSTRAINT "web_projects_modified_by_id_tg_permissions_user_id_fk";--> statement-breakpoint +ALTER TABLE "web_projects" DROP CONSTRAINT "web_projects_creator_check";--> statement-breakpoint +ALTER TABLE "web_projects" RENAME COLUMN "created_by_id" TO "legacy_created_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_projects" RENAME COLUMN "modified_by_id" TO "legacy_modified_by_tg_id";--> statement-breakpoint +ALTER TABLE "web_projects" ADD CONSTRAINT "web_projects_creator_check" CHECK ("web_projects"."legacy_created_by_tg_id" IS NOT NULL OR "web_projects"."created_by_sub" IS NOT NULL);--> statement-breakpoint + +-- Grants: the legacy table is not migrated (decision 6); the IdP table takes its name. +DROP TABLE "tg_grants";--> statement-breakpoint +ALTER TABLE "tg_grants_v2" RENAME TO "tg_grants";--> statement-breakpoint +ALTER TABLE "tg_grants" RENAME CONSTRAINT "tg_grants_v2_pkey" TO "tg_grants_pkey";--> statement-breakpoint +ALTER TABLE "tg_grants" RENAME CONSTRAINT "tg_grants_v2_validity_check" TO "tg_grants_validity_check";--> statement-breakpoint +ALTER SEQUENCE "tg_grants_v2_id_seq" RENAME TO "tg_grants_id_seq";--> statement-breakpoint +ALTER INDEX "tg_grants_v2_telegram_user_id_idx" RENAME TO "tg_grants_telegram_user_id_idx";--> statement-breakpoint + +-- Roles, group admins and Telegram links now live in the IdP; better-auth is gone. +DROP TABLE "tg_permissions";--> statement-breakpoint +DROP TABLE "tg_group_admins";--> statement-breakpoint +DROP TABLE "tg_link";--> statement-breakpoint +DROP TABLE "auth_passkey";--> statement-breakpoint +DROP TABLE "auth_sessions";--> statement-breakpoint +DROP TABLE "auth_accounts";--> statement-breakpoint +DROP TABLE "auth_verifications";--> statement-breakpoint +DROP TABLE "auth_users"; diff --git a/drizzle/meta/0028_snapshot.json b/drizzle/meta/0028_snapshot.json new file mode 100644 index 0000000..2223bf7 --- /dev/null +++ b/drizzle/meta/0028_snapshot.json @@ -0,0 +1,2944 @@ +{ + "id": "082f20b3-1a97-4dc1-93aa-32e7aa47089f", + "prevId": "a5661fff-41d2-486d-b20a-cb623b805a5b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.common_group_labels": { + "name": "common_group_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "common_group_labels_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "label": { + "name": "label", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "varchar(7)", + "primaryKey": false, + "notNull": true, + "default": "'#ffffff'" + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "common_group_labels_label_unique": { + "name": "common_group_labels_label_unique", + "nullsNotDistinct": false, + "columns": [ + "label" + ] + } + }, + "policies": {}, + "checkConstraints": { + "common_group_labels_creator_check": { + "name": "common_group_labels_creator_check", + "value": "\"common_group_labels\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"common_group_labels\".\"created_by_sub\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.common_idp_state": { + "name": "common_idp_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.esign_documents": { + "name": "esign_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pages": { + "name": "pages", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "esign_documents_request_version_idx": { + "name": "esign_documents_request_version_idx", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"esign_documents\".\"request_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "esign_documents_request_id_esign_requests_id_fk": { + "name": "esign_documents_request_id_esign_requests_id_fk", + "tableFrom": "esign_documents", + "tableTo": "esign_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.esign_drive_access": { + "name": "esign_drive_access", + "schema": "", + "columns": { + "drive_id": { + "name": "drive_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "request_roles": { + "name": "request_roles", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "request_permissions": { + "name": "request_permissions", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "sign_roles": { + "name": "sign_roles", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "sign_permissions": { + "name": "sign_permissions", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "updated_by_sub": { + "name": "updated_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.esign_events": { + "name": "esign_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "at": { + "name": "at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_sub": { + "name": "actor_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "esign_events_request_idx": { + "name": "esign_events_request_idx", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "esign_events_request_id_esign_requests_id_fk": { + "name": "esign_events_request_id_esign_requests_id_fk", + "tableFrom": "esign_events", + "tableTo": "esign_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.esign_jobs": { + "name": "esign_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_hash": { + "name": "ticket_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "signer_id": { + "name": "signer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "base_version": { + "name": "base_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "places": { + "name": "places", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "step": { + "name": "step", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "prepared": { + "name": "prepared", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "prepared_pdf": { + "name": "prepared_pdf", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "seen_at": { + "name": "seen_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "esign_jobs_request_idx": { + "name": "esign_jobs_request_idx", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "esign_jobs_request_id_esign_requests_id_fk": { + "name": "esign_jobs_request_id_esign_requests_id_fk", + "tableFrom": "esign_jobs", + "tableTo": "esign_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "esign_jobs_signer_id_esign_signers_id_fk": { + "name": "esign_jobs_signer_id_esign_signers_id_fk", + "tableFrom": "esign_jobs", + "tableTo": "esign_signers", + "columnsFrom": [ + "signer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "esign_jobs_ticket_hash_unique": { + "name": "esign_jobs_ticket_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "ticket_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.esign_profiles": { + "name": "esign_profiles", + "schema": "", + "columns": { + "sub": { + "name": "sub", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fiscal_code": { + "name": "fiscal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificate_subject": { + "name": "certificate_subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.esign_requests": { + "name": "esign_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_progress'" + }, + "ordered": { + "name": "ordered", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "delivery": { + "name": "delivery", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "drives": { + "name": "drives", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "esign_requests_status_idx": { + "name": "esign_requests_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "esign_requests_created_by_idx": { + "name": "esign_requests_created_by_idx", + "columns": [ + { + "expression": "created_by_sub", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.esign_signers": { + "name": "esign_signers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sub": { + "name": "sub", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "places": { + "name": "places", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signatures_done": { + "name": "signatures_done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "signed_at": { + "name": "signed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fiscal_code": { + "name": "fiscal_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificate_subject": { + "name": "certificate_subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "certificate_issuer": { + "name": "certificate_issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decline_reason": { + "name": "decline_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "esign_signers_request_idx": { + "name": "esign_signers_request_idx", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "esign_signers_sub_idx": { + "name": "esign_signers_sub_idx", + "columns": [ + { + "expression": "sub", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "esign_signers_request_position_idx": { + "name": "esign_signers_request_position_idx", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "esign_signers_request_id_esign_requests_id_fk": { + "name": "esign_signers_request_id_esign_requests_id_fk", + "tableFrom": "esign_signers", + "tableTo": "esign_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.people_files": { + "name": "people_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.people_identities": { + "name": "people_identities", + "schema": "", + "columns": { + "person_id": { + "name": "person_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_sub": { + "name": "account_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_number": { + "name": "member_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegram_id": { + "name": "telegram_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "people_identities_account_sub_unique": { + "name": "people_identities_account_sub_unique", + "nullsNotDistinct": false, + "columns": [ + "account_sub" + ] + }, + "people_identities_member_number_unique": { + "name": "people_identities_member_number_unique", + "nullsNotDistinct": false, + "columns": [ + "member_number" + ] + }, + "people_identities_telegram_id_unique": { + "name": "people_identities_telegram_id_unique", + "nullsNotDistinct": false, + "columns": [ + "telegram_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.people_receipt_requests": { + "name": "people_receipt_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "receipt_id": { + "name": "receipt_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "officer_sub": { + "name": "officer_sub", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_hash": { + "name": "original_hash", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "people_receipt_request_identity_idx": { + "name": "people_receipt_request_identity_idx", + "columns": [ + { + "expression": "receipt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "people_receipt_requests_request_id_esign_requests_id_fk": { + "name": "people_receipt_requests_request_id_esign_requests_id_fk", + "tableFrom": "people_receipt_requests", + "tableTo": "esign_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "people_receipt_requests_receipt_id_people_receipts_id_fk": { + "name": "people_receipt_requests_receipt_id_people_receipts_id_fk", + "tableFrom": "people_receipt_requests", + "tableTo": "people_receipts", + "columnsFrom": [ + "receipt_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.people_receipts": { + "name": "people_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "number": { + "name": "number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "people_receipts_request_id_esign_requests_id_fk": { + "name": "people_receipts_request_id_esign_requests_id_fk", + "tableFrom": "people_receipts", + "tableTo": "esign_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "people_receipts_number_unique": { + "name": "people_receipts_number_unique", + "nullsNotDistinct": false, + "columns": [ + "number" + ] + }, + "people_receipts_request_id_unique": { + "name": "people_receipts_request_id_unique", + "nullsNotDistinct": false, + "columns": [ + "request_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.people_records": { + "name": "people_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "people_records_kind_idx": { + "name": "people_records_kind_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_audit_log": { + "name": "tg_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tg_audit_log_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "admin_id": { + "name": "admin_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "until": { + "name": "until", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "actor_sub": { + "name": "actor_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_tg_id": { + "name": "actor_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "client": { + "name": "client", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "basis": { + "name": "basis", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "review": { + "name": "review", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auditlog_adminid_idx": { + "name": "auditlog_adminid_idx", + "columns": [ + { + "expression": "admin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auditlog_actor_tg_id_idx": { + "name": "auditlog_actor_tg_id_idx", + "columns": [ + { + "expression": "actor_tg_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auditlog_idempotency_key_idx": { + "name": "auditlog_idempotency_key_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_grants": { + "name": "tg_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tg_grants_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "telegram_user_id": { + "name": "telegram_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "valid_since": { + "name": "valid_since", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "valid_until": { + "name": "valid_until", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_by_sub": { + "name": "granted_by_sub", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_via_client": { + "name": "granted_via_client", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interrupted_at": { + "name": "interrupted_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "interrupted_by_sub": { + "name": "interrupted_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interrupted_by_tg_id": { + "name": "interrupted_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tg_grants_telegram_user_id_idx": { + "name": "tg_grants_telegram_user_id_idx", + "columns": [ + { + "expression": "telegram_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tg_grants_validity_check": { + "name": "tg_grants_validity_check", + "value": "\"tg_grants\".\"valid_since\" < \"tg_grants\".\"valid_until\"" + } + }, + "isRLSEnabled": false + }, + "public.tg_group_label_relations": { + "name": "tg_group_label_relations", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tg_group_label_relations_group_id_tg_groups_telegram_id_fk": { + "name": "tg_group_label_relations_group_id_tg_groups_telegram_id_fk", + "tableFrom": "tg_group_label_relations", + "tableTo": "tg_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "telegram_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tg_group_label_relations_label_id_common_group_labels_id_fk": { + "name": "tg_group_label_relations_label_id_common_group_labels_id_fk", + "tableFrom": "tg_group_label_relations", + "tableTo": "common_group_labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "tg_group_label_relations_group_id_label_id_pk": { + "name": "tg_group_label_relations_group_id_label_id_pk", + "columns": [ + "group_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_groups": { + "name": "tg_groups", + "schema": "", + "columns": { + "telegram_id": { + "name": "telegram_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "hide": { + "name": "hide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tg_groups_link_unique": { + "name": "tg_groups_link_unique", + "nullsNotDistinct": false, + "columns": [ + "link" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_messages": { + "name": "tg_messages", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "varchar(8704)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tg_messages_chat_author_timestamp_idx": { + "name": "tg_messages_chat_author_timestamp_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tg_messages_chat_id_message_id_pk": { + "name": "tg_messages_chat_id_message_id_pk", + "columns": [ + "chat_id", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_test": { + "name": "tg_test", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tg_test_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "text": { + "name": "text", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_users": { + "name": "tg_users", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "lang_code": { + "name": "lang_code", + "type": "varchar(35)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wa_group_label_relations": { + "name": "wa_group_label_relations", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wa_group_label_relations_group_id_wa_groups_group_id_fk": { + "name": "wa_group_label_relations_group_id_wa_groups_group_id_fk", + "tableFrom": "wa_group_label_relations", + "tableTo": "wa_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "group_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wa_group_label_relations_label_id_common_group_labels_id_fk": { + "name": "wa_group_label_relations_label_id_common_group_labels_id_fk", + "tableFrom": "wa_group_label_relations", + "tableTo": "common_group_labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "wa_group_label_relations_group_id_label_id_pk": { + "name": "wa_group_label_relations_group_id_label_id_pk", + "columns": [ + "group_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wa_groups": { + "name": "wa_groups", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "wa_groups_group_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "hide": { + "name": "hide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "wa_groups_link_unique": { + "name": "wa_groups_link_unique", + "nullsNotDistinct": false, + "columns": [ + "link" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_associations": { + "name": "web_associations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_associations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_it": { + "name": "description_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_en": { + "name": "description_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "facebook": { + "name": "facebook", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instagram": { + "name": "instagram", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tiktok": { + "name": "tiktok", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "x": { + "name": "x", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "youtube": { + "name": "youtube", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegram": { + "name": "telegram", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linkedin": { + "name": "linkedin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spotify": { + "name": "spotify", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "web_associations_creator_check": { + "name": "web_associations_creator_check", + "value": "\"web_associations\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"web_associations\".\"created_by_sub\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.web_faq_categories": { + "name": "web_faq_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_faq_categories_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "title_it": { + "name": "title_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "web_faq_categories_creator_check": { + "name": "web_faq_categories_creator_check", + "value": "\"web_faq_categories\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"web_faq_categories\".\"created_by_sub\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.web_faqs": { + "name": "web_faqs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_faqs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "title_it": { + "name": "title_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_it": { + "name": "description_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_en": { + "name": "description_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "web_faqs_category_id_web_faq_categories_id_fk": { + "name": "web_faqs_category_id_web_faq_categories_id_fk", + "tableFrom": "web_faqs", + "tableTo": "web_faq_categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "web_faqs_creator_check": { + "name": "web_faqs_creator_check", + "value": "\"web_faqs\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"web_faqs\".\"created_by_sub\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.web_freshman_cards": { + "name": "web_freshman_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "web_freshman_cards_creator_check": { + "name": "web_freshman_cards_creator_check", + "value": "\"web_freshman_cards\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"web_freshman_cards\".\"created_by_sub\" IS NOT NULL" + }, + "web_freshman_cards_slot_check": { + "name": "web_freshman_cards_slot_check", + "value": "\"web_freshman_cards\".\"id\" in ('featured-1', 'featured-2', 'guides-1', 'guides-2', 'guides-3', 'guides-4', 'guides-5', 'materials-1', 'tools-1', 'tools-2', 'telegram-1', 'telegram-2', 'telegram-3', 'telegram-4', 'telegram-5', 'telegram-6', 'telegram-7', 'telegram-8')" + }, + "web_freshman_cards_icon_check": { + "name": "web_freshman_cards_icon_check", + "value": "\"web_freshman_cards\".\"icon\" in ('chart', 'monitor', 'book', 'map', 'news', 'money', 'folder', 'repeat', 'refresh', 'bell', 'user', 'users', 'cart', 'home')" + } + }, + "isRLSEnabled": false + }, + "public.web_freshman_faqs": { + "name": "web_freshman_faqs", + "schema": "", + "columns": { + "position": { + "name": "position", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "faq_id": { + "name": "faq_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "web_freshman_faqs_faq_id_web_faqs_id_fk": { + "name": "web_freshman_faqs_faq_id_web_faqs_id_fk", + "tableFrom": "web_freshman_faqs", + "tableTo": "web_faqs", + "columnsFrom": [ + "faq_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "web_freshman_faqs_faq_id_unique": { + "name": "web_freshman_faqs_faq_id_unique", + "nullsNotDistinct": false, + "columns": [ + "faq_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "web_freshman_faqs_creator_check": { + "name": "web_freshman_faqs_creator_check", + "value": "\"web_freshman_faqs\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"web_freshman_faqs\".\"created_by_sub\" IS NOT NULL" + }, + "web_freshman_faqs_position_check": { + "name": "web_freshman_faqs_position_check", + "value": "\"web_freshman_faqs\".\"position\" between 0 and 4" + } + }, + "isRLSEnabled": false + }, + "public.web_group_link_reports": { + "name": "web_group_link_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_group_link_reports_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(2)", + "primaryKey": false, + "notNull": false + }, + "report_type": { + "name": "report_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "reported_link": { + "name": "reported_link", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_guides_matricole": { + "name": "web_guides_matricole", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_guides_matricole_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file": { + "name": "file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "web_guides_matricole_version_unique": { + "name": "web_guides_matricole_version_unique", + "nullsNotDistinct": false, + "columns": [ + "version" + ] + } + }, + "policies": {}, + "checkConstraints": { + "web_guides_matricole_creator_check": { + "name": "web_guides_matricole_creator_check", + "value": "\"web_guides_matricole\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"web_guides_matricole\".\"created_by_sub\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.web_projects": { + "name": "web_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_projects_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_it": { + "name": "description_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_en": { + "name": "description_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "legacy_created_by_tg_id": { + "name": "legacy_created_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "legacy_modified_by_tg_id": { + "name": "legacy_modified_by_tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_sub": { + "name": "created_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "modified_by_sub": { + "name": "modified_by_sub", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "web_projects_creator_check": { + "name": "web_projects_creator_check", + "value": "\"web_projects\".\"legacy_created_by_tg_id\" IS NOT NULL OR \"web_projects\".\"created_by_sub\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.web_test": { + "name": "web_test", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_test_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "text": { + "name": "text", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.groups_view": { + "columns": { + "telegram_id": { + "name": "telegram_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "link": { + "name": "link", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "hide": { + "name": "hide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "definition": "(select \"telegram_id\", \"title\", \"link\", \"hide\", 'tg' as \"type\" from \"tg_groups\") union all (select \"group_id\", \"title\", \"link\", \"hide\", 'wa' as \"type\" from \"wa_groups\")", + "name": "groups_view", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.group_label_relations_view": { + "columns": { + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "definition": "(select \"group_id\", \"label_id\", 'tg' as \"type\" from \"tg_group_label_relations\") union all (select \"group_id\", \"label_id\", 'wa' as \"type\" from \"wa_group_label_relations\")", + "name": "group_label_relations_view", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index ba39b69..01cc5fb 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -197,6 +197,13 @@ "when": 1792664842483, "tag": "0027_people", "breakpoints": true + }, + { + "idx": 28, + "version": "7", + "when": 1792682679485, + "tag": "0028_idp_phase6_cleanup", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index 1f340a7..e1659e4 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,7 @@ "typecheck": "tsc --noEmit", "check": "biome check", "check:fix": "biome check --write --unsafe", - "seed:user": "bun ./scripts/seed-user.ts", - "seed:db": "bun ./scripts/seed-db.ts", - "idp:export": "bun ./scripts/idp-migration-export.ts", - "idp:reconcile": "bun ./scripts/idp-migration-reconcile.ts" + "seed:db": "bun ./scripts/seed-db.ts" }, "devDependencies": { "@biomejs/biome": "^2.3.13", @@ -49,8 +46,6 @@ "dependencies": { "@azure/identity": "^4.13.0", "@azure/storage-blob": "^12.31.0", - "@better-auth/drizzle-adapter": "1.5.5", - "@better-auth/passkey": "1.5.5", "@hono/trpc-server": "^0.4.2", "@microsoft/microsoft-graph-client": "^3.0.7", "@polinetwork/auth-kit": "^1.2.0", @@ -59,7 +54,6 @@ "@socket.io/devalue-parser": "^0.1.0", "@t3-oss/env-core": "^0.13.8", "@trpc/server": "11.5.1", - "better-auth": "1.5.5", "croner": "^9.1.0", "drizzle-orm": "^0.44.7", "fast-xml-parser": "^5.11.2", @@ -76,4 +70,4 @@ "socket.io": "^4.8.3", "zod": "^4.1.11" } -} \ No newline at end of file +} diff --git a/scripts/idp-migration-export.ts b/scripts/idp-migration-export.ts deleted file mode 100644 index 0b78972..0000000 --- a/scripts/idp-migration-export.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * IdP–Telegram migration, Phase 2 step 2 (RFC v3 §13): export the legacy Telegram role holders. - * - * bun scripts/idp-migration-export.ts --out - * - * Reads tg_permissions and tg_group_admins in a read-only transaction, using only the DB_* - * variables. It does not import "@/db", which would run migrations. The output contains - * Telegram IDs and usernames: keep it out of the repository and delete it after the migration. - */ -import { writeFile } from "node:fs/promises" -import { parseArgs } from "node:util" -import { SQL } from "bun" -import { z } from "zod" -import { EXPORT_SCHEMA, type LegacyExport, legacyExportSchema } from "./lib/legacy-access" - -const { values } = parseArgs({ options: { out: { type: "string" } } }) -if (!values.out) { - console.error("Usage: bun scripts/idp-migration-export.ts --out ") - process.exit(2) -} - -const env = z - .object({ - DB_HOST: z.string().min(1), - DB_PORT: z.coerce.number().min(1).max(65535).default(5432), - DB_USER: z.string().min(1), - DB_PASS: z.string().min(1), - DB_NAME: z.string().min(3).default("polinetwork_backend"), - }) - .parse(process.env) - -const sql = new SQL({ - hostname: env.DB_HOST, - port: env.DB_PORT, - username: env.DB_USER, - password: env.DB_PASS, - database: env.DB_NAME, - max: 1, -}) - -const iso = (value: Date | string | null) => (value === null ? null : new Date(value).toISOString()) - -try { - const exported: LegacyExport = await sql.begin("read only", async (tx) => { - const holders = await tx` - SELECT p.user_id::text AS "telegramId", u.username, p.roles, - p.added_by_id::text AS "addedBy", p.modified_by_id::text AS "modifiedBy", - p.created_at AS "createdAt", p.updated_at AS "updatedAt" - FROM tg_permissions p - LEFT JOIN tg_users u ON u.user_id = p.user_id - ORDER BY p.user_id` - const groupAdmins = await tx` - SELECT a.user_id::text AS "telegramId", u.username, a.group_id::text AS "groupId", - g.title AS "groupTitle", a.added_by_id::text AS "addedBy", a.created_at AS "createdAt" - FROM tg_group_admins a - LEFT JOIN tg_users u ON u.user_id = a.user_id - LEFT JOIN tg_groups g ON g.telegram_id = a.group_id - ORDER BY a.user_id, a.group_id` - return legacyExportSchema.parse({ - schema: EXPORT_SCHEMA, - exportedAt: new Date().toISOString(), - holders: holders.map((row: Record) => ({ - ...row, - createdAt: iso(row.createdAt as Date), - updatedAt: iso(row.updatedAt as Date | null), - })), - groupAdmins: groupAdmins.map((row: Record) => ({ - ...row, - createdAt: iso(row.createdAt as Date), - })), - }) - }) - await writeFile(values.out, `${JSON.stringify(exported, null, 2)}\n`, { mode: 0o600, flag: "wx" }) - console.log( - `Exported ${exported.holders.length} role holders and ${exported.groupAdmins.length} group-admin assignments to ${values.out}` - ) -} finally { - await sql.close() -} diff --git a/scripts/idp-migration-reconcile.ts b/scripts/idp-migration-reconcile.ts deleted file mode 100644 index ff7947c..0000000 --- a/scripts/idp-migration-reconcile.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * IdP–Telegram migration, Phase 2 step 5 (RFC v3 §13): compare every legacy role holder's - * current decisions with the IdP access snapshot. - * - * bun scripts/idp-migration-reconcile.ts --export --snapshot \ - * [--json ] [--at ] - * bun scripts/idp-migration-reconcile.ts --export --snapshot-url - * - * With --snapshot-url, IDP_ACCESS_TOKEN must hold the backend client's access token for the - * snapshot endpoint (RFC §5.3). The Markdown report is printed to stdout; it names people, - * so do not paste it into public places. - */ -import { readFile, writeFile } from "node:fs/promises" -import { parseArgs } from "node:util" -import { accessSnapshotSchema, formatReport, legacyExportSchema, reconcile } from "./lib/legacy-access" - -const { values } = parseArgs({ - options: { - export: { type: "string" }, - snapshot: { type: "string" }, - "snapshot-url": { type: "string" }, - json: { type: "string" }, - at: { type: "string" }, - }, -}) - -if (!values.export || !values.snapshot === !values["snapshot-url"]) { - console.error( - "Usage: bun scripts/idp-migration-reconcile.ts --export (--snapshot | --snapshot-url ) [--json ] [--at ]" - ) - process.exit(2) -} - -async function loadSnapshot(): Promise { - if (values.snapshot) return JSON.parse(await readFile(values.snapshot, "utf8")) - const token = process.env.IDP_ACCESS_TOKEN - if (!token) throw new Error("IDP_ACCESS_TOKEN is required with --snapshot-url") - const response = await fetch(values["snapshot-url"] as string, { - headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }, - }) - if (!response.ok) throw new Error(`Snapshot request failed: HTTP ${response.status}`) - return response.json() -} - -const at = values.at ? new Date(values.at) : new Date() -if (Number.isNaN(at.getTime())) throw new Error(`Invalid --at time: ${values.at}`) - -const legacy = legacyExportSchema.parse(JSON.parse(await readFile(values.export, "utf8"))) -const snapshot = accessSnapshotSchema.parse(await loadSnapshot()) -const report = reconcile(legacy, snapshot, at) - -if (values.json) await writeFile(values.json, `${JSON.stringify(report, null, 2)}\n`, { mode: 0o600 }) -process.stdout.write(formatReport(report)) diff --git a/scripts/lib/legacy-access.ts b/scripts/lib/legacy-access.ts deleted file mode 100644 index 4ea3e60..0000000 --- a/scripts/lib/legacy-access.ts +++ /dev/null @@ -1,351 +0,0 @@ -import { z } from "zod" - -/** - * IdP–Telegram migration, Phase 2 (RFC v3 §13): compare what the legacy Telegram roles allow - * today with what the IdP access snapshot grants, for every legacy role holder. - * - * Each check reproduces one role rule as it is enforced today, and names the permission that - * replaces it (RFC §4.1, §8, §10.2). Some permissions replace several legacy rules that do not - * agree with each other, so the report compares rule by rule rather than permission by - * permission. Keep these rules in sync with the callers until they are switched to the IdP. - */ -type LegacyRule = { anyOf: readonly string[] | "any"; deny?: readonly string[] } - -export type LegacyCheck = { - id: string - description: string - permission: string - rule: LegacyRule -} - -const DASHBOARD_READ: LegacyRule = { anyOf: ["owner", "direttivo", "president", "hr", "web"], deny: ["creator"] } -const DASHBOARD_WRITE: LegacyRule = { anyOf: ["owner", "direttivo", "president"], deny: ["creator"] } -const DASHBOARD_WEB_WRITE: LegacyRule = { anyOf: ["owner", "direttivo", "president", "web"], deny: ["creator"] } - -export const LEGACY_CHECKS: readonly LegacyCheck[] = [ - { - id: "dashboard.access", - description: "Sign in to the admin dashboard (admin: hasAdminRole)", - permission: "admin:access", - rule: DASHBOARD_READ, - }, - { - id: "bot.moderate", - description: "/ban /tban /unban /kick /mute /tmute /unmute (telegram: commands/moderation)", - permission: "tg:moderate", - rule: { anyOf: ["owner", "direttivo"], deny: ["creator"] }, - }, - { - id: "bot.ban-all", - description: "/ban_all /unban_all (telegram: commands/moderation/banall.ts)", - permission: "tg:moderate:global", - rule: { anyOf: ["owner", "direttivo"] }, - }, - { - id: "bot.campaign-review", - description: "Campaign review Confirm/Release (telegram: CAMPAIGN_REVIEW_ROLES)", - permission: "tg:moderate:global", - rule: { anyOf: ["owner", "direttivo"] }, - }, - { - id: "bot.delete", - description: "/del (telegram: commands/moderation/del.ts)", - permission: "tg:messages:delete", - rule: { anyOf: ["admin", "owner", "direttivo"] }, - }, - { - id: "bot.pin", - description: "/pin /unpin (telegram: commands/pin.ts)", - permission: "tg:messages:pin", - rule: { anyOf: ["direttivo", "owner"] }, - }, - { - id: "bot.immune", - description: "Ban-all immunity and campaign-spam exemption (telegram: BYPASS_ROLES, NETWORK_PRIVILEGED_ROLES)", - permission: "tg:immune", - rule: { anyOf: ["president", "owner", "direttivo"] }, - }, - { - id: "bot.trusted", - description: "Hashtag-rule exemption: holds any role (telegram: group-specific-actions.ts)", - permission: "tg:trusted", - rule: { anyOf: "any" }, - }, - { - id: "bot.audit", - description: "/audit (telegram: commands/management/audit.ts)", - permission: "tg:audit:read", - rule: { anyOf: ["hr", "owner", "direttivo"] }, - }, - { - id: "dashboard.telegram-users", - description: "Telegram user list and details in the dashboard", - permission: "tg:users:read", - rule: DASHBOARD_READ, - }, - { - id: "dashboard.telegram-messages", - description: "Stored Telegram messages in the dashboard", - permission: "tg:messages:read", - rule: DASHBOARD_READ, - }, - { - id: "bot.groups", - description: "/updategroup /regenerate_group_links (telegram: commands/management/groups.ts)", - permission: "tg:groups:manage", - rule: { anyOf: ["owner", "direttivo"] }, - }, - { - id: "dashboard.telegram-groups", - description: "Hide or leave a Telegram group in the dashboard (admin: hasGroupWriteRole)", - permission: "tg:groups:manage", - rule: DASHBOARD_WEB_WRITE, - }, - { - id: "backend.add-bot", - description: "Add the bot to a group (backend: CAN_ADD_BOT)", - permission: "tg:bot:add", - rule: { anyOf: ["hr", "owner", "creator", "direttivo"] }, - }, - { - id: "dashboard.grants-read", - description: "View grants in the dashboard", - permission: "tg:grants:read", - rule: DASHBOARD_READ, - }, - { - id: "backend.grants-manage", - description: "Create and interrupt grants (backend: CAN_MANAGE_GRANTS)", - permission: "tg:grants:manage", - rule: { anyOf: ["president", "owner", "direttivo"] }, - }, - { - id: "bot.grants-menu", - description: "Delete a granted user's message from the grants log (telegram: tg-logger/grants.ts)", - permission: "tg:grants:manage", - rule: { anyOf: ["direttivo"] }, - }, - { - id: "dashboard.whatsapp-groups", - description: "WhatsApp group CRUD in the dashboard (admin: hasGroupWriteRole)", - permission: "wa:groups:manage", - rule: DASHBOARD_WEB_WRITE, - }, - { - id: "dashboard.group-labels", - description: "Group labels and tagging in the dashboard (admin: hasWebWriteRole)", - permission: "groups:labels:write", - rule: DASHBOARD_WEB_WRITE, - }, - { - id: "dashboard.web-content", - description: "Associations, FAQs, projects and freshman guides (admin: hasWebWriteRole)", - permission: "web:content:write", - rule: DASHBOARD_WEB_WRITE, - }, - { - id: "dashboard.group-link-reports", - description: "Resolve or dismiss group-link reports (admin: hasWebWriteRole)", - permission: "web:reports:manage", - rule: DASHBOARD_WEB_WRITE, - }, - { - id: "dashboard.azure", - description: "Azure member management; becomes creating a socio only (admin: hasWriteAdminRole)", - permission: "azure:members:create", - rule: DASHBOARD_WRITE, - }, -] - -export const CHECKED_PERMISSIONS: readonly string[] = [...new Set(LEGACY_CHECKS.map((check) => check.permission))] - -export function legacyAllows(rule: LegacyRule, roles: readonly string[]) { - if (rule.deny?.some((role) => roles.includes(role))) return false - return rule.anyOf === "any" ? roles.length > 0 : rule.anyOf.some((role) => roles.includes(role)) -} - -const telegramId = z.string().regex(/^-?\d+$/) -const timestamp = z.iso.datetime({ offset: true }) - -export const EXPORT_SCHEMA = "polinetwork.legacy-access-export/v1" - -export const legacyExportSchema = z.object({ - schema: z.literal(EXPORT_SCHEMA), - exportedAt: timestamp, - holders: z.array( - z.object({ - telegramId, - username: z.string().nullable(), - roles: z.array(z.string()), - addedBy: telegramId, - modifiedBy: telegramId.nullable(), - createdAt: timestamp, - updatedAt: timestamp.nullable(), - }) - ), - groupAdmins: z.array( - z.object({ - telegramId, - username: z.string().nullable(), - groupId: telegramId, - groupTitle: z.string().nullable(), - addedBy: telegramId, - createdAt: timestamp, - }) - ), -}) - -export type LegacyExport = z.infer - -export const accessSnapshotSchema = z.object({ - // A consumer rejects a major version it does not know (RFC §5.3). - schema: z.string().regex(/^polinetwork\.access-snapshot\/v1(\.\d+)*$/), - projection: z.string(), - generation: z.number(), - builtAt: timestamp, - sources: z.record( - z.string(), - z.object({ health: z.string(), observedAt: timestamp.nullable().optional(), error: z.string().optional() }) - ), - subjects: z.array( - z.object({ - sub: z.string(), - telegramId: telegramId.nullable(), - permissions: z.record(z.string(), z.object({ validUntil: timestamp.nullable() })), - }) - ), -}) - -export type AccessSnapshot = z.infer - -export type CheckStatus = "match" | "missing" | "extra" - -export type HolderReport = { - telegramId: string - username: string | null - roles: string[] - /** The IdP subject linked to this Telegram ID, or null if they have not linked it yet. */ - sub: string | null - checks: { id: string; permission: string; legacy: boolean; idp: boolean; status: CheckStatus }[] -} - -export type ReconciliationReport = { - at: string - snapshot: { generation: number; builtAt: string; unhealthySources: string[] } - holders: HolderReport[] - /** IdP subjects holding a checked permission who hold no legacy role. */ - notLegacyHolders: { sub: string; telegramId: string | null; permissions: string[] }[] - groupAdmins: { people: number; assignments: number } -} - -function heldAt(permissions: AccessSnapshot["subjects"][number]["permissions"], key: string, at: Date) { - const entry = permissions[key] - return !!entry && (entry.validUntil === null || new Date(entry.validUntil) > at) -} - -export function reconcile(legacy: LegacyExport, snapshot: AccessSnapshot, at = new Date()): ReconciliationReport { - const byTelegramId = new Map() - for (const subject of snapshot.subjects) { - if (subject.telegramId === null) continue - // The IdP excludes duplicated Telegram IDs (RFC §7.6); refuse a snapshot that does not. - if (byTelegramId.has(subject.telegramId)) - throw new Error(`Telegram ID ${subject.telegramId} is linked to more than one IdP subject`) - byTelegramId.set(subject.telegramId, subject) - } - - const holders = legacy.holders - .map((holder): HolderReport => { - const subject = byTelegramId.get(holder.telegramId) - return { - telegramId: holder.telegramId, - username: holder.username, - roles: [...holder.roles].sort(), - sub: subject?.sub ?? null, - checks: LEGACY_CHECKS.map((check) => { - const allowed = legacyAllows(check.rule, holder.roles) - const granted = subject ? heldAt(subject.permissions, check.permission, at) : false - const status: CheckStatus = allowed === granted ? "match" : allowed ? "missing" : "extra" - return { id: check.id, permission: check.permission, legacy: allowed, idp: granted, status } - }), - } - }) - .sort((a, b) => a.telegramId.localeCompare(b.telegramId)) - - const legacyIds = new Set(legacy.holders.map((holder) => holder.telegramId)) - const notLegacyHolders = snapshot.subjects - .filter((subject) => subject.telegramId === null || !legacyIds.has(subject.telegramId)) - .map((subject) => ({ - sub: subject.sub, - telegramId: subject.telegramId, - permissions: CHECKED_PERMISSIONS.filter((key) => heldAt(subject.permissions, key, at)), - })) - .filter((subject) => subject.permissions.length > 0) - .sort((a, b) => a.sub.localeCompare(b.sub)) - - return { - at: at.toISOString(), - snapshot: { - generation: snapshot.generation, - builtAt: snapshot.builtAt, - unhealthySources: Object.entries(snapshot.sources) - .filter(([, source]) => source.health !== "ok") - .map(([name, source]) => `${name} (${source.health}${source.error ? `: ${source.error}` : ""})`), - }, - holders, - notLegacyHolders, - groupAdmins: { - people: new Set(legacy.groupAdmins.map((entry) => entry.telegramId)).size, - assignments: legacy.groupAdmins.length, - }, - } -} - -function person(holder: { telegramId: string | null; username: string | null }) { - return holder.username ? `@${holder.username} (${holder.telegramId})` : (holder.telegramId ?? "no Telegram link") -} - -export function formatReport(report: ReconciliationReport) { - const unlinked = report.holders.filter((holder) => holder.sub === null) - const differing = report.holders.filter( - (holder) => holder.sub !== null && holder.checks.some((check) => check.status !== "match") - ) - const lines = [ - "# IdP–Telegram migration: reconciliation report", - "", - `Evaluated at ${report.at} against snapshot generation ${report.snapshot.generation} built at ${report.snapshot.builtAt}.`, - ] - if (report.snapshot.unhealthySources.length > 0) - lines.push("", `**Unhealthy snapshot sources:** ${report.snapshot.unhealthySources.join(", ")}.`) - lines.push( - "", - `- Legacy role holders: ${report.holders.length}`, - `- Not linked in the IdP yet: ${unlinked.length}`, - `- Linked with differences: ${differing.length}`, - `- Linked and matching: ${report.holders.length - unlinked.length - differing.length}`, - `- IdP subjects with checked permissions and no legacy role: ${report.notLegacyHolders.length}`, - `- Legacy per-group admins (not migrated; replaced by native chat admins): ${report.groupAdmins.people} people, ${report.groupAdmins.assignments} assignments`, - "", - "## Not linked in the IdP", - "" - ) - if (unlinked.length === 0) lines.push("Everyone has linked Telegram.") - for (const holder of unlinked) lines.push(`- ${person(holder)}: ${holder.roles.join(", ")}`) - lines.push("", "## Differences", "") - if (differing.length === 0) lines.push("No differences for linked holders.") - for (const holder of differing) { - lines.push(`### ${person(holder)}`, "", `Legacy roles: ${holder.roles.join(", ")} · IdP subject: ${holder.sub}`, "") - lines.push("| Check | Permission | Legacy | IdP | |", "| --- | --- | --- | --- | --- |") - for (const check of holder.checks.filter((entry) => entry.status !== "match")) - lines.push( - `| ${check.id} | \`${check.permission}\` | ${check.legacy ? "yes" : "no"} | ${check.idp ? "yes" : "no"} | ${check.status === "missing" ? "loses access" : "gains access"} |` - ) - lines.push("") - } - lines.push("## IdP subjects without a legacy role", "") - if (report.notLegacyHolders.length === 0) lines.push("None.") - for (const subject of report.notLegacyHolders) - lines.push(`- ${subject.sub} (${subject.telegramId ?? "no Telegram link"}): ${subject.permissions.join(", ")}`) - lines.push("", "## Checks", "") - for (const check of LEGACY_CHECKS) lines.push(`- \`${check.id}\` → \`${check.permission}\`: ${check.description}`) - return `${lines.join("\n")}\n` -} diff --git a/scripts/seed-db.ts b/scripts/seed-db.ts index 0a23434..f94d437 100644 --- a/scripts/seed-db.ts +++ b/scripts/seed-db.ts @@ -1,10 +1,8 @@ import { argv } from "bun" import { like, sql } from "drizzle-orm" import type { PgInsertValue, PgTable } from "drizzle-orm/pg-core" -import { USER_ROLE } from "@/constants" import { DB, SCHEMA } from "@/db" import { ARRAY_AUDIT_TYPE, AUDIT_BASIS, AUDIT_REVIEW, AUDIT_TYPE } from "@/db/schema/tg/audit-log" -import type { TUserRole } from "@/db/schema/tg/permissions" import { tgMessagesCipher } from "@/routers/tg/messages" import { defaultFreshmanCards } from "@/routers/web/freshman/seed" import { encryptUser } from "@/utils/users" @@ -110,12 +108,11 @@ if (tgHasData && force) { await DB.execute(sql`TRUNCATE TABLE tg_messages, tg_groups, tg_users CASCADE`) } -const { userIds, groupIds, createdUserIds, createdGroupIds } = await seedTelegramActivity() +const { userIds, groupIds, createdUserIds } = await seedTelegramActivity() /** Seeds each empty activity table, using retained users and groups for new messages. */ async function seedTelegramActivity() { const createdUserIds = new Set() - const createdGroupIds = new Set() const [firstUser] = await DB.select().from(SCHEMA.TG.users).limit(1) if (!firstUser) { console.log(`SEED: generating ${USERS_COUNT} users`) @@ -157,7 +154,6 @@ async function seedTelegramActivity() { })) for (const batch of chunk(groups, CHUNK_SIZE)) { await DB.insert(SCHEMA.TG.groups).values(batch) - for (const group of batch) createdGroupIds.add(group.telegramId) } } else { console.log("SEED: tg_groups already contains data, skipping") @@ -196,12 +192,13 @@ async function seedTelegramActivity() { console.log("SEED: tg_messages already contains data, skipping") } - return { userIds, groupIds, createdUserIds, createdGroupIds } + return { userIds, groupIds, createdUserIds } } const CATEGORY_ROOT = "didattica" const FACET_LABELS = ["ita", "eng", "online", "campus-nord", "campus-sud"] -const SEED_OWNER_ID = 1 // matches the admin app's AGENT_MODE synthetic session (telegramId: 1) +/** IdP subject recorded as the author of seeded rows. */ +const SEED_SUB = "seed-owner" const WA_SEED_LINK_PATTERN = "https://chat.whatsapp.com/SEEDFAKE%" type CourseEntry = { @@ -352,11 +349,6 @@ function labelsFor(target: LinkTarget): { categories: string[]; facets: string[] return { categories: [...categories], facets: [...facets], title } } -console.log("SEED: ensuring seed-owner permissions row (matches AGENT_MODE's synthetic session)") -await DB.insert(SCHEMA.TG.permissions) - .values({ userId: SEED_OWNER_ID, roles: [USER_ROLE.ADMIN, USER_ROLE.WEB], addedBy: SEED_OWNER_ID }) - .onConflictDoNothing() - console.log("SEED: creating didattica category + facet labels") const categoryLabelNames = new Set([CATEGORY_ROOT]) for (const entry of COURSE_ENTRIES) { @@ -367,10 +359,10 @@ for (const entry of COURSE_ENTRIES) { } } await DB.insert(SCHEMA.COMMON.groupLabels) - .values([...categoryLabelNames].map((label) => ({ label, color: "#3b82f6", createdBy: SEED_OWNER_ID }))) + .values([...categoryLabelNames].map((label) => ({ label, color: "#3b82f6", createdBySub: SEED_SUB }))) .onConflictDoNothing() await DB.insert(SCHEMA.COMMON.groupLabels) - .values(FACET_LABELS.map((label) => ({ label, color: "#94a3b8", createdBy: SEED_OWNER_ID }))) + .values(FACET_LABELS.map((label) => ({ label, color: "#94a3b8", createdBySub: SEED_SUB }))) .onConflictDoNothing() const allLabels = await DB.select().from(SCHEMA.COMMON.groupLabels) @@ -476,38 +468,10 @@ async function seedTable(name: string, table: T, rows: () => console.log(`SEED: ${name} → ${values.length} rows`) } -const seedAuthor = { createdBy: SEED_OWNER_ID } -const SEED_SUB = "seed-owner" +const seedAuthor = { createdBySub: SEED_SUB } const HOUR = 60 * 60 * 1000 const DAY = 24 * HOUR -const STAFF: { userId: number; roles: TUserRole[] }[] = [ - { userId: seededUserId(0), roles: [USER_ROLE.OWNER, USER_ROLE.ADMIN] }, - { userId: seededUserId(1), roles: [USER_ROLE.PRESIDENT, USER_ROLE.DIRETTIVO] }, - { userId: seededUserId(2), roles: [USER_ROLE.DIRETTIVO, USER_ROLE.HR] }, - { userId: seededUserId(3), roles: [USER_ROLE.ADMIN] }, - { userId: seededUserId(4), roles: [USER_ROLE.ADMIN, USER_ROLE.WEB] }, - { userId: seededUserId(5), roles: [USER_ROLE.CREATOR] }, - { userId: seededUserId(6), roles: [USER_ROLE.WEB] }, -] -const newStaff = STAFF.filter((staff) => createdUserIds.has(staff.userId)) -if (newStaff.length) { - console.log("SEED: granting roles to newly seeded Telegram users") - await DB.insert(SCHEMA.TG.permissions) - .values(newStaff.map((s) => ({ ...s, addedBy: SEED_OWNER_ID }))) - .onConflictDoNothing() -} - -const bulkGroupId = (i: number) => -1_000_000_000_000 - i - 1 -const newGroupAdmins = STAFF.slice(3) - .flatMap((s, i) => - [0, 1, 2].map((g) => ({ userId: s.userId, groupId: bulkGroupId(i * 3 + g), addedBy: SEED_OWNER_ID })) - ) - .filter((admin) => createdUserIds.has(admin.userId) && createdGroupIds.has(admin.groupId)) -if (newGroupAdmins.length) { - await DB.insert(SCHEMA.TG.groupAdmins).values(newGroupAdmins).onConflictDoNothing() -} - await seedTable("web_projects", SCHEMA.WEB.projects, () => [ { @@ -822,7 +786,7 @@ await seedTable("tg_audit_log", SCHEMA.TG.auditLog, () => { }) }) -await seedTable("tg_grants_v2", SCHEMA.TG.grantsV2, () => { +await seedTable("tg_grants", SCHEMA.TG.grants, () => { const now = Date.now() const grant = (i: number, sinceOffset: number, durationHours: number, interrupted = false) => ({ telegramUserId: seededUserId(20 + i), diff --git a/scripts/seed-user.ts b/scripts/seed-user.ts deleted file mode 100644 index 950a7c6..0000000 --- a/scripts/seed-user.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { User } from "better-auth" -import { argv } from "bun" -import { eq } from "drizzle-orm" -import { auth } from "@/auth" -import { DB, SCHEMA } from "@/db" - -const testUser = { - email: "test@example.com", - telegramId: 123456789, - telegramUsername: "testuser", - role: "owner", - name: "Test User", -} - -const force = argv.includes("--force") - -const existing = await DB.select().from(SCHEMA.AUTH.user).where(eq(SCHEMA.AUTH.user.email, testUser.email)).limit(1) -if (existing.length === 1) { - if (force) { - await DB.delete(SCHEMA.TG.link).where(eq(SCHEMA.TG.link.userId, existing[0].id)) - await DB.delete(SCHEMA.AUTH.user).where(eq(SCHEMA.AUTH.user.email, testUser.email)) - console.warn("SEED: test user deleted for recreation by using --force flag") - } else { - console.error("SEED: test user already in the database", `id:${existing[0].id}`, testUser.email) - process.exit(1) - } -} - -await auth.api.sendVerificationOTP({ - body: { - type: "sign-in", - email: testUser.email, // required - }, -}) - -console.log("SEED: open the email and insert the OTP") - -let user: User -while (true) { - const otp = prompt("Insert OTP:") - if (!otp) continue - - const res = await auth.api - .signInEmailOTP({ body: { email: testUser.email, otp, name: testUser.name } }) - .catch(() => null) - - if (res?.user.email === testUser.email) { - user = res.user - break - } -} - -await DB.update(SCHEMA.AUTH.user) // update the auth.user table - .set({ telegramId: testUser.telegramId, telegramUsername: testUser.telegramUsername }) - .where(eq(SCHEMA.AUTH.user.id, user.id)) - -console.log("SEED: test user created", testUser.email) - -await DB.insert(SCHEMA.TG.permissions) - .values({ - userId: testUser.telegramId, - addedBy: testUser.telegramId, - roles: ["owner"], - }) - .onConflictDoNothing() - -console.log("SEED: given role 'owner' to test user") - -process.exit(0) diff --git a/src/auth/index.ts b/src/auth/index.ts deleted file mode 100644 index ae850ee..0000000 --- a/src/auth/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { drizzleAdapter } from "@better-auth/drizzle-adapter" -import { passkey } from "@better-auth/passkey" -import { betterAuth } from "better-auth/minimal" -import { AUTH_PATH } from "@/constants" -import { SCHEMA } from "@/db" -import { db } from "@/db/db" -import { env } from "@/env" -import { emailOTP } from "./plugins/email-otp" -import { telegramPlugin } from "./plugins/telegram" - -export const auth = betterAuth({ - basePath: AUTH_PATH, - baseURL: env.PUBLIC_URL, - trustedOrigins: env.TRUSTED_ORIGINS, - plugins: [ - telegramPlugin(), - emailOTP(), - passkey({ - rpID: env.NODE_ENV === "production" ? "polinetwork.org" : "localhost", - rpName: "PoliNetwork APS", - }), - ], - database: drizzleAdapter(db, { - provider: "pg", - schema: { - ...SCHEMA.AUTH, - }, - }), - advanced: - env.NODE_ENV === "production" - ? { - crossSubDomainCookies: { - enabled: true, - domain: ".polinetwork.org", // Domain with a leading period - }, - defaultCookieAttributes: { - secure: true, - httpOnly: true, - sameSite: "none", // Allows CORS-based cookie sharing across subdomains - partitioned: true, // New browser standards will mandate this for foreign cookies - }, - } - : undefined, - cookieCache: { - enabled: true, - maxAge: 5 * 60, // Cache duration in seconds - }, -}) diff --git a/src/auth/plugins/email-otp.ts b/src/auth/plugins/email-otp.ts deleted file mode 100644 index 6f8d098..0000000 --- a/src/auth/plugins/email-otp.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { emailOTP as emailOTPSetup } from "better-auth/plugins" -import { sendLoginOtpEmail } from "@/emails/mailer" -import { logger } from "@/logger" - -export const emailOTP = () => - emailOTPSetup({ - expiresIn: 300, - allowedAttempts: 5, - async sendVerificationOTP({ email, otp, type }) { - if (type === "sign-in") { - const expiresInMinutes = this.expiresIn ? Math.floor(this.expiresIn / 60) : undefined - await sendLoginOtpEmail(email, otp, expiresInMinutes) - } else if (type === "email-verification") { - logger.warn({ email, type }, "User requested email verification, but it is not implemented") - // Send the OTP for email verification - // not needed as now - } else { - logger.warn({ email, type }, "User requested password reset, but it is not implemented") - // Send the OTP for password reset - // not needed as now - } - }, - }) diff --git a/src/auth/plugins/telegram/index.ts b/src/auth/plugins/telegram/index.ts deleted file mode 100644 index b2b8a12..0000000 --- a/src/auth/plugins/telegram/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -import crypto from "node:crypto" -import type { BetterAuthPlugin } from "better-auth" -import { APIError, createAuthEndpoint, sessionMiddleware } from "better-auth/api" -import { eq } from "drizzle-orm" -import { z } from "zod" -import { DB, SCHEMA } from "@/db" - -const TTL = 300 // seconds -const CODE_LENGTH = 6 - -const linkBody = z.object({ - telegramUsername: z.string().transform((s) => (s.startsWith("@") ? s.slice(1) : s)), -}) - -export const telegramPlugin = () => { - return { - id: "telegram", - schema: { - user: { - fields: { - telegramId: { - type: "number", - required: false, - unique: true, - }, - telegramUsername: { - type: "string", - required: false, - }, - }, - }, - }, - endpoints: { - startLink: createAuthEndpoint( - "/telegram/link/start", - { - method: "POST", - use: [sessionMiddleware], - body: linkBody, - }, - async (ctx) => { - const userId = ctx.context.session?.user.id - if (!userId) - throw new APIError("UNAUTHORIZED", { - message: "You must be authenticated", - }) - - const { telegramUsername } = ctx.body - const [{ code, ttl }] = await DB.insert(SCHEMA.TG.link) - .values({ - code: crypto - .randomBytes(CODE_LENGTH) - .values() - .map((v) => v % 10) - .toArray() - .join(""), - ttl: TTL, - userId, - telegramUsername, - }) - .returning() - - return ctx.json({ code, ttl }) - } - ), - verifyLink: createAuthEndpoint( - "/telegram/link/verify", - { - method: "GET", - use: [sessionMiddleware], - query: z.object({ - code: z.string().length(CODE_LENGTH).regex(/^\d+$/), - }), - }, - async (ctx) => { - const userId = ctx.context.session?.user.id - if (!userId) - throw new APIError("UNAUTHORIZED", { - message: "You must be authenticated", - }) - - const code = ctx.query.code - const res = await DB.select() - .from(SCHEMA.TG.link) - .where((t) => eq(t.code, code)) - if (res?.length !== 1) - throw new APIError("NOT_FOUND", { - message: "No link session with this code found", - }) - - const row = res[0] - - if (row.userId !== userId) - throw new APIError("FORBIDDEN", { - message: "This link session does not belong to you", - }) - - const expireTime = row.createdAt.getTime() + row.ttl * 1000 - const expired = Date.now() >= expireTime - - const verified = !!row.telegramId - if (verified) { - await DB.delete(SCHEMA.TG.link).where(eq(SCHEMA.TG.link.code, code)) - } - - return ctx.json({ expired, verified }) - } - ), - }, - } satisfies BetterAuthPlugin -} diff --git a/src/constants.ts b/src/constants.ts index 07132ff..26d0949 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,30 +1,8 @@ export const TRPC_PATH = "/api/trpc" -export const AUTH_PATH = "/api/auth" export const WS_PATH = "/ws" -export const TRUSTED_ORIGINS = [ - "http://localhost:3001", - "http://localhost:3002", - "http://localhost:3003", - "http://localhost:5173", - "http://localhost:5174", - "http://localhost:5175", -] - export const MESSAGES_RETENTION_DAYS = 7 -/** Expired sessions are deleted this many days after they expire (privacy policy, data retention). */ -export const SESSIONS_RETENTION_DAYS = 30 /** Temporary moderation measures are deleted this many months after they end (privacy policy, data retention). */ export const MODERATION_RETENTION_MONTHS = 24 /** How long revocations of permanent measures stay in the moderation audit log; see `endedMeasures`. */ export const ENDED_MODERATION_RETENTION_DAYS = 1 - -export const USER_ROLE = { - ADMIN: "admin", - HR: "hr", - PRESIDENT: "president", - DIRETTIVO: "direttivo", - CREATOR: "creator", - OWNER: "owner", - WEB: "web", -} as const diff --git a/src/cron.ts b/src/cron.ts index 1f3e6ad..816e0ae 100644 --- a/src/cron.ts +++ b/src/cron.ts @@ -1,6 +1,6 @@ import { Cron } from "croner" import { lt } from "drizzle-orm" -import { MESSAGES_RETENTION_DAYS, SESSIONS_RETENTION_DAYS } from "./constants" +import { MESSAGES_RETENTION_DAYS } from "./constants" import { DB, SCHEMA } from "./db" import { env } from "./env" import { logger } from "./logger" @@ -10,8 +10,6 @@ export function cron() { new Cron("0 23 4 * * *", async () => { logger.info("[CRON] start") await cleanMessages() - await cleanLinkCodes() - await cleanSessions() await cleanAuditLog() logger.info("[CRON] end") }) @@ -37,20 +35,6 @@ export function cron() { }) } -export async function cleanLinkCodes() { - logger.info(`[CRON] START(cleanLinkCodes)`) - - const yesterday = new Date() - yesterday.setDate(yesterday.getDate() - 1) - - try { - const deleted = await DB.delete(SCHEMA.TG.link).where(lt(SCHEMA.TG.link.createdAt, yesterday)).returning() - logger.info(`[CRON] END(cleanLinkCodes) deleted ${deleted.length} link code(s).`) - } catch (err) { - logger.error(err, "[CRON] cleanLinkCodes") - } -} - async function cleanMessages() { // do not run this cronjob unless in production if (env.NODE_ENV !== "production") return @@ -67,21 +51,6 @@ async function cleanMessages() { } } -export async function cleanSessions() { - logger.info(`[CRON] START(cleanSessions) retention days = ${SESSIONS_RETENTION_DAYS}`) - - const expiredBefore = new Date() - expiredBefore.setDate(expiredBefore.getDate() - SESSIONS_RETENTION_DAYS) - try { - const deleted = await DB.delete(SCHEMA.AUTH.session) - .where(lt(SCHEMA.AUTH.session.expiresAt, expiredBefore)) - .returning({ id: SCHEMA.AUTH.session.id }) - logger.info(`[CRON] END(cleanSessions) deleted ${deleted.length} session(s).`) - } catch (err) { - logger.error(err, "[CRON] cleanSessions") - } -} - export async function cleanAuditLog() { logger.info(`[CRON] START(cleanAuditLog)`) diff --git a/src/db/columns.ts b/src/db/columns.ts index 70dec96..82fec83 100644 --- a/src/db/columns.ts +++ b/src/db/columns.ts @@ -7,17 +7,16 @@ export const timeColumns = { } /** - * IdP subject of whoever created or last modified a row (RFC v3 §9.4). Token callers are - * recorded here; legacy callers in the Telegram ID columns. Writes set one of each pair and clear - * the other, but an older release (after a rollback) only writes the Telegram ID column. + * IdP subject of whoever created or last modified a row (RFC v3 §9.4). Rows written before the + * IdP migration keep their author's Telegram ID in the `legacy_*_tg_id` columns instead. */ export const authorSubColumns = { createdBySub: text("created_by_sub"), modifiedBySub: text("modified_by_sub"), } -/** Every row keeps a creator: a legacy Telegram ID or an IdP subject. */ -export function hasCreator(table: string, columns: { createdBy: PgColumn; createdBySub: PgColumn }) { - const condition: SQL = sql`${columns.createdBy} IS NOT NULL OR ${columns.createdBySub} IS NOT NULL` +/** Every row keeps a creator: an IdP subject, or a legacy Telegram ID for older rows. */ +export function hasCreator(table: string, columns: { legacyCreatedByTgId: PgColumn; createdBySub: PgColumn }) { + const condition: SQL = sql`${columns.legacyCreatedByTgId} IS NOT NULL OR ${columns.createdBySub} IS NOT NULL` return check(`${table}_creator_check`, condition) } diff --git a/src/db/index.ts b/src/db/index.ts index 37f6ad4..a3ead6b 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,5 +1,4 @@ import { db } from "./db" -import { schema as authSchema } from "./schema/auth" import { schema as commonSchema } from "./schema/common" import { schema as esignSchema } from "./schema/esign" import { schema as tgSchema } from "./schema/tg" @@ -9,7 +8,6 @@ import { schema as webSchema } from "./schema/web" export const DB = db export const SCHEMA = { - AUTH: authSchema, COMMON: commonSchema, ESIGN: esignSchema, TG: tgSchema, diff --git a/src/db/schema/auth/auth.ts b/src/db/schema/auth/auth.ts deleted file mode 100644 index 8afcbb5..0000000 --- a/src/db/schema/auth/auth.ts +++ /dev/null @@ -1,130 +0,0 @@ -// this schema can be produced with -// pnpx @better-auth/cli@latest generate -// you should run this when adding plugins to better-auth -// -// IMPORTANT -// here we use createTable util to add a prefix. -// the cli doesn't use that, so double check what is diffing before changing - -import { relations } from "drizzle-orm" -import { bigint, boolean, index, integer, text, timestamp } from "drizzle-orm/pg-core" -import { createTable } from "../create-table" - -export const user = createTable.auth("users", { - id: text("id").primaryKey(), - name: text("name").notNull(), - email: text("email").notNull().unique(), - emailVerified: boolean("email_verified").notNull(), - image: text("image"), - telegramId: bigint("tg_id", { mode: "number" }), - telegramUsername: text("tg_username"), - createdAt: timestamp("created_at", { withTimezone: true }).notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .$onUpdate(() => new Date()) - .notNull(), -}) - -export const session = createTable.auth( - "sessions", - { - id: text("id").primaryKey(), - expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), - token: text("token").notNull().unique(), - createdAt: timestamp("created_at", { withTimezone: true }).notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .$onUpdate(() => new Date()) - .notNull(), - ipAddress: text("ip_address"), - userAgent: text("user_agent"), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - }, - (table) => [index("session_userId_idx").on(table.userId)] -) - -export const account = createTable.auth( - "accounts", - { - id: text("id").primaryKey(), - accountId: text("account_id").notNull(), - providerId: text("provider_id").notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - accessToken: text("access_token"), - refreshToken: text("refresh_token"), - idToken: text("id_token"), - accessTokenExpiresAt: timestamp("access_token_expires_at", { withTimezone: true }), - refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { withTimezone: true }), - scope: text("scope"), - password: text("password"), - createdAt: timestamp("created_at", { withTimezone: true }).notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .$onUpdate(() => new Date()) - .notNull(), - }, - (table) => [index("account_userId_idx").on(table.userId)] -) - -export const verification = createTable.auth( - "verifications", - { - id: text("id").primaryKey(), - identifier: text("identifier").notNull(), - value: text("value").notNull(), - expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), - createdAt: timestamp("created_at", { withTimezone: true }).notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true }) - .$onUpdate(() => new Date()) - .notNull(), - }, - (table) => [index("verification_identifier_idx").on(table.identifier)] -) - -export const passkey = createTable.auth( - "passkey", - { - id: text("id").primaryKey(), - name: text("name"), - publicKey: text("public_key").notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - credentialID: text("credential_id").notNull(), - counter: integer("counter").notNull(), - deviceType: text("device_type").notNull(), - backedUp: boolean("backed_up").notNull(), - transports: text("transports"), - createdAt: timestamp("created_at", { withTimezone: true }), - aaguid: text("aaguid"), - }, - (table) => [index("passkey_userId_idx").on(table.userId), index("passkey_credentialID_idx").on(table.credentialID)] -) - -export const userRelations = relations(user, ({ many }) => ({ - sessions: many(session), - accounts: many(account), - passkeys: many(passkey), -})) - -export const sessionRelations = relations(session, ({ one }) => ({ - user: one(user, { - fields: [session.userId], - references: [user.id], - }), -})) - -export const accountRelations = relations(account, ({ one }) => ({ - user: one(user, { - fields: [account.userId], - references: [user.id], - }), -})) - -export const passkeyRelations = relations(passkey, ({ one }) => ({ - user: one(user, { - fields: [passkey.userId], - references: [user.id], - }), -})) diff --git a/src/db/schema/auth/index.ts b/src/db/schema/auth/index.ts deleted file mode 100644 index 2cc8c98..0000000 --- a/src/db/schema/auth/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as schema from "./auth" diff --git a/src/db/schema/common/group-labels.ts b/src/db/schema/common/group-labels.ts index d1220dc..3564135 100644 --- a/src/db/schema/common/group-labels.ts +++ b/src/db/schema/common/group-labels.ts @@ -1,7 +1,6 @@ import { bigint, integer, text, varchar } from "drizzle-orm/pg-core" import { hasCreator, timeColumns } from "@/db/columns" import { createTable } from "../create-table" -import { permissions } from "../tg/permissions" export const groupLabels = createTable.common( "group_labels", @@ -10,13 +9,13 @@ export const groupLabels = createTable.common( label: varchar("label", { length: 128 }).notNull().unique(), description: varchar("description"), color: varchar("color", { length: 7 }).notNull().default("#ffffff"), - createdBy: bigint("granted_by_id", { mode: "number" }).references(() => permissions.userId), - updatedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), - // IdP subjects of token callers (RFC v3 §9.4); see `authorSubColumns`. + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), + // IdP subjects of the authors (RFC v3 §9.4); see `authorSubColumns`. createdBySub: text("created_by_sub"), updatedBySub: text("modified_by_sub"), ...timeColumns, }, - (t) => [t.label, t.createdBy, t.updatedBy, hasCreator("common_group_labels", t)] + (t) => [hasCreator("common_group_labels", t)] ) diff --git a/src/db/schema/create-table.ts b/src/db/schema/create-table.ts index 321760d..260a87c 100644 --- a/src/db/schema/create-table.ts +++ b/src/db/schema/create-table.ts @@ -4,7 +4,6 @@ export const createTable = { people: pgTableCreator((tableName) => `people_${tableName}`), common: pgTableCreator((tableName) => `common_${tableName}`), esign: pgTableCreator((tableName) => `esign_${tableName}`), - auth: pgTableCreator((tableName) => `auth_${tableName}`), tg: pgTableCreator((tableName) => `tg_${tableName}`), wa: pgTableCreator((tableName) => `wa_${tableName}`), web: pgTableCreator((tableName) => `web_${tableName}`), diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts index a03a721..9d2d7e2 100644 --- a/src/db/schema/index.ts +++ b/src/db/schema/index.ts @@ -1,4 +1,3 @@ -import { schema as authSchema } from "./auth" import { schema as commonSchema } from "./common" import { schema as esignSchema } from "./esign" import { schema as peopleSchema } from "./people" @@ -8,7 +7,6 @@ import { schema as waSchema } from "./wa" import { schema as webSchema } from "./web" export const schema = { - ...authSchema, ...commonSchema, ...esignSchema, ...peopleSchema, diff --git a/src/db/schema/tg/grants.ts b/src/db/schema/tg/grants.ts index 427fc86..bf2f807 100644 --- a/src/db/schema/tg/grants.ts +++ b/src/db/schema/tg/grants.ts @@ -2,38 +2,13 @@ import { sql } from "drizzle-orm" import { bigint, check, index, integer, text, timestamp } from "drizzle-orm/pg-core" import { timeColumns } from "@/db/columns" import { createTable } from "../create-table" -import { permissions } from "./permissions" /** - * Grants made by legacy callers, identified by Telegram ID. Archived and dropped in Phase 6 - * (RFC v3 §9.3); until then a grant is active if it is active here or in `grantsV2`. + * Temporary trust for a Telegram user, granted through the IdP (RFC v3 §9.3). Active means + * `valid_since ≤ now < valid_until` and not interrupted; who interrupted never matters. */ export const grants = createTable.tg( "grants", - { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - userId: bigint("user_id", { mode: "number" }).notNull(), - grantedBy: bigint("granted_by_id", { mode: "number" }) - .references(() => permissions.userId) - .notNull(), - validSince: timestamp("valid_since", { precision: 0, withTimezone: true }).notNull(), - validUntil: timestamp("valid_until", { precision: 0, withTimezone: true }).notNull(), - interruptedBy: bigint("interrupted_by_id", { mode: "number" }).references(() => permissions.userId), - /** Set when a token caller interrupts a legacy grant: it has no `tg_permissions` row to reference. */ - interruptedAt: timestamp("interrupted_at", { precision: 0, withTimezone: true }), - reason: text("reason"), - - ...timeColumns, - }, - (t) => [index("tg_grants_user_id_idx").on(t.userId)] -) - -/** - * Grants made by IdP-authenticated callers (RFC v3 §9.3). Active means - * `valid_since ≤ now < valid_until` and not interrupted; who interrupted never matters. - */ -export const grantsV2 = createTable.tg( - "grants_v2", { id: integer().primaryKey().generatedAlwaysAsIdentity(), telegramUserId: bigint("telegram_user_id", { mode: "number" }).notNull(), @@ -49,7 +24,7 @@ export const grantsV2 = createTable.tg( ...timeColumns, }, (t) => [ - index("tg_grants_v2_telegram_user_id_idx").on(t.telegramUserId), - check("tg_grants_v2_validity_check", sql`${t.validSince} < ${t.validUntil}`), + index("tg_grants_telegram_user_id_idx").on(t.telegramUserId), + check("tg_grants_validity_check", sql`${t.validSince} < ${t.validUntil}`), ] ) diff --git a/src/db/schema/tg/index.ts b/src/db/schema/tg/index.ts index 4e1890c..2694e13 100644 --- a/src/db/schema/tg/index.ts +++ b/src/db/schema/tg/index.ts @@ -1,17 +1,13 @@ import * as auditLog from "./audit-log" import * as grants from "./grants" import * as groups from "./groups" -import * as link from "./link" import * as messages from "./messages" -import * as permissions from "./permissions" import * as test from "./test" import * as users from "./users" export const schema = { ...groups, ...test, - ...permissions, - ...link, ...messages, ...auditLog, ...users, diff --git a/src/db/schema/tg/link.ts b/src/db/schema/tg/link.ts deleted file mode 100644 index 4032e22..0000000 --- a/src/db/schema/tg/link.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { bigint, integer, text } from "drizzle-orm/pg-core" -import { timeColumns } from "@/db/columns" -import { user } from "../auth/auth" -import { createTable } from "../create-table" - -export const link = createTable.tg("link", { - code: text("code").primaryKey(), - ttl: integer("ttl").notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id), - telegramUsername: text("tg_username").notNull(), - telegramId: bigint("tg_id", { mode: "number" }).unique(), - - ...timeColumns, -}) diff --git a/src/db/schema/tg/permissions.ts b/src/db/schema/tg/permissions.ts deleted file mode 100644 index ec96cdd..0000000 --- a/src/db/schema/tg/permissions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { bigint, index, primaryKey, text } from "drizzle-orm/pg-core" -import { USER_ROLE } from "@/constants" -import { timeColumns } from "@/db/columns" -import { createTable } from "../create-table" - -export const ARRAY_USER_ROLE = [ - USER_ROLE.ADMIN, - USER_ROLE.HR, - USER_ROLE.PRESIDENT, - USER_ROLE.DIRETTIVO, - USER_ROLE.OWNER, - USER_ROLE.CREATOR, - USER_ROLE.WEB, -] as const -export type TUserRole = (typeof USER_ROLE)[keyof typeof USER_ROLE] - -export const permissions = createTable.tg("permissions", { - userId: bigint("user_id", { mode: "number" }).primaryKey(), - roles: text("roles").array().$type().default([USER_ROLE.ADMIN]).notNull(), - addedBy: bigint("added_by_id", { mode: "number" }).notNull(), - modifiedBy: bigint("modified_by_id", { mode: "number" }), - - ...timeColumns, -}) - -export const groupAdmins = createTable.tg( - "group_admins", - { - userId: bigint("user_id", { mode: "number" }).notNull(), - groupId: bigint("group_id", { mode: "number" }).notNull(), - addedBy: bigint("added_by_id", { mode: "number" }).notNull(), - - ...timeColumns, - }, - (t) => [primaryKey({ columns: [t.userId, t.groupId] }), index("user_id_idx").on(t.userId)] -) diff --git a/src/db/schema/web/associations.ts b/src/db/schema/web/associations.ts index 4d3ab5a..d6f7285 100644 --- a/src/db/schema/web/associations.ts +++ b/src/db/schema/web/associations.ts @@ -1,7 +1,6 @@ import { bigint, integer, text } from "drizzle-orm/pg-core" import { authorSubColumns, hasCreator, timeColumns } from "@/db/columns" import { createTable } from "../create-table" -import { permissions } from "../tg/permissions" export const associations = createTable.web( "associations", @@ -21,8 +20,8 @@ export const associations = createTable.web( telegram: text("telegram"), linkedin: text("linkedin"), spotify: text("spotify"), - createdBy: bigint("created_by_id", { mode: "number" }).references(() => permissions.userId), - modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), ...authorSubColumns, ...timeColumns, }, diff --git a/src/db/schema/web/faqs.ts b/src/db/schema/web/faqs.ts index 5f31eb4..1d1a337 100644 --- a/src/db/schema/web/faqs.ts +++ b/src/db/schema/web/faqs.ts @@ -1,7 +1,6 @@ import { bigint, integer, text } from "drizzle-orm/pg-core" import { authorSubColumns, hasCreator, timeColumns } from "@/db/columns" import { createTable } from "../create-table" -import { permissions } from "../tg/permissions" export const faqCategories = createTable.web( "faq_categories", @@ -10,8 +9,8 @@ export const faqCategories = createTable.web( titleIt: text("title_it").notNull(), titleEn: text("title_en").notNull(), icon: text("icon"), - createdBy: bigint("created_by_id", { mode: "number" }).references(() => permissions.userId), - modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), ...authorSubColumns, ...timeColumns, }, @@ -29,8 +28,8 @@ export const faqs = createTable.web( categoryId: integer("category_id") .references(() => faqCategories.id, { onDelete: "cascade" }) .notNull(), - createdBy: bigint("created_by_id", { mode: "number" }).references(() => permissions.userId), - modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), ...authorSubColumns, ...timeColumns, }, diff --git a/src/db/schema/web/freshman.ts b/src/db/schema/web/freshman.ts index 5a99070..5b6e909 100644 --- a/src/db/schema/web/freshman.ts +++ b/src/db/schema/web/freshman.ts @@ -3,7 +3,6 @@ import { bigint, check, integer, text } from "drizzle-orm/pg-core" import { authorSubColumns, hasCreator, timeColumns } from "@/db/columns" import { freshmanIcons, freshmanSlots } from "@/routers/web/freshman/constants" import { createTable } from "../create-table" -import { permissions } from "../tg/permissions" import { faqs } from "./faqs" export const freshmanCards = createTable.web( @@ -14,8 +13,8 @@ export const freshmanCards = createTable.web( description: text("description").notNull(), icon: text("icon", { enum: freshmanIcons }).notNull(), href: text("href").notNull(), - createdBy: bigint("created_by_id", { mode: "number" }).references(() => permissions.userId), - modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), ...authorSubColumns, ...timeColumns, }, @@ -46,8 +45,8 @@ export const freshmanFaqs = createTable.web( .notNull() .unique() .references(() => faqs.id, { onDelete: "restrict" }), - createdBy: bigint("created_by_id", { mode: "number" }).references(() => permissions.userId), - modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), ...authorSubColumns, ...timeColumns, }, diff --git a/src/db/schema/web/guides_matricole.ts b/src/db/schema/web/guides_matricole.ts index 2ea56ed..32fe1d5 100644 --- a/src/db/schema/web/guides_matricole.ts +++ b/src/db/schema/web/guides_matricole.ts @@ -1,7 +1,6 @@ import { bigint, integer, text } from "drizzle-orm/pg-core" import { authorSubColumns, hasCreator, timeColumns } from "@/db/columns" import { createTable } from "../create-table" -import { permissions } from "../tg/permissions" export const guidesMatricole = createTable.web( "guides_matricole", @@ -10,8 +9,8 @@ export const guidesMatricole = createTable.web( version: text("version").notNull().unique(), date: text("date").notNull(), file: text("file").notNull(), - createdBy: bigint("created_by_id", { mode: "number" }).references(() => permissions.userId), - modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), ...authorSubColumns, ...timeColumns, }, diff --git a/src/db/schema/web/projects.ts b/src/db/schema/web/projects.ts index 53dfeaa..1565b9a 100644 --- a/src/db/schema/web/projects.ts +++ b/src/db/schema/web/projects.ts @@ -1,7 +1,6 @@ import { bigint, integer, text } from "drizzle-orm/pg-core" import { authorSubColumns, hasCreator, timeColumns } from "@/db/columns" import { createTable } from "../create-table" -import { permissions } from "../tg/permissions" export const projectsCategories = ["news", "deprecated", "general"] as const @@ -18,8 +17,8 @@ export const projects = createTable.web( link: text("link"), category: text("category", { enum: projectsCategories }).notNull().default("general"), order: integer("order").notNull().default(0), - createdBy: bigint("created_by_id", { mode: "number" }).references(() => permissions.userId), - modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + legacyCreatedByTgId: bigint("legacy_created_by_tg_id", { mode: "number" }), + legacyModifiedByTgId: bigint("legacy_modified_by_tg_id", { mode: "number" }), ...authorSubColumns, ...timeColumns, }, diff --git a/src/emails/mailer.tsx b/src/emails/mailer.tsx index c9098ce..f85d993 100644 --- a/src/emails/mailer.tsx +++ b/src/emails/mailer.tsx @@ -6,7 +6,6 @@ import { env } from "@/env" import type { RequestUpdate } from "@/esign/service" import { logger } from "@/logger" import MemberNumberChanged from "./templates/member-number-changed" -import OtpEmail from "./templates/otp" import RequestUpdateEmail, { requestUpdateSubject } from "./templates/request-update" import SignatureRequestEmail from "./templates/signature-request" import SignedDocumentEmail, { type SignedDocumentProps } from "./templates/signed-document" @@ -46,11 +45,6 @@ async function devSendMail( const sendEmail = env.NODE_ENV === "development" && env.USE_DEV_MAILER ? devSendMail : azureSendEmail -export async function sendLoginOtpEmail(toAddress: string, otp: string, expiresInMinutes?: number) { - const subject = makeSubject("Your login code for PoliNetwork") - return sendEmail(toAddress, subject, ) -} - export async function sendWelcomeEmail( toAddress: string, accountCredentials: { email: string; password: string }, diff --git a/src/emails/templates/otp.tsx b/src/emails/templates/otp.tsx deleted file mode 100644 index f97d3af..0000000 --- a/src/emails/templates/otp.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { - Body, - CodeInline, - Container, - Head, - Heading, - Html, - Preview, - pixelBasedPreset, - Section, - Tailwind, - Text, -} from "@react-email/components" -import { Footer } from "../components/footer" -import { Logo } from "../components/logo" -import type { EmailTemplate } from "../types" - -type Props = { - otp: string - expiresInMinutes?: number - // type: "LOGIN" // use it to differentiate OTPs -} - -const OtpEmail: EmailTemplate = ({ otp, expiresInMinutes }) => { - return ( - - - - - - Your verification code is: {otp} - - - - - {/* Main Content Container */} - - Verification Code - - {/* Content Section */} -
- - Use the following code to verify your identity: - - -
- {otp} -
- - {expiresInMinutes && ( - - This code will expire in {expiresInMinutes} minutes. - - )} - - - If you didn't request this code, you can safely ignore this email. - -
-
- -