diff --git a/backend/api/authentication.ts b/backend/api/authentication.ts index b089987c5..ef83919b1 100644 --- a/backend/api/authentication.ts +++ b/backend/api/authentication.ts @@ -1,3 +1,4 @@ +import { limitAuthAttempts } from '../helpers/rateLimit.ts' import type { FastifyInstance } from 'fastify' /** @@ -141,6 +142,8 @@ async function routes(app: FastifyInstance) { config: { publicAccess: true }, + // -> Guessing is what this endpoint is attacked with; see `helpers/rateLimit.ts` + onRequest: limitAuthAttempts, schema: { summary: 'Login', tags: ['Authentication'], @@ -223,6 +226,8 @@ async function routes(app: FastifyInstance) { config: { publicAccess: true }, + // -> Guessing is what this endpoint is attacked with; see `helpers/rateLimit.ts` + onRequest: limitAuthAttempts, schema: { summary: 'Change Password From Login', tags: ['Authentication'], @@ -317,6 +322,8 @@ async function routes(app: FastifyInstance) { config: { publicAccess: true }, + // -> Guessing is what this endpoint is attacked with; see `helpers/rateLimit.ts` + onRequest: limitAuthAttempts, schema: { summary: 'Submit a 2FA Security Code From Login', description: @@ -405,6 +412,8 @@ async function routes(app: FastifyInstance) { config: { publicAccess: true }, + // -> Guessing is what this endpoint is attacked with; see `helpers/rateLimit.ts` + onRequest: limitAuthAttempts, schema: { summary: 'Get the options for logging in with a passkey', description: @@ -467,6 +476,8 @@ async function routes(app: FastifyInstance) { config: { publicAccess: true }, + // -> Guessing is what this endpoint is attacked with; see `helpers/rateLimit.ts` + onRequest: limitAuthAttempts, schema: { summary: 'Login With a Passkey', description: diff --git a/backend/api/index.ts b/backend/api/index.ts index a57749ebf..375236fe3 100644 --- a/backend/api/index.ts +++ b/backend/api/index.ts @@ -45,6 +45,7 @@ async function routes(app: FastifyInstance) { app.register(import('./tags.ts')) app.register(import('./tree.ts')) app.register(import('./users.ts'), { prefix: '/users' }) + app.register(import('./watching.ts')) } export default routes diff --git a/backend/api/pages.ts b/backend/api/pages.ts index b4899e7af..168108738 100644 --- a/backend/api/pages.ts +++ b/backend/api/pages.ts @@ -3,6 +3,7 @@ import type { FastifyInstance, FastifyRequest } from 'fastify' import type { PageActor, PageInput } from '../models/pages.ts' import { SEARCH_ORDER_BY, type SearchOrderBy } from '../models/search.ts' import { generatePathHash } from '../helpers/common.ts' +import { limitAuthAttempts } from '../helpers/rateLimit.ts' /** Comma-separated query lists, which is how the browser sends a multi-valued filter here. */ function splitList(value?: string): string[] { @@ -486,16 +487,23 @@ async function routes(app: FastifyInstance) { They are answered here from the page already in hand, against rules already in memory, which is what makes a page view one request instead of four. */ + const actorId = actor?.id ?? null + const [approvalState, isWatching] = await Promise.all([ + WIKI.models.approvals.pageViewerState(req, req.params.siteId, { + id: page.id, + path: page.path, + tags: page.tags ?? [], + allowContributions: page.allowContributions + }), + // -> One indexed lookup on (pageId, userId), and none at all for a reader with no account + WIKI.models.pageWatching.isWatching(page.id, actorId) + ]) return { ...page, viewer: { permissions: pagePermissionsFor(req, page), - ...(await WIKI.models.approvals.pageViewerState(req, req.params.siteId, { - id: page.id, - path: page.path, - tags: page.tags ?? [], - allowContributions: page.allowContributions - })) + ...approvalState, + isWatching } } } @@ -511,6 +519,8 @@ async function routes(app: FastifyInstance) { }>( '/sites/:siteId/pages/:pageIdOrHash/unlock', { + // -> A password endpoint like the ones in `api/authentication.ts`, and limited with them + onRequest: limitAuthAttempts, schema: { summary: 'Unlock a password-protected page', description: diff --git a/backend/api/schemas/page.ts b/backend/api/schemas/page.ts index 51ee14f51..29eeb5dc8 100644 --- a/backend/api/schemas/page.ts +++ b/backend/api/schemas/page.ts @@ -228,6 +228,11 @@ export async function registerSchemas(app: FastifyInstance): Promise { type: 'boolean', description: 'The requester reviews this page. Always false without an account.' }, + isWatching: { + type: 'boolean', + description: + 'The requester has asked to be told about changes to this page. Always false without an account, since a watch belongs to one.' + }, pendingSubmissions: { type: 'array', items: { $ref: 'PageEditSubmission#' }, @@ -238,6 +243,32 @@ export async function registerSchemas(app: FastifyInstance): Promise { } }) + /** + * WATCHED PAGE - A page somebody asked to be told about, as their inbox lists it + */ + app.addSchema({ + $id: 'WatchedPage', + type: 'object', + properties: { + pageId: { type: 'string', format: 'uuid' }, + path: { type: 'string' }, + locale: { type: 'string' }, + title: { type: 'string' }, + description: { type: ['string', 'null'] }, + icon: { type: ['string', 'null'] }, + updatedAt: { + type: 'string', + format: 'date-time', + description: 'When the page last changed, which is what watching it is about.' + }, + watchedAt: { + type: 'string', + format: 'date-time', + description: 'When the caller started watching it.' + } + } + }) + /** * INCLUDED PAGE - Another page's render, as an include block draws it inside the page being read */ diff --git a/backend/api/schemas/security.ts b/backend/api/schemas/security.ts index a22aef25d..964c599c8 100644 --- a/backend/api/schemas/security.ts +++ b/backend/api/schemas/security.ts @@ -75,6 +75,27 @@ export async function registerSchemas(app: FastifyInstance): Promise { type: 'boolean', description: 'Stored, but there is no upload endpoint yet.' }, + authRateLimitEnabled: { + type: 'boolean', + description: + 'Whether the authentication endpoints — signing in, second factors, password changes from the login screen, passkey ceremonies and page unlocks — refuse a client that has attempted too often. Counted per client address, in the database, so the limit holds across instances.' + }, + authRateLimitMax: { + type: 'integer', + minimum: 1, + description: 'Attempts allowed within the window. The one that exceeds it earns the ban.' + }, + authRateLimitWindow: { + type: 'string', + maxLength: 16, + description: 'How long attempts are counted over, as a duration — e.g. `5m`, `2h`, `1d`.' + }, + authRateLimitBan: { + type: 'string', + maxLength: 16, + description: + 'How long a client is refused for once it goes over, as a duration — e.g. `15m`, `1h`. Attempts made while banned do not extend it.' + }, authJwtAudience: { type: 'string', maxLength: 255, diff --git a/backend/api/watching.ts b/backend/api/watching.ts new file mode 100644 index 000000000..e1e716c32 --- /dev/null +++ b/backend/api/watching.ts @@ -0,0 +1,180 @@ +import { actorFrom, mayOnPage, unlockedFor } from './pages.ts' +import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' + +/** + * The page being watched, as this requester is allowed to see it. + * + * Watching a page is a thing done TO a page, so it goes through the same gate as reading one: an + * anonymous requester never gets here at all, and a page somebody may not read is answered as though + * it were not there. A password is not part of it — the watcher is asking to be told when the page + * changes, not to read what it says. + */ +async function loadWatchablePage(req: FastifyRequest, siteId: string, pageId: string) { + const page = await WIKI.models.pages.getPage({ + siteId, + id: pageId, + unlocked: (id: string) => unlockedFor(req, id) + }) + if (!page || !mayOnPage(req, 'read:pages', page)) { + return null + } + return page +} + +/** + * The user doing the watching, or a refusal. + * + * Watching belongs to an account: it is a list somebody comes back to, and a row has to point at a + * person for a notification to ever have a recipient. There is no permission for it beyond being + * logged in — anybody who may read a page may ask to hear about it. + */ +function watcherOf(req: FastifyRequest, reply: FastifyReply): string | null { + const actor = actorFrom(req) + if (!actor) { + reply.unauthorized('Watching a page requires a logged in user.') + return null + } + return actor.id +} + +const pageParams = { + type: 'object', + properties: { + siteId: { type: 'string', format: 'uuid' }, + pageId: { type: 'string', format: 'uuid' } + }, + required: ['siteId', 'pageId'] +} + +/** + * Page Watching API Routes + * + * Who has asked to be told when a page changes. Nothing is sent yet — notifications are not built — + * so these keep the list: the bell on a page writes to it, and the inbox reads it back. + */ +async function routes(app: FastifyInstance) { + /** + * WATCH A PAGE + */ + app.put<{ Params: { siteId: string; pageId: string } }>( + '/sites/:siteId/pages/:pageId/watch', + { + /* + No route-level `permissions`: this is decided per page, by whether the caller may read it — + which comes from a group's rules and not from the group-wide list that hook consults. + */ + schema: { + summary: 'Watch a page', + description: + 'Records that the caller wants to hear about changes to this page. Watching a page already watched changes nothing and still answers 200, so the button can be pressed twice without it meaning anything different.', + tags: ['Pages'], + params: pageParams, + response: { + 200: { + description: 'The page is being watched', + type: 'object', + properties: { + ok: { type: 'boolean' }, + isWatching: { type: 'boolean' } + } + } + } + } + }, + async (req, reply) => { + const userId = watcherOf(req, reply) + if (!userId) { + return reply + } + const page = await loadWatchablePage(req, req.params.siteId, req.params.pageId) + if (!page) { + return reply.notFound('This page does not exist.') + } + await WIKI.models.pageWatching.watch({ + siteId: req.params.siteId, + pageId: page.id, + userId + }) + return { ok: true, isWatching: true } + } + ) + + /** + * UNWATCH A PAGE + */ + app.delete<{ Params: { siteId: string; pageId: string } }>( + '/sites/:siteId/pages/:pageId/watch', + { + // -> Same as above: readable is the test, and it is per page + schema: { + summary: 'Stop watching a page', + description: + 'Forgets that the caller wanted to hear about this page. A page that was not being watched answers the same way, since the outcome asked for — no longer watching it — already holds.', + tags: ['Pages'], + params: pageParams, + response: { + 200: { + description: 'The page is no longer being watched', + type: 'object', + properties: { + ok: { type: 'boolean' }, + isWatching: { type: 'boolean' } + } + } + } + } + }, + async (req, reply) => { + const userId = watcherOf(req, reply) + if (!userId) { + return reply + } + /* + The page is NOT loaded first. Unwatching has to keep working for a page that has since been + made unreadable, or the row would be stuck there with nothing in the interface able to remove + it — and there is nothing to protect anyway: this only ever deletes the caller's own row. + */ + await WIKI.models.pageWatching.unwatch({ pageId: req.params.pageId, userId }) + return { ok: true, isWatching: false } + } + ) + + /** + * LIST WATCHED PAGES + */ + app.get<{ Params: { siteId: string } }>( + '/sites/:siteId/watching', + { + // -> Everything it returns is the caller's own, so being logged in is the whole of the check + schema: { + summary: 'List the pages the caller is watching', + description: + 'The watch list of the caller on this site, most recently watched first. Titles and paths come from the pages themselves, so a page that has been renamed or moved is listed where it is now.', + tags: ['Pages'], + params: { + type: 'object', + properties: { + siteId: { type: 'string', format: 'uuid' } + }, + required: ['siteId'] + }, + response: { + 200: { + description: 'Watched pages', + type: 'array', + items: { $ref: 'WatchedPage#' } + } + } + } + }, + async (req, reply) => { + const userId = watcherOf(req, reply) + if (!userId) { + return reply + } + return WIKI.models.pageWatching.listForUser(req.params.siteId, userId) + } + ) +} + +export default routes diff --git a/backend/base.yml b/backend/base.yml index bc44ece3f..376e09e19 100644 --- a/backend/base.yml +++ b/backend/base.yml @@ -72,6 +72,10 @@ defaults: forceAssetDownload: true disallowOpenRedirect: true enforceSameOriginReferrerPolicy: true + authRateLimitEnabled: true + authRateLimitMax: 10 + authRateLimitWindow: '2m' + authRateLimitBan: '15m' flags: experimental: false authDebug: false diff --git a/backend/db/migrations/20260802202726_page_watching/migration.sql b/backend/db/migrations/20260802202726_page_watching/migration.sql new file mode 100644 index 000000000..6efe2f0c5 --- /dev/null +++ b/backend/db/migrations/20260802202726_page_watching/migration.sql @@ -0,0 +1,13 @@ +CREATE TABLE "pageWatching" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(), + "createdAt" timestamp DEFAULT now() NOT NULL, + "pageId" uuid NOT NULL, + "siteId" uuid NOT NULL, + "userId" uuid NOT NULL +); +--> statement-breakpoint +CREATE INDEX "pageWatching_user_site_idx" ON "pageWatching" ("userId","siteId");--> statement-breakpoint +CREATE UNIQUE INDEX "pageWatching_page_user_idx" ON "pageWatching" ("pageId","userId");--> statement-breakpoint +ALTER TABLE "pageWatching" ADD CONSTRAINT "pageWatching_pageId_pages_id_fkey" FOREIGN KEY ("pageId") REFERENCES "pages"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "pageWatching" ADD CONSTRAINT "pageWatching_siteId_sites_id_fkey" FOREIGN KEY ("siteId") REFERENCES "sites"("id");--> statement-breakpoint +ALTER TABLE "pageWatching" ADD CONSTRAINT "pageWatching_userId_users_id_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE; \ No newline at end of file diff --git a/backend/db/migrations/20260802202726_page_watching/snapshot.json b/backend/db/migrations/20260802202726_page_watching/snapshot.json new file mode 100644 index 000000000..f4726f86c --- /dev/null +++ b/backend/db/migrations/20260802202726_page_watching/snapshot.json @@ -0,0 +1,5371 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "4dce552a-7858-4b4d-8495-9564163cff38", + "prevIds": [ + "c1bd06d8-5a54-4c8e-b41e-4da6140abbd5" + ], + "ddl": [ + { + "values": [ + "document", + "image", + "other" + ], + "name": "assetKind", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "pending", + "success", + "error" + ], + "name": "hookState", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "active", + "completed", + "failed", + "interrupted" + ], + "name": "jobHistoryState", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "draft", + "published", + "scheduled" + ], + "name": "pagePublishState", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "inherit", + "override", + "overrideExact", + "hide", + "hideExact" + ], + "name": "treeNavigationMode", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "folder", + "page", + "asset" + ], + "name": "treeType", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "apiKeys", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "approvalRules", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "assets", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "authentication", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "blocks", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "groups", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "hooks", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "iconSets", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "icons", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobHistory", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobLock", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobSchedule", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobs", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "locales", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "navigation", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageEditSubmissions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageHistory", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageWatching", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pages", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "settings", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sites", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "storage", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tags", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tree", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "userAvatars", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "userGroups", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "userKeys", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "users", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "varchar(8)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "keyShort", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "groups", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "expiration", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isRevoked", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'START'", + "generated": null, + "identity": null, + "name": "match", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "path", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "submitterGroups", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "reviewerGroups", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileName", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileExt", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isSystem", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "assetKind", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'other'", + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'application/octet-stream'", + "generated": null, + "identity": null, + "name": "mimeType", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileSize", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "preview", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "storageInfo", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "module", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "displayName", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "registration", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "allowedEmailRegex", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 1, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "autoEnrollGroups", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "block", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "icon", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isCustom", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "permissions", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rules", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "redirectOnLogin", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "redirectOnFirstLogin", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "redirectOnLogout", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isSystem", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "events", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "url", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "includeMetadata", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "includeContent", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "acceptUntrusted", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authHeader", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "hookState", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastErrorMessage", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "prefix", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "info", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refreshedAt", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "prefix", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "body", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "16", + "generated": null, + "identity": null, + "name": "width", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "16", + "generated": null, + "identity": null, + "name": "height", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "left", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "top", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "rotate", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "hFlip", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "vFlip", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "task", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "jobHistoryState", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "useWorker", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "wasScheduled", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "attempt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "maxRetries", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastErrorMessage", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "executedBy", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "startedAt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "completedAt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "jobLock" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastCheckedBy", + "entityType": "columns", + "schema": "public", + "table": "jobLock" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "lastCheckedAt", + "entityType": "columns", + "schema": "public", + "table": "jobLock" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "task", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cron", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'system'", + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "task", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "useWorker", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "retries", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "maxRetries", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "waitUntil", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isScheduled", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "createdBy", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "code", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nativeName", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(8)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "language", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(3)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "region", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(4)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "script", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isRTL", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "strings", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "completeness", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "navigation" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "items", + "entityType": "columns", + "schema": "public", + "table": "navigation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "navigation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "patch", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "baseHash", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "guestName", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "guestEmail", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'updated'", + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "changedFields", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locale", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "path", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reason", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "versionDate", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locale", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "path", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "hash", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "alias", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "icon", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "pagePublishState", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'draft'", + "generated": null, + "identity": null, + "name": "publishState", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "publishStartDate", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "publishEndDate", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "relations", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "render", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "searchContent", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "tsvector", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ts", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "tags", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "toc", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "editor", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "contentType", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isBrowsable", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isSearchable", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": { + "as": "\"pages\".\"publishState\" != 'draft' AND \"pages\".\"isSearchable\"", + "type": "stored" + }, + "identity": null, + "name": "isSearchableComputed", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "ratingScore", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "ratingCount", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "scripts", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "historyData", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creatorId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ownerId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "settings" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "value", + "entityType": "columns", + "schema": "public", + "table": "settings" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "hostname", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "module", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "contentTypes", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "assetDelivery", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "versioning", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tag", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "usageCount", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "ltree", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "folderPath", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileName", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "hash", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "treeType", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tree", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locale", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "treeNavigationMode", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'inherit'", + "generated": null, + "identity": null, + "name": "navigationMode", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "navigationId", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "tags", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "userAvatars" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "userAvatars" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "userGroups" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "groupId", + "entityType": "columns", + "schema": "public", + "table": "userGroups" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "validUntil", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "auth", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "passkeys", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "prefs", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "hasAvatar", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isActive", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isSystem", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isVerified", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastLoginAt", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "approvalRules_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "approvalRules" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "assets_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "assets" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "blocks_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "blocks" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "language", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "locales_language_idx", + "entityType": "indexes", + "schema": "public", + "table": "locales" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "navigation_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "navigation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_pageId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_authorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"authorId\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_page_author_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "versionDate", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageHistory_pageId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "locale", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "path", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "versionDate", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageHistory_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageHistory_authorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageWatching_user_site_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageWatching_page_user_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_authorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "creatorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_creatorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "ownerId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_ownerId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "ts", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gin", + "concurrently": false, + "name": "pages_ts_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tags", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gin", + "concurrently": false, + "name": "pages_tags_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "isSearchableComputed", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_isSearchableComputed_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "module", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "storage_composite_idx", + "entityType": "indexes", + "schema": "public", + "table": "storage" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tags_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "tag", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tags_composite_idx", + "entityType": "indexes", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "folderPath", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_folderpath_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "folderPath", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gist", + "concurrently": false, + "name": "tree_folderpath_gist_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "fileName", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_fileName_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tree", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_type_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "locale", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_locale_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "navigationMode", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_navigationMode_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "navigationId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_navigationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tags", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gin", + "concurrently": false, + "name": "tree_tags_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userGroups_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "groupId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userGroups_groupId_idx", + "entityType": "indexes", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "groupId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userGroups_composite_idx", + "entityType": "indexes", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userKeys_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "userKeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "lastLoginAt", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_lastLoginAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "approvalRules_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "approvalRules" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "assets_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "assets" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "assets_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "assets" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "blocks_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "blocks" + }, + { + "nameExplicit": false, + "columns": [ + "prefix" + ], + "schemaTo": "public", + "tableTo": "iconSets", + "columnsTo": [ + "prefix" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "icons_prefix_iconSets_prefix_fkey", + "entityType": "fks", + "schema": "public", + "table": "icons" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "navigation_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "navigation" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageEditSubmissions_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageEditSubmissions_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageEditSubmissions_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "pageHistory_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageHistory_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageWatching_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageWatching_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageWatching_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "creatorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_creatorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "ownerId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_ownerId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "sessions_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "storage_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "storage" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "tags_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "tree_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "userGroups_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": false, + "columns": [ + "groupId" + ], + "schemaTo": "public", + "tableTo": "groups", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "userGroups_groupId_groups_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "userKeys_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "userKeys" + }, + { + "columns": [ + "prefix", + "name" + ], + "nameExplicit": false, + "name": "icons_pkey", + "entityType": "pks", + "schema": "public", + "table": "icons" + }, + { + "columns": [ + "userId", + "groupId" + ], + "nameExplicit": false, + "name": "userGroups_pkey", + "entityType": "pks", + "schema": "public", + "table": "userGroups" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "apiKeys_pkey", + "schema": "public", + "table": "apiKeys", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "approvalRules_pkey", + "schema": "public", + "table": "approvalRules", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "assets_pkey", + "schema": "public", + "table": "assets", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "authentication_pkey", + "schema": "public", + "table": "authentication", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "blocks_pkey", + "schema": "public", + "table": "blocks", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "groups_pkey", + "schema": "public", + "table": "groups", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "hooks_pkey", + "schema": "public", + "table": "hooks", + "entityType": "pks" + }, + { + "columns": [ + "prefix" + ], + "nameExplicit": false, + "name": "iconSets_pkey", + "schema": "public", + "table": "iconSets", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jobHistory_pkey", + "schema": "public", + "table": "jobHistory", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "jobLock_pkey", + "schema": "public", + "table": "jobLock", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jobSchedule_pkey", + "schema": "public", + "table": "jobSchedule", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jobs_pkey", + "schema": "public", + "table": "jobs", + "entityType": "pks" + }, + { + "columns": [ + "code" + ], + "nameExplicit": false, + "name": "locales_pkey", + "schema": "public", + "table": "locales", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "navigation_pkey", + "schema": "public", + "table": "navigation", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pageEditSubmissions_pkey", + "schema": "public", + "table": "pageEditSubmissions", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pageHistory_pkey", + "schema": "public", + "table": "pageHistory", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pageWatching_pkey", + "schema": "public", + "table": "pageWatching", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pages_pkey", + "schema": "public", + "table": "pages", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "sessions_pkey", + "schema": "public", + "table": "sessions", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "settings_pkey", + "schema": "public", + "table": "settings", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "sites_pkey", + "schema": "public", + "table": "sites", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "storage_pkey", + "schema": "public", + "table": "storage", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tags_pkey", + "schema": "public", + "table": "tags", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tree_pkey", + "schema": "public", + "table": "tree", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "userAvatars_pkey", + "schema": "public", + "table": "userAvatars", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "userKeys_pkey", + "schema": "public", + "table": "userKeys", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "users_pkey", + "schema": "public", + "table": "users", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": [ + "hostname" + ], + "nullsNotDistinct": false, + "name": "sites_hostname_key", + "schema": "public", + "table": "sites", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "users_email_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/backend/db/migrations/20260802211525_rate_limits/migration.sql b/backend/db/migrations/20260802211525_rate_limits/migration.sql new file mode 100644 index 000000000..0052e144b --- /dev/null +++ b/backend/db/migrations/20260802211525_rate_limits/migration.sql @@ -0,0 +1,9 @@ +CREATE TABLE "rateLimits" ( + "key" varchar(255) PRIMARY KEY, + "hits" integer DEFAULT 0 NOT NULL, + "windowStartedAt" timestamp DEFAULT now() NOT NULL, + "bannedUntil" timestamp, + "updatedAt" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE INDEX "rateLimits_updatedAt_idx" ON "rateLimits" ("updatedAt"); \ No newline at end of file diff --git a/backend/db/migrations/20260802211525_rate_limits/snapshot.json b/backend/db/migrations/20260802211525_rate_limits/snapshot.json new file mode 100644 index 000000000..bbe993e6a --- /dev/null +++ b/backend/db/migrations/20260802211525_rate_limits/snapshot.json @@ -0,0 +1,5473 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "65dd9b6a-600a-4b92-9d12-bab4daddae6d", + "prevIds": [ + "4dce552a-7858-4b4d-8495-9564163cff38" + ], + "ddl": [ + { + "values": [ + "document", + "image", + "other" + ], + "name": "assetKind", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "pending", + "success", + "error" + ], + "name": "hookState", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "active", + "completed", + "failed", + "interrupted" + ], + "name": "jobHistoryState", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "draft", + "published", + "scheduled" + ], + "name": "pagePublishState", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "inherit", + "override", + "overrideExact", + "hide", + "hideExact" + ], + "name": "treeNavigationMode", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "folder", + "page", + "asset" + ], + "name": "treeType", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "apiKeys", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "approvalRules", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "assets", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "authentication", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "blocks", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "groups", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "hooks", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "iconSets", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "icons", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobHistory", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobLock", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobSchedule", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "jobs", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "locales", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "navigation", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageEditSubmissions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageHistory", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageWatching", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pages", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "rateLimits", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "settings", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sites", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "storage", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tags", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tree", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "userAvatars", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "userGroups", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "userKeys", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "users", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "varchar(8)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "keyShort", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "groups", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "expiration", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isRevoked", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "apiKeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'START'", + "generated": null, + "identity": null, + "name": "match", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "path", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "submitterGroups", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "reviewerGroups", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "approvalRules" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileName", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileExt", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isSystem", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "assetKind", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'other'", + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'application/octet-stream'", + "generated": null, + "identity": null, + "name": "mimeType", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "bigint", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileSize", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "preview", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "storageInfo", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "assets" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "module", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "displayName", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "registration", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "allowedEmailRegex", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 1, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "autoEnrollGroups", + "entityType": "columns", + "schema": "public", + "table": "authentication" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "block", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "icon", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isCustom", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "permissions", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rules", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "redirectOnLogin", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "redirectOnFirstLogin", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "redirectOnLogout", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isSystem", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "events", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "url", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "includeMetadata", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "includeContent", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "acceptUntrusted", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authHeader", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "hookState", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastErrorMessage", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "hooks" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "prefix", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "info", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refreshedAt", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "iconSets" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "prefix", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "body", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "16", + "generated": null, + "identity": null, + "name": "width", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "16", + "generated": null, + "identity": null, + "name": "height", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "left", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "top", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "rotate", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "hFlip", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "vFlip", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "icons" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "task", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "jobHistoryState", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "useWorker", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "wasScheduled", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "attempt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "maxRetries", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastErrorMessage", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "executedBy", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "startedAt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "completedAt", + "entityType": "columns", + "schema": "public", + "table": "jobHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "jobLock" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastCheckedBy", + "entityType": "columns", + "schema": "public", + "table": "jobLock" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "lastCheckedAt", + "entityType": "columns", + "schema": "public", + "table": "jobLock" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "task", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cron", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'system'", + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "jobSchedule" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "task", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "useWorker", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "retries", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "maxRetries", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "waitUntil", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isScheduled", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "createdBy", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "jobs" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "code", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nativeName", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(8)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "language", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(3)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "region", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(4)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "script", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isRTL", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "strings", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "completeness", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "navigation" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "items", + "entityType": "columns", + "schema": "public", + "table": "navigation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "navigation" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "patch", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "baseHash", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "guestName", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "guestEmail", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'updated'", + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "changedFields", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locale", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "path", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reason", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "versionDate", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageHistory" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "pageWatching" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locale", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "path", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "hash", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "alias", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "icon", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "pagePublishState", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'draft'", + "generated": null, + "identity": null, + "name": "publishState", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "publishStartDate", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "publishEndDate", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "relations", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "render", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "searchContent", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "tsvector", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ts", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "tags", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "toc", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "editor", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "contentType", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isBrowsable", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "isSearchable", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": { + "as": "\"pages\".\"publishState\" != 'draft' AND \"pages\".\"isSearchable\"", + "type": "stored" + }, + "identity": null, + "name": "isSearchableComputed", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "ratingScore", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "ratingCount", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "scripts", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "historyData", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "creatorId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ownerId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pages" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "rateLimits" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "hits", + "entityType": "columns", + "schema": "public", + "table": "rateLimits" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "windowStartedAt", + "entityType": "columns", + "schema": "public", + "table": "rateLimits" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bannedUntil", + "entityType": "columns", + "schema": "public", + "table": "rateLimits" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "rateLimits" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "settings" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "value", + "entityType": "columns", + "schema": "public", + "table": "settings" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "hostname", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "sites" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "module", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isEnabled", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "contentTypes", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "assetDelivery", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "versioning", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "config", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "storage" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tag", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "usageCount", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "tags" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "ltree", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "folderPath", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "fileName", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "hash", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "treeType", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tree", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locale", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "treeNavigationMode", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'inherit'", + "generated": null, + "identity": null, + "name": "navigationMode", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "navigationId", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "ARRAY[]", + "generated": null, + "identity": null, + "name": "tags", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "tree" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "userAvatars" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "userAvatars" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "userGroups" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "groupId", + "entityType": "columns", + "schema": "public", + "table": "userGroups" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "validUntil", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "userKeys" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "auth", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "passkeys", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "prefs", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "hasAvatar", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isActive", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isSystem", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isVerified", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lastLoginAt", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "approvalRules_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "approvalRules" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "assets_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "assets" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "blocks_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "blocks" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "language", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "locales_language_idx", + "entityType": "indexes", + "schema": "public", + "table": "locales" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "navigation_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "navigation" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_pageId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_authorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"authorId\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageEditSubmissions_page_author_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "versionDate", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageHistory_pageId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "locale", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "path", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "versionDate", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageHistory_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageHistory_authorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageWatching_user_site_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageWatching_page_user_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_authorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "creatorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_creatorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "ownerId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_ownerId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "ts", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gin", + "concurrently": false, + "name": "pages_ts_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tags", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gin", + "concurrently": false, + "name": "pages_tags_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "isSearchableComputed", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_isSearchableComputed_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updatedAt", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "rateLimits_updatedAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "rateLimits" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sessions_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "module", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "storage_composite_idx", + "entityType": "indexes", + "schema": "public", + "table": "storage" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tags_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "tag", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tags_composite_idx", + "entityType": "indexes", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "folderPath", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_folderpath_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "folderPath", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gist", + "concurrently": false, + "name": "tree_folderpath_gist_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "fileName", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_fileName_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_hash_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tree", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_type_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "locale", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_locale_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "navigationMode", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_navigationMode_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "navigationId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_navigationId_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tags", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "gin", + "concurrently": false, + "name": "tree_tags_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tree_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userGroups_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "groupId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userGroups_groupId_idx", + "entityType": "indexes", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "groupId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userGroups_composite_idx", + "entityType": "indexes", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "userKeys_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "userKeys" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "lastLoginAt", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_lastLoginAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "approvalRules_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "approvalRules" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "assets_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "assets" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "assets_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "assets" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "blocks_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "blocks" + }, + { + "nameExplicit": false, + "columns": [ + "prefix" + ], + "schemaTo": "public", + "tableTo": "iconSets", + "columnsTo": [ + "prefix" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "icons_prefix_iconSets_prefix_fkey", + "entityType": "fks", + "schema": "public", + "table": "icons" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "navigation_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "navigation" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageEditSubmissions_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageEditSubmissions_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageEditSubmissions_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageEditSubmissions" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "pageHistory_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageHistory_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageHistory" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageWatching_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageWatching_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageWatching_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageWatching" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "creatorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_creatorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "ownerId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_ownerId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pages_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pages" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "sessions_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "storage_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "storage" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "tags_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tags" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "tree_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tree" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "userGroups_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": false, + "columns": [ + "groupId" + ], + "schemaTo": "public", + "tableTo": "groups", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "userGroups_groupId_groups_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "userGroups" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "userKeys_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "userKeys" + }, + { + "columns": [ + "prefix", + "name" + ], + "nameExplicit": false, + "name": "icons_pkey", + "entityType": "pks", + "schema": "public", + "table": "icons" + }, + { + "columns": [ + "userId", + "groupId" + ], + "nameExplicit": false, + "name": "userGroups_pkey", + "entityType": "pks", + "schema": "public", + "table": "userGroups" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "apiKeys_pkey", + "schema": "public", + "table": "apiKeys", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "approvalRules_pkey", + "schema": "public", + "table": "approvalRules", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "assets_pkey", + "schema": "public", + "table": "assets", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "authentication_pkey", + "schema": "public", + "table": "authentication", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "blocks_pkey", + "schema": "public", + "table": "blocks", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "groups_pkey", + "schema": "public", + "table": "groups", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "hooks_pkey", + "schema": "public", + "table": "hooks", + "entityType": "pks" + }, + { + "columns": [ + "prefix" + ], + "nameExplicit": false, + "name": "iconSets_pkey", + "schema": "public", + "table": "iconSets", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jobHistory_pkey", + "schema": "public", + "table": "jobHistory", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "jobLock_pkey", + "schema": "public", + "table": "jobLock", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jobSchedule_pkey", + "schema": "public", + "table": "jobSchedule", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "jobs_pkey", + "schema": "public", + "table": "jobs", + "entityType": "pks" + }, + { + "columns": [ + "code" + ], + "nameExplicit": false, + "name": "locales_pkey", + "schema": "public", + "table": "locales", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "navigation_pkey", + "schema": "public", + "table": "navigation", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pageEditSubmissions_pkey", + "schema": "public", + "table": "pageEditSubmissions", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pageHistory_pkey", + "schema": "public", + "table": "pageHistory", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pageWatching_pkey", + "schema": "public", + "table": "pageWatching", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pages_pkey", + "schema": "public", + "table": "pages", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "rateLimits_pkey", + "schema": "public", + "table": "rateLimits", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "sessions_pkey", + "schema": "public", + "table": "sessions", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "settings_pkey", + "schema": "public", + "table": "settings", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "sites_pkey", + "schema": "public", + "table": "sites", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "storage_pkey", + "schema": "public", + "table": "storage", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tags_pkey", + "schema": "public", + "table": "tags", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tree_pkey", + "schema": "public", + "table": "tree", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "userAvatars_pkey", + "schema": "public", + "table": "userAvatars", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "userKeys_pkey", + "schema": "public", + "table": "userKeys", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "users_pkey", + "schema": "public", + "table": "users", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": [ + "hostname" + ], + "nullsNotDistinct": false, + "name": "sites_hostname_key", + "schema": "public", + "table": "sites", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "users_email_key", + "schema": "public", + "table": "users", + "entityType": "uniques" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/backend/db/schema.ts b/backend/db/schema.ts index 98e7dac8c..ee8cbd956 100644 --- a/backend/db/schema.ts +++ b/backend/db/schema.ts @@ -497,6 +497,68 @@ export const pageEditSubmissions = pgTable( ] ) +// PAGE WATCHING ----------------------- +/** + * A page somebody asked to be told about, one row per person per page. + * + * A row IS the watch: there is no `isEnabled` to turn off, because unwatching a page is not a state a + * page keeps — it is the absence of interest, and the row goes. Which is also why the whole table can + * be read as "everyone to notify about this page" when notifications are built on top of it. + * + * `siteId` is carried alongside `pageId` rather than reached through the page, since every query here + * is scoped to one site: the watch list belongs to an inbox, and an inbox belongs to a site. + */ +export const pageWatching = pgTable( + 'pageWatching', + { + id: uuid().primaryKey().defaultRandom(), + createdAt: timestamp().notNull().defaultNow(), + pageId: uuid() + .notNull() + .references(() => pages.id, { onDelete: 'cascade' }), + siteId: uuid() + .notNull() + .references(() => sites.id), + userId: uuid() + .notNull() + .references(() => users.id, { onDelete: 'cascade' }) + }, + (table) => [ + // -> Covers the site scoping too, being the leading column: this is the inbox's own query + index('pageWatching_user_site_idx').on(table.userId, table.siteId), + // -> Watching a page twice is watching it once, so the second attempt is a no-op rather than a row + uniqueIndex('pageWatching_page_user_idx').on(table.pageId, table.userId) + ] +) + +// RATE LIMITS ------------------------- +/** + * One counter per rate-limited client, and the ban it has earned itself. + * + * In the database rather than in each instance's memory because a limit every instance enforces on + * its own is a limit multiplied by however many are running — and because a ban has to hold when the + * next attempt lands on another one. Every read and write of a row happens in a single upserting + * statement (`models/rateLimits.ts`), which is what makes concurrent attempts count exactly once. + * + * Rows are self-correcting: an expired window or ban is reset by the next attempt on that key. They + * are only ever deleted to reclaim space — see the `purgeRateLimits` task. + */ +export const rateLimits = pgTable( + 'rateLimits', + { + /** What is being limited and who by, e.g. `auth:203.0.113.4`. */ + key: varchar({ length: 255 }).primaryKey(), + /** Attempts made inside the current window. */ + hits: integer().notNull().default(0), + windowStartedAt: timestamp().notNull().defaultNow(), + /** When the ban lifts. Null for a client that has not earned one. */ + bannedUntil: timestamp(), + updatedAt: timestamp().notNull().defaultNow() + }, + // -> How the purge finds rows nothing has touched in a long while + (table) => [index('rateLimits_updatedAt_idx').on(table.updatedAt)] +) + // SETTINGS ---------------------------- export const settings = pgTable('settings', { key: varchar({ length: 255 }).notNull().primaryKey(), diff --git a/backend/helpers/common.ts b/backend/helpers/common.ts index 78355a185..721af688a 100644 --- a/backend/helpers/common.ts +++ b/backend/helpers/common.ts @@ -11,6 +11,18 @@ export interface Deferred { promise: Promise } +/** Seconds in each unit a duration setting may be written with. See `durationToSeconds`. */ +const DURATION_UNIT_SECONDS = { + s: 1, + m: 60, + h: 3600, + d: 86400, + w: 604800, + y: 31536000 +} as const + +type DurationUnit = keyof typeof DURATION_UNIT_SECONDS + /* eslint-disable promise/param-names */ export function createDeferred(): Deferred { let result: Promise | undefined @@ -120,6 +132,25 @@ export function generatePathHash(str: string, seed = 0): string { return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16) } +/** + * How long a duration written the way the admin area writes them lasts, in seconds. + * + * `30s`, `15m`, `2h`, `7d`, `2w`, `1y` — one number and one unit, which is the form every duration + * setting takes (the JWT ones included) and the form `DURATION_PATTERN` in `models/security.ts` + * accepts. A year is 365 days and a month is not offered at all: these measure how long something + * lasts, not what date it lands on, so a calendar has no say in it. + * + * @param fallback Returned for anything unparseable, so one bad setting cannot turn a limit off + */ +export function durationToSeconds(value: unknown, fallback: number): number { + const match = /^(\d+)([smhdwy])$/.exec(String(value ?? '').trim()) + if (!match) { + return fallback + } + const seconds = Number(match[1]) * DURATION_UNIT_SECONDS[match[2] as DurationUnit] + return seconds > 0 ? seconds : fallback +} + /** * Get default value of type * diff --git a/backend/helpers/rateLimit.ts b/backend/helpers/rateLimit.ts new file mode 100644 index 000000000..3d786d05c --- /dev/null +++ b/backend/helpers/rateLimit.ts @@ -0,0 +1,71 @@ +import { durationToSeconds } from './common.ts' +import type { FastifyReply, FastifyRequest } from 'fastify' +import type { RateLimitPolicy } from '../models/rateLimits.ts' + +/** + * Defaults for the limit on the authentication endpoints, used until an administrator saves their own + * and whenever a stored value is missing or unusable. Ten attempts in five minutes is far more than a + * person signing in needs and far less than guessing a password takes. + */ +const AUTH_DEFAULTS: RateLimitPolicy = { + max: 10, + windowSeconds: 300, + banSeconds: 900 +} + +/** + * The configured policy. + * + * Every field falls back on its own, so one unusable value leaves the rest of the limit standing + * rather than turning it off — which is the failure mode worth avoiding here. The two durations are + * stored as an operator wrote them (`5m`, `15m`, `1d`), the way the JWT settings beside them are. + */ +function authPolicy(): RateLimitPolicy { + const security = WIKI.config.security ?? {} + const max = Number(security.authRateLimitMax) + return { + max: Number.isFinite(max) && max > 0 ? Math.floor(max) : AUTH_DEFAULTS.max, + windowSeconds: durationToSeconds(security.authRateLimitWindow, AUTH_DEFAULTS.windowSeconds), + banSeconds: durationToSeconds(security.authRateLimitBan, AUTH_DEFAULTS.banSeconds) + } +} + +/** + * Refuse an attempt at an authentication endpoint once a client has made too many. + * + * Written as a per-route `onRequest` hook — `{ onRequest: limitAuthAttempts, schema: … }` — so that it + * runs before the body is even parsed, and so that the routes it guards say so where they are declared + * rather than in a list somewhere else. The endpoints that carry it are the ones where the request + * IS the guess: signing in, answering a second factor, changing a password from the login screen, + * a passkey ceremony, and unlocking a page. + * + * One counter per client address, shared by all of them: an attacker working through passwords on two + * of these endpoints is one attacker, and splitting the count per endpoint would let them have the + * limit twice over. `req.ip` is what the client is identified by, which behind a proxy means the + * `trustProxy` security setting has to be on for this to see anything but the proxy. + * + * Attempts are counted whether or not they succeed. A limit on failures only would leave the endpoint + * open to being hammered with valid credentials, and the numbers are set for a person signing in, who + * does not come close to them. + */ +export async function limitAuthAttempts(req: FastifyRequest, reply: FastifyReply): Promise { + if (WIKI.config.security?.authRateLimitEnabled === false) { + return + } + const verdict = await WIKI.models.rateLimits.consume(`auth:${req.ip}`, authPolicy()) + if (verdict.allowed) { + return + } + WIKI.models.flags.authDebug( + `Rate limit: refused ${req.method} ${req.url} from ${req.ip}, ${verdict.retryAfter}s left of its ban.` + ) + /* + 429 rather than 403, and with `Retry-After`: this is not a refusal to serve the client, it is the + same answer as before with a time on it — which is what a legitimate user locked out by a shared + address needs to be told. + */ + reply.header('Retry-After', String(verdict.retryAfter)) + return reply.tooManyRequests( + `Too many attempts. Try again in ${Math.ceil(verdict.retryAfter / 60)} minute(s).` + ) +} diff --git a/backend/locales/en.json b/backend/locales/en.json index 76924922d..43c6cca3f 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -717,6 +717,7 @@ "admin.security.disallowIframeHint": "Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.", "admin.security.disallowOpenRedirect": "Block Open Redirect", "admin.security.disallowOpenRedirectHint": "Prevents user controlled URLs from directing to websites outside of your wiki. This provides Open Redirect protection.", + "admin.security.durationPlaceholder": "e.g. 15m, 2h, 1d", "admin.security.enforce2fa": "Enforce 2FA", "admin.security.enforce2faHint": "Force all users to use Two-Factor Authentication when using an authentication provider with a user / password form.", "admin.security.enforceHsts": "Enforce HSTS", @@ -740,6 +741,18 @@ "admin.security.maxUploadSizeHint": "The maximum size for a single file. Final value in base 2.", "admin.security.maxUploadSizeInvalid": "The maximum upload size must be a positive value, e.g. 10 MB.", "admin.security.maxUploadSizeSuffix": "bytes", + "admin.security.rateLimit": "Rate Limiting", + "admin.security.rateLimitBan": "Ban Duration", + "admin.security.rateLimitBanHint": "How long a client is refused for once it goes over, as a duration. Attempts made while banned do not extend it.", + "admin.security.rateLimitEnabled": "Limit Authentication Attempts", + "admin.security.rateLimitEnabledHint": "Refuse repeated attempts to sign in, answer a second factor, change a password from the login screen, use a passkey or unlock a page. Counted per client address, in the database, so the limit holds across instances.", + "admin.security.rateLimitMax": "Max Attempts", + "admin.security.rateLimitMaxHint": "How many attempts a client may make within the time window below.", + "admin.security.rateLimitMaxSuffix": "attempts", + "admin.security.rateLimitProxyWarn": "Behind a reverse proxy, turn on \"Trust X-Forwarded-* Proxy Headers\" above — without it every visitor looks like the proxy and shares one limit.", + "admin.security.rateLimitRecommended": "Keeping rate limiting enabled is highly recommended: without it, passwords, second factors and page passwords can be guessed as fast as the server will answer.", + "admin.security.rateLimitWindow": "Time Window", + "admin.security.rateLimitWindowHint": "How long attempts are counted over, as a duration — 30s, 5m, 2h, 1d. Going over the limit within it earns a ban.", "admin.security.restartRequired": "Header, CORS and proxy settings are applied when the server starts, so they take effect after a restart.", "admin.security.saveFailed": "Failed to save the security configuration.", "admin.security.saveSuccess": "Security configuration updated successfully.", @@ -1575,9 +1588,13 @@ "common.page.unlockTitle": "Unlock this page", "common.page.unpublished": "Unpublished", "common.page.unpublishedWarning": "This page is not published.", + "common.page.unwatch": "Stop Watching", + "common.page.unwatchFailed": "Could not stop watching this page.", "common.page.versionId": "Version ID {id}", "common.page.viewingSource": "Viewing source of page {path}", "common.page.viewingSourceVersion": "Viewing source as of {date} of page {path}", + "common.page.watch": "Watch Page", + "common.page.watchFailed": "Could not watch this page.", "common.pageSelector.createTitle": "Select New Page Location", "common.pageSelector.folderEmptyWarning": "This folder is empty.", "common.pageSelector.moveTitle": "Move / Rename Page Location", @@ -2063,7 +2080,15 @@ "inbox.reviewViewPage": "View Page", "inbox.title": "Inbox & Notifications", "inbox.watching": "Watching", - "inbox.watchingInfo": "Nothing here yet.", + "inbox.watchingHint": "Open a page and press the bell in its header to start watching it.", + "inbox.watchingInfo": "Pages you asked to be told about, most recently added first.", + "inbox.watchingLoadFailed": "Failed to load your watched pages.", + "inbox.watchingNone": "You are not watching any page yet.", + "inbox.watchingSince": "Watching since {date}", + "inbox.watchingUnwatch": "Stop Watching", + "inbox.watchingUnwatchFailed": "Could not stop watching this page.", + "inbox.watchingUnwatched": "You are no longer watching {title}.", + "inbox.watchingUpdated": "Last modified on {date}", "linkPicker.emptyFolder": "There are no pages in this folder.", "linkPicker.linkUrl": "Link URL", "linkPicker.loadFailed": "Failed to load the page tree.", diff --git a/backend/models/index.ts b/backend/models/index.ts index b3854c46f..f988c2867 100644 --- a/backend/models/index.ts +++ b/backend/models/index.ts @@ -13,7 +13,9 @@ import { locales } from './locales.ts' import { navigation } from './navigation.ts' import { pageHistory } from './pageHistory.ts' import { pages } from './pages.ts' +import { pageWatching } from './pageWatching.ts' import { passkeys } from './passkeys.ts' +import { rateLimits } from './rateLimits.ts' import { rendering } from './rendering.ts' import { search } from './search.ts' import { security } from './security.ts' @@ -41,7 +43,9 @@ export default { navigation, pageHistory, pages, + pageWatching, passkeys, + rateLimits, rendering, search, security, diff --git a/backend/models/jobs.ts b/backend/models/jobs.ts index 031374a07..c80887372 100644 --- a/backend/models/jobs.ts +++ b/backend/models/jobs.ts @@ -46,6 +46,11 @@ class Jobs { // cron: '0 */6 * * *', // type: 'system' // }, + { + task: 'purgeRateLimits', + cron: '10 * * * *', + type: 'system' + }, { task: 'updateLocales', cron: '0 0 * * *', diff --git a/backend/models/pageWatching.ts b/backend/models/pageWatching.ts new file mode 100644 index 000000000..90b53d4a5 --- /dev/null +++ b/backend/models/pageWatching.ts @@ -0,0 +1,102 @@ +import { and, desc, eq } from 'drizzle-orm' +import { pageWatching as watchingTable, pages as pagesTable } from '../db/schema.ts' + +/** A watched page, as the inbox lists it. */ +export interface WatchedPage { + pageId: string + path: string + locale: string + title: string + description: string | null + icon: string | null + /** When the page itself last changed, which is what a watcher is watching FOR. */ + updatedAt: Date + /** When this person started watching, i.e. how long they have been asking to be told. */ + watchedAt: Date +} + +/** + * Page watching model + * + * Who has asked to be told about which pages. Nothing is sent yet — notifications are not built — so + * this is the list and nothing more: the bell on a page reads it, the inbox lists it, and whatever + * delivers the news later has one place to ask who wants it. + */ +class PageWatching { + /** + * Whether this user is watching this page. + * + * Answered for the page view, which asks about every page it draws, so it is a single indexed lookup + * on the pair — and not asked at all for a guest, who cannot watch anything. + */ + async isWatching(pageId: string, userId: string | null): Promise { + if (!userId) { + return false + } + const rows = await WIKI.db + .select({ id: watchingTable.id }) + .from(watchingTable) + .where(and(eq(watchingTable.pageId, pageId), eq(watchingTable.userId, userId))) + .limit(1) + return rows.length > 0 + } + + /** + * Start watching a page. + * + * Idempotent: watching a page one is already watching is what the reader asked for, and the unique + * index turns the second row into nothing rather than into an error. + */ + async watch({ + siteId, + pageId, + userId + }: { + siteId: string + pageId: string + userId: string + }): Promise { + await WIKI.db + .insert(watchingTable) + .values({ siteId, pageId, userId }) + .onConflictDoNothing({ target: [watchingTable.pageId, watchingTable.userId] }) + } + + /** + * Stop watching a page. Also idempotent, for the same reason: the outcome asked for is that no row + * exists, and it does not. + */ + async unwatch({ pageId, userId }: { pageId: string; userId: string }): Promise { + await WIKI.db + .delete(watchingTable) + .where(and(eq(watchingTable.pageId, pageId), eq(watchingTable.userId, userId))) + } + + /** + * The pages this user watches on a site, most recently watched first. + * + * Joined to the pages rather than storing a copy of the title and the path, so a page that is + * renamed or moved is listed where it is now — which is the point of watching it. A deleted page + * takes its rows with it through the foreign key, so nothing here can point at one that is gone. + */ + async listForUser(siteId: string, userId: string): Promise { + const rows = await WIKI.db + .select({ + pageId: pagesTable.id, + path: pagesTable.path, + locale: pagesTable.locale, + title: pagesTable.title, + description: pagesTable.description, + icon: pagesTable.icon, + updatedAt: pagesTable.updatedAt, + watchedAt: watchingTable.createdAt + }) + .from(watchingTable) + .innerJoin(pagesTable, eq(pagesTable.id, watchingTable.pageId)) + .where(and(eq(watchingTable.userId, userId), eq(watchingTable.siteId, siteId))) + .orderBy(desc(watchingTable.createdAt)) + return rows as WatchedPage[] + } +} + +export const pageWatching = new PageWatching() diff --git a/backend/models/rateLimits.ts b/backend/models/rateLimits.ts new file mode 100644 index 000000000..1ce2cdf0d --- /dev/null +++ b/backend/models/rateLimits.ts @@ -0,0 +1,121 @@ +import { lt, sql } from 'drizzle-orm' +import { rateLimits as rateLimitsTable } from '../db/schema.ts' + +/** How a limit is configured: what it allows, over how long, and what it costs to exceed. */ +export interface RateLimitPolicy { + /** Attempts allowed within the window. The one that exceeds it is what earns the ban. */ + max: number + /** Length of the window, in seconds. */ + windowSeconds: number + /** How long the ban lasts once earned, in seconds. */ + banSeconds: number +} + +/** What the caller does about an attempt. */ +export interface RateLimitVerdict { + /** Whether the attempt may proceed. */ + allowed: boolean + /** Attempts made in the current window, the one just counted included. */ + hits: number + /** Seconds until the client may try again. Zero while it is still allowed. */ + retryAfter: number +} + +/** + * Rate limits model + * + * A fixed window per key, with a ban for going over it — the counter that stands behind the login and + * the other endpoints where guessing is the attack (`helpers/rateLimit.ts` is what applies it). + * + * Every attempt is one statement, and the statement decides everything: whether the window has rolled + * over, whether the ban has lifted, what the count now is, and whether this attempt has just earned a + * ban. Doing it in the database rather than in the process is the whole point — two instances behind + * a load balancer share one counter, and a ban issued by one is honoured by both. + */ +class RateLimits { + /** + * Count an attempt against a key, and say whether it may proceed. + * + * The row is read, rolled over, incremented and possibly banned in a single upsert, so concurrent + * attempts cannot both read the same count and both decide they are under the limit. What comes + * back is the row as it now stands. + * + * A banned key stops counting: its ban runs for exactly as long as it was set for, rather than + * being pushed further out by every attempt made during it. When the ban lifts, the window starts + * again from nothing — having served it, a client is not one attempt away from serving another. + * + * @param key What is being limited and who by, e.g. `auth:203.0.113.4` + */ + async consume(key: string, policy: RateLimitPolicy): Promise { + const window = sql`make_interval(secs => ${policy.windowSeconds})` + /* + The three cases, in the order the CASE arms decide them: + + 1. still banned -- nothing changes; the attempt is refused + 2. window rolled over, or a ban has just expired -- the row starts again at this attempt + 3. otherwise -- one more attempt, and a ban if that is one too many + + `"rateLimits"` rather than the aliased `excluded`: these have to read the row AS IT WAS, and + `excluded` is the row this statement proposed. + */ + const rolledOver = sql`"rateLimits"."bannedUntil" is not null or "rateLimits"."windowStartedAt" <= now() - ${window}` + const stillBanned = sql`"rateLimits"."bannedUntil" > now()` + const rows = await WIKI.db.execute(sql` + insert into "rateLimits" ("key", "hits", "windowStartedAt", "updatedAt") + values (${key}, 1, now(), now()) + on conflict ("key") do update set + "hits" = case + when ${stillBanned} then "rateLimits"."hits" + when ${rolledOver} then 1 + else "rateLimits"."hits" + 1 + end, + "windowStartedAt" = case + when ${stillBanned} then "rateLimits"."windowStartedAt" + when ${rolledOver} then now() + else "rateLimits"."windowStartedAt" + end, + "bannedUntil" = case + when ${stillBanned} then "rateLimits"."bannedUntil" + when ${rolledOver} then null + when "rateLimits"."hits" + 1 > ${policy.max} then now() + make_interval(secs => ${policy.banSeconds}) + else null + end, + "updatedAt" = now() + returning + "hits", + "bannedUntil" > now() as "isBanned", + greatest(0, ceil(extract(epoch from coalesce("bannedUntil", now()) - now())))::int as "retryAfter" + `) + const row = (rows as any).rows?.[0] ?? (rows as any)[0] + return { + allowed: !row?.isBanned, + hits: Number(row?.hits ?? 0), + retryAfter: row?.isBanned ? Number(row.retryAfter) : 0 + } + } + + /** + * Forget a key, e.g. once the attempt it was counting has succeeded. + */ + async reset(key: string): Promise { + await WIKI.db.delete(rateLimitsTable).where(sql`${rateLimitsTable.key} = ${key}`) + } + + /** + * Drop rows nothing has touched for a day. + * + * Only ever about reclaiming space: a stale row is already harmless, since the next attempt on that + * key rolls its window over before reading it. A key nobody has used in a day is one nobody is + * being limited by. + * + * @returns How many rows were dropped + */ + async purgeStale(): Promise { + const result = await WIKI.db + .delete(rateLimitsTable) + .where(lt(rateLimitsTable.updatedAt, sql`now() - interval '1 day'`)) + return result.rowCount ?? 0 + } +} + +export const rateLimits = new RateLimits() diff --git a/backend/models/security.ts b/backend/models/security.ts index d8d6fa8b7..788621cb8 100644 --- a/backend/models/security.ts +++ b/backend/models/security.ts @@ -2,6 +2,10 @@ import { CORS_MODES, parseCspDirectives } from '../helpers/security.ts' /** Fields stored in the `security` settings blob. */ export const SECURITY_FIELDS = [ + 'authRateLimitBan', + 'authRateLimitEnabled', + 'authRateLimitMax', + 'authRateLimitWindow', 'corsConfig', 'corsMode', 'cspDirectives', @@ -112,6 +116,20 @@ class Security { return 'Enforcing HSTS needs a duration greater than zero.' } + if (merged.authRateLimitEnabled) { + if (!(merged.authRateLimitMax > 0)) { + return 'The attempt limit must be greater than zero.' + } + for (const [field, label] of [ + ['authRateLimitWindow', 'time window'], + ['authRateLimitBan', 'ban duration'] + ] as const) { + if (!DURATION_PATTERN.test(`${merged[field] ?? ''}`.trim())) { + return `The ${label} must be a duration such as 30s, 15m, 2h or 1d.` + } + } + } + for (const [field, label] of [ ['authJwtExpiration', 'token expiration'], ['authJwtRenewablePeriod', 'token renewal period'] diff --git a/backend/tasks/simple/purge-rate-limits.ts b/backend/tasks/simple/purge-rate-limits.ts new file mode 100644 index 000000000..650b613de --- /dev/null +++ b/backend/tasks/simple/purge-rate-limits.ts @@ -0,0 +1,13 @@ +export async function task(): Promise { + WIKI.logger.info('Purging stale rate limit counters...') + + try { + const purged = await WIKI.models.rateLimits.purgeStale() + + WIKI.logger.info(`Purged ${purged} stale rate limit counters: [ COMPLETED ]`) + } catch (err: any) { + WIKI.logger.error('Purging stale rate limit counters: [ FAILED ]') + WIKI.logger.error(err.message) + throw err + } +} diff --git a/frontend/src/assets/icons.generated.js b/frontend/src/assets/icons.generated.js index 69850f5e8..c2aaa1f16 100644 --- a/frontend/src/assets/icons.generated.js +++ b/frontend/src/assets/icons.generated.js @@ -5,7 +5,7 @@ never waits on (or depends on) the icon service. Regenerate with `npm run icons` after adding or removing an icon; `check-icons.mjs` fails the build if this drifts. - 257 icons. + 259 icons. */ export const BUNDLED_ICONS = { "la:angle-double-right": {"body":"","width":32,"height":32}, @@ -22,7 +22,6 @@ export const BUNDLED_ICONS = { "la:bell": {"body":"","width":32,"height":32}, "la:bolt": {"body":"","width":32,"height":32}, "la:book": {"body":"","width":32,"height":32}, - "la:bookmark": {"body":"","width":32,"height":32}, "la:broadcast-tower": {"body":"","width":32,"height":32}, "la:broom": {"body":"","width":32,"height":32}, "la:calendar": {"body":"","width":32,"height":32}, @@ -154,6 +153,9 @@ export const BUNDLED_ICONS = { "mdi:alpha-t-box-outline": {"body":"","width":24,"height":24}, "mdi:arrow-vertical-lock": {"body":"","width":24,"height":24}, "mdi:basketball": {"body":"","width":24,"height":24}, + "mdi:bell": {"body":"","width":24,"height":24}, + "mdi:bell-off-outline": {"body":"","width":24,"height":24}, + "mdi:bell-outline": {"body":"","width":24,"height":24}, "mdi:book-plus": {"body":"","width":24,"height":24}, "mdi:car": {"body":"","width":24,"height":24}, "mdi:check": {"body":"","width":24,"height":24}, diff --git a/frontend/src/components/PageActionsCol.vue b/frontend/src/components/PageActionsCol.vue index 3e056a60b..c411b3518 100644 --- a/frontend/src/components/PageActionsCol.vue +++ b/frontend/src/components/PageActionsCol.vue @@ -22,78 +22,6 @@ @click="togglePageProperties"> Page Properties - - - - - - - - {{ pendingCount }} - - - {{ t('inbox.pendingReview') }} - - - - - - {{ t('inbox.reviewNone') }} - - - - - - - - - {{ submission.author.name || t('inbox.reviewUnknownAuthor') }} - - {{ humanizeDate(submission.createdAt) }} - - - {{ t('inbox.reviewStale') }} - - - - - - @@ -131,7 +117,6 @@ import { computed, onMounted, reactive, ref, watch } from 'vue' import { useRouter, useRoute } from 'vue-router' import { useMeta } from '@/composables/meta' -import { notify } from '@/composables/notify' import { useMinWidth } from '@/composables/screen' import { useI18n } from 'vue-i18n' @@ -236,15 +221,6 @@ const showSidebarActions = computed(() => siteStore.locales.showMenu || canBrows const canEditNav = computed(() => { return userStore.authenticated && userStore.can('manage:navigation') }) - -// METHODS - -function notImplemented() { - notify({ - type: 'negative', - message: 'Not implemented' - }) -}