diff --git a/backend/api/index.ts b/backend/api/index.ts index 67f554955..c569285a7 100644 --- a/backend/api/index.ts +++ b/backend/api/index.ts @@ -50,6 +50,7 @@ async function routes(app: FastifyInstance) { app.register(import('./mail.ts'), { prefix: '/mail' }) app.register(import('./navigation.ts')) app.register(import('./pages.ts')) + app.register(import('./ratings.ts')) app.register(import('./scheduler.ts'), { prefix: '/scheduler' }) app.register(import('./sites.ts'), { prefix: '/sites' }) app.register(import('./storage.ts')) diff --git a/backend/api/pages.ts b/backend/api/pages.ts index 48f8bff2b..b1b6e4182 100644 --- a/backend/api/pages.ts +++ b/backend/api/pages.ts @@ -676,7 +676,11 @@ async function routes(app: FastifyInstance) { is what makes a page view one request instead of four. */ const actorId = actor?.id ?? null - const [approvalState, isWatching, commentsCount, blog] = await Promise.all([ + // -> Both switches: the site's ratings mode and the page's own `allowRatings` + const ratingMode = page.allowRatings + ? WIKI.models.pageRatings.modeFor(req.params.siteId) + : null + const [approvalState, isWatching, commentsCount, blog, ownRating] = await Promise.all([ WIKI.models.approvals.pageViewerState(req, req.params.siteId, { id: page.id, path: page.path, @@ -699,11 +703,19 @@ async function routes(app: FastifyInstance) { which is one lookup on the unique `(siteId, locale, path)` index over the page's own ancestors, and no lookup at all for a page at the site root. */ - WIKI.models.blogs.blogFor(req.params.siteId, page.locale, page.path) + WIKI.models.blogs.blogFor(req.params.siteId, page.locale, page.path), + /* + The reader's own rating, which is theirs and so cannot be cached on the page the way the + totals are: one lookup on the primary key, and none for a guest or where ratings are off. + */ + ratingMode ? WIKI.models.pageRatings.valueFor(page.id, actorId, ratingMode) : 0 ]) + const { ratings, ...pageFields } = page return { - ...page, + ...pageFields, commentsCount, + // -> Off the totals cached on the row just loaded, so no aggregate per view + rating: ratingMode ? WIKI.models.pageRatings.summaryFromCache(ratings, ratingMode) : null, /* Only what the page view draws: a post shows the name of the blog it is in and links to it. The blog's own settings are not a fact about this page -- the front page carries them, and @@ -713,7 +725,8 @@ async function routes(app: FastifyInstance) { viewer: { permissions: pagePermissionsFor(req, page), ...approvalState, - isWatching + isWatching, + rating: ownRating } } } diff --git a/backend/api/ratings.ts b/backend/api/ratings.ts new file mode 100644 index 000000000..e163290d9 --- /dev/null +++ b/backend/api/ratings.ts @@ -0,0 +1,175 @@ +import { audit } from '../helpers/audit.ts' +import { actorFrom, mayOnPage, unlockedFor } from './pages.ts' +import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' +import type { RatingMode } from '../models/pageRatings.ts' +import type { Page } from '../models/pages.ts' + +const pageParams = { + type: 'object', + properties: { + siteId: { type: 'string', format: 'uuid' }, + pageId: { type: 'string', format: 'uuid' } + }, + required: ['siteId', 'pageId'] +} + +const ratingResponse = { + description: 'How the page is rated now, and what the caller gave it', + type: 'object', + properties: { + ok: { type: 'boolean' }, + rating: { $ref: 'PageRatingSummary#' }, + value: { + type: 'integer', + description: 'The caller’s own rating, 0 once withdrawn.' + } + } +} + +/** + * The page being rated and the scale it is rated on, or a refusal. + * + * Rating belongs to an account — one opinion per person needs a person to hang it on — so an + * anonymous request never gets as far as the page. Beyond that it is the same gate as reading: a page + * somebody may not read is answered as though it were not there. Both switches have to be on, the + * site's ratings mode and the page's own `allowRatings`. + */ +async function loadRateable( + req: FastifyRequest<{ Params: { siteId: string; pageId: string } }>, + reply: FastifyReply +): Promise<{ userId: string; mode: RatingMode; page: Page } | null> { + const actor = actorFrom(req) + if (!actor) { + reply.unauthorized('Rating a page requires a logged in user.') + return null + } + const mode = WIKI.models.pageRatings.modeFor(req.params.siteId) + if (!mode) { + reply.forbidden('Ratings are turned off for this site.') + return null + } + const page = await WIKI.models.pages.getPage({ + siteId: req.params.siteId, + id: req.params.pageId, + unlocked: (id: string) => unlockedFor(req, id) + }) + if (!page || !mayOnPage(req, 'read:pages', page)) { + reply.notFound('This page does not exist.') + return null + } + if (!page.allowRatings) { + reply.forbidden('Ratings are turned off for this page.') + return null + } + return { userId: actor.id, mode, page } +} + +/** + * Page Ratings API Routes + * + * A reader's thumbs or stars for a page. How a page is rated so far comes with the page itself + * (`rating`, and the reader's own under `viewer.rating`), so there is no read route here. + * + * No route-level `permissions` on either: the test is `read:pages` on the page, which a group's page + * rules decide and the hook in `index.ts` cannot see. + */ +async function routes(app: FastifyInstance) { + /** + * RATE A PAGE + */ + app.put<{ Params: { siteId: string; pageId: string }; Body: { value: number } }>( + '/sites/:siteId/pages/:pageId/rating', + { + schema: { + summary: 'Rate a page', + description: + 'Records the caller’s rating of the page, replacing any they gave it before. The scale is the site’s ratings mode: `1` or `-1` for thumbs, `1` to `5` for stars.\n\nNeeds a logged in user who may read the page, with ratings on for both the site and the page.', + tags: ['Pages'], + params: pageParams, + body: { + type: 'object', + required: ['value'], + properties: { + value: { type: 'integer', minimum: -1, maximum: 5 } + } + }, + response: { 200: ratingResponse } + } + }, + async (req, reply) => { + const target = await loadRateable(req, reply) + if (!target) { + return reply + } + const { userId, mode, page } = target + if (!WIKI.models.pageRatings.isValid(mode, req.body.value)) { + return reply.badRequest( + mode === 'thumbs' + ? 'A thumbs rating is 1 or -1.' + : 'A star rating is a whole number from 1 to 5.' + ) + } + const totals = await WIKI.models.pageRatings.rate({ + pageId: page.id, + userId, + mode, + value: req.body.value + }) + + await audit(req, 'page', 'ratePage', { + pageId: page.id, + siteId: req.params.siteId, + locale: page.locale, + path: page.path, + mode, + value: req.body.value + }) + + return { + ok: true, + rating: WIKI.models.pageRatings.summaryFromCache(totals, mode), + value: req.body.value + } + } + ) + + /** + * WITHDRAW A RATING + */ + app.delete<{ Params: { siteId: string; pageId: string } }>( + '/sites/:siteId/pages/:pageId/rating', + { + schema: { + summary: 'Withdraw a rating', + description: + 'Forgets the caller’s rating of the page. A page they had not rated answers the same way, since the outcome asked for already holds.', + tags: ['Pages'], + params: pageParams, + response: { 200: ratingResponse } + } + }, + async (req, reply) => { + const target = await loadRateable(req, reply) + if (!target) { + return reply + } + const { userId, mode, page } = target + const totals = await WIKI.models.pageRatings.unrate({ pageId: page.id, userId }) + + await audit(req, 'page', 'unratePage', { + pageId: page.id, + siteId: req.params.siteId, + locale: page.locale, + path: page.path + }) + + return { + ok: true, + rating: WIKI.models.pageRatings.summaryFromCache(totals, mode), + value: 0 + } + } + ) +} + +export default routes diff --git a/backend/api/schemas/page.ts b/backend/api/schemas/page.ts index 294a80b66..2298fa1ae 100644 --- a/backend/api/schemas/page.ts +++ b/backend/api/schemas/page.ts @@ -125,6 +125,7 @@ export async function registerSchemas(app: FastifyInstance): Promise { allowComments: { type: 'boolean' }, allowContributions: { type: 'boolean' }, allowRatings: { type: 'boolean' }, + showLastEditedBy: { type: 'boolean' }, showSidebar: { type: 'boolean' }, showTags: { type: 'boolean' }, showToc: { type: 'boolean' }, @@ -229,6 +230,12 @@ export async function registerSchemas(app: FastifyInstance): Promise { description: 'How many comments this page has, which is what the Talk tab’s badge counts. Always 0 unless the site uses the built-in comments provider. Present when a page is fetched on its own.' }, + rating: { + anyOf: [{ type: 'null' }, { $ref: 'PageRatingSummary#' }], + description: + 'How readers have rated the page, on the site’s current scale. Null when ratings are off for the site or for the page. Present when a page is fetched on its own.' + }, + showLastEditedBy: { type: 'boolean' }, showSidebar: { type: 'boolean' }, showTags: { type: 'boolean' }, showToc: { type: 'boolean' }, @@ -246,6 +253,7 @@ export async function registerSchemas(app: FastifyInstance): Promise { navigationMode: { type: 'string' }, authorId: { type: 'string', format: 'uuid' }, authorName: { type: 'string' }, + authorHasAvatar: { type: 'boolean' }, createdAt: { type: 'string', format: 'date-time' }, updatedAt: { type: 'string', format: 'date-time' }, blog: { @@ -287,6 +295,11 @@ export async function registerSchemas(app: FastifyInstance): Promise { description: 'The requester has asked to be told about changes to this page. Always false without an account, since a watch belongs to one.' }, + rating: { + type: 'integer', + description: + 'The requester’s own rating of this page on the site’s current scale, or 0 for none. Always 0 without an account, since a rating belongs to one.' + }, pendingSubmissions: { type: 'array', items: { $ref: 'PageEditSubmission#' }, @@ -297,6 +310,28 @@ export async function registerSchemas(app: FastifyInstance): Promise { } }) + /** + * PAGE RATING SUMMARY - How readers have rated a page, on the site's current scale + */ + app.addSchema({ + $id: 'PageRatingSummary', + type: 'object', + properties: { + mode: { + type: 'string', + enum: ['thumbs', 'stars'], + description: 'The scale. Only ratings given on it are counted.' + }, + count: { type: 'integer', description: 'How many readers have rated the page.' }, + average: { + type: 'number', + description: 'Mean rating: 1 to 5 for stars, -1 to 1 for thumbs. 0 when nobody has rated.' + }, + up: { type: 'integer', description: 'Thumbs up. Always 0 under stars.' }, + down: { type: 'integer', description: 'Thumbs down. Always 0 under stars.' } + } + }) + /** * WATCHED PAGE - A page somebody asked to be told about, as their inbox lists it */ diff --git a/backend/api/schemas/site.ts b/backend/api/schemas/site.ts index 1ab21b8cd..0bbf3d028 100644 --- a/backend/api/schemas/site.ts +++ b/backend/api/schemas/site.ts @@ -91,18 +91,22 @@ export async function registerSchemas(app: FastifyInstance): Promise { collaborativeEditing: { type: 'boolean' }, - ratings: { - type: 'boolean' - }, ratingsMode: { type: 'string', - enum: ['off', 'stars', 'thumbs'] + enum: ['off', 'stars', 'thumbs'], + description: + 'Whether readers may rate pages, and on which scale. Off by default. A page can still opt out on its own with `allowRatings`; only a logged in reader who may read the page can rate it.' }, comments: { type: 'boolean', description: 'Whether this site has comments at all. Which provider handles them is `comments.provider`; this turns every one of them off without losing that choice, and a page can still opt out on its own with `allowComments`.' }, + lastEditedBy: { + type: 'boolean', + description: + 'Whether a page may show who last edited it, in its sidebar. A page can still opt out on its own with `showLastEditedBy`.' + }, reasonForChange: { type: 'string', enum: ['off', 'optional', 'required'] diff --git a/backend/api/sites.ts b/backend/api/sites.ts index 9e27b0a14..848a00c47 100644 --- a/backend/api/sites.ts +++ b/backend/api/sites.ts @@ -487,11 +487,6 @@ async function routes(app: FastifyInstance) { } } - // -> Keep the legacy `features.ratings` flag in sync with the ratings mode - if (config.features?.ratingsMode !== undefined) { - config.features.ratings = config.features.ratingsMode !== 'off' - } - /* The pasted-uploads destination is stored in one form, so that what the admin area reads back is what an upload will do with it -- `assets/`, `./assets` and `assets` are the same folder, and diff --git a/backend/db/migrations/20260924204030_page-ratings/migration.sql b/backend/db/migrations/20260924204030_page-ratings/migration.sql new file mode 100644 index 000000000..8014332d6 --- /dev/null +++ b/backend/db/migrations/20260924204030_page-ratings/migration.sql @@ -0,0 +1,16 @@ +CREATE TABLE "pageRatings" ( + "pageId" uuid, + "userId" uuid, + "kind" varchar(16) NOT NULL, + "value" integer NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "pageRatings_pkey" PRIMARY KEY("pageId","userId") +); +--> statement-breakpoint +ALTER TABLE "pages" ADD COLUMN "ratings" jsonb DEFAULT '{}' NOT NULL;--> statement-breakpoint +ALTER TABLE "pages" DROP COLUMN "ratingScore";--> statement-breakpoint +ALTER TABLE "pages" DROP COLUMN "ratingCount";--> statement-breakpoint +CREATE INDEX "pageRatings_userId_idx" ON "pageRatings" ("userId");--> statement-breakpoint +ALTER TABLE "pageRatings" ADD CONSTRAINT "pageRatings_pageId_pages_id_fkey" FOREIGN KEY ("pageId") REFERENCES "pages"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "pageRatings" ADD CONSTRAINT "pageRatings_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/20260924204030_page-ratings/snapshot.json b/backend/db/migrations/20260924204030_page-ratings/snapshot.json new file mode 100644 index 000000000..554a049be --- /dev/null +++ b/backend/db/migrations/20260924204030_page-ratings/snapshot.json @@ -0,0 +1,7373 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "5f4ecf6d-668d-405e-a03e-8b038be23d80", + "prevIds": [ + "1e2090db-a459-4dfe-8e9d-5387a3653ed5" + ], + "ddl": [ + { + "values": [ + "document", + "image", + "other" + ], + "name": "assetKind", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "pending", + "success", + "error" + ], + "name": "hookState", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "open", + "finished", + "failed" + ], + "name": "importSessionState", + "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": "auditLog", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "authentication", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "blocks", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "comments", + "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": "importIdMap", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "importSessions", + "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": "pageLinks", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageRatings", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pageRenderQueue", + "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": "siteAssets", + "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": "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": "auditLog" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "ts", + "entityType": "columns", + "schema": "public", + "table": "auditLog" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "auditLog" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "action", + "entityType": "columns", + "schema": "public", + "table": "auditLog" + }, + { + "type": "varchar(45)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "clientIP", + "entityType": "columns", + "schema": "public", + "table": "auditLog" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "auditLog" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "auditLog" + }, + { + "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": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "definition", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "packageData", + "entityType": "columns", + "schema": "public", + "table": "blocks" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "checksum", + "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": "comments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "parentId", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorId", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "authorName", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "authorEmail", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "authorIP", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "meta", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "comments" + }, + { + "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": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "externalId", + "entityType": "columns", + "schema": "public", + "table": "groups" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isProvisioned", + "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": null, + "generated": null, + "identity": null, + "name": "sessionId", + "entityType": "columns", + "schema": "public", + "table": "importIdMap" + }, + { + "type": "varchar(32)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "entity", + "entityType": "columns", + "schema": "public", + "table": "importIdMap" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "sourceId", + "entityType": "columns", + "schema": "public", + "table": "importIdMap" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "targetId", + "entityType": "columns", + "schema": "public", + "table": "importIdMap" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "namespace", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "varchar(32)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "sites", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "includes", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "overwrite", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'markdown'", + "generated": null, + "identity": null, + "name": "htmlConversion", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "importSessionState", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'open'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "progress", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "warnings", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "actorId", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "importSessions" + }, + { + "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": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isInstalled", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "hash", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "customCode", + "entityType": "columns", + "schema": "public", + "table": "locales" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "customName", + "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": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "locale", + "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": "pageLinks" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "varchar(2048)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "href", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "targetSiteId", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "targetLocale", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "targetPath", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "targetRef", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageLinks" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageRatings" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "userId", + "entityType": "columns", + "schema": "public", + "table": "pageRatings" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "pageRatings" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value", + "entityType": "columns", + "schema": "public", + "table": "pageRatings" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pageRatings" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "pageRatings" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "gen_random_uuid()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "allowScripts", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "allowStyles", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "createdAt", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "type": "timestamp", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updatedAt", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pageId", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "requestedById", + "entityType": "columns", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "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": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "localeGroupId", + "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": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "ratings", + "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": null, + "generated": null, + "identity": null, + "name": "siteId", + "entityType": "columns", + "schema": "public", + "table": "siteAssets" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "siteAssets" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "data", + "entityType": "columns", + "schema": "public", + "table": "siteAssets" + }, + { + "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": "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": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "handle", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "externalId", + "entityType": "columns", + "schema": "public", + "table": "users" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "isProvisioned", + "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": "ts", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "auditLog_ts_idx", + "entityType": "indexes", + "schema": "public", + "table": "auditLog" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "ts", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "auditLog_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "auditLog" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "kind", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "ts", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "auditLog_kind_idx", + "entityType": "indexes", + "schema": "public", + "table": "auditLog" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "action", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "ts", + "isExpression": false, + "asc": false, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "auditLog_action_idx", + "entityType": "indexes", + "schema": "public", + "table": "auditLog" + }, + { + "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": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "createdAt", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "comments_page_created_idx", + "entityType": "indexes", + "schema": "public", + "table": "comments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "parentId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "comments_parentId_idx", + "entityType": "indexes", + "schema": "public", + "table": "comments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "authorId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "comments_authorId_idx", + "entityType": "indexes", + "schema": "public", + "table": "comments" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "externalId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "groups_externalId_idx", + "entityType": "indexes", + "schema": "public", + "table": "groups" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "createdAt", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "importSessions_createdAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "importSessions" + }, + { + "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": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "locale", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "navigation_siteId_locale_key", + "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": "targetSiteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "targetLocale", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "targetPath", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageLinks_target_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "targetSiteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "kind", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "targetRef", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageLinks_targetRef_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageLinks_pageId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "siteId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageLinks_siteId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pageId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "href", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageLinks_pageId_href_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "userId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageRatings_userId_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageRatings" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "createdAt", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pageRenderQueue_createdAt_idx", + "entityType": "indexes", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "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": "localeGroupId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "locale", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_localeGroupId_locale_idx", + "entityType": "indexes", + "schema": "public", + "table": "pages" + }, + { + "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 + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pages_siteId_locale_path_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": true, + "columns": [ + { + "value": "externalId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_externalId_idx", + "entityType": "indexes", + "schema": "public", + "table": "users" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "lower(\"handle\")", + "isExpression": true, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "users_handle_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": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "auditLog_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "auditLog" + }, + { + "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": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comments_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "comments" + }, + { + "nameExplicit": false, + "columns": [ + "parentId" + ], + "schemaTo": "public", + "tableTo": "comments", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "comments_parentId_comments_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "comments" + }, + { + "nameExplicit": false, + "columns": [ + "authorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "comments_authorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "comments" + }, + { + "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": [ + "sessionId" + ], + "schemaTo": "public", + "tableTo": "importSessions", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "importIdMap_sessionId_importSessions_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "importIdMap" + }, + { + "nameExplicit": false, + "columns": [ + "actorId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "importSessions_actorId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "importSessions" + }, + { + "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": [ + "targetSiteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageLinks_targetSiteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageLinks_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageLinks_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageLinks" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageRatings_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageRatings" + }, + { + "nameExplicit": false, + "columns": [ + "userId" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageRatings_userId_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageRatings" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "schemaTo": "public", + "tableTo": "pages", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pageRenderQueue_pageId_pages_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "nameExplicit": false, + "columns": [ + "siteId" + ], + "schemaTo": "public", + "tableTo": "sites", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "pageRenderQueue_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "nameExplicit": false, + "columns": [ + "requestedById" + ], + "schemaTo": "public", + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "pageRenderQueue_requestedById_users_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pageRenderQueue" + }, + { + "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": "siteAssets_siteId_sites_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "siteAssets" + }, + { + "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": [ + "sessionId", + "entity", + "sourceId" + ], + "nameExplicit": false, + "name": "importIdMap_pkey", + "entityType": "pks", + "schema": "public", + "table": "importIdMap" + }, + { + "columns": [ + "pageId", + "userId" + ], + "nameExplicit": false, + "name": "pageRatings_pkey", + "entityType": "pks", + "schema": "public", + "table": "pageRatings" + }, + { + "columns": [ + "siteId", + "kind" + ], + "nameExplicit": false, + "name": "siteAssets_pkey", + "entityType": "pks", + "schema": "public", + "table": "siteAssets" + }, + { + "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": "auditLog_pkey", + "schema": "public", + "table": "auditLog", + "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": "comments_pkey", + "schema": "public", + "table": "comments", + "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": "importSessions_pkey", + "schema": "public", + "table": "importSessions", + "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": "pageLinks_pkey", + "schema": "public", + "table": "pageLinks", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pageRenderQueue_pkey", + "schema": "public", + "table": "pageRenderQueue", + "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": [ + "customCode" + ], + "nullsNotDistinct": false, + "name": "locales_customCode_key", + "schema": "public", + "table": "locales", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": [ + "pageId" + ], + "nullsNotDistinct": false, + "name": "pageRenderQueue_pageId_key", + "schema": "public", + "table": "pageRenderQueue", + "entityType": "uniques" + }, + { + "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 0a118b145..94f7ebd47 100644 --- a/backend/db/schema.ts +++ b/backend/db/schema.ts @@ -679,8 +679,12 @@ export const pages = pgTable( (): SQL => sql`${pages.publishState} != 'draft' AND ${pages.isSearchable}` ), password: varchar({ length: 255 }), - ratingScore: integer().notNull().default(0), - ratingCount: timestamp().notNull().defaultNow(), + /** + * How readers have rated the page, per scale: `{ thumbs?: { count, sum, up, down }, stars?: … }`. + * A cache of the `pageRatings` rows, rewritten by every rating and withdrawal (`models/pageRatings.ts`) + * so that a page view reads it off the row it already loads instead of aggregating per view. + */ + ratings: jsonb().notNull().default({}), scripts: jsonb().notNull().default({}), historyData: jsonb().notNull().default({}), createdAt: timestamp().notNull().defaultNow(), @@ -962,6 +966,39 @@ export const pageWatching = pgTable( ] ) +// PAGE RATINGS ------------------------ +/** + * One reader's rating of one page. + * + * `kind` is the site's ratings mode the rating was given under — `thumbs` (`value` is 1 or -1) or + * `stars` (1 to 5) — because the two scales cannot be added together, and a site may switch between + * them. Only the rows of the mode in force are counted, so switching back finds the old ratings where + * they were. Rating again under the other mode replaces the row: a reader has one opinion of a page. + * + * The totals are cached on the page (`pages.ratings`), one entry per scale, and rewritten from these + * rows whenever one of the page's changes. A rating removed by a deleted account's cascade is not + * subtracted until the page is next rated. + */ +export const pageRatings = pgTable( + 'pageRatings', + { + pageId: uuid() + .notNull() + .references(() => pages.id, { onDelete: 'cascade' }), + userId: uuid() + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + kind: varchar({ length: 16 }).notNull(), + value: integer().notNull(), + createdAt: timestamp().notNull().defaultNow(), + updatedAt: timestamp().notNull().defaultNow() + }, + (table) => [ + primaryKey({ columns: [table.pageId, table.userId] }), + index('pageRatings_userId_idx').on(table.userId) + ] +) + // PAGE RENDER QUEUE ------------------- /** * A page waiting for the server to render it, one row per page. diff --git a/backend/locales/en.json b/backend/locales/en.json index c3cff8fa5..da2f9c16a 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -182,6 +182,7 @@ "admin.audit.actions.purgeEmptyFolders": "Deleted the empty folders", "admin.audit.actions.purgePageHistory": "Purged page history", "admin.audit.actions.purgeSampleContent": "Purged the sample content", + "admin.audit.actions.ratePage": "Rated a page", "admin.audit.actions.rebuildPageLinks": "Rebuilt the page link index", "admin.audit.actions.rebuildSearchIndex": "Rebuilt the search index", "admin.audit.actions.refreshIconSets": "Refreshed the icon sets", @@ -205,6 +206,7 @@ "admin.audit.actions.togglePasswordLogin": "Turned password sign-in on or off", "admin.audit.actions.unassignUserFromGroup": "Removed a user from a group", "admin.audit.actions.unlockPage": "Unlocked a password-protected page", + "admin.audit.actions.unratePage": "Withdrew a page rating", "admin.audit.actions.unwatchPage": "Stopped watching a page", "admin.audit.actions.updateAnalytics": "Changed the analytics configuration", "admin.audit.actions.updateApiState": "Turned the API on or off", @@ -484,8 +486,10 @@ "admin.general.allowCollaborativeEditingHint": "Can several people edit the same page at the same time, seeing each other's cursors and changes live? Applies to the markdown editor. Changes are still only stored when someone saves the page.", "admin.general.allowComments": "Allow Comments", "admin.general.allowCommentsHint": "Can users leave comments on pages? Can be restricted using Page Rules.", + "admin.general.allowLastEditedBy": "Allow Last Edited By", + "admin.general.allowLastEditedByHint": "Can the \"Last Edited By\" info be displayed on pages?", "admin.general.allowRatings": "Allow Ratings", - "admin.general.allowRatingsHint": "Can users leave ratings on pages? Can be restricted using Page Rules.", + "admin.general.allowRatingsHint": "Can logged in users rate the pages they can read, and on which scale? A page can still turn ratings off in its properties.", "admin.general.allowSearch": "Allow Search", "admin.general.allowSearchHint": "Can users search for content they have read access to?", "admin.general.banner": "Site-wide Banner", @@ -1936,6 +1940,11 @@ "common.page.private": "Private", "common.page.published": "Published", "common.page.ratePage": "Rate this page", + "common.page.ratingAverage": "{average} out of 5", + "common.page.ratingCount": "No ratings yet | 1 rating | {count} ratings", + "common.page.ratingFailed": "Could not save your rating.", + "common.page.ratingThumbsDown": "Thumbs down", + "common.page.ratingThumbsUp": "Thumbs up", "common.page.returnNormalView": "Return to Normal View", "common.page.share": "Share", "common.page.suggestDiscarded": "Your suggested edits have been discarded.", @@ -2296,6 +2305,7 @@ "editor.props.selectIcon": "Select Icon...", "editor.props.shortDescription": "Short Description", "editor.props.showInTree": "Show in Site Navigation", + "editor.props.showLastEditedBy": "Show Last Edited By", "editor.props.showSidebar": "Show Sidebar", "editor.props.showTags": "Show Tags", "editor.props.showToc": "Show Table of Contents", diff --git a/backend/models/auditLog.ts b/backend/models/auditLog.ts index 15b72ffd9..01e5b1815 100644 --- a/backend/models/auditLog.ts +++ b/backend/models/auditLog.ts @@ -35,6 +35,8 @@ export const AUDIT_ACTIONS = { 'unlockPage', 'watchPage', 'unwatchPage', + 'ratePage', + 'unratePage', 'submitPageEdit', 'approvePageEdit', 'rejectPageEdit', diff --git a/backend/models/index.ts b/backend/models/index.ts index de9fcda4d..41b71518d 100644 --- a/backend/models/index.ts +++ b/backend/models/index.ts @@ -20,6 +20,7 @@ import { metrics } from './metrics.ts' import { navigation } from './navigation.ts' import { pageHistory } from './pageHistory.ts' import { pageLinks } from './pageLinks.ts' +import { pageRatings } from './pageRatings.ts' import { pages } from './pages.ts' import { pageWatching } from './pageWatching.ts' import { passkeys } from './passkeys.ts' @@ -59,6 +60,7 @@ export default { navigation, pageHistory, pageLinks, + pageRatings, pages, pageWatching, passkeys, diff --git a/backend/models/pageHistory.ts b/backend/models/pageHistory.ts index d7710bfb1..31f5fa885 100644 --- a/backend/models/pageHistory.ts +++ b/backend/models/pageHistory.ts @@ -44,7 +44,7 @@ export type PurgeTimeframe = keyof typeof purgeTimeframes * Taken straight off the stored row, so a field added to a page is captured here without this list * being touched. The exclusions are either derived from the content (`render`, `searchContent`, * `ts`), fixed for the page's whole life (`id`, `siteId`, `creatorId`, `createdAt`), or bookkeeping - * that says nothing about the version (`hash`, `updatedAt`, `authorId`, `ratingScore`, `ratingCount`, + * that says nothing about the version (`hash`, `updatedAt`, `authorId`, `ratings`, * `historyData`, `isSearchableComputed`). * * `toc` is derived as well and is kept regardless, because the version view draws a contents column @@ -66,8 +66,7 @@ const EXCLUDED_FROM_META = new Set([ 'render', 'searchContent', 'ts', - 'ratingScore', - 'ratingCount', + 'ratings', 'historyData', 'isSearchableComputed', // -> Held in columns of their own @@ -91,8 +90,7 @@ const NOT_REPORTED_AS_CHANGED = new Set([ 'hash', 'authorId', 'updatedAt', - 'ratingScore', - 'ratingCount', + 'ratings', 'historyData', 'isSearchableComputed' ]) diff --git a/backend/models/pageRatings.ts b/backend/models/pageRatings.ts new file mode 100644 index 000000000..12a63fa39 --- /dev/null +++ b/backend/models/pageRatings.ts @@ -0,0 +1,188 @@ +import { and, eq, sql } from 'drizzle-orm' +import { pageRatings as ratingsTable, pages as pagesTable } from '../db/schema.ts' + +/** The two scales a site can rate its pages on. `off` is the absence of one. */ +export const RATING_MODES = ['thumbs', 'stars'] as const +export type RatingMode = (typeof RATING_MODES)[number] + +/** The totals of one scale, as cached on the page. */ +interface RatingTotals { + count: number + sum: number + up: number + down: number +} + +/** `pages.ratings`: the totals per scale, absent for a scale nobody has rated the page on. */ +export type PageRatingsCache = Partial> + +/** How a page has been rated, under the mode in force. */ +export interface RatingSummary { + mode: RatingMode + /** How many readers have rated it. */ + count: number + /** Mean of the ratings, 1 to 5 for stars and -1 to 1 for thumbs. 0 when nobody has rated it. */ + average: number + /** Thumbs up. Always 0 under stars. */ + up: number + /** Thumbs down. Always 0 under stars. */ + down: number +} + +/** + * Page ratings model + * + * One rating per reader per page, on whichever scale the site uses — see the table's comment in + * `db/schema.ts`. Who may rate is the route's question (a logged in reader who may read the page); + * everything here is about the scale and the totals. + * + * **The totals are cached on the page**, in `pages.ratings`, and a page view reads them from there: + * pages are read far more often than they are rated, so the aggregate is worked out on the write. + * Every scale is kept, so switching the site between thumbs and stars and back finds the totals where + * they were. + */ +class PageRatings { + /** + * The scale this site rates on, or null when ratings are off for it. + * + * `features.ratingsMode` under General → Features. A page's own `allowRatings` is the other half, + * and is the caller's to check, since it is a property of the page in hand. + */ + modeFor(siteId: string): RatingMode | null { + const mode = WIKI.sites[siteId]?.config?.features?.ratingsMode + return (RATING_MODES as readonly string[]).includes(mode) ? mode : null + } + + /** Whether `value` is a rating the scale accepts: ±1 for thumbs, a whole 1 to 5 for stars. */ + isValid(mode: RatingMode, value: number): boolean { + if (!Number.isInteger(value)) { + return false + } + return mode === 'thumbs' ? value === 1 || value === -1 : value >= 1 && value <= 5 + } + + /** How a page has been rated under `mode`, from the totals cached on it. No query. */ + summaryFromCache(cache: PageRatingsCache | null | undefined, mode: RatingMode): RatingSummary { + const totals = cache?.[mode] + const count = totals?.count ?? 0 + return { + mode, + count, + average: count > 0 ? Math.round(((totals?.sum ?? 0) / count) * 100) / 100 : 0, + up: mode === 'thumbs' ? (totals?.up ?? 0) : 0, + down: mode === 'thumbs' ? (totals?.down ?? 0) : 0 + } + } + + /** + * This reader's own rating of a page under `mode`, or 0 for none. + * + * The one part of a page view's rating that cannot come off the page, being the reader's own: a + * single lookup on the primary key, and none for a guest. A rating given under the other scale + * reads as none, since it cannot be drawn on this one and rating again replaces it. + */ + async valueFor(pageId: string, userId: string | null, mode: RatingMode): Promise { + if (!userId) { + return 0 + } + const [row] = await WIKI.db + .select({ value: ratingsTable.value }) + .from(ratingsTable) + .where( + and( + eq(ratingsTable.pageId, pageId), + eq(ratingsTable.userId, userId), + eq(ratingsTable.kind, mode) + ) + ) + return row?.value ?? 0 + } + + /** + * Rate a page, replacing whatever this reader had given it before, on either scale. + * + * @returns The page's totals as they stand after the rating. + */ + async rate({ + pageId, + userId, + mode, + value + }: { + pageId: string + userId: string + mode: RatingMode + value: number + }): Promise { + return this.#changeAndRecount(pageId, (tx) => + tx + .insert(ratingsTable) + .values({ pageId, userId, kind: mode, value }) + .onConflictDoUpdate({ + target: [ratingsTable.pageId, ratingsTable.userId], + set: { kind: mode, value, updatedAt: new Date() } + }) + ) + } + + /** + * Withdraw this reader's rating of a page. Idempotent: no row is the outcome asked for. + * + * @returns The page's totals as they stand after the withdrawal. + */ + async unrate({ pageId, userId }: { pageId: string; userId: string }): Promise { + return this.#changeAndRecount(pageId, (tx) => + tx + .delete(ratingsTable) + .where(and(eq(ratingsTable.pageId, pageId), eq(ratingsTable.userId, userId))) + ) + } + + /** + * Apply a change to a page's ratings and rewrite the totals cached on it, in one transaction. + * + * The page row is locked first. Two readers rating at once would otherwise each count the rows + * without the other's uncommitted one, and whichever committed last would cache a total that is one + * rating short for good. Recounted rather than adjusted by the difference, so a cache that has + * drifted (a rating removed by a deleted account's cascade) is put right by the next rating. + */ + async #changeAndRecount( + pageId: string, + change: (tx: any) => Promise + ): Promise { + return WIKI.db.transaction(async (tx) => { + await tx + .select({ id: pagesTable.id }) + .from(pagesTable) + .where(eq(pagesTable.id, pageId)) + .for('update') + await change(tx) + const rows = await tx + .select({ + kind: ratingsTable.kind, + count: sql`count(*)::int`, + sum: sql`coalesce(sum(${ratingsTable.value}), 0)::int`, + up: sql`(count(*) filter (where ${ratingsTable.value} > 0))::int`, + down: sql`(count(*) filter (where ${ratingsTable.value} < 0))::int` + }) + .from(ratingsTable) + .where(eq(ratingsTable.pageId, pageId)) + .groupBy(ratingsTable.kind) + const cache: PageRatingsCache = {} + for (const row of rows) { + if ((RATING_MODES as readonly string[]).includes(row.kind)) { + cache[row.kind as RatingMode] = { + count: Number(row.count), + sum: Number(row.sum), + up: Number(row.up), + down: Number(row.down) + } + } + } + await tx.update(pagesTable).set({ ratings: cache }).where(eq(pagesTable.id, pageId)) + return cache + }) + } +} + +export const pageRatings = new PageRatings() diff --git a/backend/models/pages.ts b/backend/models/pages.ts index 2949b5f22..97a4f00f9 100644 --- a/backend/models/pages.ts +++ b/backend/models/pages.ts @@ -13,6 +13,7 @@ import type { FastifyRequest } from 'fastify' import type { RenderPermissions, TocNode } from './rendering.ts' import type { DeletedEntry } from './tree.ts' import type { StoragePageContent, StoragePageRef } from './storage.ts' +import type { PageRatingsCache } from './pageRatings.ts' /** What each editor produces, which is what the content column holds. */ const EDITOR_CONTENT_TYPES: Record = { @@ -205,6 +206,7 @@ const CONFIG_FIELDS = [ 'allowComments', 'allowContributions', 'allowRatings', + 'showLastEditedBy', 'showSidebar', 'showTags', 'showToc', @@ -294,6 +296,7 @@ export interface Page { allowComments: boolean allowContributions: boolean allowRatings: boolean + showLastEditedBy: boolean showSidebar: boolean showTags: boolean showToc: boolean @@ -305,6 +308,13 @@ export interface Page { navigationMode: string authorId: string authorName: string + /** Whether the account that saved the version that stands has uploaded an avatar. */ + authorHasAvatar: boolean + /** + * The cached rating totals, per scale. Internal: the API answers with `rating`, the summary on the + * site's current scale — see `pageRatings.summaryFromCache`. + */ + ratings: PageRatingsCache createdAt: Date updatedAt: Date } @@ -340,6 +350,7 @@ export interface PageInput { allowComments?: boolean allowContributions?: boolean allowRatings?: boolean + showLastEditedBy?: boolean showSidebar?: boolean showTags?: boolean showToc?: boolean @@ -798,6 +809,7 @@ class Pages { allowComments: config.allowComments ?? true, allowContributions: config.allowContributions ?? true, allowRatings: config.allowRatings ?? true, + showLastEditedBy: config.showLastEditedBy ?? true, showSidebar: config.showSidebar ?? true, showTags: config.showTags ?? true, showToc: config.showToc ?? true, @@ -809,6 +821,8 @@ class Pages { navigationMode: row.navigationMode ?? 'inherit', authorId: row.authorId, authorName: row.authorName ?? '', + authorHasAvatar: row.authorHasAvatar ?? false, + ratings: row.ratings ?? {}, createdAt: row.createdAt, updatedAt: row.updatedAt } @@ -1556,6 +1570,7 @@ class Pages { .select({ page: pagesTable, authorName: usersTable.name, + authorHasAvatar: usersTable.hasAvatar, navigationId: treeTable.navigationId, navigationMode: treeTable.navigationMode }) @@ -1583,6 +1598,7 @@ class Pages { { ...row.page, authorName: row.authorName, + authorHasAvatar: row.authorHasAvatar, navigationId: row.navigationId, navigationMode: row.navigationMode, // -> A second query only for a page that is part of a set, which is a column read away @@ -2877,6 +2893,7 @@ class Pages { allowComments: input.allowComments ?? existing.allowComments ?? true, allowContributions: input.allowContributions ?? existing.allowContributions ?? true, allowRatings: input.allowRatings ?? existing.allowRatings ?? true, + showLastEditedBy: input.showLastEditedBy ?? existing.showLastEditedBy ?? true, showSidebar: input.showSidebar ?? existing.showSidebar ?? true, showTags: input.showTags ?? existing.showTags ?? true, showToc: input.showToc ?? existing.showToc ?? true, diff --git a/backend/models/search.ts b/backend/models/search.ts index 581bc818f..d12104529 100644 --- a/backend/models/search.ts +++ b/backend/models/search.ts @@ -370,6 +370,60 @@ class Search { ? sql`CASE WHEN p.password IS NULL THEN ${headline} ELSE NULL END` : headline + const where = sql.join(conditions, sql` AND `) + const relevancy = hasQuery ? sql`ts_rank(p.ts, ${tsQuery})` : sql`0` + + /* + Which pages this searcher may read is decided BEFORE the results are paged, never after. + + A page rule can be a regular expression or a set of tags, so the deciding rule is only knowable + per row and cannot go in the `WHERE`. Filtering the rows of one `LIMIT`ed batch instead used to + leave that batch short -- or empty, with a Load More under it -- and a total still counting every + page the searcher had been refused, which is itself a statement about pages they may not see. A + title and a count are content too. + + So with rules to apply, the first query ranks every match but reads only what a rule looks at, + and the second fetches the full row -- excerpt included, the expensive part -- for the one batch + that is returned. An actor who is above the rules gets the database's own paging and count. + */ + const checkRules = !!actor && !actor.permissions.includes('manage:system') + + let pageIds: string[] + let totalHits: number + if (checkRules) { + const candidates = await WIKI.db.execute(sql` + SELECT p.id, p.path, p.locale, p.tags, ${relevancy} AS relevancy + FROM pages p + WHERE ${where} + ORDER BY ${ordering} + `) + const readable = ((candidates.rows ?? candidates) as any[]).filter((row) => + WIKI.models.groups.checkAccess(actor, 'read:pages', { + siteId, + path: row.path as string, + locale: row.locale as string, + tags: (row.tags ?? []) as string[] + }) + ) + pageIds = readable.slice(offset, offset + limit).map((row) => row.id as string) + totalHits = readable.length + } else { + const candidates = await WIKI.db.execute(sql` + SELECT p.id, ${relevancy} AS relevancy, COUNT(*) OVER() AS "totalHits" + FROM pages p + WHERE ${where} + ORDER BY ${ordering} + LIMIT ${limit} OFFSET ${offset} + `) + const rows = (candidates.rows ?? candidates) as any[] + pageIds = rows.map((row) => row.id as string) + totalHits = Number(rows[0]?.totalHits ?? 0) + } + + if (pageIds.length < 1) { + return { results: [], totalHits } + } + const rows = await WIKI.db.execute(sql` SELECT p.id, @@ -381,65 +435,37 @@ class Search { p.tags, to_char(p."createdAt" AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS "createdAt", to_char(p."updatedAt" AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS "updatedAt", - ${hasQuery ? sql`ts_rank(p.ts, ${tsQuery})` : sql`0`} AS relevancy, - ${highlight} AS highlight, - COUNT(*) OVER() AS "totalHits" + ${relevancy} AS relevancy, + ${highlight} AS highlight FROM pages p - WHERE ${sql.join(conditions, sql` AND `)} - ORDER BY ${ordering} - LIMIT ${limit} OFFSET ${offset} + WHERE p.id = ANY(${sql.param(pageIds)}::uuid[]) `) - - /* - Filtered here rather than in SQL: a page rule can be a regular expression or a set of tags, so - the deciding rule is only knowable per row. Search must not be a way around page permissions — - a title and an excerpt are content too. - */ - const visible = actor - ? ((rows.rows ?? rows) as any[]).filter((row) => - WIKI.models.groups.checkAccess(actor, 'read:pages', { - siteId, - path: row.path as string, - locale: row.locale as string, - tags: (row.tags ?? []) as string[] - }) - ) - : ((rows.rows ?? rows) as any[]) - - const result = visible.map((row) => ({ - id: row.id as string, - path: row.path as string, - locale: row.locale as string, - title: row.title as string, - description: row.description ?? null, - icon: row.icon ?? null, - tags: (row.tags ?? []) as string[], - createdAt: row.createdAt as string, - updatedAt: row.updatedAt as string, - relevancy: Number(row.relevancy ?? 0), - // -> Escaped first, so the only markup that survives is the emphasis postgres marked - highlight: row.highlight - ? escapeHtml(row.highlight as string) - .replaceAll(HL_START, '') - .replaceAll(HL_STOP, '') - : null - })) - - return { - results: result, - /* - The count postgres reported, less whatever the rules just removed from this page of results. - Not exact when rows are dropped -- the window function counted every match, including ones on - later pages this reader may not see -- but a total that ignored the filtering entirely would - promise results that do not exist. - */ - totalHits: Math.max( - 0, - Number((rows.rows ?? rows)[0]?.totalHits ?? 0) - - ((rows.rows ?? rows) as any[]).length + - visible.length - ) - } + // -> `ANY` answers in whatever order the planner likes; the order is the first query's + const byId = new Map(((rows.rows ?? rows) as any[]).map((row) => [row.id as string, row])) + + const result = pageIds + .map((id) => byId.get(id)) + .filter(Boolean) + .map((row) => ({ + id: row.id as string, + path: row.path as string, + locale: row.locale as string, + title: row.title as string, + description: row.description ?? null, + icon: row.icon ?? null, + tags: (row.tags ?? []) as string[], + createdAt: row.createdAt as string, + updatedAt: row.updatedAt as string, + relevancy: Number(row.relevancy ?? 0), + // -> Escaped first, so the only markup that survives is the emphasis postgres marked + highlight: row.highlight + ? escapeHtml(row.highlight as string) + .replaceAll(HL_START, '') + .replaceAll(HL_STOP, '') + : null + })) + + return { results: result, totalHits } } /** diff --git a/backend/models/sites.ts b/backend/models/sites.ts index a0da019d1..85f16385a 100644 --- a/backend/models/sites.ts +++ b/backend/models/sites.ts @@ -133,12 +133,12 @@ class Sites { backlinks: true, browse: true, collaborativeEditing: true, - ratings: false, ratingsMode: 'off', // -> On, because what decides whether a site has comments is whether a provider has // been picked. This is the switch that turns them all off without losing that // choice, which is only useful to somebody who has already made it. comments: true, + lastEditedBy: true, reasonForChange: 'optional', search: true }, @@ -457,9 +457,9 @@ class Sites { backlinks: true, browse: true, collaborativeEditing: true, - ratings: false, ratingsMode: 'off', comments: true, + lastEditedBy: true, reasonForChange: 'optional', search: true }, diff --git a/backend/models/tree.ts b/backend/models/tree.ts index 891b597da..533ed9806 100644 --- a/backend/models/tree.ts +++ b/backend/models/tree.ts @@ -1216,6 +1216,7 @@ class Tree { allowComments: page.allowComments, allowContributions: page.allowContributions, allowRatings: page.allowRatings, + showLastEditedBy: page.showLastEditedBy, showSidebar: page.showSidebar, showTags: page.showTags }, diff --git a/frontend/src/assets/icons.generated.js b/frontend/src/assets/icons.generated.js index b637e21c8..30e991f7b 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. - 278 icons. + 281 icons. */ export const BUNDLED_ICONS = { "la:angle-down": {"body":"","width":32,"height":32}, @@ -138,6 +138,7 @@ export const BUNDLED_ICONS = { "la:square-full": {"body":"","width":32,"height":32}, "la:star": {"body":"","width":32,"height":32}, "la:star-half-alt": {"body":"","width":32,"height":32}, + "la:star-solid": {"body":"","width":32,"height":32}, "la:stop": {"body":"","width":32,"height":32}, "la:stream": {"body":"","width":32,"height":32}, "la:sun": {"body":"","width":32,"height":32}, @@ -280,6 +281,8 @@ export const BUNDLED_ICONS = { "mdi:table-row-remove": {"body":"","width":24,"height":24}, "mdi:table-split-cell": {"body":"","width":24,"height":24}, "mdi:text-box-outline": {"body":"","width":24,"height":24}, + "mdi:thumb-down": {"body":"","width":24,"height":24}, + "mdi:thumb-up": {"body":"","width":24,"height":24}, "mdi:tooltip-plus-outline": {"body":"","width":24,"height":24}, "mdi:toy-brick-plus": {"body":"","width":24,"height":24}, "mdi:transfer-down": {"body":"","width":24,"height":24}, diff --git a/frontend/src/components/PagePropertiesDialog.vue b/frontend/src/components/PagePropertiesDialog.vue index 7f56f1ecd..8313ac11a 100644 --- a/frontend/src/components/PagePropertiesDialog.vue +++ b/frontend/src/components/PagePropertiesDialog.vue @@ -273,6 +273,16 @@ checked-icon="la:check" unchecked-icon="la:times" /> +
+ +
@@ -296,7 +306,8 @@ checked-icon="la:check" unchecked-icon="la:times" /> -
+ +
- +
@@ -32,9 +32,9 @@ import WIcon from './WIcon.vue' * Star rating. * * Simplification: the component this replaces accepted per-star arrays for the icon and colour, a - * separate half-star icon, and a `no-reset` switch. The one caller passes a single icon and a - * single colour, so this takes scalars; half ratings are not offered because nothing produced - * them. + * separate half-star icon, and a `no-reset` switch. The one caller passes a single colour and at + * most two icons -- one for a lit star and one for the rest -- so this takes scalars; half ratings + * are not offered because nothing produced them. * * Behaviour is otherwise preserved, including the two details that are easy to lose: clicking the * star you are already on resets the rating to zero, and the arrow keys move FOCUS between stars @@ -56,6 +56,11 @@ const props = defineProps({ type: String, default: 'mdi:star' }, + /** Icon reference for a lit star, e.g. a solid one over an outlined `icon`. Defaults to `icon`. */ + activeIcon: { + type: String, + default: null + }, /** Theme colour name. Defaults to the same yellow the original used. */ color: { type: String, @@ -100,13 +105,29 @@ const rootStyle = computed(() => ({ fontSize: props.size ? (NAMED_SIZES[props.size] ?? props.size) : undefined })) +/** + * Whether star `i` is lit: within the hover preview while there is one, within the rating otherwise. + */ +function isActive(i) { + return hovered.value === 0 ? props.modelValue >= i : hovered.value >= i +} + +/** + * A lit star takes `activeIcon`. So does one the hover preview is about to give up, which is still + * part of the committed rating -- its lower opacity is what says it would go, not its shape. + */ +function iconFor(i) { + const lit = isActive(i) || (hovered.value > 0 && props.modelValue >= i) + return lit && props.activeIcon ? props.activeIcon : props.icon +} + /** * Three states, matching the original's opacities: a star is lit when it is within the rating (or * within the hover preview), half-lit when the hover preview is currently BELOW the committed * rating -- so you can see what you are about to give up -- and dim otherwise. */ function iconClass(i) { - const active = hovered.value === 0 ? props.modelValue >= i : hovered.value >= i + const active = isActive(i) const exSelected = hovered.value > 0 && props.modelValue >= i && hovered.value < i return [ active ? 'opacity-100' : exSelected ? 'opacity-70' : 'opacity-40', diff --git a/frontend/src/helpers/pageVersions.js b/frontend/src/helpers/pageVersions.js index 68a054ce6..c923583eb 100644 --- a/frontend/src/helpers/pageVersions.js +++ b/frontend/src/helpers/pageVersions.js @@ -185,6 +185,7 @@ export function versionPageProps(version) { allowComments: config.allowComments, allowContributions: config.allowContributions, allowRatings: config.allowRatings, + showLastEditedBy: config.showLastEditedBy, showSidebar: config.showSidebar, showTags: config.showTags, showToc: config.showToc, diff --git a/frontend/src/pages/AdminGeneral.vue b/frontend/src/pages/AdminGeneral.vue index 61e2b088f..39102f7a1 100644 --- a/frontend/src/pages/AdminGeneral.vue +++ b/frontend/src/pages/AdminGeneral.vue @@ -223,25 +223,36 @@ - + + + + {{ t(`admin.general.allowRatings`) }} + {{ t(`admin.general.allowRatingsHint`) }} + + + + + + + + + + {{ t(`admin.general.allowLastEditedBy`) }} + {{ t(`admin.general.allowLastEditedByHint`) }} + + + + + + @@ -614,7 +625,6 @@ import { notify } from '@/composables/notify' import { loading } from '@/composables/loading' import { useAdminStore } from '@/stores/admin' -import { useFlagsStore } from '@/stores/flags' import { useSiteStore } from '@/stores/site' import UtilCodeEditor from '@/components/UtilCodeEditor.vue' @@ -631,7 +641,6 @@ import { toMerged } from 'es-toolkit/object' // STORES const adminStore = useAdminStore() -const flagsStore = useFlagsStore() const siteStore = useSiteStore() // I18N @@ -665,10 +674,6 @@ function defaultConfig() { }, pageExtensions: '', logoText: false, - ratings: { - index: false, - follow: false - }, features: { backlinks: true, /* @@ -679,9 +684,9 @@ function defaultConfig() { reason read the other way -- both are `!== false` on the server. */ collaborativeEditing: false, - ratings: false, ratingsMode: 'off', comments: true, + lastEditedBy: true, reasonForChange: 'required' }, discoverable: false, @@ -821,6 +826,7 @@ async function save() { // silent: the toggle moved, the save succeeded, and the next load put it back. collaborativeEditing: state.config.features?.collaborativeEditing ?? false, comments: state.config.features?.comments ?? true, + lastEditedBy: state.config.features?.lastEditedBy ?? true, ratingsMode: state.config.features?.ratingsMode ?? 'off', reasonForChange: state.config.features?.reasonForChange ?? 'required', search: state.config.features?.search ?? false diff --git a/frontend/src/pages/Index.vue b/frontend/src/pages/Index.vue index ac4173d84..7e645aed7 100644 --- a/frontend/src/pages/Index.vue +++ b/frontend/src/pages/Index.vue @@ -327,7 +327,7 @@ thing and belongs where there is room for it -->
{{ t('common.page.contents') }}
-
+
-
@@ -564,7 +613,7 @@ const route = useRoute() // I18N -const { t } = useI18n() +const { t, locale } = useI18n() // COMPOSABLES @@ -603,7 +652,8 @@ const state = reactive({ * panel over the article rather than a column beside it. */ tocPanelOpen: false, - currentRating: 3, + /** A rating is on its way to the server, which holds the controls still until it answers. */ + ratingBusy: false, /** * Which view the reader is on: `article`, `talk` or `links`. * @@ -709,6 +759,46 @@ const showToc = computed(() => { const showTags = computed(() => { return pageStore.showTags && (pageStore.tags?.length > 0 || state.tagEditMode) }) +/* + Both switches have to be on: the site's ratings mode under General -> Features, and the page's own + `allowRatings`. The server has already combined them -- `rating` comes back null otherwise -- and + it is also what says which scale is in force, so that is what is read here. +*/ +const showRatings = computed(() => { + return Boolean(pageStore.rating) && pageStore.allowRatings +}) +/* + A logged in reader who may read the page. Only an account can rate, since one opinion per person + needs a person to hang it on. +*/ +const canRate = computed(() => { + return userStore.authenticated && userStore.pagePermissions.includes('read:pages') +}) +const ratingStarsCaption = computed(() => { + const { count, average } = pageStore.rating + const total = t('common.page.ratingCount', count) + if (count < 1) { + return total + } + const formatted = new Intl.NumberFormat(locale.value, { + minimumFractionDigits: 1, + maximumFractionDigits: 1 + }).format(average) + return `${t('common.page.ratingAverage', { average: formatted })} · ${total}` +}) +/* + Both switches have to be on: the site's under General -> Features, and the page's own in its + properties dialog. `authorId` is the account that saved the version that stands -- every save moves + it -- so it is the last editor rather than whoever created the page. +*/ +const showLastEditedBy = computed(() => { + return ( + siteStore.features.lastEditedBy && pageStore.showLastEditedBy && Boolean(pageStore.authorId) + ) +}) +const lastEditorInitial = computed(() => { + return (pageStore.authorName || '?').trim().charAt(0).toUpperCase() +}) /* Whether this user may save a change to the page. Editing the tags is a save -- they go up with the rest of the page rather than through an endpoint of their own -- so the test is the pair the PATCH @@ -1314,6 +1404,26 @@ async function createPage() { loading.hide() } +/** + * Rate the page, or withdraw the rating with 0 -- which is what clicking the star already given does. + * The controls are held while the request is out, so a second click cannot race the first. + */ +async function rate(value) { + state.ratingBusy = true + try { + await pageStore.pageRate(value) + } catch (err) { + notify({ type: 'negative', message: t('common.page.ratingFailed'), caption: err.message }) + } finally { + state.ratingBusy = false + } +} + +/** A thumb pressed again takes it back, the way a star clicked again does. */ +function rateThumb(value) { + return rate(pageStore.viewerRating === value ? 0 : value) +} + /** * Back out of a path that has no page. `router.back()` alone lands on the wiki's own error screen for * a reader who arrived at this URL directly, having nothing to go back to, so that case goes home. @@ -1334,6 +1444,22 @@ function goBack() { BOTH ways: as a discrete property it flips at the end of the transition when going to hidden, and at the start when coming back, which is exactly the timing a fade wants. */ +.page-last-editor { + display: inline-flex; + align-items: center; + color: inherit; + text-decoration: none; + + /* -> The size of a top-level entry in the contents above it (`.page-toc-item--d0`) */ + > span { + font-size: 0.8125rem; + } + + &:hover > span { + text-decoration: underline; + } +} + .tags-edit-btn { transition: opacity 0.2s var(--ease-standard), diff --git a/frontend/src/pages/Tags.vue b/frontend/src/pages/Tags.vue index 0e892061a..7c43215ac 100644 --- a/frontend/src/pages/Tags.vue +++ b/frontend/src/pages/Tags.vue @@ -383,11 +383,7 @@ const state = reactive({ orderBy: 'title', orderByDirection: 'asc', results: [], - /** - * How many ROWS have been asked for, which is not `results.length`: the search API drops rows the - * page rules refuse after it has counted them, so its own offset is the only thing that can be - * paged with. - */ + /** How many results have been asked for, which is the offset the next batch starts at. */ fetched: 0, total: 0 }) diff --git a/frontend/src/pages/UserProfile.vue b/frontend/src/pages/UserProfile.vue index 4c7be8767..b49956723 100644 --- a/frontend/src/pages/UserProfile.vue +++ b/frontend/src/pages/UserProfile.vue @@ -132,12 +132,6 @@
-
({ ...r, tags: [...(r.tags ?? [])].sort() })) ) /* - Advanced by what was ASKED for, not by what came back. - - The search filters its rows against this reader's page rules after the database has already - applied the limit, so a batch can arrive short -- or empty -- with more behind it. Counting the - rows that survived would ask for the same batch again on the next press, and a list whose whole - first batch was filtered away would do it for ever. + The search applies this reader's page rules before it pages, so every batch but the last is + full and `totalHits` counts only pages they may open -- never the ones they were refused. */ list.offset += PAGE_SIZE list.total = resp?.totalHits ?? 0 diff --git a/frontend/src/stores/page.js b/frontend/src/stores/page.js index 7b40352c8..2bf2d821b 100644 --- a/frontend/src/stores/page.js +++ b/frontend/src/stores/page.js @@ -40,6 +40,7 @@ const DUPLICATED_PAGE_PROPS = [ 'scriptCss', 'scriptJsLoad', 'scriptJsUnload', + 'showLastEditedBy', 'showSidebar', 'showTags', 'showToc', @@ -57,6 +58,7 @@ export const usePageStore = defineStore('page', { allowRatings: true, authorId: 0, authorName: '', + authorHasAvatar: false, /** * The blog this page is a post of, as `{ path, title }`, or null for a page that is not in one. * @@ -144,6 +146,7 @@ export const usePageStore = defineStore('page', { scriptJsLoad: '', scriptJsUnload: '', scriptCss: '', + showLastEditedBy: true, showSidebar: true, showTags: true, showToc: true, @@ -172,7 +175,17 @@ export const usePageStore = defineStore('page', { * Whether this reader has asked to be told about changes to this page. Always false for a guest: * a watch belongs to an account, which is what a notification would eventually be sent to. */ - isWatching: false + isWatching: false, + /** + * How readers have rated this page, as `{ mode, count, average, up, down }` on the site's current + * scale, or null when ratings are off for the site or for the page. + */ + rating: null, + /** + * This reader's own rating on that scale: 1 or -1 for thumbs, 1 to 5 for stars, 0 for none. + * Always 0 for a guest, who cannot rate. + */ + viewerRating: 0 }), getters: { breadcrumbs: (state) => { @@ -372,6 +385,31 @@ export const usePageStore = defineStore('page', { throw err } }, + /** + * PAGE - RATE + * + * Gives this page a rating, or withdraws it with 0. Moved first and put back on a refusal, the way + * `pageWatch` is; the summary is then taken from the server's answer, since other readers may have + * rated in the meantime. + * + * @throws Whatever the request failed with, for the caller to report. + */ + async pageRate(value) { + const siteStore = useSiteStore() + const previous = this.viewerRating + this.viewerRating = value + try { + const url = `sites/${siteStore.id}/pages/${this.id}/rating` + const resp = await ( + value ? API_CLIENT.put(url, { json: { value } }) : API_CLIENT.delete(url) + ).json() + this.$patch({ rating: resp.rating, viewerRating: resp.value }) + } catch (err) { + this.viewerRating = previous + console.warn(err) + throw err + } + }, /** * PAGE - APPLY VIEWER STATE * @@ -397,7 +435,8 @@ export const usePageStore = defineStore('page', { hasOpenSuggestion: viewer.hasOpenSuggestion === true, canReview: viewer.canReview === true, pendingSubmissions: viewer.pendingSubmissions ?? [], - isWatching: viewer.isWatching === true + isWatching: viewer.isWatching === true, + viewerRating: viewer.rating ?? 0 }) }, /** @@ -445,6 +484,8 @@ export const usePageStore = defineStore('page', { canReview: false, pendingSubmissions: [], isWatching: false, + rating: null, + viewerRating: 0, blog: null, notFound: true }) @@ -604,6 +645,7 @@ export const usePageStore = defineStore('page', { allowComments: props.allowComments ?? false, allowContributions: props.allowContributions ?? true, allowRatings: props.allowRatings ?? true, + showLastEditedBy: props.showLastEditedBy ?? true, showSidebar: props.showSidebar ?? true, showTags: props.showTags ?? true, showToc: props.showToc ?? true, @@ -952,6 +994,7 @@ export const usePageStore = defineStore('page', { 'scriptJsLoad', 'scriptJsUnload', 'scriptCss', + 'showLastEditedBy', 'showSidebar', 'showTags', 'showToc', diff --git a/frontend/src/stores/site.js b/frontend/src/stores/site.js index a4e468c5f..fe4fd2f81 100644 --- a/frontend/src/stores/site.js +++ b/frontend/src/stores/site.js @@ -113,6 +113,7 @@ export const useSiteStore = defineStore('site', { backlinks: true, browse: false, collaborativeEditing: false, + lastEditedBy: true, ratingsMode: 'off', reasonForChange: 'required', search: false