diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index a9503bc88..99dcc6dea 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -2,11 +2,11 @@ name: Build, Lint & Test on: pull_request: - branches: "*" types: - opened - edited - synchronize + - reopened jobs: build: @@ -47,3 +47,6 @@ jobs: - name: Run tests run: pnpm test + + - name: Run type tests + run: pnpm test:types diff --git a/AGENTS.md b/AGENTS.md index fdf412912..a1303e51c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ You are VitNode, an expert AI coding assistant. Follow repository conventions an # React / Next.js -- Arrow functions for components — never `React.FC`. +- Arrow functions for components - never `React.FC`. - No `any`; use `unknown` as rarely as possible. - Use `AutoForm` for forms instead of hand-built form components. - `React.lazy` + `Suspense` for content-heavy dialogs (e.g. dialogs in forms). @@ -25,16 +25,16 @@ import { Activity } from "react"; ### Caching APIs -- `revalidateTag(tag, profile)` — profile is required for SWR: `revalidateTag("blog-posts", "max")` (prefer `'max'`; also `'days'`, `'hours'`) or inline `{ revalidate: 3600 }`. -- `updateTag(\`user-${userId}\`)` — Server Actions only, read-your-writes semantics. -- `refresh()` — Server Actions only, refreshes uncached data, never touches the cache. +- `revalidateTag(tag, profile)` - profile is required for SWR: `revalidateTag("blog-posts", "max")` (prefer `'max'`; also `'days'`, `'hours'`) or inline `{ revalidate: 3600 }`. +- `updateTag(\`user-${userId}\`)` - Server Actions only, read-your-writes semantics. +- `refresh()` - Server Actions only, refreshes uncached data, never touches the cache. # Coding Guidelines - Always implement best practices for performance, security, and accessibility. - Semantic HTML (`main`, `header`) with correct ARIA roles/attributes, `sr-only` for screen-reader-only text, and alt text on all images unless decorative or repetitive. - Emit events for important actions (create, update, delete) so other components can react; use events instead of prop drilling or context. Document them in `apps/docs/content/docs/dev/events/built-in-events.mdx`. -- AI features use the Vercel AI SDK only — resolve models via the `c.get("ai")` registry and call native SDK functions. +- AI features use the Vercel AI SDK only - resolve models via the `c.get("ai")` registry and call native SDK functions. # Design @@ -43,7 +43,7 @@ import { Activity } from "react"; - Exactly 3–5 colors total. Never use purple or violet prominently. - If you override a background color, you MUST override its text color for contrast. - Prefer semantic design tokens (`bg-background`, `text-foreground`). -- Use the Tailwind spacing scale (`p-4`, `mx-2`) — never arbitrary values (`p-[16px]`). +- Use the Tailwind spacing scale (`p-4`, `mx-2`) - never arbitrary values (`p-[16px]`). - Use `gap-*` classes for spacing. Never use `space-*`, and never mix margin/padding with gap on the same element. - Use semantic (`items-center`, `justify-between`) and responsive (`md:grid-cols-2`) classes. - No floats or absolute positioning unless absolutely necessary. @@ -53,7 +53,7 @@ import { Activity } from "react"; # Documentation - Document every new feature. Keep it simple, SEO-friendly, and understandable at any skill level. -- Friendly and lightly funny tone — don't overdo it. +- Friendly and lightly funny tone - don't overdo it. - Skip big comments; code is self-documenting. - Request images with a comment: `// Image prompt: {here_prompt_to_generate_image}` - Put install commands in tabbed code blocks with correct syntax highlighting: @@ -81,5 +81,8 @@ npm i x # Testing - Admin login: `test@test.com` / `Test123!` -- Write and run vitest unit tests for all new features and bug fixes — skip only if vitest isn't configured. -- Don't write tests for trivial code unless they have complex logic or edge cases. +- Write and run vitest unit tests for all new features and bug fixes - skip only if vitest isn't configured. +- Don't write tests: + - for trivial code unless they have complex logic or edge cases + - for tests where it uses a database or external API + - how UI should be rendered (use playwright for that to write e2e tests) diff --git a/apps/api/package.json b/apps/api/package.json index 291fc14ac..52db9277d 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -43,6 +43,7 @@ "@types/react-dom": "^19.2.3", "@types/ws": "^8.18.1", "@vitnode/blog": "workspace:*", + "@vitnode/example": "workspace:*", "@vitnode/config": "workspace:*", "@vitnode/nodemailer": "workspace:*", "dotenv": "^17.4.2", diff --git a/apps/api/src/vitnode.api.config.ts b/apps/api/src/vitnode.api.config.ts index 8667b7cc2..b9ed1119f 100644 --- a/apps/api/src/vitnode.api.config.ts +++ b/apps/api/src/vitnode.api.config.ts @@ -2,6 +2,7 @@ import { google } from "@ai-sdk/google"; import { blogApiPlugin } from "@vitnode/blog/config.api"; // import { LocalStorageAdapter } from "@vitnode/core/api/adapters/storage/local"; import { buildApiConfig } from "@vitnode/core/vitnode.config"; +import { exampleApiPlugin } from "@vitnode/example/config.api"; import { NodeCronAdapter } from "@vitnode/node-cron"; import { NodemailerEmailAdapter } from "@vitnode/nodemailer"; // import { S3StorageAdapter } from "@vitnode/s3"; @@ -17,7 +18,7 @@ export const POSTGRES_URL = process.env.POSTGRES_URL ?? "postgresql://root:root@localhost:5432/vitnode"; export const vitNodeApiConfig = buildApiConfig({ - plugins: [blogApiPlugin()], + plugins: [blogApiPlugin(), exampleApiPlugin()], ai: { models: [ { diff --git a/apps/docs/content/docs/dev/advanced/queue.mdx b/apps/docs/content/docs/dev/advanced/queue.mdx index 6ac581513..dbb5ca90c 100644 --- a/apps/docs/content/docs/dev/advanced/queue.mdx +++ b/apps/docs/content/docs/dev/advanced/queue.mdx @@ -145,9 +145,86 @@ import { TypeTable } from "fumadocs-ui/components/type-table"; type: "Date", default: "now", }, + pluginId: { + description: + "Who owns the handler, when that is not the plugin handling the request. The worker resolves handlers by `${pluginId}:${name}`, so dispatching a core task from a plugin route needs this - otherwise nothing ever claims the row.", + type: "string", + default: "the requesting plugin, or @vitnode/core", + }, + tx: { + description: + "Join an existing transaction instead of using the request handle. Needed whenever the row the task refers to is written in the same unit of work, or the queue row can commit while that row rolls back.", + type: "Transaction", + }, }} /> + + The [Content Engine](/docs/dev/content-engine/publication-and-editorial#4-scheduled-publishing) books a schedule row + and its queue task in one transaction, from a plugin's route, against a task + core registers. That needs `tx` for atomicity and `pluginId` so the worker can + find the handler - and it is a shape any plugin can reuse. + + +## Tasks core ships + +| Task | Attempts | What it does | +| --- | --- | --- | +| `send-email` | 3 | Delivers a rendered email through the configured provider | +| `rebuild-search-index` | 3 | Clears and rebuilds the [search](/docs/dev/content-engine/public-api-and-caching#2-full-text-search-indexing) index, whole or per collection | +| `content-schedule` | 3 | Runs one [scheduled publication](/docs/dev/content-engine/publication-and-editorial#4-scheduled-publishing). A cancelled, rescheduled or already-executed schedule is a no-op | +| `content-schedule-effects` | 5 | Emits the event, syncs search and expires the cache for a scheduled transition that has **already committed**. Never republishes | + +`content-schedule` is worth reading as a pattern: its payload is +`{ scheduleId, generation }` and nothing else. Every real value is re-read from +the row under `FOR UPDATE`, so a task left over from a plan that has since +changed finds a mismatch and quietly does nothing - which is far more reliable +than trying to delete a queued row. It holds that lock from the claim all the +way to the commit, so a cancel arriving mid-flight waits and then honestly +reports that the schedule already ran. + +### Why the effects are a second task + +The pair is worth reading as a pattern too, because splitting them is the whole +point: + +```text +content-schedule claim → publish → revision → settle → enqueue effects + ── one transaction ────────────────────────────────── +content-schedule-effects event → search → cache bridge +``` + +A transition is a database write that either committed or did not. Announcing it +is three calls to systems a transaction cannot reach. Retrying them **together** +would re-run a publish that is idempotent - so the second run finds nothing +changed and skips the announcements entirely, which is how a scheduled unpublish +ends up permanently serving a page it should have expired. + +The effects row is written inside the transition's transaction, so it exists if +and only if the transition committed, and it carries everything frozen rather +than re-reading a record that may have moved on. + +All three have to land for the task to succeed - a listener that threw, a search +engine that refused the write, or any configured web origin that did not accept +its invalidation each fail the run, and the reasons are combined into one +`effectsError`. `EventsModel.emit()` never throws, so the task reads +`EventEmitResult.failures` rather than waiting for an exception that is not +coming. + +Delivery is at-least-once: the search write and the cache expiry are idempotent, +but a listener can see one `published` twice, so a listener that must act once +keys off the `scheduleId` in the payload. + +The event's envelope is stamped with the plugin that owns the **content type**, +not with core. Core owns the handler; `content.example.article.published` +belongs to the example plugin however it was triggered. + + + The schedule stays `completed` and the reason lands in `effectsError` on the + schedule row. Nothing is ever moved back to `pending` because an event + bounced - the record really did publish. + + ## Retries and backoff If a handler throws, the task is retried with an exponential backoff diff --git a/apps/docs/content/docs/dev/advanced/redis.mdx b/apps/docs/content/docs/dev/advanced/redis.mdx index a2887abb4..181bdd053 100644 --- a/apps/docs/content/docs/dev/advanced/redis.mdx +++ b/apps/docs/content/docs/dev/advanced/redis.mdx @@ -88,8 +88,8 @@ export const vitNodeApiConfig = buildApiConfig({ ### Options -The `redis` object accepts a `url` plus any -[ioredis](https://github.com/redis/ioredis) `RedisOptions`. +The `redis` object is a +[node-redis](https://github.com/redis/node-redis) `RedisClientOptions`. import { TypeTable } from "fumadocs-ui/components/type-table"; @@ -97,17 +97,34 @@ import { TypeTable } from "fumadocs-ui/components/type-table"; type={{ url: { description: - "The Redis connection string (redis:// or rediss://). When omitted, ioredis connects to localhost:6379 using the other options.", + "The Redis connection string (redis:// or rediss://). When omitted, node-redis connects to localhost:6379 using the other options.", type: "string", }, - "...RedisOptions": { + password: { description: - "Any ioredis option, e.g. host, port, password, db, tls. enableOfflineQueue defaults to false so cache commands fail fast (and fall through) when Redis is unreachable - override it if you prefer queuing.", - type: "RedisOptions", + "Password used to authenticate. Pair it with username when your Redis uses ACL users.", + type: "string", + }, + socket: { + description: + "Connection-level options: host, port, tls, connectTimeout, reconnectStrategy.", + type: "RedisSocketOptions", + }, + "...RedisClientOptions": { + description: + "Any other node-redis option, e.g. database or name. disableOfflineQueue defaults to true so cache commands fail fast (and fall through) when Redis is unreachable - override it if you prefer queuing.", + type: "RedisClientOptions", }, }} /> + + VitNode opens the connection at boot without blocking startup, and node-redis + keeps retrying with an exponential backoff if Redis is unreachable. Requests + that land before the socket is ready behave exactly like a Redis outage: the + cache reports a miss and the request goes to the database. + + ## Using the cache Access the cache in any route or model with `c.get("cache")`. diff --git a/apps/docs/content/docs/dev/ai/index.mdx b/apps/docs/content/docs/dev/ai/index.mdx index b78ff5b69..de5470902 100644 --- a/apps/docs/content/docs/dev/ai/index.mdx +++ b/apps/docs/content/docs/dev/ai/index.mdx @@ -7,7 +7,7 @@ description: Easily configure and resolve AI models in VitNode using the native VitNode brings AI powers straight into your backend via the built-in [Vercel AI SDK](https://ai-sdk.dev/). Configure your models once in `buildApiConfig`, grab them in any route handler using `c.get("ai")`, and call native AI SDK functions directly. Zero wrappers, zero hassle! 🚀 -AI is completely **optional** — if you don't configure any models, VitNode won't touch any external services. +AI is completely **optional** - if you don't configure any models, VitNode won't touch any external services. ## Quick Setup @@ -59,11 +59,23 @@ Define your model registry in `buildApiConfig`. The **first model** listed acts export const vitNodeApiConfig = buildApiConfig({ ai: { models: [ - { id: "default", name: "Claude Sonnet 5", model: "anthropic/claude-sonnet-5" }, - { id: "fast", name: "Claude Haiku 4.5", model: "anthropic/claude-haiku-4.5" }, + { + id: "default", + name: "Claude Sonnet 5", + model: "anthropic/claude-sonnet-5", + }, + { + id: "fast", + name: "Claude Haiku 4.5", + model: "anthropic/claude-haiku-4.5", + }, ], embeddingModels: [ - { id: "default", name: "Text Embedding 3 Small", model: "openai/text-embedding-3-small" }, + { + id: "default", + name: "Text Embedding 3 Small", + model: "openai/text-embedding-3-small", + }, ], imageModels: [ { id: "default", name: "GPT Image 1", model: "openai/gpt-image-1" }, @@ -78,8 +90,16 @@ import { anthropic } from "@ai-sdk/anthropic"; export const vitNodeApiConfig = buildApiConfig({ ai: { models: [ - { id: "default", name: "Claude Sonnet 5", model: anthropic("claude-sonnet-5") }, - { id: "fast", name: "Claude Haiku 4.5", model: anthropic("claude-haiku-4.5") }, + { + id: "default", + name: "Claude Sonnet 5", + model: anthropic("claude-sonnet-5"), + }, + { + id: "fast", + name: "Claude Haiku 4.5", + model: anthropic("claude-haiku-4.5"), + }, ], }, }); @@ -115,4 +135,3 @@ import { TypeTable } from "fumadocs-ui/components/type-table"; /> Ready to generate text or create embeddings? Check out the [Usage](/docs/dev/ai/usage) guide! - diff --git a/apps/docs/content/docs/dev/content-engine/admincp.mdx b/apps/docs/content/docs/dev/content-engine/admincp.mdx new file mode 100644 index 000000000..e8702a1a8 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/admincp.mdx @@ -0,0 +1,335 @@ +--- +title: AdminCP Integration +description: Step-by-step guide to zero-code AdminCP management screens, custom form layouts, AutoForm dialogs, and screen overrides. +icon: Layout +--- + +Content Engine automatically builds interactive management screens in the VitNode AdminCP without requiring you to write Next.js page components or custom form components. + +## Prerequisites & Context + +AdminCP UI generation requires registering your content definition in `src/config.tsx` using `contentTypeAdmin`: + +```tsx title="src/config.tsx" +import { buildPlugin, contentTypeAdmin } from "@vitnode/core/lib/plugin"; +import { NotebookPenIcon } from "lucide-react"; +import { articleContentType } from "@/content/article"; + +export const examplePlugin = () => + buildPlugin({ + pluginId: "@vitnode/example", + messages, + contentTypes: [ + contentTypeAdmin({ + definition: articleContentType, + icon: , + }), + ], + }); +``` + +- **Zero-Code Navigation**: Generates `/admin/content/{admin.path}` automatically, + defaulting to the id with its dots as slashes (`example.article` → + `example/article`). +- **i18n Localization**: The screen title, the record's own noun, table headers, form + labels, enum values and form section headings all resolve from your plugin's locale + JSON (`src/locales/en.json`). Every key is optional, and every one has a readable + fallback - so a screen works before it is translated, and no string in it is stuck + in English afterwards. + +--- + +## Step-by-Step Customization + + + + +### Step 1: Name the Record & Configure the List + +In `src/content/article.ts`, define the record's name, columns, search, and sorting +behavior: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + code: field.text({ required: true }), + status: field.enum({ values: ["draft", "published"], defaultValue: "draft" }), + author: field.user(), + }, + admin: { // [!code ++] + titleField: "title", // [!code ++] + list: { // [!code ++] + columns: ["title", "code", "status", "author", "updatedAt"], // [!code ++] + searchableFields: ["title", "code"], // [!code ++] + orderableFields: ["title", "code", "status"], // [!code ++] + defaultOrderBy: "updatedAt", // [!code ++] + defaultOrder: "desc", // [!code ++] + }, // [!code ++] + }, // [!code ++] +}); +``` + +#### The record's noun is a translation, and only a translation + +There is **no display name in the definition**. A name written there would be one +language's answer, in the one file that is read in every language - so the noun lives +in your messages, written as an [ICU cardinal +plural](https://next-intl.dev/docs/usage/translations#cardinal-pluralization): + +```json title="src/locales/en.json" +{ + "@vitnode/example": { + "content": { + "article": { + "label": "{count, plural, one {Article} other {Articles}}", // [!code ++] + "title": "Articles", + "desc": "Write and manage articles." + } + } + } +} +``` + +The AdminCP asks this message for the form it needs - the singular for +`Create {name}`, `{name} has been updated` and every confirmation dialog, the +plural for `Back to {name}` and the screen heading. + + + A `{ singular, plural }` pair can only hold two forms, and plenty of languages + have more. Polish needs three for one noun, and ICU is what knows which one a + number selects: + + ```json title="src/locales/pl.json" + { + "label": "{count, plural, one {Artykuł} few {Artykuły} many {Artykułów} other {Artykułu}}" + } + ``` + + `one` → *Artykuł*, `few` → *Artykuły*. A pair of strings in the definition could + not express that, and no amount of translating the frames around it would fix a + Polish screen reading "Utwórz Article". + + +| Key | Falls back to | +| --- | --- | +| `content.{entity}.label` | a name derived from the id ("Article"), in every number | +| `content.{entity}.title` | the translated plural, then that derived name | +| `content.{entity}.desc` | nothing - the screen simply has no lead-in | +| `content.{entity}.fields.{field}` | the humanised field name ("Published at") | +| `content.{entity}.enums.{field}.{value}` | the humanised value | + +`{entity}` is the content type id without its plugin segment: `example.article` → +`article`, `example.kb.article` → `kb_article`. + +#### The URL says what the screen says + +The screens live at `/admin/content/{admin.path}`, and the default path is the id +with its dots as slashes. That is right until the id and the screen disagree - a +content type called `blog.post` whose every heading reads "Articles" should not be +opened at an address that says `post`. `admin.path` is where the URL is written +down: + +```ts title="src/content/post.ts" +export const blogPostContentType = defineContentType({ + id: "blog.post", + tableName: "blog_posts", + admin: { + path: "blog/articles", // [!code ++] + }, +}); +``` + +```text +/admin/content/blog/articles list +/admin/content/blog/articles/create create +/admin/content/blog/articles/42/edit edit +``` + +The id does **not** move with it, and that is the point: `blog.post` is the event +name, the permission key and the message key - contracts written into roles, into +listeners and into every locale file - while the path is the only part of a content +type a person ever types. Renaming one to improve the other is churn with no payer. + +| Rule | Why | +| --- | --- | +| Lowercase segments of letters, digits and dashes, split by `/` | It is a URL, and a `[...slug]` route matches it segment by segment | +| Cannot end in `create` or `edit` | Those two words are what tells a list screen from a generated form page | +| Site-wide unique | `/admin/content/{path}` carries no plugin id, so two content types claiming one path would leave one of them with no screen at all. The app refuses to boot instead | + + + Changing `admin.path` on a content type that has shipped breaks every link to it - + there is no redirect, because the old path is simply not registered any more. If + the old address is worth keeping, a one-line page that redirects to + `contentAdminHref(definition)` is what the blog plugin does with + `/admin/blog/posts`. + + + + +### Step 2: Organize Form Fields into Sections + +`admin.form.sections` groups the generated form - dialog or page - into titled cards. +A section carries a **`name`**, not a heading: the heading is a translation, so the +same form reads correctly in every language the AdminCP is installed in. + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + code: field.text({ required: true }), + excerpt: field.textarea({ nullable: true }), + status: field.enum({ values: ["draft", "published"] }), + author: field.user(), + }, + admin: { + form: { // [!code ++] + sections: [ // [!code ++] + { name: "general", fields: ["title", "code", "excerpt"] }, // [!code ++] + { name: "publishing", fields: ["status", "author"] }, // [!code ++] + ], // [!code ++] + }, // [!code ++] + }, +}); +``` + +The headings come from your plugin's messages, under the content type's own key: + +```json title="src/locales/en.json" +{ + "@vitnode/example": { + "content": { + "article": { + "form": { + "general": { // [!code ++] + "title": "General information", // [!code ++] + "desc": "Main content details" // [!code ++] + }, // [!code ++] + "publishing": { // [!code ++] + "title": "Publishing settings", // [!code ++] + "desc": "Status and author options" // [!code ++] + } // [!code ++] + } + } + } + } +} +``` + +| Key | Required | What it does | +| --- | --- | --- | +| `name` | yes | Stable id and i18n key segment. Lowercase letters, digits, `_` | +| `fields` | yes | The fields in this section, in order | +| `…form.{name}.title` | no | The heading. Falls back to the humanised `name` ("General") | +| `…form.{name}.desc` | no | A line under the heading. Omitted entirely when absent | + + + When `sections` are declared they replace `admin.form.fields` - the concatenation + of their `fields`, in section order, *is* the form. Declaring both is an error, + because they are two answers to one question. A field in no section is not on the + form, exactly as it would be if left out of `admin.form.fields`. + + Each field belongs to one section, each `name` is used once, and no section is + empty. All three are checked when the content type is defined, not when the form + is opened. + + +A plugin that registers its own `forms.layout` component keeps full control: an +explicit layout wins over declared sections. Sections are the shorthand - they are +rendered with the very same `ContentFormSection` primitive a hand-written layout +uses, so the two look identical. + + + +### Step 3: Take Over the Layout, With i18n Hooks + +When declared sections are not enough - a wide writing column beside a metadata +sidebar, say - register a **layout**. It decides where fields go and nothing else: +the schema, validation, defaults, mutation, version precondition, structured errors, +toast and cache invalidation all stay with the Content Engine. + +Any text the layout adds itself is yours to translate, so reach for +`useTranslations` rather than a literal: + +```tsx title="src/views/admin/article/form-layout.tsx" +"use client"; + +import { + ContentFormActions, + ContentFormField, + ContentFormLayoutGrid, + ContentFormMain, + ContentFormSection, + ContentFormSidebar, + ContentFormStatus, +} from "@vitnode/core/content/admin-form"; +import { useTranslations } from "next-intl"; // [!code ++] + +export const ArticleFormLayout = () => { + const t = useTranslations("@vitnode/example.admin.article.form"); // [!code ++] + + return ( + + + + + + + + + + {/* [!code ++] */} + + + + + {/* [!code ++] */} + + + + + + ); +}; +``` + +Register it under `forms` in `src/config.tsx`: + +```tsx title="src/config.tsx" +import { buildPlugin, contentTypeAdmin } from "@vitnode/core/lib/plugin"; +import { NotebookPenIcon } from "lucide-react"; +import { articleContentType } from "@/content/article"; +import { ArticleFormLayout } from "@/views/admin/article/form-layout"; // [!code ++] + +export const examplePlugin = () => + buildPlugin({ + pluginId: "@vitnode/example", + messages, + contentTypes: [ + contentTypeAdmin({ + definition: articleContentType, + icon: , + // One layout for create and edit. Pass `{ create: { layout }, edit: { layout } }` + // instead to give each its own. + forms: { layout: ArticleFormLayout }, // [!code ++] + }), + ], + }); +``` + + + A field the layout never names is dropped from the payload. `ContentFormRemainingFields` + places whatever is left, and in development the engine logs the names of any fields a + layout forgot. + + +Field *components* are a separate override, and they compose with either arrangement - +`fields: { content: { component: MyEditor } }` keeps the engine's label, validation and +language switcher and swaps only the input. + + + diff --git a/apps/docs/content/docs/dev/content-engine/content-delivery-and-seo.mdx b/apps/docs/content/docs/dev/content-engine/content-delivery-and-seo.mdx new file mode 100644 index 000000000..ce9340b51 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/content-delivery-and-seo.mdx @@ -0,0 +1,122 @@ +--- +title: Content Delivery & SEO +description: Step-by-step guide to integrating content types with Next.js App Router, managing slug redirect history, auto-generating SEO metadata, canonical links, hreflang tags, and XML sitemaps. +icon: Compass +--- + +Content Engine provides frontend content delivery abstractions (`delivery: true`) to streamline routing, automatic 308 redirects, SEO meta generation, and XML sitemap creation. + +## Prerequisites & Context + +Frontend content delivery maps a client definition (e.g. `articleContentType`) and server model (`articleContent`) directly into Next.js App Router dynamic routes: + +- **`delivery: { basePath: "/articles", ... }`**: Configures route bases, automatic slug 308 redirects, SEO meta fields, and sitemap frequency. +- **`resolveContentDelivery(articleContent, { slug, locale })`**: A Next.js server utility from `@vitnode/core/content/next` that fetches content, resolves slug redirects, generates SEO metadata, and handles 404 fallbacks. + +--- + +## Step-by-Step Delivery Setup + + + + +### Step 1: Enable Content Delivery in Definition + +Add `delivery` settings to `src/content/article.ts`: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + publication: true, + editorial: true, + delivery: { // [!code ++] + basePath: "/articles", // [!code ++] + redirects: true, // Auto 308 redirects on slug rename // [!code ++] + seo: { // [!code ++] + titleField: "title", // [!code ++] + descriptionField: "excerpt", // [!code ++] + }, // [!code ++] + sitemap: { // [!code ++] + changefreq: "weekly", // [!code ++] + priority: 0.8, // [!code ++] + }, // [!code ++] + }, // [!code ++] + fields: { + title: field.text({ required: true }), + slug: field.slug({ from: "title" }), + excerpt: field.textarea({ nullable: true }), + }, +}); +``` + + + +### Step 2: Implement Next.js Dynamic Route with i18n & Metadata + +Create `app/[locale]/articles/[slug]/page.tsx`. Use `useTranslations` from `next-intl` to localize UI labels instead of hardcoding plain text strings: + +```tsx title="app/[locale]/articles/[slug]/page.tsx" +import { notFound, redirect } from "next/navigation"; +import { useTranslations } from "next-intl"; // [!code ++] +import { resolveContentDelivery } from "@vitnode/core/content/next"; +import { articleContent } from "@/database/articles"; + +interface Props { + params: Promise<{ locale: string; slug: string }>; +} + +export async function generateMetadata({ params }: Props) { + const { locale, slug } = await params; + const delivery = await resolveContentDelivery(articleContent, { slug, locale }); + + if (delivery.type !== "found") return {}; + + return delivery.seo; // Generates title, description, openGraph, canonical URLs +} + +export default async function ArticlePage({ params }: Props) { + const { locale, slug } = await params; + const delivery = await resolveContentDelivery(articleContent, { slug, locale }); + + if (delivery.type === "redirect") { + redirect(delivery.destination, delivery.status); // 308 permanent redirect + } + + if (delivery.type === "not_found") { + notFound(); + } + + const t = useTranslations("@vitnode/example"); // [!code ++] + const { item } = delivery; + + return ( +
+
+

{item.title}

+

{t("articles.published_on", { date: item.publishedAt })}

{/* [!code ++] */} +
+ +
+

{item.excerpt}

+
+ +
+

{t("articles.author_label", { name: item.author.name })}

{/* [!code ++] */} +
+
+ ); +} +``` +
+ +
+ +--- + +## Automatic SEO Capabilities + +- **308 Permanent Redirects**: Renaming an item's slug preserves historical URLs, issuing automatic 308 redirects to preserve SEO rank. +- **Canonical URLs**: Constructed using the application's base URL and current slug. +- **Hreflang Tags**: Outputs `` tags for active translations. +- **XML Sitemaps**: Serves paginated XML sitemaps at `/sitemap-articles.xml`. diff --git a/apps/docs/content/docs/dev/content-engine/database-and-migrations.mdx b/apps/docs/content/docs/dev/content-engine/database-and-migrations.mdx new file mode 100644 index 000000000..45235cbe1 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/database-and-migrations.mdx @@ -0,0 +1,119 @@ +--- +title: Database & Migrations +description: How the Content Engine maps content models to PostgreSQL tables using createContentModel, handles system columns, and executes Drizzle Kit migrations. +icon: Database +--- + +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; + +Content Engine content types compile directly into standard PostgreSQL database tables via Drizzle ORM. + +## Prerequisites & Context + +Before setting up database tables, you must have a content type definition (e.g. `articleContentType`) created using `defineContentType` in `src/content/article.ts`: + +```ts title="src/content/article.ts" +import { defineContentType, field } from "@vitnode/core/content"; + +// 1. Client-safe definition (imported by both API and AdminCP) +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + code: field.text({ required: true }), + }, +}); +``` + +- **`createContentModel(definition, options)`**: A backend utility from `@vitnode/core/content/server` that converts a client-safe content definition into a server-side Drizzle ORM model. +- **`articleContent`**: The compiled model object containing `.table` (raw Drizzle table), `.service(c)` (CRUD helper), and `.schemas` (Zod schemas). + +--- + +## Step-by-Step Database Setup + + + + +### Step 1: Create Database Model File + +Create your model file under `src/database/articles.ts`. Import `createContentModel` from `@vitnode/core/content/server` and your definition `articleContentType`: + +```ts title="src/database/articles.ts" +import { createContentModel } from "@vitnode/core/content/server"; +import { articleContentType } from "@/content/article"; +import { example_categories } from "./categories"; + +// Compile client definition into a server Drizzle model +export const articleContent = createContentModel(articleContentType, { + references: { + category: () => example_categories.id, + }, +}); + +// Export raw Drizzle table for migration discovery +export const example_articles = articleContent.table; +``` + + + +### Step 2: Add Database Indexes + +Add single or composite indexes inside your content definition file `src/content/article.ts`: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + code: field.text({ required: true }), + status: field.enum({ values: ["draft", "published"] }), + }, + indexes: [ // [!code ++] + { on: ["status", "createdAt"] }, // Composite index // [!code ++] + { on: ["code"], unique: true }, // Unique constraint // [!code ++] + ], // [!code ++] +}); +``` + + + +### Step 3: Run Database Migrations + +Compile your plugins and apply schema changes: + + + +```bash tab="bun" +bun run build:plugins && bun run db:migrate +``` + +```bash tab="pnpm" +pnpm build:plugins && pnpm db:migrate +``` + +```bash tab="npm" +npm run build:plugins && npm run db:migrate +``` + + + + + + +--- + +## Automatic System Columns + +When `createContentModel` compiles a model, it automatically includes standard system columns: + +| Column | Postgres Type | Description | +| :--- | :--- | :--- | +| `id` | `serial` / `integer` | Primary key identifier | +| `createdAt` | `timestamp` | Auto-populated creation timestamp | +| `updatedAt` | `timestamp` | Auto-updated modification timestamp | + +If [`publication`](/docs/dev/content-engine/publication-and-editorial) is enabled, `status` and `publishedAt` columns are added. +If [`editorial`](/docs/dev/content-engine/publication-and-editorial) is enabled, a `version` column is added for optimistic concurrency control. diff --git a/apps/docs/content/docs/dev/content-engine/defining-a-content-type.mdx b/apps/docs/content/docs/dev/content-engine/defining-a-content-type.mdx new file mode 100644 index 000000000..14df23b73 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/defining-a-content-type.mdx @@ -0,0 +1,195 @@ +--- +title: Defining a Content Type +description: Learn the three-file architecture, how to declare content models, register them step-by-step, generate database migrations, and configure i18n keys. +icon: FilePlus2 +--- + +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; + +Every content type in VitNode consists of three compact, dedicated files. This strict division keeps your client bundle lightweight while giving server APIs full access to Drizzle ORM. + +## The Three-File Architecture + +| File Path | Role | Allowed Dependencies | +| :--- | :--- | :--- | +| `src/content/*.ts` | **Client-Safe Definition** | `zod`, `@vitnode/core/content` | +| `src/database/*.ts` | **Database Model** | Drizzle ORM, `@vitnode/core/content/server` | +| `src/config.tsx` | **AdminCP Frontend** | React, Lucide Icons, `@vitnode/core/lib/plugin` | + +--- + +## Step-by-Step Implementation Guide + + + + +### Step 1: Declare the Content Definition + +Create a new file `src/content/article.ts`. Import `defineContentType` and `field` from `@vitnode/core/content`. Do **not** import Drizzle, React, or server utilities here to keep it client-safe. + +```ts title="src/content/article.ts" +import { defineContentType, field } from "@vitnode/core/content"; + +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true, minLength: 3, maxLength: 200 }), + code: field.text({ required: true, maxLength: 100, unique: true }), + excerpt: field.textarea({ maxLength: 500, nullable: true }), + status: field.enum({ + values: ["draft", "published"], + defaultValue: "draft", + }), + publishedAt: field.dateTime({ nullable: true }), + author: field.user(), + }, + admin: { + titleField: "title", + list: { + columns: ["title", "code", "status", "author", "updatedAt"], + searchableFields: ["title", "code", "excerpt"], + orderableFields: ["title", "code", "status"], + defaultOrderBy: "updatedAt", + defaultOrder: "desc", + }, + }, +}); +``` + + + +### Step 2: Build the Database Table Model + +Create `src/database/articles.ts`. Pass the definition to `createContentModel` to compile the database table representation. + +```ts title="src/database/articles.ts" +import { createContentModel } from "@vitnode/core/content/server"; +import { articleContentType } from "@/content/article"; + +export const articleContent = createContentModel(articleContentType); + +// Export the table directly so Drizzle Kit discovers it during migrations +export const example_articles = articleContent.table; +``` + + + +### Step 3: Register Content Types in API & Frontend + +First, attach the content module to your plugin's admin module on the API side: + +```ts title="src/api/modules/admin/admin.module.ts" +import { buildModule } from "@vitnode/core/api/lib/module"; +import { buildContentAdminModule } from "@vitnode/core/content/server"; // [!code ++] +import { articleContent } from "@/database/articles"; // [!code ++] +import { CONFIG_PLUGIN } from "@/const"; + +export const adminModule = buildModule({ + pluginId: CONFIG_PLUGIN.pluginId, + name: "admin", + routes: [], + modules: [ + buildContentAdminModule({ // [!code ++] + pluginId: CONFIG_PLUGIN.pluginId, // [!code ++] + contentTypes: [articleContent], // [!code ++] + }), // [!code ++] + ], +}); +``` + +Second, register `contentTypeAdmin` in your plugin's frontend configuration: + +```tsx title="src/config.tsx" +import { buildPlugin, contentTypeAdmin } from "@vitnode/core/lib/plugin"; +import { NotebookPenIcon } from "lucide-react"; +import { articleContentType } from "@/content/article"; // [!code ++] + +export const examplePlugin = () => + buildPlugin({ + pluginId: "@vitnode/example", + messages, + contentTypes: [ + contentTypeAdmin({ // [!code ++] + definition: articleContentType, // [!code ++] + icon: , // [!code ++] + }), // [!code ++] + ], + }); +``` + + + +### Step 4: Build Plugins and Run Database Migrations + +Generate Drizzle Kit migrations and apply schema updates to your database: + + + +```bash tab="bun" +bun run build:plugins && bun run db:migrate +``` + +```bash tab="pnpm" +pnpm build:plugins && pnpm db:migrate +``` + +```bash tab="npm" +npm run build:plugins && npm run db:migrate +``` + + + + + + +--- + +## Internationalization (i18n) Configuration + +Instead of relying on hardcoded plain text strings in UI components, VitNode humanizes field names automatically and resolves translation keys from your plugin's locale files. + +Add i18n translation keys under your plugin namespace in `src/locales/en.json`: + +```json title="src/locales/en.json" +{ + "@vitnode/example": { + "content": { + "article": { + "label": "{count, plural, one {Article} other {Articles}}", + "title": "Articles", + "desc": "Manage blog articles and news releases.", + "fields": { + "title": "Article Title", + "code": "Article Code", + "excerpt": "Short Excerpt", + "publishedAt": "Publication Date" + }, + "enums": { + "status": { + "draft": "Draft Copy", + "published": "Live" + } + }, + "form": { + "general": { "title": "General information" } + } + } + } + } +} +``` + +The translation key convention follows `{pluginId}.content.{entity}` where `entity` corresponds to your content type ID without the plugin prefix (e.g. `example.article` → `article`). + +`label` is the record's own noun, written as an [ICU cardinal +plural](https://next-intl.dev/docs/usage/translations#cardinal-pluralization) so a +language with more than two number forms can supply all of them. It is what the +AdminCP interpolates into `Create {name}`, `{name} has been deleted` and every +confirmation, and it is the **only** place that noun is written - a definition +declares no display name. Untranslated, the screens fall back to a name derived from +the id (`example.article` → "Article"). Keys +under `form` title the sections declared in +[`admin.form.sections`](/docs/dev/content-engine/admincp). See [AdminCP +Integration](/docs/dev/content-engine/admincp) for the full key list and its +fallbacks. diff --git a/apps/docs/content/docs/dev/content-engine/fields.mdx b/apps/docs/content/docs/dev/content-engine/fields.mdx new file mode 100644 index 000000000..89c896505 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/fields.mdx @@ -0,0 +1,145 @@ +--- +title: Field Reference +description: Complete step-by-step guide to adding and configuring field types in Content Engine, including Postgres column mappings, Zod validation, and AdminCP controls. +icon: LayoutGrid +--- + +Content Engine provides field descriptors exported from `@vitnode/core/content` via the `field` helper. + +## Supported Field Types Summary + +| Field Method | Postgres Column | Zod Schema Type | AdminCP Form Control | +| :--- | :--- | :--- | :--- | +| `field.text()` | `varchar(length)` | `z.string()` | Text Input | +| `field.textarea()` | `text()` | `z.string()` | Textarea | +| `field.number()` | `integer()` or `numeric()` | `z.number()` | Number Input | +| `field.boolean()` | `boolean()` | `z.boolean()` | Switch Toggle | +| `field.enum()` | `varchar(length)` | `z.enum([...])` | Select Dropdown | +| `field.dateTime()` | `timestamp({ mode: 'date' })` | `z.date()` | Date Picker | +| `field.user()` | `integer()` (FK to `core_users`) | `z.number()` | User Picker Combobox | +| `field.user({ multiple: true })` | Junction table (FK to `core_users`) | `z.array(z.number())` | Multi-select People Combobox | +| `field.slug()` | `varchar(length)` | `z.string()` | Slug Input + Auto-Generator | + +--- + +## Step-by-Step Field Configuration + + + + +### Step 1: Add Text and Textarea Fields + +Start by defining basic text fields using `field.text()` for short text inputs and `field.textarea()` for multi-line content: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true, minLength: 3, maxLength: 200 }), + code: field.text({ required: true, maxLength: 100, unique: true }), + summary: field.textarea({ maxLength: 1000, nullable: true }), + }, +}); +``` + + + +### Step 2: Add Numbers, Booleans, and Enums + +Extend your content fields by adding numeric metrics, switch toggles, and dropdown select options: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true, minLength: 3, maxLength: 200 }), + code: field.text({ required: true, maxLength: 100, unique: true }), + summary: field.textarea({ maxLength: 1000, nullable: true }), + views: field.number({ integer: true, min: 0, defaultValue: 0 }), // [!code ++] + isFeatured: field.boolean({ defaultValue: false }), // [!code ++] + status: field.enum({ // [!code ++] + values: ["draft", "published", "archived"], // [!code ++] + defaultValue: "draft", // [!code ++] + }), // [!code ++] + }, +}); +``` + + + +### Step 3: Add Dates, Users, and Auto-Slugs + +Add date pickers, user reference fields, and automatic URL slug generators: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + slug: field.slug({ from: "title", maxLength: 120 }), // [!code ++] + publishedAt: field.dateTime({ nullable: true }), // [!code ++] + author: field.user({ required: true }), // [!code ++] + }, +}); +``` + + + +### Step 4: Let a Field Hold Many People + +`field.user({ multiple: true })` turns one author into a byline. The value moves +off the row into a generated junction table pointing at `core_users`, exactly as +a to-many `field.relation()` does, and the AdminCP renders a multi-select +combobox - chosen people as removable chips, with the same input searching for +the next one - instead of a single picker: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + authors: field.user({ multiple: true, ordered: true }), // [!code ++] + }, +}); +``` + +The value is a list of user ids - `create({ authors: [3, 9] })` - and the +collection API works on it like any other set: + +```ts +await articleContent.service(c).relations.authors.add(articleId, userId); +await articleContent.service(c).relations.authors.reorder(articleId, [9, 3]); +``` + + +A to-many field is never `required` and never `nullable`: the empty set is what +"nobody" looks like, and there is no column to be null. `defineContentType` +rejects both, along with `onDelete: "set null"` - a junction row has nothing to +null, so a deleted account's membership is deleted (`cascade`) or the deletion is +refused (`restrict`). + +Use `min: 1` when a record must have at least one. It is enforced by the +generated schema, so the API answers 400 and the AdminCP form refuses to submit: + +```ts +categories: field.relation({ min: 1, multiple: true, target: () => categoryType }), +``` + + + + + +--- + +## Complex & Advanced Field Types + +For nested structures, arrays, relationships, and multi-language support: + +- **`field.relation()`**: Connects content types together. See [Relations & Advanced Modeling](/docs/dev/content-engine/relations-and-advanced-modeling). +- **`field.structured()`**: Stores nested Zod objects in a Postgres `jsonb` column. +- **`field.repeatable()`**: Stores arrays of items in a Postgres `jsonb` column. +- **`localized: true`**: Marks text or textarea fields for translation. See [Localization](/docs/dev/content-engine/localization). diff --git a/apps/docs/content/docs/dev/content-engine/index.mdx b/apps/docs/content/docs/dev/content-engine/index.mdx new file mode 100644 index 000000000..2a96a82e5 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/index.mdx @@ -0,0 +1,109 @@ +--- +title: Content Engine +description: Declare a content type once in TypeScript and get a real Postgres table, Zod schemas, a typed service, CRUD routes, staff permissions, an AdminCP screen, and typed events. +icon: Boxes +--- + +Writing CRUD boilerplate for plugins — Drizzle tables, Zod schemas, Hono routes, permissions, data tables, auto-forms, and events — takes a lot of time. + +The **Content Engine** generates all of it from a single client-safe declaration: + +```ts title="src/content/article.ts" +import { defineContentType, field } from "@vitnode/core/content"; + +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true, minLength: 3, maxLength: 200 }), + code: field.text({ required: true, maxLength: 100, unique: true }), + status: field.enum({ values: ["draft", "published"], defaultValue: "draft" }), + publishedAt: field.dateTime({ nullable: true }), + author: field.user(), + }, + admin: { + }, +}); +``` + +## What You Get + +A single definition gives you: +- **Database Table**: Dedicated `example_articles` Postgres table with primary keys, indexes, and standard Drizzle migrations. +- **Zod Schemas**: Pre-built schemas for create, update, select, filters, ordering, and forms. +- **Typed Service**: Self-validating service methods (`findById`, `findMany`, `create`, `update`, `delete`). +- **Staff Permissions**: Automatic staff permission checks (`can_view`, `can_create`, `can_edit`, `can_delete`, `can_publish`). +- **Zero-Code AdminCP**: Fully interactive list, search, sorting, and form dialogs without writing Next.js pages. +- **Typed Events**: `content.example.article.created`, `.updated`, and `.deleted` event hooks. + +## How the Three Files Fit Together + +Content Engine keeps definitions separate from database and UI logic so your browser bundle stays lightweight while backend APIs gain full access to Drizzle ORM: + +| File | Purpose | Why It's Separate | +| :--- | :--- | :--- | +| **`src/content/article.ts`** | Definition | **Client-Safe**. Imports `zod` only. Shared between browser and server without dragging Drizzle ORM to the client. | +| **`src/database/articles.ts`** | Database Model | **Server-Only**. Calls `createContentModel` to generate raw Drizzle tables (`.table`) and typed CRUD service methods (`.service(c)`). | +| **`src/config.tsx` & `src/api/`** | Integration | **App Registration**. Registers zero-code AdminCP management screens (`contentTypeAdmin`) and secure backend routes (`buildContentAdminModule`). | + +--- + +## Explore Content Engine + + + + + + + + + + + + + + diff --git a/apps/docs/content/docs/dev/content-engine/localization.mdx b/apps/docs/content/docs/dev/content-engine/localization.mdx new file mode 100644 index 000000000..6885a2350 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/localization.mdx @@ -0,0 +1,105 @@ +--- +title: Localization & Translations +description: Step-by-step guide to building multi-language content models with localized fields, translation tables, independent workflows, and localized public APIs. +icon: Languages +--- + +Content Engine supports localized fields, storing translatable text in dedicated per-language tables (`{tableName}_translations`) while keeping shared fields in the main table. + +## Prerequisites & Context + +Multi-language support is configured in your client-safe definition file `src/content/article.ts`. + +When `createContentModel` compiles a localized model, it automatically generates a separate secondary database table (`{tableName}_translations`) to hold per-language strings without altering the schema of the main table. + +--- + +## Step-by-Step Multi-Language Setup + + + + +### Step 1: Mark Localized Fields in Definition + +Set `localization` settings and add `localized: true` to translatable fields in `src/content/article.ts`: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + localization: { // [!code ++] + defaultLanguage: "en", // [!code ++] + }, // [!code ++] + fields: { + code: field.text({ required: true }), // Shared field in main table + title: field.text({ required: true, localized: true }), // Translatable field // [!code ++] + content: field.textarea({ localized: true }), // Translatable field // [!code ++] + }, +}); +``` + + + +### Step 2: Understand the Database Tables + +Compiling this model automatically builds two PostgreSQL tables: + +1. **Main Table** (`example_articles`): Stores `id`, `code`, timestamps, and shared non-localized columns. +2. **Translation Table** (`example_articles_translations`): Stores `id`, foreign key `article_id`, `locale` (`varchar(10)`), `title`, and `content`. + +```text + example_articles example_articles_translations + ┌─────┬────────┬──────────┐ ┌─────┬────────────┬────────┬────────┬─────────┐ + │ id │ code │ author │ ◄───┐ │ id │ article_id │ locale │ title │ content │ + ├─────┼────────┼──────────┤ │ ├─────┼────────────┼────────┼────────┼─────────┤ + │ 1 │ ART-01 │ User #5 │ └─│ 101 │ 1 │ en │ Hello │ World │ + └─────┴────────┴──────────┘ │ 102 │ 1 │ fr │ Bonjour│ Monde │ + └─────┴────────────┴────────┴────────┴─────────┘ +``` + + + +### Step 3: Render Localized Content with i18n Fallbacks + +When fetching localized content via public API or `resolveContentDelivery`, translatable fields (`title`, `content`) resolve in the active locale. + +For UI wrapper elements (e.g. status badges, section headers), use `useTranslations` from `next-intl`: + +```tsx title="src/components/article-card.tsx" +import { useTranslations } from "next-intl"; // [!code ++] + +interface Props { + article: { + title: string; // Already resolved to current locale by Content Engine + content: string; + }; +} + +export const ArticleCard = ({ article }: Props) => { + const t = useTranslations("@vitnode/example"); // [!code ++] + + return ( +
+ {t("content.article.badge_label")} {/* [!code ++] */} +

{article.title}

+

{article.content}

+
+ ); +}; +``` + +**Fallback Flow**: +1. Returns `fr` translation if published. +2. Falls back to `defaultLanguage` (`en`) if published. +3. Returns `404 Not Found` if no published translation exists. +
+ +
+ +--- + +## AdminCP Translation Features + +- **Language Selector Tabs**: Automatically rendered in AdminCP form dialogs. +- **Independent Status**: Each translation language manages its own draft/published lifecycle. +- **Independent Revisions**: Rollbacks can be performed per-locale without impacting other languages. diff --git a/apps/docs/content/docs/dev/content-engine/meta.json b/apps/docs/content/docs/dev/content-engine/meta.json new file mode 100644 index 000000000..8daa46355 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/meta.json @@ -0,0 +1,19 @@ +{ + "title": "Content Engine", + "description": "Declare a content type once, get the table, API, AdminCP, permissions and events", + "icon": "Boxes", + "pages": [ + "index", + "defining-a-content-type", + "fields", + "database-and-migrations", + "services-and-api", + "publication-and-editorial", + "public-api-and-caching", + "localization", + "relations-and-advanced-modeling", + "content-delivery-and-seo", + "admincp", + "production-and-security" + ] +} diff --git a/apps/docs/content/docs/dev/content-engine/production-and-security.mdx b/apps/docs/content/docs/dev/content-engine/production-and-security.mdx new file mode 100644 index 000000000..274e49481 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/production-and-security.mdx @@ -0,0 +1,86 @@ +--- +title: Production & Security +description: Step-by-step guide to concurrency control, security enforcement, failure retries, performance scaling, and system limitations. +icon: ShieldCheck +--- + +Deploying Content Engine models to production environments requires understanding concurrency protections, database security, performance indexing, and architectural boundaries. + +## Prerequisites & Context + +Production hardening applies to both your client definition `articleContentType` and compiled server model `articleContent`: + +- **Concurrency Locks**: `ContentVersionConflict` is thrown when updating an `editorial: true` content type whose `version` has changed concurrently. +- **Staff Permissions**: Enforced automatically by `buildContentAdminModule` for all CRUD routes. +- **Diagnostics**: `getContentEngineDiagnostics()` provides startup and registered content type metrics. + +--- + +## Step-by-Step Production Best Practices + + + + +### Step 1: Handle Concurrency & Optimistic Lock Retries + +When `editorial` is enabled, update operations expect a matching `version` integer. Handle `ContentVersionConflict` by re-fetching and retrying: + +```ts +import { ContentVersionConflict } from "@vitnode/core/content"; + +try { + await service.update(articleId, { title: "New Title", version: 2 }); +} catch (err) { + if (err instanceof ContentVersionConflict) { // [!code ++] + // Refetch latest record version and retry update // [!code ++] + const latest = await service.findById(articleId); // [!code ++] + await service.update(articleId, { title: "New Title", version: latest.version }); // [!code ++] + } +} +``` + + + +### Step 2: Enforce Staff Permissions and Public Allowlists + +Ensure all custom administrative actions check staff permissions, and verify that `publicApi.fields` lists only non-sensitive attributes: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + publication: true, + publicApi: { + fields: ["title", "code", "excerpt"], + }, + fields: { + title: field.text({ required: true }), + code: field.text({ required: true }), + excerpt: field.textarea({ nullable: true }), + internalNotes: field.textarea({ nullable: true }), // Private internal column + }, +}); +``` + + + +### Step 3: Inspect Engine Diagnostics + +Log diagnostic info during startup or debugging: + +```ts +import { getContentEngineDiagnostics } from "@vitnode/core/content/server"; + +const diagnostics = getContentEngineDiagnostics(); +console.log(`Registered Content Types: ${diagnostics.totalContentTypes}`); +``` + + + + +--- + +## Architectural Boundaries & System Limitations + +- **Code-First Architecture**: Content types must be declared in TypeScript source control and compiled before migrations are generated. There is no runtime no-code UI for creating tables dynamically. +- **No Direct Schema Polymorphism**: A single field cannot reference multiple unrelated tables. Use structured JSON fields or separate relation tables instead. diff --git a/apps/docs/content/docs/dev/content-engine/public-api-and-caching.mdx b/apps/docs/content/docs/dev/content-engine/public-api-and-caching.mdx new file mode 100644 index 000000000..7c5346c5b --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/public-api-and-caching.mdx @@ -0,0 +1,120 @@ +--- +title: Public API, Search & Caching +description: Step-by-step guide to exposing public read-only endpoints, field allowlists, full-text search indexing, and SWR tag-based caching. +icon: Globe +--- + +Content Engine allows exposing read-only endpoints to your web frontend while keeping administrative data private. + +## Prerequisites & Context + +Public read access requires a content type definition (e.g. `articleContentType` in `src/content/article.ts`) compiled into a server model `articleContent` in `src/database/articles.ts`: + +```ts title="src/database/articles.ts" +import { createContentModel } from "@vitnode/core/content/server"; +import { articleContentType } from "@/content/article"; + +export const articleContent = createContentModel(articleContentType); +``` + +- **Field Allowlisting**: By default, no fields are exposed publicly. You must explicitly declare `publicApi.fields`. +- **`buildContentPublicModule`**: An API plugin module from `@vitnode/core/content/server` that mounts public endpoints at `/api/{plugin}/{entity}`. + +--- + +## Step-by-Step Implementation + + + + +### Step 1: Configure Public API Field Allowlist + +In `src/content/article.ts`, add `publicApi` and list fields allowed for public reads: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + publication: true, + publicApi: { // [!code ++] + fields: ["title", "code", "excerpt", "publishedAt", "author"], // [!code ++] + }, // [!code ++] + fields: { + title: field.text({ required: true }), + code: field.text({ required: true }), + excerpt: field.textarea({ nullable: true }), + adminNotes: field.textarea({ nullable: true }), // Excluded from public API + }, +}); +``` + + + +### Step 2: Register Public API Module + +Attach `buildContentPublicModule` at the root level of your API plugin configuration: + +```ts title="src/config.api.ts" +import { buildContentPublicModule } from "@vitnode/core/content/server"; // [!code ++] +import { articleContent } from "@/database/articles"; + +export const exampleApiPlugin = () => + buildApiPlugin({ + pluginId: CONFIG_PLUGIN.pluginId, + modules: [ + adminModule, + buildContentPublicModule({ // [!code ++] + pluginId: CONFIG_PLUGIN.pluginId, // [!code ++] + contentTypes: [articleContent], // [!code ++] + }), // [!code ++] + ], + }); +``` + +This creates: +- `GET /api/example/articles`: Paginated list of published articles. +- `GET /api/example/articles/[id]`: Single published article details. + + + +### Step 3: Enable Global Search Indexing + +Add `search` options to automatically sync items with the global search index: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + search: { // [!code ++] + titleField: "title", // [!code ++] + textField: "excerpt", // [!code ++] + }, // [!code ++] + fields: { + title: field.text({ required: true }), + excerpt: field.textarea({ nullable: true }), + }, +}); +``` + + + +### Step 4: Manage SWR Cache Tags in Server Actions + +Use VitNode's cache helpers to invalidate or revalidate tags inside Server Actions: + +```ts title="src/actions/update-article.ts" +"use server"; + +import { revalidateTag, updateTag } from "@vitnode/core/cache"; + +export async function updateArticleAction(id: number, data: unknown) { + // Perform update... + + // Revalidate SWR list and item tags + revalidateTag(`content-public-item-example-article-${id}`, "max"); + updateTag(`user-${userId}`); +} +``` + + + diff --git a/apps/docs/content/docs/dev/content-engine/publication-and-editorial.mdx b/apps/docs/content/docs/dev/content-engine/publication-and-editorial.mdx new file mode 100644 index 000000000..a1d728344 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/publication-and-editorial.mdx @@ -0,0 +1,122 @@ +--- +title: Publication, Editorial & Scheduling +description: Step-by-step guide to configuring draft/published states, version control, optimistic locking, preview tokens, and scheduled publishing. +icon: Clock +--- + +Content Engine provides four opt-in capabilities for managing content lifecycles: **Publication**, **Editorial Controls**, **Preview Tokens**, and **Scheduled Publishing**. + +## Prerequisites & Context + +Lifecycle features are configured inside your client-safe definition file (e.g. `src/content/article.ts`) using `defineContentType`: + +```ts title="src/content/article.ts" +import { defineContentType, field } from "@vitnode/core/content"; + +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + }, +}); +``` + +Enabling these flags automatically alters the database table structure compiled by `createContentModel` and adds new capabilities to the AdminCP interface and API service methods. + +--- + +## Step-by-Step Configuration + + + + +### Step 1: Enable Publication Lifecycle + +Add `publication: true` to your content definition file to enable draft/published states: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + publication: true, // [!code ++] + fields: { + title: field.text({ required: true }), + }, +}); +``` + +This adds `status` (`"draft"` \| `"published"`) and `publishedAt` columns to your table, enables `can_publish` permissions, and renders status filters in AdminCP. + + + +### Step 2: Enable Editorial Revisions & Optimistic Locking + +Add `editorial` settings to track revision history and prevent race conditions: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + publication: true, + editorial: { // [!code ++] + revisions: { retention: 30 }, // Retain last 30 revisions // [!code ++] + }, // [!code ++] + fields: { + title: field.text({ required: true }), + }, +}); +``` + +Updates will automatically check and increment a `version` column. If concurrent edits occur, Content Engine throws `ContentVersionConflict`. + + + +### Step 3: Configure Draft Preview Tokens + +Add `preview` configurations to generate secure, temporary preview links for reviewers: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + publication: true, + editorial: { + revisions: { retention: 30 }, + preview: { // [!code ++] + url: ({ id, token }) => `/articles/${id}?previewToken=${token}`, // [!code ++] + ttlMinutes: 60, // [!code ++] + }, // [!code ++] + }, + fields: { + title: field.text({ required: true }), + }, +}); +``` + + + +### Step 4: Enable Scheduled Publishing + +Add `scheduling: true` under `editorial` to publish content at future dates via the background queue: + +```ts title="src/content/article.ts" +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + publication: true, + editorial: { + revisions: { retention: 30 }, + preview: { + url: ({ id, token }) => `/articles/${id}?previewToken=${token}`, + }, + scheduling: true, // [!code ++] + }, + fields: { + title: field.text({ required: true }), + }, +}); +``` + + + diff --git a/apps/docs/content/docs/dev/content-engine/relations-and-advanced-modeling.mdx b/apps/docs/content/docs/dev/content-engine/relations-and-advanced-modeling.mdx new file mode 100644 index 000000000..6d1a32f08 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/relations-and-advanced-modeling.mdx @@ -0,0 +1,192 @@ +--- +title: Relations & Advanced Modeling +description: Step-by-step guide to connecting content types with foreign key relations, relation pickers, JSON objects, and arrays. +icon: Network +--- + +Content Engine provides relationship descriptors, structured JSON objects, and repeatable array fields for advanced data modeling. + +## Prerequisites & Context + +Relationships and complex fields are declared inside your client definition file `src/content/article.ts`. + +When using `field.relation()`, you must also supply a foreign key resolution thunk under `references` in `src/database/articles.ts` when calling `createContentModel`: + +- **`field.relation()`**: Specifies target content type and delete actions (`restrict`, `cascade`, `set null`). +- **`references`**: A map supplied to `createContentModel` so Drizzle ORM can establish physical foreign keys in Postgres. + +--- + +## Step-by-Step Implementation + + + + +### Step 1: Declare a Relation Field + +In your content definition, use `field.relation` and pass a thunk returning the target content type: + +```ts title="src/content/article.ts" +import { defineContentType, field } from "@vitnode/core/content"; +import { categoryContentType } from "./category"; + +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + category: field.relation({ // [!code ++] + required: true, // [!code ++] + onDelete: "restrict", // [!code ++] + target: () => categoryContentType, // [!code ++] + }), // [!code ++] + }, +}); +``` + + + +### Step 2: Map Database Foreign Keys + +In your database file, provide a thunk matching the relation field key under `references`: + +```ts title="src/database/articles.ts" +import { createContentModel } from "@vitnode/core/content/server"; +import { articleContentType } from "@/content/article"; +import { example_categories } from "./categories"; + +export const articleContent = createContentModel(articleContentType, { + references: { + category: () => example_categories.id, // [!code ++] + }, +}); +``` + +A **to-many** field needs its junction table exported too, or `drizzle-kit` will +never see it and no migration will create it: + +```ts title="src/database/articles.ts" +export const example_articles_categories = // [!code ++] + articleContent.advancedTables.junctions.categories; // [!code ++] +``` + +A to-many `field.user()` needs no `references` entry at all - the engine owns +`core_users` and wires the junction's foreign key itself - but its junction table +still has to be exported for the same reason. + + + +### Step 2b: Hold Many Targets, or Many People + +`multiple: true` moves a reference off the row and into a generated junction +table with two foreign keys, a `position`, and a unique `(itemId, position)`. +There is no comma-separated column and no JSON array anywhere in it, so +`ON DELETE` still means something and "which articles is this category on" stays +an indexed lookup: + +```ts title="src/content/article.ts" +fields: { + // Many categories, at least one, unordered - a set of categories has no + // natural first member, so the engine stores it in ascending id order. + categories: field.relation({ + min: 1, + multiple: true, + target: () => categoryContentType, + }), + // Many people, in the order of the byline. `field.user` takes exactly the + // same three arguments, because a set of people is stored the same way. + authors: field.user({ multiple: true, ordered: true }), +} +``` + +Both read and write as a list of ids, through the same collection API: + +```ts +const service = articleContent.service(c); + +await service.relations.categories.set(articleId, [4, 7]); +await service.relations.authors.add(articleId, userId); +await service.relations.authors.get(articleId); // [9, 3] - the byline order +``` + + +A to-many reference cannot be a column in the AdminCP list (`admin.list.columns`) +or an `orderBy` - it is on another table, and a list that loaded it would issue a +query per row. It belongs on the **form**, where the AdminCP renders it as a +multi-select combobox: what is chosen sits in the control as removable chips, and +the same input searches for the next one. For an `ordered` field the chip order +is the stored order, and a new choice is appended. + + +#### What an option looks like + +A picker reads its options from the **target's** own `admin` block: the name from +`titleField` - resolved in the reader's language when that field is localized - +and, when the target declares one, a colour swatch from `colorField`: + +```ts title="src/content/category.ts" +export const categoryContentType = defineContentType({ + id: "example.category", + tableName: "example_categories", + fields: { + name: field.text({ localized: true, required: true }), + color: field.text({ maxLength: 50, nullable: true }), + }, + admin: { + titleField: "name", + colorField: "color", // [!code ++] a swatch beside the name, in every picker + }, +}); +``` + +`colorField` must name a **shared column**: a colour is a property of the record +rather than of the language somebody reads it in, so a localized one is a +definition-time error. A row whose colour is null simply has no swatch. + + + +### Step 3: Add Structured JSON Objects + +Use `field.structured` to validate and store nested JSON objects in a Postgres `jsonb` column: + +```ts title="src/content/article.ts" +import { z } from "zod"; + +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + seoMeta: field.structured({ // [!code ++] + schema: z.object({ // [!code ++] + ogTitle: z.string().optional(), // [!code ++] + ogDescription: z.string().optional(), // [!code ++] + }), // [!code ++] + }), // [!code ++] + }, +}); +``` + + + +### Step 4: Add Repeatable JSON Arrays + +Use `field.repeatable` to store arrays of items in a Postgres `jsonb` column: + +```ts title="src/content/article.ts" +import { z } from "zod"; + +export const articleContentType = defineContentType({ + id: "example.article", + tableName: "example_articles", + fields: { + title: field.text({ required: true }), + tags: field.repeatable({ // [!code ++] + schema: z.string().min(2), // [!code ++] + }), // [!code ++] + }, +}); +``` + + + diff --git a/apps/docs/content/docs/dev/content-engine/services-and-api.mdx b/apps/docs/content/docs/dev/content-engine/services-and-api.mdx new file mode 100644 index 000000000..b5dde3db7 --- /dev/null +++ b/apps/docs/content/docs/dev/content-engine/services-and-api.mdx @@ -0,0 +1,111 @@ +--- +title: Services, API & Events +description: Step-by-step guide to using typed CRUD service methods, Zod schemas, staff permissions, and typed event handlers. +icon: Server +--- + +When you instantiate a content model with `createContentModel`, Content Engine builds a fully typed service, Zod validation schemas, staff permissions, and event emissions. + +## Prerequisites & Context + +This guide assumes you have compiled your content definition into a server model using `createContentModel` in `src/database/articles.ts`: + +```ts title="src/database/articles.ts" +import { createContentModel } from "@vitnode/core/content/server"; +import { articleContentType } from "@/content/article"; + +// articleContent is the compiled model instance +export const articleContent = createContentModel(articleContentType); +``` + +- **`articleContent.service(c)`**: Returns a self-validating service scoped to the current Hono request context `c`, executing queries with database connection and user credentials. +- **`articleContent.schemas`**: An object containing pre-built Zod schemas (`create`, `update`, `select`, `filter`, `order`). + +--- + +## Step-by-Step API & Service Usage + + + + +### Step 1: Query Records with Service Methods + +Access the service via `articleContent.service(c)` inside custom Hono route handlers: + +```ts title="src/api/routes/articles.route.ts" +import { Hono } from "hono"; +import { articleContent } from "@/database/articles"; + +const app = new Hono(); + +app.get("/custom-articles", async (c) => { + const service = articleContent.service(c); + + // Query paginated list with sorting and filtering + const { data, total } = await service.findMany({ + page: 1, + limit: 10, + sort: { column: "createdAt", order: "desc" }, + filter: { status: "published" }, + }); + + return c.json({ data, total }); +}); +``` + + + +### Step 2: Use Generated Zod Schemas + +Validate request bodies using pre-compiled Zod schemas: + +```ts title="src/api/routes/articles.route.ts" +app.post("/custom-articles", async (c) => { + const schemas = articleContent.schemas; // [!code ++] + const body = schemas.create.parse(await c.req.json()); // [!code ++] + + const newArticle = await articleContent.service(c).create(body); // [!code ++] + return c.json(newArticle); +}); +``` + + + +### Step 3: Listen to Typed Content Events + +Register event handlers using `eventEngine.on` to react whenever content is created, updated, or deleted: + +```ts title="src/hooks/on-article-created.ts" +import { eventEngine } from "@vitnode/core/events"; + +eventEngine.on("content.example.article.created", async (payload) => { + console.log(`Article #${payload.id} created by user #${payload.actor.id}`); +}); +``` + + + + +--- + +## Core Service Methods Reference + +| Method | Parameters | Return Type | Description | +| :--- | :--- | :--- | :--- | +| `findById(id)` | `number` | `Promise` | Find single record by primary key | +| `findMany(options)` | `{ page, limit, sort, filter }` | `Promise<{ data, total }>` | Query paginated list with sorting and filtering | +| `create(data)` | `CreateInput` | `Promise` | Insert record and emit `created` event | +| `update(id, data)` | `id, UpdateInput` | `Promise` | Update record and emit `updated` event | +| `delete(id)` | `number` | `Promise` | Delete record and emit `deleted` event | + +--- + +## Automatic Staff Permissions + +Registering `buildContentAdminModule` automatically seeds staff permissions based on your content type ID: + +- `admin_{plugin}_content_{entity}_view` +- `admin_{plugin}_content_{entity}_create` +- `admin_{plugin}_content_{entity}_edit` +- `admin_{plugin}_content_{entity}_delete` +- `admin_{plugin}_content_{entity}_publish` (if publication is enabled) diff --git a/apps/docs/content/docs/dev/database/index.mdx b/apps/docs/content/docs/dev/database/index.mdx index 75bcdb1c4..439e29eca 100644 --- a/apps/docs/content/docs/dev/database/index.mdx +++ b/apps/docs/content/docs/dev/database/index.mdx @@ -10,20 +10,28 @@ VitNode plugins seamlessly integrate with databases using [Drizzle ORM](https:// Create your database schema in the `database` directory of your plugin. Each table should be defined in its own file for better organization. ```ts title="plugins/{plugin_name}/src/database/categories.ts" -import { pgTable, serial, timestamp } from "drizzle-orm/pg-core"; +import { pgTable } from "drizzle-orm/pg-core"; -export const blog_categories = pgTable("blog_categories", { - id: serial().primaryKey(), - createdAt: timestamp().notNull().defaultNow(), - updatedAt: timestamp() +export const blog_categories = pgTable("blog_categories", t => ({ + id: t.serial().primaryKey(), + createdAt: t.timestamp().notNull().defaultNow(), + updatedAt: t + .timestamp() .notNull() - .$onUpdate(() => new Date()) -}); + .defaultNow() + .$onUpdate(() => new Date()), +})).enableRLS(); ``` + + Hand-writing the table, its schemas, its routes and its AdminCP screen is a + lot of repetition. The [Content Engine](/docs/dev/content-engine) generates all + of it from one declaration - and produces exactly this kind of table. + + ## Accessing Database -Access the database in your plugin handlers using `c.get('database')` from the Hono context. This provides a Drizzle ORM instance for all your database operations. +Access the database in your plugin handlers using `c.get("db")` from the Hono context. This provides a Drizzle ORM instance for all your database operations. ```ts title="plugins/{plugin_name}/src/routes/posts.ts" export const postsRoute = buildRoute({ @@ -32,10 +40,10 @@ export const postsRoute = buildRoute({ handler: async (c) => { // [!code ++:7] const data = await c - .get("database") + .get("db") .select({ id: blog_posts.id, - categoryId: blog_posts.categoryId + status: blog_posts.status }) .from(blog_posts); diff --git a/apps/docs/content/docs/dev/events/built-in-events.mdx b/apps/docs/content/docs/dev/events/built-in-events.mdx index 09a4df9ab..4f05bdda9 100644 --- a/apps/docs/content/docs/dev/events/built-in-events.mdx +++ b/apps/docs/content/docs/dev/events/built-in-events.mdx @@ -18,10 +18,10 @@ the emitting plugin are needed, the event map is global. | `role.deleted` | `{ roleId }` | A role is deleted in the AdminCP | | `blog.post.created` | `{ postId, categoryId }` | A blog post is created | | `blog.post.updated` | `{ postId, categoryId }` | A blog post is edited | -| `blog.post.deleted` | `{ postId, categoryId }` | A blog post is deleted | +| `blog.post.deleted` | `{ postId }` | A blog post is deleted | | `blog.category.created` | `{ categoryId }` | A blog category is created | | `blog.category.updated` | `{ categoryId }` | A blog category is edited | -| `blog.category.deleted` | `{ categoryId, postIds }` | A blog category (and its posts, via cascade) is deleted | +| `blog.category.deleted` | `{ categoryId, postIds }` | A blog category is deleted (`postIds` is always empty) | ## Core @@ -166,10 +166,19 @@ themselves, and core will emit it once deletion lands. ## Blog (`@vitnode/blog`) + + The blog runs on the [Content + Engine](/docs/dev/content-engine), so the events that describe what actually + happened are `content.blog.post.*` and `content.blog.category.*` - they carry + changed fields, revision ids, publication transitions, per-locale translation + events and slug history. The four names below are re-emitted from those by + listeners in the plugin, so existing consumers keep working. Prefer the + `content.*` ones for anything new. + + ### blog.category.created / blog.category.updated -Emitted after a category (and its translated titles) is created or edited in -the AdminCP. +Re-emitted after `content.blog.category.created` / `.updated`. + + + The row is gone by the time this is emitted, so there is nothing left to read + it from - and inventing one would put a wrong id into an audit trail. A + listener that needs the category should watch `content.blog.post.deleted` and + keep its own index. + + ### blog.category.deleted -Emitted after a category is deleted. Deleting a category cascade-deletes its -posts at the database level, so the payload carries the ids of the posts that -were removed with it. +Re-emitted after `content.blog.category.deleted`. -**Use cases:** the blog plugin itself ships a listener on this event -(`cleanup-category-search`) that removes the cascade-deleted posts from the -search index - a good template for cleaning up any data your plugin keys by -post id. +## Content Engine events + +Every content type declared with the +[Content Engine](/docs/dev/content-engine) emits three events, named after its +id. For `example.article` that means: + +```text +content.example.article.created +content.example.article.updated +content.example.article.deleted +``` + +A content type that opts into +[publication](/docs/dev/content-engine/publication-and-editorial) emits two more: + +```text +content.example.article.published +content.example.article.unpublished +``` + +And one that opts into +[`editorial`](/docs/dev/content-engine/publication-and-editorial) emits one more, plus two with +[`editorial.scheduling`](/docs/dev/content-engine/publication-and-editorial#4-scheduled-publishing): + +```text +content.example.article.restored +content.example.article.scheduled +content.example.article.schedule_cancelled +``` + +A content type that opts into +[`localization`](/docs/dev/content-engine/localization) emits three more, plus +two with `publication` and one with `editorial`: + +```text +content.example.article.translation_created +content.example.article.translation_updated +content.example.article.translation_deleted + +content.example.article.translation_published (with publication) +content.example.article.translation_unpublished (with publication) + +content.example.article.translation_restored (with editorial) +``` + +And one that opts into +[`delivery`](/docs/dev/content-engine/content-delivery-and-seo) emits two more: + +```text +content.example.article.delivery_slug_changed +content.example.article.delivery_redirect_created +``` + +These arrive **alongside** `updated` (or `restored`, or `translation_updated`), never +instead of one: a field moving and a URL moving are different facts with different +audiences. A listener that mirrors content wants the first; one that warms a CDN, tells +an external search engine or writes to an edge redirect table wants the second, and +would otherwise have to inspect `changedFields` for a slug field whose name it cannot +know. + +`delivery_redirect_created` fires only when the old address had genuinely been +**publicly addressable** - so an article whose slug was corrected three times while it +was still a draft emits nothing, and a published article that moves emits exactly one. +That is the difference between "a URL now needs a redirect" and "somebody edited a +field". Both payloads carry `previousPath` and `canonicalPath`, and `locale` is `null` +when the slug is shared. + +There is deliberately no sitemap event: every mutation that changes a sitemap line +already emits one of these or a publication event, and a third carrying no new +information would be one more thing to keep consistent for no listener's benefit. + +Every translation event carries `locale` and `languageId`. They are deliberately +**not** folded into `updated`: a shared update and a Polish translation update +are different domain facts with different consequences - one invalidates every +language, the other invalidates one - and a listener that had to inspect +`changedFields` to tell them apart would get it wrong the first time a field was +renamed. `changedFields` on a translation event names localized fields only, +never a shared one. + +They are registered on the global map by the owning plugin with a single +`declare module` block, so the names and payloads are as strongly typed as any +core event - `changedFields` narrows to that content type's own field names. + + + +Booking a schedule changes no field value, so it consumes no version and writes +no revision - `scheduled` is a different thing from `published`. When the +schedule fires, the resulting transition emits the ordinary `published` or +`unpublished` with `scheduledBy` and `scheduleId` set. + +### A scheduled event may arrive twice + +Announcements for a scheduled transition run in a +[durable queue task](/docs/dev/content-engine/scheduling#all-three-have-to-land) +that retries whenever the event, the search write or a cache origin failed - and +a retry re-emits an event that some listeners already received. Delivery is +**at-least-once**, deliberately: the alternative is a transactional outbox, +which Stage 4 does not have. + +A listener whose work must happen exactly once keys off `scheduleId`, which is +stable across every attempt at the same booking: + +```ts +handler: async (c, payload) => { + if (payload.scheduleId && (await alreadyDone(payload.scheduleId))) return; + + await sendTheAnnouncement(payload.contentId); +}; +``` + +An interactive publish is emitted once, by the route that performed it, and +carries no `scheduleId`. + +### The envelope's owner is the content type's plugin + +`pluginId` on the envelope answers "whose event is this", not "who was running +at the time". Those come apart the moment something happens on a schedule: core +owns the queue handler, so `c.get("plugin")` says `@vitnode/core`, while +`content.example.article.published` belongs to the example plugin as much as it +ever did. + +```text +queue task owner @vitnode/core ← who runs the handler +event envelope @vitnode/example ← who owns the content type +``` + +The Content Engine passes the owner explicitly on every emit, so ownership does +not depend on which route module or queue handler invoked it. Your own code can +do the same when it emits on someone else's behalf: + +```ts +await c.get("events").emit("blog.post.created", payload, { + pluginId: "@vitnode/blog", +}); +``` + +Omit the option and nothing changes: the envelope falls back to +`c.get("plugin")` and then to `@vitnode/core`, exactly as before. Pass it +rather than swapping `c.get("plugin")` - that context is shared with the +logger, the permission checks and every other model on the request. + +The envelope also carries the actor and the timestamp, so the payloads stay +minimal. + +### Failures are reported, not thrown + +`emit()` never throws. Listeners run after the write it describes has +committed, and a broken listener is not a reason to tell somebody their save +failed - so a failure comes back in the result instead: + +```ts +const result = await c.get("events").emit("blog.post.created", payload); + +result.delivered; // listeners that ran +result.failures; // [{ pluginId, module, listener, error }] +``` + +Interactive routes ignore that result on purpose, because the mutation +succeeded either way. Background work usually should not: the scheduled-effects +task inspects `failures` and retries the whole delivery when it is non-empty. + +These are emitted by the **generated routes**, not by the Content Service. A +route emits exactly one event per successful mutation, after the database write +has returned - a failed validation, a delete blocked by a foreign key, a no-op +update, a no-op publish and a restore that changes nothing all emit nothing, and +`published`, `unpublished` and `restored` never come with an `updated` alongside +them. Calling `service.publish()` or any other service method directly changes +the database and emits nothing; that code owns its own follow-up. See +[Generated events](/docs/dev/content-engine/services-and-api#event-system). + +`restored` carries `changedFields` exactly like `updated`, so a listener written +for one ports to the other in a line - which is why a restore does not emit both. + +**Use cases:** reindex the row for search, invalidate a CDN entry, or mirror the +change into a plugin-owned projection. See +[Generated events](/docs/dev/content-engine/services-and-api#event-system). ## Deliberately not emitted (yet) diff --git a/apps/docs/content/docs/dev/index.mdx b/apps/docs/content/docs/dev/index.mdx index f04aff5ce..96cdad078 100644 --- a/apps/docs/content/docs/dev/index.mdx +++ b/apps/docs/content/docs/dev/index.mdx @@ -8,9 +8,29 @@ icon: Power We're working hard to bring you the best documentation experience. +## Support + +- [Postgres 18-19](https://www.postgresql.org/) (min: v18, recommended: v19) - database support. + +### Supported Package Managers + +- [bun](https://bun.com/) (min: v1.1, recommended: v1.3) +- [pnpm](https://pnpm.io/) (min: v10, recommended: v11) +- [node.js](https://nodejs.org/) (min: v22, recommended: v24) + +### Optional Support + +- [Redis](https://redis.io/) (min: v7, recommended: v8) - caching and session management. +- [Docker](https://www.docker.com/) (min: v24, recommended: v25) - containerization and deployment. +- [ElasticSearch](https://www.elastic.co/elasticsearch/) (min: v8, recommended: v9) - advanced search capabilities. +- [NodeMailer](https://nodemailer.com/about/) - email sending capabilities. +- [Resend](https://resend.com/) - email sending capabilities. +- [S3](https://aws.amazon.com/s3/) - file storage. +- [Supabase](https://supabase.com/) - database management and file storage. + ## Get started -import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; diff --git a/apps/docs/content/docs/dev/meta.json b/apps/docs/content/docs/dev/meta.json index b4dbb3272..47d11afc0 100644 --- a/apps/docs/content/docs/dev/meta.json +++ b/apps/docs/content/docs/dev/meta.json @@ -12,6 +12,7 @@ "---Framework---", "plugins", "database", + "content-engine", "fetcher", "working-with-users", "i18n", diff --git a/apps/docs/content/docs/dev/search.mdx b/apps/docs/content/docs/dev/search.mdx index 9012af24a..e75da632f 100644 --- a/apps/docs/content/docs/dev/search.mdx +++ b/apps/docs/content/docs/dev/search.mdx @@ -28,7 +28,16 @@ config change followed by a rebuild. ## Indexing content -Any API handler can (re)index or remove an item through `c.get("search")`: + + A content type only needs a + [`search` block](/docs/dev/content-engine/public-api-and-caching#2-full-text-search-indexing) - publishing, editing, + unpublishing and deleting a record then keep its document in step + automatically, and it joins the rebuild without any of the wiring below. + + +Any API handler can (re)index or remove an item through `c.get("search")`. It is +an **API-process** model: a Next.js server action or server component cannot +reach `c.get("search")` at all, so anything Next-side goes over HTTP. ```ts // After creating or updating an item @@ -83,9 +92,11 @@ translated can leave `languageCode` empty (`""`) - those rows are **language agnostic** and match every locale, so single-language plugins need no changes. - Postgres full-text ranking uses the `english` text-search configuration for - all languages (there is no bundled dictionary for most locales). Matching - still works across languages; only stemming and stop-words are English-tuned. + Postgres full-text ranking picks a text-search configuration per locale + (`polish` for `pl`, `german` for `de`, and so on), falling back to `simple` for + a locale with no bundled dictionary - and for a document with no + `languageCode`, which matches every locale. Matching works across languages + either way; only stemming and stop-words differ. ## Registering a rebuild indexer @@ -106,9 +117,13 @@ export const blogPostSearchIndexer: SearchIndexer = { .limit(limit) .offset(offset); - // An indexer may emit several documents per item (e.g. one per language). - // `offset`/`limit` page over items, not documents. - return rows.flatMap(buildSearchDocumentsForPost); + // Two counts, and they are not interchangeable: one item can emit several + // documents (one per language) or none at all. `itemsRead` is what the + // rebuild pages by, and `itemsRead: 0` is what ends it. + return { + documents: rows.flatMap(buildSearchDocumentsForPost), + itemsRead: rows.length, + }; }, }; @@ -120,9 +135,119 @@ export const blogApiPlugin = () => }); ``` -Trigger a rebuild from **AdminCP → System → Search → Rebuild index**. It runs as a -background queue task, so a [cron adapter](/docs/dev/cron) must be configured for -the queue to drain. +One item type may only have **one** indexer. Two plugins claiming the same +`itemType` is a startup error naming both, because they would otherwise overwrite +each other's documents on every rebuild. + +`itemsRead` is the number of **source items** the page read, not the number of +search documents it produced. They differ whenever one item emits several +documents (one per language) or none at all (a row that cannot be projected). + + + Returning `documents.length` as `itemsRead`, or ending the loop on an empty + `documents` array, silently truncates the index: a page whose rows all fail to + project would stop the rebuild before the valid rows behind it. Report the rows + you read. + + +### The older array result + +`load` may still return a bare `SearchDocument[]`: + +```ts +load: async (c, offset, limit) => await buildDocuments(c, offset, limit), +``` + +It keeps working, so an indexer written before the page contract needs no +changes. It is **deprecated**, though, and it is not equally safe: + +- it carries no source count, so the rebuild advances by the **requested page + size** and waits for an empty array to stop; +- it therefore cannot express "rows were read, none could be projected" - such a + page looks exactly like the end of the source, and the rows behind it are never + indexed. + +Return a `SearchIndexerPage` in new code. The array form is removed only in a +future major release. + +### Ownership + +A document may name the plugin that owns it: + +```ts +{ itemType: "blog_post", itemId: 1, pluginId: "@vitnode/blog", /* ... */ } +``` + +Set it whenever you know the owner. A rebuild runs inside the **core** cron +request, so the request's plugin is not the owner - an explicit `pluginId` is what +makes a rebuilt document identical to a live-indexed one. An indexer that names +none has its registering plugin stamped on during the rebuild, so existing +indexers keep working; the request's plugin, then `"core"`, are the remaining +fallbacks. An empty or whitespace-only value counts as naming nobody. Ownership is +resolved once, in the search model, so the canonical row and the mirrored document +can never disagree. + +**AdminCP → Advanced → Search** shows the registered indexer's plugin. When a +collection has documents but no registered indexer, it shows the owner stored on +those documents instead, and `unknown` only when neither source knows. Such a +collection is never relabelled as core. + +Trigger a rebuild from **AdminCP → Advanced → Search → Rebuild index**. It runs as +a background queue task, so a [cron adapter](/docs/dev/cron) must be configured +for the queue to drain. + + + Rebuilding drops the documents it is about to replace first - the whole index + for "rebuild everything", or one collection for a single reindex. So search + returns less while it runs. + + +The two rebuilds differ in what they are allowed to destroy: + +- **A scoped rebuild requires a registered `SearchIndexer`.** It never clears an + item type it cannot rebuild - the request is rejected with a 404, and the queue + task refuses it too, in case it was dispatched directly or queued while the + indexer was still there. +- **A full rebuild clears the whole index and recreates only collections with a + registered indexer.** Documents belonging to an item type with no indexer are + therefore removed, and come back only when their plugin writes them again. On a + site with live-only indexers, that is worth knowing before clicking it. + +## Unmanaged collections + +Indexing and rebuilding are **two independent capabilities**: + +- `c.get("search").index(document)` keeps a collection current as your plugin + writes. This is all a plugin needs to be searchable. +- A registered `SearchIndexer` additionally lets the whole collection be + reproduced from its source, which is what a rebuild does. + +Registering an indexer is optional, so a plugin may write documents live and +register none. That collection works perfectly during normal operation; it simply +cannot take part in a deterministic rebuild. + +A collection is **unmanaged by the rebuild system** when documents for its +`itemType` are in the index but no `SearchIndexer` is registered for it. + + + It does **not** prove the plugin is uninstalled or inactive. A live-only plugin + looks exactly the same from the index's point of view, and may be keeping the + collection completely up to date. All VitNode can tell is that it has no way to + rebuild it. + + +**AdminCP → Advanced → Search** labels those rows *Unmanaged*, shows the plugin +stored on their documents, and says no rebuild indexer is registered. Coverage is +left blank rather than calculated: with no indexer there is no source count, and +`11 / 11` would claim a collection nothing can rebuild is fully covered. + +*Reindex* is replaced by **Remove documents**, behind a confirmation. It deletes +what is currently indexed and rebuilds nothing - but it does not stop anything +either, so a live-writing plugin may recreate those documents on its next write. +It is a way to clear a stale indexed state, not a way to uninstall a collection. + +Registering an indexer for the item type makes the collection rebuildable, and the +row becomes an ordinary one. ## Giving a type an icon and label @@ -156,6 +281,39 @@ export const vitNodeApiConfig = buildApiConfig({ After changing the engine, run a rebuild so the new engine is populated from `core_search_index`. +### Provider capabilities + +A provider may declare what it can do. Most of it is about ranking and is purely +optional, but one entry is a hard requirement for multi-language content: + +```ts +capabilities: { + authorBoost: false, + facets: false, + languageScopedDelete: true, // [!code highlight] + timeDecay: false, +}, +``` + +`languageScopedDelete` says that `delete(c, itemType, itemId, languageCode)` +honours its fourth argument. It has to be **declared** because JavaScript cannot +tell: a provider written as `delete(c, itemType, itemId)` accepts the extra +argument and drops it, so taking one translation down would remove every language +from that provider's store while `core_search_index` removed one - silently, and +permanently. + +So an install that pairs a provider without it with a content type that is both +[localized](/docs/dev/content-engine/localization) and searchable refuses to boot: + +```text +[Search] The "legacy-engine" search provider does not support language-scoped +deletion, but the content type "example.article" is localized and searchable … +``` + +Both bundled providers declare it. Nothing changes for a provider that indexes +single-language content only - the capability is checked when a content type +needs it, and not otherwise. + ## Admin status **AdminCP → System → Search** shows the active engine, its health, how many items diff --git a/apps/docs/content/docs/guides/blog.mdx b/apps/docs/content/docs/guides/blog.mdx index 86cbf5f65..18de7e595 100644 --- a/apps/docs/content/docs/guides/blog.mdx +++ b/apps/docs/content/docs/guides/blog.mdx @@ -85,3 +85,165 @@ npm run dev + +## How it is built + +The blog is the Content Engine's reference implementation. Two content types, +three component overrides and one layout - and no CRUD of its own: + +```text +plugins/blog/src/ +├── content/category.ts Blog Category (blog.category) +├── content/post.ts Blog Article (blog.post) +├── database/{categories,posts}.ts createContentModel(...) +├── config.tsx contentTypeAdmin(...) x2 +└── views/admin/ + ├── category/color-field.tsx AutoFormColor override + ├── category/color-cell.tsx table cell override + ├── article/editor-field.tsx AutoFormEditor override + └── article/form-layout.tsx the editor screen +``` + +There is no `api/modules/admin/**`, no create/edit dialog, no manual validation, +no manual search sync and no hand-written slug uniqueness check. The generated +routes, forms, permissions, events, search documents and canonical URLs come +from the two definitions. + +### Categories - the simple example + +Dialog create and edit, because a name and a colour do not need a page: + +```ts title="src/content/category.ts" +export const blogCategoryContentType = defineContentType({ + id: "blog.category", + tableName: "blog_categories", + + localization: { enabled: true, defaultLocale: "en", fallback: "default" }, + + fields: { + color: field.text({ maxLength: 50, nullable: true }), + name: field.text({ localized: true, required: true, maxLength: 100 }), + }, + + admin: { + permissionModule: "categories", + path: "blog/categories", + titleField: "name", + create: { mode: "dialog" }, + edit: { mode: "dialog" }, + list: { columns: ["name", "color", "updatedAt"] }, + }, +}); +``` + +The colour is the AdminCP's own picker through a +[field override](/docs/dev/content-engine/admincp#form-layout-customization), +and the colour column is a swatch **plus** the value in words. + +`name` is localized and is still the list's first column and the content type's +`titleField`. That is the split the engine draws: showing a localized value is a +projection the AdminCP resolves in *your* language, while ordering and filtering +stay on the base table. The list reads: + +```text +Name Color Updated +Aktualności ● #3260c0 2 days ago +Poradniki ● #23a06b a week ago +``` + +and the dialog is one form, with the switcher inside the field that needs one: + +```text +Name [ Aktualności ] [ PL ▾ ] + +Color [ ● #3260c0 ] + + Cancel Save +``` + + + `titleField: "name"` fixes the list, the toasts and the page headings, because + the AdminCP resolves a localized title from the translation it already loaded. + The **relation picker** is a different query: a relation label is resolved from + a shared column on the target with a SQL join, and a localized content type has + none - so the article's category picker labels its options `#3` rather than + "Aktualności". Resolving one from the translation table is a Content Engine + change, not something a plugin should paper over. + + +### Articles - the rich example + +Page create and edit, a custom layout, `AutoFormEditor` for the body, a native +relation to the category, an author, publication, editorial history, search and +delivery: + +```text +/admin/content/blog/articles list +/admin/content/blog/articles/create create +/admin/content/blog/articles/42/edit edit +``` + +The id is still `blog.post` - the URL is `blog/articles` because +[`admin.path`](/docs/dev/content-engine/admincp#the-url-says-what-the-screen-says) +says so, and a screen called "Articles" should not live at an address that says +`post`. + +```text +┌───────────────────────────────────────────────────────┐ +│ Title │ Publish │ +│ [............................] │ Status: Draft │ +│ │ [ Save ] │ +│ Content ├──────────────────────┤ +│ ┌────────────────────────────┐ │ Article settings │ +│ │ AutoFormEditor │ │ Friendly URL │ +│ └────────────────────────────┘ │ Category │ +│ │ Author │ +└───────────────────────────────────────────────────────┘ +``` + +Below `lg` it is a single column: body first, then metadata, then the actions. + +Articles are localized, and the layout does not know it. `title`, `content` and +`friendlyUrl` are stored per language and `categoryId` and `authorId` are not - +so the first three render their own small language switchers and the last two do +not, from one `ContentFormField` call each: + +```text +Title [ Tytuł artykułu ] [ PL ▾ ] +Content [ AutoFormEditor ] [ PL ▾ ] +Friendly URL [ tytul-artykulu ] [ PL ▾ ] +Category Aktualności +Author Maciej +``` + +Everything opens in the language you are reading VitNode in - not in +`defaultLocale` - and switching `Title` to English leaves the editor in Polish. +One Save writes the base row and every changed language in one transaction. +Per-language publish, history and delete live in the list's **Languages** row +action. + +### Upgrading from an older blog + +Migration `0035_migrate_blog_to_content_engine.sql` is additive. No table is +dropped and no record moves: + +- `blog_categories` and `blog_posts` keep their names, ids, colours, categories, + authors and timestamps. +- The text moves out of `core_languages_words` and into + `blog_categories_translations` / `blog_posts_translations`, one row per + language that actually had a translation. +- Every existing article becomes `published` with `publishedAt = createdAt` - + they were all publicly readable before, and that is the one publication fact + the old schema can prove. `version` starts at 1 and no revision history is + invented. +- A record with no default-locale translation gets one built from a name it + already has, rather than being left unreadable. + +Two things do change, deliberately: + +| Was | Now | +| --- | --- | +| `GET /api/@vitnode/blog/posts` | `GET /api/@vitnode/blog/content/blog` | +| `GET /api/@vitnode/blog/categories` | removed - categories have no public URL | +| `/admin/blog/posts`, `/admin/blog/categories` | redirect to the generated screens | +| `blog.post.deleted` carried `categoryId` | it does not; see [Built-in events](/docs/dev/events/built-in-events#blogpostdeleted) | diff --git a/apps/docs/content/docs/ui/meta.json b/apps/docs/content/docs/ui/meta.json index e9356625b..71129e393 100644 --- a/apps/docs/content/docs/ui/meta.json +++ b/apps/docs/content/docs/ui/meta.json @@ -22,9 +22,11 @@ "input-group", "nullable-number", "radio-group", + "roles", "select", "switch", "textarea", + "user", "---UI---", "..." ] diff --git a/apps/docs/content/docs/ui/roles.mdx b/apps/docs/content/docs/ui/roles.mdx new file mode 100644 index 000000000..eea4c0318 --- /dev/null +++ b/apps/docs/content/docs/ui/roles.mdx @@ -0,0 +1,150 @@ +--- +title: Roles +description: Search and pick roles for an Auto Form field - one, or as many as you like. +--- + +## Preview + + + +## Usage + +```ts +import { z } from "zod"; +import { AutoForm } from "@vitnode/core/components/form/auto-form"; +import { AutoFormRoles } from "@vitnode/core/components/form/fields/input-roles"; +``` + +One component covers both shapes, because the difference is the value and +nothing else - the search, the colour, the language resolution and the empty +state are identical, and two copies is how they drift. + +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; + + + + +```ts +const formSchema = z.object({ + roleId: z.number(), +}); +``` + +```tsx + ( + + ), + }, + ]} +/> +``` + +The value is a single id, and picking again replaces it. + + + + + +```ts +const formSchema = z.object({ + roleIds: z.array(z.number()).min(1), +}); +``` + +```tsx + ( + + ), + }, + ]} +/> +``` + +The value is an array of ids. Chosen roles appear above the picker as removable +chips, and the picker **appends** rather than replaces - picking one that is +already chosen removes it, which is what the tick beside it in the list means. + + + + +Out of the box it searches the AdminCP roles list. The **guest** role is never +offered: it is the role a request has when it has no account, so it is not +something to assign to anybody. + +## Editing an existing record + +Same rule as [User](/docs/ui/user): the picker can only name ids it has seen, so +an edit form passes the roles it already knows about. + +```tsx + +``` + +## Keeping a role out of the list + +`excludeIds` drops options another field already owns - a primary-role picker +and a secondary-role picker should not both offer the same one: + +```tsx + +``` + +## Names are resolved per reader + +A role carries one name per language. The field renders the active locale's, and +falls back to the first translation rather than to the id - a role with no +translation in your language is still a role somebody named: + +```ts +import { roleOptionName } from "@vitnode/core/components/form/fields/input-roles"; + +roleOptionName(role, "pl"); // "Administrator PL", or the first name it has +``` + +## Props + +| Prop | Type | Default | What it does | +| ------------------- | ------------------------------------------ | ------------------ | -------------------------------------------- | +| `multiple` | `boolean` | `false` | `number[]` instead of `number`, with chips | +| `label` | `ReactNode` | - | Field label | +| `description` | `ReactNode` | - | Help text under the control | +| `placeholder` | `string` | `Select an option` | Shown while nothing is chosen | +| `searchPlaceholder` | `string` | `Search...` | Placeholder inside the search box | +| `selected` | `RoleOption[]` | `[]` | Roles the field opens on | +| `excludeIds` | `number[]` | `[]` | Roles the picker must not offer | +| `search` | `(value: string) => Promise` | AdminCP roles list | Replaces the lookup | +| `disabled` | `boolean` | `false` | Blocks opening the picker and removing chips | + +`RoleOption` is `{ id, color, name }`, where `name` is the raw +`{ languageCode, name }[]` - the server has no business deciding which language +the person clicking reads in. + +## See also + +- [User](/docs/ui/user) - the same idea for people. +- [Combobox](/docs/ui/combobox) - when the options are strings rather than records. diff --git a/apps/docs/content/docs/ui/user.mdx b/apps/docs/content/docs/ui/user.mdx new file mode 100644 index 000000000..4f17d90cd --- /dev/null +++ b/apps/docs/content/docs/ui/user.mdx @@ -0,0 +1,129 @@ +--- +title: User +description: Pick a person by name, with their avatar, for an Auto Form field. +--- + +## Preview + + + +## Usage + +```ts +import { z } from "zod"; +import { AutoForm } from "@vitnode/core/components/form/auto-form"; +import { AutoFormUser } from "@vitnode/core/components/form/fields/input-users"; +``` + +The value is the **user id**, so the schema is a plain number and the payload +needs no unwrapping: + +```ts +const formSchema = z.object({ + authorId: z.number(), +}); +``` + +```tsx + ( + + ), + }, + ]} +/> +``` + +Out of the box it searches the AdminCP users list, which means it answers with +whatever that route lets the signed-in admin see - the permission check lives +there and is not repeated in the component. + +## Editing an existing record + +A picker cannot show a name it has never fetched. An edit form starts holding an +id, so pass the person it already knows about as `selected`: + +```tsx + +``` + +Without it the field opens on the placeholder, as though nobody were chosen. +Whatever the search returns afterwards is remembered on top of that, so a person +picked a moment ago still reads as their name. + + + A nullable author is `z.number().nullable()`. The field renders the + placeholder for `null` and never invents a value - clearing one is up to your + own control, because "no author" and "author not chosen yet" are the same + state here. + + +## Searching somewhere else + +`search` replaces the lookup entirely - a plugin scoping to its own members, a +different endpoint, or fixtures in a test: + +```tsx + await searchReviewers(value)} +/> +``` + +It runs on every open with an empty string, and again - debounced - as the +person types. That is deliberate: the list is a live view of who exists, and a +cached one offers somebody who was deleted since. + +## Props + +| Prop | Type | Default | What it does | +| ------------------- | ------------------------------------------ | ------------------ | ------------------------------------------------ | +| `label` | `ReactNode` | - | Field label | +| `description` | `ReactNode` | - | Help text under the control | +| `placeholder` | `string` | `Select an option` | Shown while nothing is chosen | +| `searchPlaceholder` | `string` | `Search...` | Placeholder inside the search box | +| `selected` | `PartialUserOption \| null` | - | The person the field opens on | +| `search` | `(value: string) => Promise` | AdminCP users list | Replaces the lookup | +| `clearable` | `boolean` | `false` | Adds a button that sets the field back to nobody | +| `disabled` | `boolean` | `false` | Blocks opening the picker | + +`UserOption` is `{ id, name, nameCode, avatarColor }` - the columns it takes to +recognise a person on sight. `selected` accepts a **partial** one, because the +caller often knows only an id and a name. + +A generated avatar needs a colour, and that is the column a caller who resolved +only a _name_ does not have. Rather than invent one - the wrong colour reads as a +different person - the field draws a neutral placeholder in the same box, so the +name stays where it is when a search fills the real avatar in. + +## In the Content Engine + +A [`field.user()`](/docs/dev/content-engine/fields) renders this field +automatically - the author picker on a blog post is this component. Its options +come from the content type's own picker route rather than from the users list, +so an editor who may write articles can choose an author without also being +trusted to browse the member list. + +## See also + +- [Roles](/docs/ui/roles) - the same idea for roles, single or multiple. +- [Combobox](/docs/ui/combobox) - when the options are strings rather than people. diff --git a/apps/docs/migrations/0022_add_example_content.sql b/apps/docs/migrations/0022_add_example_content.sql new file mode 100644 index 000000000..2ea6ea1bb --- /dev/null +++ b/apps/docs/migrations/0022_add_example_content.sql @@ -0,0 +1,34 @@ +CREATE TABLE "example_articles" ( + "id" serial PRIMARY KEY NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "title" varchar(200) NOT NULL, + "code" varchar(100) NOT NULL, + "excerpt" text, + "views" integer DEFAULT 0 NOT NULL, + "featured" boolean DEFAULT false NOT NULL, + "status" varchar(64) DEFAULT 'draft' NOT NULL, + "publishedAt" timestamp, + "author" integer, + "category" integer NOT NULL +); +--> statement-breakpoint +ALTER TABLE "example_articles" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "example_categories" ( + "id" serial PRIMARY KEY NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "name" varchar(100) NOT NULL +); +--> statement-breakpoint +ALTER TABLE "example_categories" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "example_articles" ADD CONSTRAINT "example_articles_author_core_users_id_fk" FOREIGN KEY ("author") REFERENCES "public"."core_users"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_articles" ADD CONSTRAINT "example_articles_category_example_categories_id_fk" FOREIGN KEY ("category") REFERENCES "public"."example_categories"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE INDEX "example_articles_status_created_at_idx" ON "example_articles" USING btree ("status","createdAt");--> statement-breakpoint +CREATE UNIQUE INDEX "example_articles_code_key" ON "example_articles" USING btree ("code");--> statement-breakpoint +CREATE INDEX "example_articles_author_idx" ON "example_articles" USING btree ("author");--> statement-breakpoint +CREATE INDEX "example_articles_category_idx" ON "example_articles" USING btree ("category");--> statement-breakpoint +CREATE INDEX "example_articles_created_at_idx" ON "example_articles" USING btree ("createdAt");--> statement-breakpoint +CREATE INDEX "example_articles_updated_at_idx" ON "example_articles" USING btree ("updatedAt");--> statement-breakpoint +CREATE INDEX "example_categories_created_at_idx" ON "example_categories" USING btree ("createdAt");--> statement-breakpoint +CREATE INDEX "example_categories_updated_at_idx" ON "example_categories" USING btree ("updatedAt"); \ No newline at end of file diff --git a/apps/docs/migrations/0023_add_publication_to_example_articles.sql b/apps/docs/migrations/0023_add_publication_to_example_articles.sql new file mode 100644 index 000000000..bdce07b1f --- /dev/null +++ b/apps/docs/migrations/0023_add_publication_to_example_articles.sql @@ -0,0 +1,10 @@ +-- `example.article` moved from a hand-rolled `status` enum + `publishedAt` +-- field to the generated `publication` columns. The column names and types line +-- up, so the data survives - except for `archived`, which the generated status +-- does not have. Those rows become drafts. +UPDATE "example_articles" SET "status" = 'draft' WHERE "status" NOT IN ('draft', 'published');--> statement-breakpoint +ALTER TABLE "example_articles" ALTER COLUMN "status" SET DATA TYPE varchar(32);--> statement-breakpoint +ALTER TABLE "example_articles" ALTER COLUMN "status" SET DEFAULT 'draft';--> statement-breakpoint +CREATE INDEX "example_articles_status_published_at_idx" ON "example_articles" USING btree ("status","publishedAt");--> statement-breakpoint +-- The engine's invariant: a published row always carries a publication date. +UPDATE "example_articles" SET "publishedAt" = "createdAt" WHERE "status" = 'published' AND "publishedAt" IS NULL; diff --git a/apps/docs/migrations/0024_add_example_article_slug.sql b/apps/docs/migrations/0024_add_example_article_slug.sql new file mode 100644 index 000000000..c20f98f7f --- /dev/null +++ b/apps/docs/migrations/0024_add_example_article_slug.sql @@ -0,0 +1,55 @@ +-- `example.article` gained `field.slug({ source: "title" })`. +-- +-- Drizzle Kit generates this as a single `ADD COLUMN "slug" varchar(160) NOT +-- NULL`, which fails on a populated table: there is no default to backfill the +-- existing rows with. So the column arrives nullable, gets a value derived from +-- the title, and only then becomes NOT NULL and unique. That is the recipe for +-- every slug added to a table that already has data. +ALTER TABLE "example_articles" ADD COLUMN "slug" varchar(160);--> statement-breakpoint +-- The same normalisation `slugify` performs, in the subset of it SQL can do +-- without an extension: lowercase, non-alphanumerics to dashes, trimmed. +-- Accented characters simply drop out here rather than transliterating, which +-- is why the next statement exists. +UPDATE "example_articles" +SET "slug" = NULLIF( + trim(both '-' from regexp_replace(lower(left("title", 160)), '[^a-z0-9]+', '-', 'g')), + '' +);--> statement-breakpoint +-- Every row gets its id appended - not just the ambiguous ones. +-- +-- Suffixing only duplicates looks tidier and is wrong: the rescue value is +-- itself a slug, so it can land on a natural one that was left alone. Titles +-- "Foo 2", "Foo", "Foo" normalise to "foo-2", "foo", "foo"; rescuing only the +-- pair produces "foo-2" and "foo-3", and the first of those is already taken by +-- row 1. The same trap catches a title that normalises to nothing and falls +-- back to its id, against a row whose title genuinely normalised to that +-- number. Both only surface as a failure two statements later, when the unique +-- index is created. +-- +-- Appending the id everywhere makes a collision impossible rather than +-- unlikely: every value ends in `-`, or is `` when the title left +-- nothing behind, and ids are unique. Two results can only be equal if their +-- ids are, so there is nothing left to check. +-- +-- The base is truncated *first*, to leave exactly enough room for "-" and the +-- id: a 160-character slug plus a suffix would overflow varchar(160). The trim +-- runs after the truncation, so cutting mid-word cannot leave a trailing dash. +-- `NULLIF` + `concat_ws` drop an empty base entirely, so a row with no usable +-- title becomes just its id rather than "-12". +-- +-- This is backfill behaviour, and it stops here. A slug chosen at runtime is +-- never silently suffixed; a collision there is a 409. +UPDATE "example_articles" AS a +SET "slug" = concat_ws( + '-', + NULLIF( + trim( + both '-' from + left(coalesce(a."slug", ''), 160 - 1 - length(a."id"::text)) + ), + '' + ), + a."id" +);--> statement-breakpoint +ALTER TABLE "example_articles" ALTER COLUMN "slug" SET NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "example_articles_slug_key" ON "example_articles" USING btree ("slug"); diff --git a/apps/docs/migrations/0025_add_content_revisions.sql b/apps/docs/migrations/0025_add_content_revisions.sql new file mode 100644 index 000000000..973e967a3 --- /dev/null +++ b/apps/docs/migrations/0025_add_content_revisions.sql @@ -0,0 +1,20 @@ +CREATE TABLE "core_content_revisions" ( + "id" serial PRIMARY KEY NOT NULL, + "pluginId" varchar(255) NOT NULL, + "contentTypeId" varchar(100) NOT NULL, + "itemId" integer NOT NULL, + "version" integer NOT NULL, + "operation" varchar(20) NOT NULL, + "snapshot" jsonb DEFAULT '{}'::jsonb NOT NULL, + "changedFields" jsonb DEFAULT '[]'::jsonb NOT NULL, + "actorType" varchar(16) DEFAULT 'system' NOT NULL, + "actorUserId" integer, + "restoredFromRevisionId" integer, + "createdAt" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "core_content_revisions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "core_content_revisions" ADD CONSTRAINT "core_content_revisions_actorUserId_core_users_id_fk" FOREIGN KEY ("actorUserId") REFERENCES "public"."core_users"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +CREATE UNIQUE INDEX "core_content_revisions_item_version_unique" ON "core_content_revisions" USING btree ("contentTypeId","itemId","version");--> statement-breakpoint +CREATE INDEX "core_content_revisions_plugin_id_idx" ON "core_content_revisions" USING btree ("pluginId");--> statement-breakpoint +CREATE INDEX "core_content_revisions_actor_user_id_idx" ON "core_content_revisions" USING btree ("actorUserId"); \ No newline at end of file diff --git a/apps/docs/migrations/0026_add_example_article_editorial.sql b/apps/docs/migrations/0026_add_example_article_editorial.sql new file mode 100644 index 000000000..99ae91f24 --- /dev/null +++ b/apps/docs/migrations/0026_add_example_article_editorial.sql @@ -0,0 +1 @@ +ALTER TABLE "example_articles" ADD COLUMN "version" integer DEFAULT 1 NOT NULL; \ No newline at end of file diff --git a/apps/docs/migrations/0027_add_content_schedules.sql b/apps/docs/migrations/0027_add_content_schedules.sql new file mode 100644 index 000000000..5fb3475f1 --- /dev/null +++ b/apps/docs/migrations/0027_add_content_schedules.sql @@ -0,0 +1,23 @@ +CREATE TABLE "core_content_schedules" ( + "id" serial PRIMARY KEY NOT NULL, + "pluginId" varchar(255) NOT NULL, + "contentTypeId" varchar(100) NOT NULL, + "itemId" integer NOT NULL, + "action" varchar(16) NOT NULL, + "scheduledFor" timestamp NOT NULL, + "generation" integer DEFAULT 1 NOT NULL, + "status" varchar(16) DEFAULT 'pending' NOT NULL, + "createdBy" integer, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "completedAt" timestamp, + "lastError" text +); +--> statement-breakpoint +ALTER TABLE "core_content_schedules" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "core_content_schedules" ADD CONSTRAINT "core_content_schedules_createdBy_core_users_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."core_users"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint +CREATE UNIQUE INDEX "core_content_schedules_active_unique" ON "core_content_schedules" USING btree ("contentTypeId","itemId","action") WHERE status = 'pending';--> statement-breakpoint +CREATE INDEX "core_content_schedules_due_idx" ON "core_content_schedules" USING btree ("status","scheduledFor");--> statement-breakpoint +CREATE INDEX "core_content_schedules_item_idx" ON "core_content_schedules" USING btree ("contentTypeId","itemId");--> statement-breakpoint +CREATE INDEX "core_content_schedules_plugin_id_idx" ON "core_content_schedules" USING btree ("pluginId");--> statement-breakpoint +CREATE INDEX "core_content_schedules_created_by_idx" ON "core_content_schedules" USING btree ("createdBy"); \ No newline at end of file diff --git a/apps/docs/migrations/0028_add_content_schedule_effects_error.sql b/apps/docs/migrations/0028_add_content_schedule_effects_error.sql new file mode 100644 index 000000000..f53e168a7 --- /dev/null +++ b/apps/docs/migrations/0028_add_content_schedule_effects_error.sql @@ -0,0 +1 @@ +ALTER TABLE "core_content_schedules" ADD COLUMN "effectsError" text; \ No newline at end of file diff --git a/apps/docs/migrations/0029_add_example_localized_articles.sql b/apps/docs/migrations/0029_add_example_localized_articles.sql new file mode 100644 index 000000000..bd44b28d9 --- /dev/null +++ b/apps/docs/migrations/0029_add_example_localized_articles.sql @@ -0,0 +1,27 @@ +CREATE TABLE "example_localized_articles" ( + "id" serial PRIMARY KEY NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "featured" boolean DEFAULT false NOT NULL +); +--> statement-breakpoint +ALTER TABLE "example_localized_articles" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "example_localized_articles_translations" ( + "itemId" integer NOT NULL, + "languageId" integer NOT NULL, + "version" integer DEFAULT 1 NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "title" varchar(200) NOT NULL, + "slug" varchar(160) NOT NULL, + "body" text NOT NULL, + CONSTRAINT "example_localized_articles_translations_item_id_language_id_pk" PRIMARY KEY("itemId","languageId") +); +--> statement-breakpoint +ALTER TABLE "example_localized_articles_translations" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "example_localized_articles_translations" ADD CONSTRAINT "example_localized_articles_translations_itemId_example_localized_articles_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."example_localized_articles"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_localized_articles_translations" ADD CONSTRAINT "example_localized_articles_translations_languageId_core_languages_id_fk" FOREIGN KEY ("languageId") REFERENCES "public"."core_languages"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE INDEX "example_localized_articles_created_at_idx" ON "example_localized_articles" USING btree ("createdAt");--> statement-breakpoint +CREATE INDEX "example_localized_articles_updated_at_idx" ON "example_localized_articles" USING btree ("updatedAt");--> statement-breakpoint +CREATE INDEX "example_localized_articles_translations_language_id_idx" ON "example_localized_articles_translations" USING btree ("languageId");--> statement-breakpoint +CREATE UNIQUE INDEX "example_localized_articles_translations_language_id_slug_key" ON "example_localized_articles_translations" USING btree ("languageId","slug"); \ No newline at end of file diff --git a/apps/docs/migrations/0030_add_translation_editorial.sql b/apps/docs/migrations/0030_add_translation_editorial.sql new file mode 100644 index 000000000..f8adc3851 --- /dev/null +++ b/apps/docs/migrations/0030_add_translation_editorial.sql @@ -0,0 +1,13 @@ +DROP INDEX "example_localized_articles_translations_language_id_idx";--> statement-breakpoint +DROP INDEX "core_content_revisions_item_version_unique";--> statement-breakpoint +ALTER TABLE "core_content_revisions" ADD COLUMN "languageId" integer;--> statement-breakpoint +ALTER TABLE "example_localized_articles" ADD COLUMN "publishedAt" timestamp;--> statement-breakpoint +ALTER TABLE "example_localized_articles" ADD COLUMN "status" varchar(32) DEFAULT 'draft' NOT NULL;--> statement-breakpoint +ALTER TABLE "example_localized_articles" ADD COLUMN "version" integer DEFAULT 1 NOT NULL;--> statement-breakpoint +ALTER TABLE "example_localized_articles_translations" ADD COLUMN "publishedAt" timestamp;--> statement-breakpoint +ALTER TABLE "example_localized_articles_translations" ADD COLUMN "status" varchar(32) DEFAULT 'draft' NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "core_content_revisions_translation_version_unique" ON "core_content_revisions" USING btree ("contentTypeId","itemId","languageId","version") WHERE "languageId" IS NOT NULL;--> statement-breakpoint +CREATE INDEX "core_content_revisions_language_idx" ON "core_content_revisions" USING btree ("contentTypeId","itemId","languageId","version");--> statement-breakpoint +CREATE INDEX "example_localized_articles_status_published_at_idx" ON "example_localized_articles" USING btree ("status","publishedAt");--> statement-breakpoint +CREATE INDEX "example_localized_articles_translations_language_id_status_idx" ON "example_localized_articles_translations" USING btree ("languageId","status");--> statement-breakpoint +CREATE UNIQUE INDEX "core_content_revisions_item_version_unique" ON "core_content_revisions" USING btree ("contentTypeId","itemId","version") WHERE "languageId" IS NULL; \ No newline at end of file diff --git a/apps/docs/migrations/0031_add_example_advanced_articles.sql b/apps/docs/migrations/0031_add_example_advanced_articles.sql new file mode 100644 index 000000000..a337dadff --- /dev/null +++ b/apps/docs/migrations/0031_add_example_advanced_articles.sql @@ -0,0 +1,75 @@ +CREATE TABLE "example_advanced_articles" ( + "id" serial PRIMARY KEY NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "publishedAt" timestamp, + "status" varchar(32) DEFAULT 'draft' NOT NULL, + "version" integer DEFAULT 1 NOT NULL, + "syndicationIndexable" boolean DEFAULT true NOT NULL, + "syndicationPriority" integer DEFAULT 5 NOT NULL +); +--> statement-breakpoint +ALTER TABLE "example_advanced_articles" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "example_advanced_articles_categories" ( + "itemId" integer NOT NULL, + "relatedItemId" integer NOT NULL, + "position" integer NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "example_advanced_articles_categories_pk" PRIMARY KEY("itemId","relatedItemId") +); +--> statement-breakpoint +ALTER TABLE "example_advanced_articles_categories" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "example_advanced_articles_faq" ( + "id" serial PRIMARY KEY NOT NULL, + "itemId" integer NOT NULL, + "position" integer NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "question" varchar(200) NOT NULL, + "answer" text NOT NULL +); +--> statement-breakpoint +ALTER TABLE "example_advanced_articles_faq" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "example_advanced_articles_related_articles" ( + "itemId" integer NOT NULL, + "relatedItemId" integer NOT NULL, + "position" integer NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "example_advanced_articles_related_articles_pk" PRIMARY KEY("itemId","relatedItemId") +); +--> statement-breakpoint +ALTER TABLE "example_advanced_articles_related_articles" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "example_advanced_articles_translations" ( + "itemId" integer NOT NULL, + "languageId" integer NOT NULL, + "version" integer DEFAULT 1 NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "publishedAt" timestamp, + "status" varchar(32) DEFAULT 'draft' NOT NULL, + "title" varchar(200) NOT NULL, + "slug" varchar(160) NOT NULL, + "seoTitle" varchar(200), + "seoDescription" text, + CONSTRAINT "example_advanced_articles_translations_item_id_language_id_pk" PRIMARY KEY("itemId","languageId") +); +--> statement-breakpoint +ALTER TABLE "example_advanced_articles_translations" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "example_advanced_articles_categories" ADD CONSTRAINT "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."example_advanced_articles"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_advanced_articles_categories" ADD CONSTRAINT "example_advanced_articles_categories_relatedItemId_example_categories_id_fk" FOREIGN KEY ("relatedItemId") REFERENCES "public"."example_categories"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_advanced_articles_faq" ADD CONSTRAINT "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."example_advanced_articles"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_advanced_articles_related_articles" ADD CONSTRAINT "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."example_advanced_articles"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_advanced_articles_related_articles" ADD CONSTRAINT "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk" FOREIGN KEY ("relatedItemId") REFERENCES "public"."example_advanced_articles"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_advanced_articles_translations" ADD CONSTRAINT "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."example_advanced_articles"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "example_advanced_articles_translations" ADD CONSTRAINT "example_advanced_articles_translations_languageId_core_languages_id_fk" FOREIGN KEY ("languageId") REFERENCES "public"."core_languages"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE INDEX "example_advanced_articles_syndication_priority_idx" ON "example_advanced_articles" USING btree ("syndicationPriority");--> statement-breakpoint +CREATE INDEX "example_advanced_articles_created_at_idx" ON "example_advanced_articles" USING btree ("createdAt");--> statement-breakpoint +CREATE INDEX "example_advanced_articles_updated_at_idx" ON "example_advanced_articles" USING btree ("updatedAt");--> statement-breakpoint +CREATE INDEX "example_advanced_articles_status_published_at_idx" ON "example_advanced_articles" USING btree ("status","publishedAt");--> statement-breakpoint +CREATE UNIQUE INDEX "example_advanced_articles_categories_position_key" ON "example_advanced_articles_categories" USING btree ("itemId","position");--> statement-breakpoint +CREATE INDEX "example_advanced_articles_categories_related_item_id_idx" ON "example_advanced_articles_categories" USING btree ("relatedItemId");--> statement-breakpoint +CREATE UNIQUE INDEX "example_advanced_articles_faq_position_key" ON "example_advanced_articles_faq" USING btree ("itemId","position");--> statement-breakpoint +CREATE UNIQUE INDEX "example_advanced_articles_related_articles_position_key" ON "example_advanced_articles_related_articles" USING btree ("itemId","position");--> statement-breakpoint +CREATE INDEX "example_advanced_articles_related_articles_related_item_id_idx" ON "example_advanced_articles_related_articles" USING btree ("relatedItemId");--> statement-breakpoint +CREATE INDEX "example_advanced_articles_translations_language_id_status_idx" ON "example_advanced_articles_translations" USING btree ("languageId","status");--> statement-breakpoint +CREATE UNIQUE INDEX "example_advanced_articles_translations_language_id_slug_key" ON "example_advanced_articles_translations" USING btree ("languageId","slug"); \ No newline at end of file diff --git a/apps/docs/migrations/0032_add_content_slug_history.sql b/apps/docs/migrations/0032_add_content_slug_history.sql new file mode 100644 index 000000000..cb6e056e6 --- /dev/null +++ b/apps/docs/migrations/0032_add_content_slug_history.sql @@ -0,0 +1,17 @@ +CREATE TABLE "core_content_slug_history" ( + "id" serial PRIMARY KEY NOT NULL, + "pluginId" varchar(255) NOT NULL, + "contentTypeId" varchar(100) NOT NULL, + "itemId" integer NOT NULL, + "languageId" integer, + "slug" varchar(160) NOT NULL, + "path" varchar(512) NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "retiredAt" timestamp +); +--> statement-breakpoint +ALTER TABLE "core_content_slug_history" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE UNIQUE INDEX "core_content_slug_history_shared_unique" ON "core_content_slug_history" USING btree ("contentTypeId","slug") WHERE "languageId" IS NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "core_content_slug_history_locale_unique" ON "core_content_slug_history" USING btree ("contentTypeId","languageId","slug") WHERE "languageId" IS NOT NULL;--> statement-breakpoint +CREATE INDEX "core_content_slug_history_item_idx" ON "core_content_slug_history" USING btree ("contentTypeId","itemId","languageId");--> statement-breakpoint +CREATE INDEX "core_content_slug_history_plugin_id_idx" ON "core_content_slug_history" USING btree ("pluginId"); \ No newline at end of file diff --git a/apps/docs/migrations/0033_add_example_article_no_index.sql b/apps/docs/migrations/0033_add_example_article_no_index.sql new file mode 100644 index 000000000..cab8ed154 --- /dev/null +++ b/apps/docs/migrations/0033_add_example_article_no_index.sql @@ -0,0 +1 @@ +ALTER TABLE "example_advanced_articles" ADD COLUMN "syndicationNoIndex" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/apps/docs/migrations/0034_add_example_article_no_index_flag.sql b/apps/docs/migrations/0034_add_example_article_no_index_flag.sql new file mode 100644 index 000000000..892dcf524 --- /dev/null +++ b/apps/docs/migrations/0034_add_example_article_no_index_flag.sql @@ -0,0 +1,10 @@ +-- `example.article` gained `field.boolean({ nullable: true })` as its Stage 8 +-- `delivery.seo.noIndexField`. +-- +-- Nullable and undefaulted on purpose, because that is what an upgrade really +-- looks like: every row that already exists gets `NULL`, and `NULL` has to mean +-- "index me" in both places that read it - the `robots` metadata rendered into +-- the page and the predicate that decides what the sitemap lists. A boolean +-- added with `DEFAULT false NOT NULL` would rewrite the table and hide the case +-- worth testing. +ALTER TABLE "example_articles" ADD COLUMN "noIndex" boolean; diff --git a/apps/docs/migrations/0035_migrate_blog_to_content_engine.sql b/apps/docs/migrations/0035_migrate_blog_to_content_engine.sql new file mode 100644 index 000000000..cd66b71e3 --- /dev/null +++ b/apps/docs/migrations/0035_migrate_blog_to_content_engine.sql @@ -0,0 +1,192 @@ +CREATE TABLE "blog_categories_translations" ( + "itemId" integer NOT NULL, + "languageId" integer NOT NULL, + "version" integer DEFAULT 1 NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "name" varchar(100) NOT NULL, + CONSTRAINT "blog_categories_translations_item_id_language_id_pk" PRIMARY KEY("itemId","languageId") +); +--> statement-breakpoint +ALTER TABLE "blog_categories_translations" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "blog_posts_translations" ( + "itemId" integer NOT NULL, + "languageId" integer NOT NULL, + "version" integer DEFAULT 1 NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "updatedAt" timestamp DEFAULT now() NOT NULL, + "publishedAt" timestamp, + "status" varchar(32) DEFAULT 'draft' NOT NULL, + "title" varchar(255) NOT NULL, + "friendlyUrl" varchar(255) NOT NULL, + "content" text NOT NULL, + CONSTRAINT "blog_posts_translations_item_id_language_id_pk" PRIMARY KEY("itemId","languageId") +); +--> statement-breakpoint +ALTER TABLE "blog_posts_translations" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "blog_posts" DROP CONSTRAINT "blog_posts_categoryId_blog_categories_id_fk"; +--> statement-breakpoint +ALTER TABLE "blog_categories" ALTER COLUMN "updatedAt" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "blog_posts" ALTER COLUMN "updatedAt" SET DEFAULT now();--> statement-breakpoint +ALTER TABLE "blog_posts" ADD COLUMN "publishedAt" timestamp;--> statement-breakpoint +ALTER TABLE "blog_posts" ADD COLUMN "status" varchar(32) DEFAULT 'draft' NOT NULL;--> statement-breakpoint +ALTER TABLE "blog_posts" ADD COLUMN "version" integer DEFAULT 1 NOT NULL;--> statement-breakpoint +ALTER TABLE "blog_categories_translations" ADD CONSTRAINT "blog_categories_translations_itemId_blog_categories_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."blog_categories"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "blog_categories_translations" ADD CONSTRAINT "blog_categories_translations_languageId_core_languages_id_fk" FOREIGN KEY ("languageId") REFERENCES "public"."core_languages"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "blog_posts_translations" ADD CONSTRAINT "blog_posts_translations_itemId_blog_posts_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."blog_posts"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "blog_posts_translations" ADD CONSTRAINT "blog_posts_translations_languageId_core_languages_id_fk" FOREIGN KEY ("languageId") REFERENCES "public"."core_languages"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE INDEX "blog_categories_translations_language_id_idx" ON "blog_categories_translations" USING btree ("languageId");--> statement-breakpoint +CREATE INDEX "blog_posts_translations_language_id_status_idx" ON "blog_posts_translations" USING btree ("languageId","status");--> statement-breakpoint +CREATE UNIQUE INDEX "blog_posts_translations_language_id_friendly_url_key" ON "blog_posts_translations" USING btree ("languageId","friendlyUrl");--> statement-breakpoint +ALTER TABLE "blog_posts" ADD CONSTRAINT "blog_posts_categoryId_blog_categories_id_fk" FOREIGN KEY ("categoryId") REFERENCES "public"."blog_categories"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE INDEX "blog_categories_created_at_idx" ON "blog_categories" USING btree ("createdAt");--> statement-breakpoint +CREATE INDEX "blog_categories_updated_at_idx" ON "blog_categories" USING btree ("updatedAt");--> statement-breakpoint +CREATE INDEX "blog_posts_status_created_at_idx" ON "blog_posts" USING btree ("status","createdAt");--> statement-breakpoint +CREATE INDEX "blog_posts_category_id_idx" ON "blog_posts" USING btree ("categoryId");--> statement-breakpoint +CREATE INDEX "blog_posts_author_id_idx" ON "blog_posts" USING btree ("authorId");--> statement-breakpoint +CREATE INDEX "blog_posts_created_at_idx" ON "blog_posts" USING btree ("createdAt");--> statement-breakpoint +CREATE INDEX "blog_posts_updated_at_idx" ON "blog_posts" USING btree ("updatedAt");--> statement-breakpoint +CREATE INDEX "blog_posts_status_published_at_idx" ON "blog_posts" USING btree ("status","publishedAt");--> statement-breakpoint +-- +-- Data migration: the blog's own storage -> the Content Engine's. +-- +-- Nothing above this line dropped a table or a column, and nothing below moves a +-- record: ids, categories, authors and timestamps stay exactly where they are. +-- What moves is the *text*, out of `core_languages_words` and into the two +-- translation tables the engine reads. +-- + +-- 1. Publication. Every article that exists today is publicly readable - the old +-- public route returned every row and every search document was written +-- `isPublic: true` - so they all migrate as published. `publishedAt` is +-- `createdAt`, which is the only publication date the old schema can prove; +-- no revision history is fabricated, so `version` stays at its default of 1. +UPDATE "blog_posts" +SET "status" = 'published', "publishedAt" = "createdAt" +WHERE "status" = 'draft' AND "publishedAt" IS NULL;--> statement-breakpoint + +-- 2. Category names. One row per (category, language) that actually had a title, +-- so a language nobody translated into stays untranslated rather than being +-- invented. A stored empty title would break `name`'s minimum length, so it +-- falls back to a unique placeholder an editor can see and fix. +INSERT INTO "blog_categories_translations" + ("itemId", "languageId", "version", "createdAt", "updatedAt", "name") +SELECT + c."id", + l."id", + 1, + c."createdAt", + c."updatedAt", + LEFT(COALESCE(NULLIF(w."value", ''), 'category-' || c."id"), 100) +FROM "core_languages_words" w +JOIN "blog_categories" c ON c."id" = w."itemId" +JOIN "core_languages" l ON l."code" = w."languageCode" +WHERE w."pluginCode" = '@vitnode/blog' + AND w."tableName" = 'blog_categories' + AND w."variable" = 'title' +ON CONFLICT DO NOTHING;--> statement-breakpoint + +-- 3. Article text. The three variables the plugin kept side by side become one +-- row, for each (article, language) pair that had any of them. A missing +-- friendly URL falls back to something unique rather than to an empty string, +-- which the new UNIQUE (languageId, friendlyUrl) index would reject on the +-- second article. +INSERT INTO "blog_posts_translations" ( + "itemId", "languageId", "version", "createdAt", "updatedAt", + "publishedAt", "status", "title", "friendlyUrl", "content" +) +SELECT + p."id", + l."id", + 1, + p."createdAt", + p."updatedAt", + p."createdAt", + 'published', + LEFT(COALESCE(w."title", ''), 255), + LEFT( + COALESCE(NULLIF(w."friendlyUrl", ''), 'post-' || p."id" || '-' || l."code"), + 255 + ), + COALESCE(w."content", '') +FROM ( + SELECT + "itemId", + "languageCode", + MAX("value") FILTER (WHERE "variable" = 'title') AS "title", + MAX("value") FILTER (WHERE "variable" = 'content') AS "content", + MAX("value") FILTER (WHERE "variable" = 'friendlyUrl') AS "friendlyUrl" + FROM "core_languages_words" + WHERE "pluginCode" = '@vitnode/blog' + AND "tableName" = 'blog_posts' + AND "variable" IN ('title', 'content', 'friendlyUrl') + GROUP BY "itemId", "languageCode" +) w +JOIN "blog_posts" p ON p."id" = w."itemId" +JOIN "core_languages" l ON l."code" = w."languageCode" +ON CONFLICT DO NOTHING;--> statement-breakpoint + +-- 4. The default locale. A localized content type refuses to leave a record +-- without a translation in its `defaultLocale`, so a record that was only ever +-- written in another language gets an English row built from the name it +-- already has in whichever language it does have. Nothing is invented: the +-- value is one the record genuinely carries. +INSERT INTO "blog_categories_translations" + ("itemId", "languageId", "version", "createdAt", "updatedAt", "name") +SELECT + c."id", + l."id", + 1, + c."createdAt", + c."updatedAt", + LEFT( + COALESCE( + ( + SELECT NULLIF(t."name", '') + FROM "blog_categories_translations" t + WHERE t."itemId" = c."id" + ORDER BY t."languageId" + LIMIT 1 + ), + 'category-' || c."id" + ), + 100 + ) +FROM "blog_categories" c +JOIN "core_languages" l ON l."code" = 'en' +ON CONFLICT DO NOTHING;--> statement-breakpoint + +INSERT INTO "blog_posts_translations" ( + "itemId", "languageId", "version", "createdAt", "updatedAt", + "publishedAt", "status", "title", "friendlyUrl", "content" +) +SELECT + p."id", + l."id", + 1, + p."createdAt", + p."updatedAt", + p."createdAt", + 'published', + LEFT(COALESCE(NULLIF(source."title", ''), 'post-' || p."id"), 255), + LEFT('post-' || p."id" || '-en', 255), + COALESCE(source."content", '') +FROM "blog_posts" p +JOIN "core_languages" l ON l."code" = 'en' +LEFT JOIN LATERAL ( + SELECT t."title", t."content" + FROM "blog_posts_translations" t + WHERE t."itemId" = p."id" + ORDER BY t."languageId" + LIMIT 1 +) source ON TRUE +ON CONFLICT DO NOTHING;--> statement-breakpoint + +-- 5. The old storage, now that everything in it has a new home. Scoped to rows +-- that were genuinely migrated: a word in a language the install does not have +-- could not be copied, so it is left where it is rather than deleted. +DELETE FROM "core_languages_words" w +USING "core_languages" l +WHERE w."pluginCode" = '@vitnode/blog' + AND w."tableName" IN ('blog_categories', 'blog_posts') + AND l."code" = w."languageCode"; diff --git a/apps/docs/migrations/0036_add_core_secrets.sql b/apps/docs/migrations/0036_add_core_secrets.sql new file mode 100644 index 000000000..ffa881d19 --- /dev/null +++ b/apps/docs/migrations/0036_add_core_secrets.sql @@ -0,0 +1,7 @@ +CREATE TABLE "core_secrets" ( + "name" varchar(100) PRIMARY KEY NOT NULL, + "value" text NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "core_secrets" ENABLE ROW LEVEL SECURITY; \ No newline at end of file diff --git a/apps/docs/migrations/0037_migrate_blog_to_many_categories_and_authors.sql b/apps/docs/migrations/0037_migrate_blog_to_many_categories_and_authors.sql new file mode 100644 index 000000000..c1f15ef9c --- /dev/null +++ b/apps/docs/migrations/0037_migrate_blog_to_many_categories_and_authors.sql @@ -0,0 +1,59 @@ +CREATE TABLE "blog_posts_author_id" ( + "itemId" integer NOT NULL, + "relatedItemId" integer NOT NULL, + "position" integer NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "blog_posts_author_id_pk" PRIMARY KEY("itemId","relatedItemId") +); +--> statement-breakpoint +ALTER TABLE "blog_posts_author_id" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "blog_posts_category_id" ( + "itemId" integer NOT NULL, + "relatedItemId" integer NOT NULL, + "position" integer NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "blog_posts_category_id_pk" PRIMARY KEY("itemId","relatedItemId") +); +--> statement-breakpoint +ALTER TABLE "blog_posts_category_id" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "blog_posts" DROP CONSTRAINT "blog_posts_categoryId_blog_categories_id_fk"; +--> statement-breakpoint +ALTER TABLE "blog_posts" DROP CONSTRAINT "blog_posts_authorId_core_users_id_fk"; +--> statement-breakpoint +DROP INDEX "blog_posts_category_id_idx";--> statement-breakpoint +DROP INDEX "blog_posts_author_id_idx";--> statement-breakpoint +ALTER TABLE "blog_posts_author_id" ADD CONSTRAINT "blog_posts_author_id_itemId_blog_posts_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."blog_posts"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "blog_posts_author_id" ADD CONSTRAINT "blog_posts_author_id_relatedItemId_core_users_id_fk" FOREIGN KEY ("relatedItemId") REFERENCES "public"."core_users"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "blog_posts_category_id" ADD CONSTRAINT "blog_posts_category_id_itemId_blog_posts_id_fk" FOREIGN KEY ("itemId") REFERENCES "public"."blog_posts"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "blog_posts_category_id" ADD CONSTRAINT "blog_posts_category_id_relatedItemId_blog_categories_id_fk" FOREIGN KEY ("relatedItemId") REFERENCES "public"."blog_categories"("id") ON DELETE restrict ON UPDATE cascade;--> statement-breakpoint +CREATE UNIQUE INDEX "blog_posts_author_id_position_key" ON "blog_posts_author_id" USING btree ("itemId","position");--> statement-breakpoint +CREATE INDEX "blog_posts_author_id_related_item_id_idx" ON "blog_posts_author_id" USING btree ("relatedItemId");--> statement-breakpoint +CREATE UNIQUE INDEX "blog_posts_category_id_position_key" ON "blog_posts_category_id" USING btree ("itemId","position");--> statement-breakpoint +CREATE INDEX "blog_posts_category_id_related_item_id_idx" ON "blog_posts_category_id" USING btree ("relatedItemId");--> statement-breakpoint +-- +-- Data migration: one category and one author per article -> a set of each. +-- +-- Everything above created the junction tables; everything below fills them +-- from the columns that are about to be dropped. Backfill *then* drop, in one +-- transaction, so there is no moment where an article's category exists in +-- neither place. +-- +-- `position` is 0 for every row, which is exactly right for a set of one: the +-- engine's unique `(itemId, position)` is satisfied, an unordered field (the +-- categories) reads back in id order anyway, and an ordered one (the authors) +-- opens with its single author first. +-- + +-- 1. Categories. `NOT NULL` on the old column, so every article has exactly one +-- and every article ends up with exactly one membership. +INSERT INTO "blog_posts_category_id" ("itemId", "relatedItemId", "position", "createdAt") +SELECT "id", "categoryId", 0, "createdAt" FROM "blog_posts";--> statement-breakpoint + +-- 2. Authors. Nullable on the old column - an article whose author was deleted +-- had `NULL` there and gets no membership, which is the same fact stored the +-- new way: the empty set is what "nobody" looks like. +INSERT INTO "blog_posts_author_id" ("itemId", "relatedItemId", "position", "createdAt") +SELECT "id", "authorId", 0, "createdAt" FROM "blog_posts" WHERE "authorId" IS NOT NULL;--> statement-breakpoint + +ALTER TABLE "blog_posts" DROP COLUMN "categoryId";--> statement-breakpoint +ALTER TABLE "blog_posts" DROP COLUMN "authorId"; \ No newline at end of file diff --git a/apps/docs/migrations/meta/0022_snapshot.json b/apps/docs/migrations/meta/0022_snapshot.json new file mode 100644 index 000000000..d3d9f71d9 --- /dev/null +++ b/apps/docs/migrations/meta/0022_snapshot.json @@ -0,0 +1,2493 @@ +{ + "id": "5bd0b1de-db52-41b3-86a7-4f4a9a7ca0b7", + "prevId": "bce1e602-a192-4c9c-b6d5-8be955f985d9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0023_snapshot.json b/apps/docs/migrations/meta/0023_snapshot.json new file mode 100644 index 000000000..17f524d07 --- /dev/null +++ b/apps/docs/migrations/meta/0023_snapshot.json @@ -0,0 +1,2514 @@ +{ + "id": "32678677-ed07-41e5-a08e-d7a6887a0395", + "prevId": "5bd0b1de-db52-41b3-86a7-4f4a9a7ca0b7", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0024_snapshot.json b/apps/docs/migrations/meta/0024_snapshot.json new file mode 100644 index 000000000..91d78207f --- /dev/null +++ b/apps/docs/migrations/meta/0024_snapshot.json @@ -0,0 +1,2535 @@ +{ + "id": "f73d7a47-f42f-4629-8af1-b388c220a427", + "prevId": "32678677-ed07-41e5-a08e-d7a6887a0395", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0025_snapshot.json b/apps/docs/migrations/meta/0025_snapshot.json new file mode 100644 index 000000000..ba89f1462 --- /dev/null +++ b/apps/docs/migrations/meta/0025_snapshot.json @@ -0,0 +1,2696 @@ +{ + "id": "4f1c20aa-86f5-4d5b-9b4f-75101d57d8de", + "prevId": "f73d7a47-f42f-4629-8af1-b388c220a427", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0026_snapshot.json b/apps/docs/migrations/meta/0026_snapshot.json new file mode 100644 index 000000000..1890e22be --- /dev/null +++ b/apps/docs/migrations/meta/0026_snapshot.json @@ -0,0 +1,2703 @@ +{ + "id": "410ddb95-6db7-4923-96be-141fc9454cb6", + "prevId": "4f1c20aa-86f5-4d5b-9b4f-75101d57d8de", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0027_snapshot.json b/apps/docs/migrations/meta/0027_snapshot.json new file mode 100644 index 000000000..0f7a264b8 --- /dev/null +++ b/apps/docs/migrations/meta/0027_snapshot.json @@ -0,0 +1,2913 @@ +{ + "id": "949a0b2c-84b1-43ba-83fa-2e2ab286905c", + "prevId": "410ddb95-6db7-4923-96be-141fc9454cb6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0028_snapshot.json b/apps/docs/migrations/meta/0028_snapshot.json new file mode 100644 index 000000000..7ccb924a1 --- /dev/null +++ b/apps/docs/migrations/meta/0028_snapshot.json @@ -0,0 +1,2919 @@ +{ + "id": "ad292f66-b888-469c-84fc-5b2fb5dd0dcd", + "prevId": "949a0b2c-84b1-43ba-83fa-2e2ab286905c", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0029_snapshot.json b/apps/docs/migrations/meta/0029_snapshot.json new file mode 100644 index 000000000..cf75f2b31 --- /dev/null +++ b/apps/docs/migrations/meta/0029_snapshot.json @@ -0,0 +1,3126 @@ +{ + "id": "48b24402-18d7-4bf2-88e4-f0c651f02a1d", + "prevId": "ad292f66-b888-469c-84fc-5b2fb5dd0dcd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_idx": { + "name": "example_localized_articles_translations_language_id_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0030_snapshot.json b/apps/docs/migrations/meta/0030_snapshot.json new file mode 100644 index 000000000..4888b3300 --- /dev/null +++ b/apps/docs/migrations/meta/0030_snapshot.json @@ -0,0 +1,3260 @@ +{ + "id": "5bc8c627-98d3-4dc7-992e-3958232a8780", + "prevId": "48b24402-18d7-4bf2-88e4-f0c651f02a1d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0031_snapshot.json b/apps/docs/migrations/meta/0031_snapshot.json new file mode 100644 index 000000000..d44f07de2 --- /dev/null +++ b/apps/docs/migrations/meta/0031_snapshot.json @@ -0,0 +1,3868 @@ +{ + "id": "c3a84fce-ca99-43a8-8b83-a8be82faeed9", + "prevId": "5bc8c627-98d3-4dc7-992e-3958232a8780", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles": { + "name": "example_advanced_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "syndicationIndexable": { + "name": "syndicationIndexable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "syndicationPriority": { + "name": "syndicationPriority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + } + }, + "indexes": { + "example_advanced_articles_syndication_priority_idx": { + "name": "example_advanced_articles_syndication_priority_idx", + "columns": [ + { + "expression": "syndicationPriority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_created_at_idx": { + "name": "example_advanced_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_updated_at_idx": { + "name": "example_advanced_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_status_published_at_idx": { + "name": "example_advanced_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_categories": { + "name": "example_advanced_articles_categories", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_categories_position_key": { + "name": "example_advanced_articles_categories_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_categories_related_item_id_idx": { + "name": "example_advanced_articles_categories_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_categories_relatedItemId_example_categories_id_fk": { + "name": "example_advanced_articles_categories_relatedItemId_example_categories_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_categories_pk": { + "name": "example_advanced_articles_categories_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_faq": { + "name": "example_advanced_articles_faq", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_advanced_articles_faq_position_key": { + "name": "example_advanced_articles_faq_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_faq", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_related_articles": { + "name": "example_advanced_articles_related_articles", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_related_articles_position_key": { + "name": "example_advanced_articles_related_articles_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_related_articles_related_item_id_idx": { + "name": "example_advanced_articles_related_articles_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_related_articles_pk": { + "name": "example_advanced_articles_related_articles_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_translations": { + "name": "example_advanced_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "seoTitle": { + "name": "seoTitle", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "seoDescription": { + "name": "seoDescription", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "example_advanced_articles_translations_language_id_status_idx": { + "name": "example_advanced_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_translations_language_id_slug_key": { + "name": "example_advanced_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_translations_languageId_core_languages_id_fk": { + "name": "example_advanced_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_translations_item_id_language_id_pk": { + "name": "example_advanced_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0032_snapshot.json b/apps/docs/migrations/meta/0032_snapshot.json new file mode 100644 index 000000000..386f622ef --- /dev/null +++ b/apps/docs/migrations/meta/0032_snapshot.json @@ -0,0 +1,4029 @@ +{ + "id": "b7094309-91e5-43f2-b9f9-d5666d73f0f4", + "prevId": "c3a84fce-ca99-43a8-8b83-a8be82faeed9", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_slug_history": { + "name": "core_content_slug_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retiredAt": { + "name": "retiredAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_slug_history_shared_unique": { + "name": "core_content_slug_history_shared_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_locale_unique": { + "name": "core_content_slug_history_locale_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_item_idx": { + "name": "core_content_slug_history_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_plugin_id_idx": { + "name": "core_content_slug_history_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles": { + "name": "example_advanced_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "syndicationIndexable": { + "name": "syndicationIndexable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "syndicationPriority": { + "name": "syndicationPriority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + } + }, + "indexes": { + "example_advanced_articles_syndication_priority_idx": { + "name": "example_advanced_articles_syndication_priority_idx", + "columns": [ + { + "expression": "syndicationPriority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_created_at_idx": { + "name": "example_advanced_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_updated_at_idx": { + "name": "example_advanced_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_status_published_at_idx": { + "name": "example_advanced_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_categories": { + "name": "example_advanced_articles_categories", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_categories_position_key": { + "name": "example_advanced_articles_categories_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_categories_related_item_id_idx": { + "name": "example_advanced_articles_categories_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_categories_relatedItemId_example_categories_id_fk": { + "name": "example_advanced_articles_categories_relatedItemId_example_categories_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_categories_pk": { + "name": "example_advanced_articles_categories_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_faq": { + "name": "example_advanced_articles_faq", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_advanced_articles_faq_position_key": { + "name": "example_advanced_articles_faq_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_faq", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_related_articles": { + "name": "example_advanced_articles_related_articles", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_related_articles_position_key": { + "name": "example_advanced_articles_related_articles_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_related_articles_related_item_id_idx": { + "name": "example_advanced_articles_related_articles_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_related_articles_pk": { + "name": "example_advanced_articles_related_articles_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_translations": { + "name": "example_advanced_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "seoTitle": { + "name": "seoTitle", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "seoDescription": { + "name": "seoDescription", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "example_advanced_articles_translations_language_id_status_idx": { + "name": "example_advanced_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_translations_language_id_slug_key": { + "name": "example_advanced_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_translations_languageId_core_languages_id_fk": { + "name": "example_advanced_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_translations_item_id_language_id_pk": { + "name": "example_advanced_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0033_snapshot.json b/apps/docs/migrations/meta/0033_snapshot.json new file mode 100644 index 000000000..e1ce93871 --- /dev/null +++ b/apps/docs/migrations/meta/0033_snapshot.json @@ -0,0 +1,4036 @@ +{ + "id": "318c5944-3dbe-4646-a80e-fd047f44db84", + "prevId": "b7094309-91e5-43f2-b9f9-d5666d73f0f4", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_slug_history": { + "name": "core_content_slug_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retiredAt": { + "name": "retiredAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_slug_history_shared_unique": { + "name": "core_content_slug_history_shared_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_locale_unique": { + "name": "core_content_slug_history_locale_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_item_idx": { + "name": "core_content_slug_history_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_plugin_id_idx": { + "name": "core_content_slug_history_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles": { + "name": "example_advanced_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "syndicationIndexable": { + "name": "syndicationIndexable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "syndicationNoIndex": { + "name": "syndicationNoIndex", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "syndicationPriority": { + "name": "syndicationPriority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + } + }, + "indexes": { + "example_advanced_articles_syndication_priority_idx": { + "name": "example_advanced_articles_syndication_priority_idx", + "columns": [ + { + "expression": "syndicationPriority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_created_at_idx": { + "name": "example_advanced_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_updated_at_idx": { + "name": "example_advanced_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_status_published_at_idx": { + "name": "example_advanced_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_categories": { + "name": "example_advanced_articles_categories", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_categories_position_key": { + "name": "example_advanced_articles_categories_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_categories_related_item_id_idx": { + "name": "example_advanced_articles_categories_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_categories_relatedItemId_example_categories_id_fk": { + "name": "example_advanced_articles_categories_relatedItemId_example_categories_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_categories_pk": { + "name": "example_advanced_articles_categories_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_faq": { + "name": "example_advanced_articles_faq", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_advanced_articles_faq_position_key": { + "name": "example_advanced_articles_faq_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_faq", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_related_articles": { + "name": "example_advanced_articles_related_articles", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_related_articles_position_key": { + "name": "example_advanced_articles_related_articles_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_related_articles_related_item_id_idx": { + "name": "example_advanced_articles_related_articles_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_related_articles_pk": { + "name": "example_advanced_articles_related_articles_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_translations": { + "name": "example_advanced_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "seoTitle": { + "name": "seoTitle", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "seoDescription": { + "name": "seoDescription", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "example_advanced_articles_translations_language_id_status_idx": { + "name": "example_advanced_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_translations_language_id_slug_key": { + "name": "example_advanced_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_translations_languageId_core_languages_id_fk": { + "name": "example_advanced_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_translations_item_id_language_id_pk": { + "name": "example_advanced_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0034_snapshot.json b/apps/docs/migrations/meta/0034_snapshot.json new file mode 100644 index 000000000..6166a911e --- /dev/null +++ b/apps/docs/migrations/meta/0034_snapshot.json @@ -0,0 +1,4042 @@ +{ + "id": "0f660415-9144-44ed-9d96-78cd76711ebf", + "prevId": "318c5944-3dbe-4646-a80e-fd047f44db84", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_slug_history": { + "name": "core_content_slug_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retiredAt": { + "name": "retiredAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_slug_history_shared_unique": { + "name": "core_content_slug_history_shared_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_locale_unique": { + "name": "core_content_slug_history_locale_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_item_idx": { + "name": "core_content_slug_history_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_plugin_id_idx": { + "name": "core_content_slug_history_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles": { + "name": "example_advanced_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "syndicationIndexable": { + "name": "syndicationIndexable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "syndicationNoIndex": { + "name": "syndicationNoIndex", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "syndicationPriority": { + "name": "syndicationPriority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + } + }, + "indexes": { + "example_advanced_articles_syndication_priority_idx": { + "name": "example_advanced_articles_syndication_priority_idx", + "columns": [ + { + "expression": "syndicationPriority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_created_at_idx": { + "name": "example_advanced_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_updated_at_idx": { + "name": "example_advanced_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_status_published_at_idx": { + "name": "example_advanced_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_categories": { + "name": "example_advanced_articles_categories", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_categories_position_key": { + "name": "example_advanced_articles_categories_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_categories_related_item_id_idx": { + "name": "example_advanced_articles_categories_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_categories_relatedItemId_example_categories_id_fk": { + "name": "example_advanced_articles_categories_relatedItemId_example_categories_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_categories_pk": { + "name": "example_advanced_articles_categories_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_faq": { + "name": "example_advanced_articles_faq", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_advanced_articles_faq_position_key": { + "name": "example_advanced_articles_faq_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_faq", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_related_articles": { + "name": "example_advanced_articles_related_articles", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_related_articles_position_key": { + "name": "example_advanced_articles_related_articles_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_related_articles_related_item_id_idx": { + "name": "example_advanced_articles_related_articles_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_related_articles_pk": { + "name": "example_advanced_articles_related_articles_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_translations": { + "name": "example_advanced_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "seoTitle": { + "name": "seoTitle", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "seoDescription": { + "name": "seoDescription", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "example_advanced_articles_translations_language_id_status_idx": { + "name": "example_advanced_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_translations_language_id_slug_key": { + "name": "example_advanced_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_translations_languageId_core_languages_id_fk": { + "name": "example_advanced_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_translations_item_id_language_id_pk": { + "name": "example_advanced_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "noIndex": { + "name": "noIndex", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0035_snapshot.json b/apps/docs/migrations/meta/0035_snapshot.json new file mode 100644 index 000000000..4b679e430 --- /dev/null +++ b/apps/docs/migrations/meta/0035_snapshot.json @@ -0,0 +1,4456 @@ +{ + "id": "42b7098a-c42b-4c70-8673-087b8ff56ce4", + "prevId": "0f660415-9144-44ed-9d96-78cd76711ebf", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_slug_history": { + "name": "core_content_slug_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retiredAt": { + "name": "retiredAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_slug_history_shared_unique": { + "name": "core_content_slug_history_shared_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_locale_unique": { + "name": "core_content_slug_history_locale_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_item_idx": { + "name": "core_content_slug_history_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_plugin_id_idx": { + "name": "core_content_slug_history_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "blog_categories_created_at_idx": { + "name": "blog_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_categories_updated_at_idx": { + "name": "blog_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories_translations": { + "name": "blog_categories_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blog_categories_translations_language_id_idx": { + "name": "blog_categories_translations_language_id_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_categories_translations_itemId_blog_categories_id_fk": { + "name": "blog_categories_translations_itemId_blog_categories_id_fk", + "tableFrom": "blog_categories_translations", + "tableTo": "blog_categories", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_categories_translations_languageId_core_languages_id_fk": { + "name": "blog_categories_translations_languageId_core_languages_id_fk", + "tableFrom": "blog_categories_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_categories_translations_item_id_language_id_pk": { + "name": "blog_categories_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "blog_posts_status_created_at_idx": { + "name": "blog_posts_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_category_id_idx": { + "name": "blog_posts_category_id_idx", + "columns": [ + { + "expression": "categoryId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_author_id_idx": { + "name": "blog_posts_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_created_at_idx": { + "name": "blog_posts_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_updated_at_idx": { + "name": "blog_posts_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_status_published_at_idx": { + "name": "blog_posts_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts_translations": { + "name": "blog_posts_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "friendlyUrl": { + "name": "friendlyUrl", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blog_posts_translations_language_id_status_idx": { + "name": "blog_posts_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_translations_language_id_friendly_url_key": { + "name": "blog_posts_translations_language_id_friendly_url_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "friendlyUrl", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_posts_translations_itemId_blog_posts_id_fk": { + "name": "blog_posts_translations_itemId_blog_posts_id_fk", + "tableFrom": "blog_posts_translations", + "tableTo": "blog_posts", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_posts_translations_languageId_core_languages_id_fk": { + "name": "blog_posts_translations_languageId_core_languages_id_fk", + "tableFrom": "blog_posts_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_posts_translations_item_id_language_id_pk": { + "name": "blog_posts_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles": { + "name": "example_advanced_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "syndicationIndexable": { + "name": "syndicationIndexable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "syndicationNoIndex": { + "name": "syndicationNoIndex", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "syndicationPriority": { + "name": "syndicationPriority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + } + }, + "indexes": { + "example_advanced_articles_syndication_priority_idx": { + "name": "example_advanced_articles_syndication_priority_idx", + "columns": [ + { + "expression": "syndicationPriority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_created_at_idx": { + "name": "example_advanced_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_updated_at_idx": { + "name": "example_advanced_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_status_published_at_idx": { + "name": "example_advanced_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_categories": { + "name": "example_advanced_articles_categories", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_categories_position_key": { + "name": "example_advanced_articles_categories_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_categories_related_item_id_idx": { + "name": "example_advanced_articles_categories_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_categories_relatedItemId_example_categories_id_fk": { + "name": "example_advanced_articles_categories_relatedItemId_example_categories_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_categories_pk": { + "name": "example_advanced_articles_categories_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_faq": { + "name": "example_advanced_articles_faq", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_advanced_articles_faq_position_key": { + "name": "example_advanced_articles_faq_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_faq", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_related_articles": { + "name": "example_advanced_articles_related_articles", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_related_articles_position_key": { + "name": "example_advanced_articles_related_articles_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_related_articles_related_item_id_idx": { + "name": "example_advanced_articles_related_articles_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_related_articles_pk": { + "name": "example_advanced_articles_related_articles_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_translations": { + "name": "example_advanced_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "seoTitle": { + "name": "seoTitle", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "seoDescription": { + "name": "seoDescription", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "example_advanced_articles_translations_language_id_status_idx": { + "name": "example_advanced_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_translations_language_id_slug_key": { + "name": "example_advanced_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_translations_languageId_core_languages_id_fk": { + "name": "example_advanced_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_translations_item_id_language_id_pk": { + "name": "example_advanced_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "noIndex": { + "name": "noIndex", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0036_snapshot.json b/apps/docs/migrations/meta/0036_snapshot.json new file mode 100644 index 000000000..cf83f00f2 --- /dev/null +++ b/apps/docs/migrations/meta/0036_snapshot.json @@ -0,0 +1,4488 @@ +{ + "id": "73f97c77-4699-41af-83fc-fe63a1f6196a", + "prevId": "42b7098a-c42b-4c70-8673-087b8ff56ce4", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_slug_history": { + "name": "core_content_slug_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retiredAt": { + "name": "retiredAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_slug_history_shared_unique": { + "name": "core_content_slug_history_shared_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_locale_unique": { + "name": "core_content_slug_history_locale_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_item_idx": { + "name": "core_content_slug_history_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_plugin_id_idx": { + "name": "core_content_slug_history_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_secrets": { + "name": "core_secrets", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "blog_categories_created_at_idx": { + "name": "blog_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_categories_updated_at_idx": { + "name": "blog_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories_translations": { + "name": "blog_categories_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blog_categories_translations_language_id_idx": { + "name": "blog_categories_translations_language_id_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_categories_translations_itemId_blog_categories_id_fk": { + "name": "blog_categories_translations_itemId_blog_categories_id_fk", + "tableFrom": "blog_categories_translations", + "tableTo": "blog_categories", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_categories_translations_languageId_core_languages_id_fk": { + "name": "blog_categories_translations_languageId_core_languages_id_fk", + "tableFrom": "blog_categories_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_categories_translations_item_id_language_id_pk": { + "name": "blog_categories_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categoryId": { + "name": "categoryId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "blog_posts_status_created_at_idx": { + "name": "blog_posts_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_category_id_idx": { + "name": "blog_posts_category_id_idx", + "columns": [ + { + "expression": "categoryId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_author_id_idx": { + "name": "blog_posts_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_created_at_idx": { + "name": "blog_posts_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_updated_at_idx": { + "name": "blog_posts_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_status_published_at_idx": { + "name": "blog_posts_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_posts_categoryId_blog_categories_id_fk": { + "name": "blog_posts_categoryId_blog_categories_id_fk", + "tableFrom": "blog_posts", + "tableTo": "blog_categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "blog_posts_authorId_core_users_id_fk": { + "name": "blog_posts_authorId_core_users_id_fk", + "tableFrom": "blog_posts", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts_translations": { + "name": "blog_posts_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "friendlyUrl": { + "name": "friendlyUrl", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blog_posts_translations_language_id_status_idx": { + "name": "blog_posts_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_translations_language_id_friendly_url_key": { + "name": "blog_posts_translations_language_id_friendly_url_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "friendlyUrl", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_posts_translations_itemId_blog_posts_id_fk": { + "name": "blog_posts_translations_itemId_blog_posts_id_fk", + "tableFrom": "blog_posts_translations", + "tableTo": "blog_posts", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_posts_translations_languageId_core_languages_id_fk": { + "name": "blog_posts_translations_languageId_core_languages_id_fk", + "tableFrom": "blog_posts_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_posts_translations_item_id_language_id_pk": { + "name": "blog_posts_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles": { + "name": "example_advanced_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "syndicationIndexable": { + "name": "syndicationIndexable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "syndicationNoIndex": { + "name": "syndicationNoIndex", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "syndicationPriority": { + "name": "syndicationPriority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + } + }, + "indexes": { + "example_advanced_articles_syndication_priority_idx": { + "name": "example_advanced_articles_syndication_priority_idx", + "columns": [ + { + "expression": "syndicationPriority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_created_at_idx": { + "name": "example_advanced_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_updated_at_idx": { + "name": "example_advanced_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_status_published_at_idx": { + "name": "example_advanced_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_categories": { + "name": "example_advanced_articles_categories", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_categories_position_key": { + "name": "example_advanced_articles_categories_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_categories_related_item_id_idx": { + "name": "example_advanced_articles_categories_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_categories_relatedItemId_example_categories_id_fk": { + "name": "example_advanced_articles_categories_relatedItemId_example_categories_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_categories_pk": { + "name": "example_advanced_articles_categories_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_faq": { + "name": "example_advanced_articles_faq", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_advanced_articles_faq_position_key": { + "name": "example_advanced_articles_faq_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_faq", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_related_articles": { + "name": "example_advanced_articles_related_articles", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_related_articles_position_key": { + "name": "example_advanced_articles_related_articles_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_related_articles_related_item_id_idx": { + "name": "example_advanced_articles_related_articles_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_related_articles_pk": { + "name": "example_advanced_articles_related_articles_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_translations": { + "name": "example_advanced_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "seoTitle": { + "name": "seoTitle", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "seoDescription": { + "name": "seoDescription", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "example_advanced_articles_translations_language_id_status_idx": { + "name": "example_advanced_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_translations_language_id_slug_key": { + "name": "example_advanced_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_translations_languageId_core_languages_id_fk": { + "name": "example_advanced_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_translations_item_id_language_id_pk": { + "name": "example_advanced_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "noIndex": { + "name": "noIndex", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/0037_snapshot.json b/apps/docs/migrations/meta/0037_snapshot.json new file mode 100644 index 000000000..7a5a60f5b --- /dev/null +++ b/apps/docs/migrations/meta/0037_snapshot.json @@ -0,0 +1,4639 @@ +{ + "id": "7734935b-4c55-40b8-ba19-41961e485bb4", + "prevId": "73f97c77-4699-41af-83fc-fe63a1f6196a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.core_admin_permissions": { + "name": "core_admin_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_admin_permissions_role_id_idx": { + "name": "core_admin_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_permissions_user_id_idx": { + "name": "core_admin_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_permissions_roleId_core_roles_id_fk": { + "name": "core_admin_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_permissions_userId_core_users_id_fk": { + "name": "core_admin_permissions_userId_core_users_id_fk", + "tableFrom": "core_admin_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_sessions": { + "name": "core_admin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_admin_sessions_token_idx": { + "name": "core_admin_sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_admin_sessions_user_id_idx": { + "name": "core_admin_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_sessions_userId_core_users_id_fk": { + "name": "core_admin_sessions_userId_core_users_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_admin_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_admin_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_sessions_token_unique": { + "name": "core_admin_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_revisions": { + "name": "core_content_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "changedFields": { + "name": "changedFields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "actorType": { + "name": "actorType", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actorUserId": { + "name": "actorUserId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "restoredFromRevisionId": { + "name": "restoredFromRevisionId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_content_revisions_item_version_unique": { + "name": "core_content_revisions_item_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_translation_version_unique": { + "name": "core_content_revisions_translation_version_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_language_idx": { + "name": "core_content_revisions_language_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_plugin_id_idx": { + "name": "core_content_revisions_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_revisions_actor_user_id_idx": { + "name": "core_content_revisions_actor_user_id_idx", + "columns": [ + { + "expression": "actorUserId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_revisions_actorUserId_core_users_id_fk": { + "name": "core_content_revisions_actorUserId_core_users_id_fk", + "tableFrom": "core_content_revisions", + "tableTo": "core_users", + "columnsFrom": [ + "actorUserId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_schedules": { + "name": "core_content_schedules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "scheduledFor": { + "name": "scheduledFor", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "createdBy": { + "name": "createdBy", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effectsError": { + "name": "effectsError", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_schedules_active_unique": { + "name": "core_content_schedules_active_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_due_idx": { + "name": "core_content_schedules_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduledFor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_item_idx": { + "name": "core_content_schedules_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_plugin_id_idx": { + "name": "core_content_schedules_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_schedules_created_by_idx": { + "name": "core_content_schedules_created_by_idx", + "columns": [ + { + "expression": "createdBy", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_content_schedules_createdBy_core_users_id_fk": { + "name": "core_content_schedules_createdBy_core_users_id_fk", + "tableFrom": "core_content_schedules", + "tableTo": "core_users", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_content_slug_history": { + "name": "core_content_slug_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentTypeId": { + "name": "contentTypeId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retiredAt": { + "name": "retiredAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_content_slug_history_shared_unique": { + "name": "core_content_slug_history_shared_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_locale_unique": { + "name": "core_content_slug_history_locale_unique", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"languageId\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_item_idx": { + "name": "core_content_slug_history_item_idx", + "columns": [ + { + "expression": "contentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_content_slug_history_plugin_id_idx": { + "name": "core_content_slug_history_plugin_id_idx", + "columns": [ + { + "expression": "pluginId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_cron": { + "name": "core_cron", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "lastRun": { + "name": "lastRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "module": { + "name": "module", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "nextRun": { + "name": "nextRun", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "schedule": { + "name": "schedule", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_admin_dashboard": { + "name": "core_admin_dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "widgets": { + "name": "widgets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_admin_dashboard_user_id_idx": { + "name": "core_admin_dashboard_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_admin_dashboard_userId_core_users_id_fk": { + "name": "core_admin_dashboard_userId_core_users_id_fk", + "tableFrom": "core_admin_dashboard", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_admin_dashboard_userId_unique": { + "name": "core_admin_dashboard_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_files": { + "name": "core_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "folder": { + "name": "folder", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "mimeType": { + "name": "mimeType", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_files_user_id_idx": { + "name": "core_files_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_files_userId_core_users_id_fk": { + "name": "core_files_userId_core_users_id_fk", + "tableFrom": "core_files", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_files_key_unique": { + "name": "core_files_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages": { + "name": "core_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time24": { + "name": "time24", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "core_languages_code_idx": { + "name": "core_languages_code_idx", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_languages_name_idx": { + "name": "core_languages_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_languages_code_unique": { + "name": "core_languages_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_languages_words": { + "name": "core_languages_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "pluginCode": { + "name": "pluginCode", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tableName": { + "name": "tableName", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "variable": { + "name": "variable", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_languages_words_lang_code_idx": { + "name": "core_languages_words_lang_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_languages_words_languageCode_core_languages_code_fk": { + "name": "core_languages_words_languageCode_core_languages_code_fk", + "tableFrom": "core_languages_words", + "tableTo": "core_languages", + "columnsFrom": [ + "languageCode" + ], + "columnsTo": [ + "code" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_logs": { + "name": "core_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(45)", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'GET'" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'localhost'" + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "statusCode": { + "name": "statusCode", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "userId": { + "name": "userId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "test123": { + "name": "test123", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "core_logs_userId_core_users_id_fk": { + "name": "core_logs_userId_core_users_id_fk", + "tableFrom": "core_logs", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_moderators_permissions": { + "name": "core_moderators_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "unrestricted": { + "name": "unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "core_moderators_permissions_role_id_idx": { + "name": "core_moderators_permissions_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_moderators_permissions_user_id_idx": { + "name": "core_moderators_permissions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_moderators_permissions_roleId_core_roles_id_fk": { + "name": "core_moderators_permissions_roleId_core_roles_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_moderators_permissions_userId_core_users_id_fk": { + "name": "core_moderators_permissions_userId_core_users_id_fk", + "tableFrom": "core_moderators_permissions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_queue": { + "name": "core_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "queue": { + "name": "queue", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "availableAt": { + "name": "availableAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reservedAt": { + "name": "reservedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lastError": { + "name": "lastError", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completedAt": { + "name": "completedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "core_queue_status_available_at_idx": { + "name": "core_queue_status_available_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "availableAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_roles": { + "name": "core_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default": { + "name": "default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "root": { + "name": "root", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "guest": { + "name": "guest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "allowUploadFiles": { + "name": "allowUploadFiles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "totalMaxStorage": { + "name": "totalMaxStorage", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maxStorageForSubmit": { + "name": "maxStorageForSubmit", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_search_index": { + "name": "core_search_index", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pluginId": { + "name": "pluginId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "itemType": { + "name": "itemType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageCode": { + "name": "languageCode", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "authorId": { + "name": "authorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": true, + "generated": { + "as": "setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"title\", '')), 'A') || setweight(to_tsvector(CASE lower(split_part(\"core_search_index\".\"languageCode\", '-', 1)) WHEN 'da' THEN 'danish'::regconfig WHEN 'de' THEN 'german'::regconfig WHEN 'en' THEN 'english'::regconfig WHEN 'es' THEN 'spanish'::regconfig WHEN 'fi' THEN 'finnish'::regconfig WHEN 'fr' THEN 'french'::regconfig WHEN 'hu' THEN 'hungarian'::regconfig WHEN 'it' THEN 'italian'::regconfig WHEN 'nl' THEN 'dutch'::regconfig WHEN 'no' THEN 'norwegian'::regconfig WHEN 'pl' THEN 'polish'::regconfig WHEN 'pt' THEN 'portuguese'::regconfig WHEN 'ro' THEN 'romanian'::regconfig WHEN 'ru' THEN 'russian'::regconfig WHEN 'sv' THEN 'swedish'::regconfig WHEN 'tr' THEN 'turkish'::regconfig ELSE 'simple'::regconfig END, coalesce(\"core_search_index\".\"content\", '')), 'B')", + "type": "stored" + } + }, + "containerType": { + "name": "containerType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "containerId": { + "name": "containerId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isPublic": { + "name": "isPublic", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "indexedAt": { + "name": "indexedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_search_index_search_vector_idx": { + "name": "core_search_index_search_vector_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "core_search_index_created_at_idx": { + "name": "core_search_index_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_author_id_idx": { + "name": "core_search_index_author_id_idx", + "columns": [ + { + "expression": "authorId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_item_type_idx": { + "name": "core_search_index_item_type_idx", + "columns": [ + { + "expression": "itemType", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_language_code_idx": { + "name": "core_search_index_language_code_idx", + "columns": [ + { + "expression": "languageCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_search_index_is_public_idx": { + "name": "core_search_index_is_public_idx", + "columns": [ + { + "expression": "isPublic", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_search_index_authorId_core_users_id_fk": { + "name": "core_search_index_authorId_core_users_id_fk", + "tableFrom": "core_search_index", + "tableTo": "core_users", + "columnsFrom": [ + "authorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_search_index_item_unique": { + "name": "core_search_index_item_unique", + "nullsNotDistinct": false, + "columns": [ + "itemType", + "itemId", + "languageCode" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_secrets": { + "name": "core_secrets", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions": { + "name": "core_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "deviceId": { + "name": "deviceId", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "core_sessions_user_id_idx": { + "name": "core_sessions_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_sessions_userId_core_users_id_fk": { + "name": "core_sessions_userId_core_users_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_sessions_deviceId_core_sessions_known_devices_id_fk": { + "name": "core_sessions_deviceId_core_sessions_known_devices_id_fk", + "tableFrom": "core_sessions", + "tableTo": "core_sessions_known_devices", + "columnsFrom": [ + "deviceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_token_unique": { + "name": "core_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_sessions_known_devices": { + "name": "core_sessions_known_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lastSeen": { + "name": "lastSeen", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_sessions_known_devices_ip_address_idx": { + "name": "core_sessions_known_devices_ip_address_idx", + "columns": [ + { + "expression": "ipAddress", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_sessions_known_devices_publicId_unique": { + "name": "core_sessions_known_devices_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users": { + "name": "core_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "nameCode": { + "name": "nameCode", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "newsletter": { + "name": "newsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatarColor": { + "name": "avatarColor", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "birthday": { + "name": "birthday", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'en'" + } + }, + "indexes": { + "core_users_name_code_idx": { + "name": "core_users_name_code_idx", + "columns": [ + { + "expression": "nameCode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_name_idx": { + "name": "core_users_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_email_idx": { + "name": "core_users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_roleId_core_roles_id_fk": { + "name": "core_users_roleId_core_roles_id_fk", + "tableFrom": "core_users", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "core_users_language_core_languages_code_fk": { + "name": "core_users_language_core_languages_code_fk", + "tableFrom": "core_users", + "tableTo": "core_languages", + "columnsFrom": [ + "language" + ], + "columnsTo": [ + "code" + ], + "onDelete": "set default", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_nameCode_unique": { + "name": "core_users_nameCode_unique", + "nullsNotDistinct": false, + "columns": [ + "nameCode" + ] + }, + "core_users_name_unique": { + "name": "core_users_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "core_users_email_unique": { + "name": "core_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_confirm_emails": { + "name": "core_users_confirm_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_confirm_emails_userId_core_users_id_fk": { + "name": "core_users_confirm_emails_userId_core_users_id_fk", + "tableFrom": "core_users_confirm_emails", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_confirm_emails_token_unique": { + "name": "core_users_confirm_emails_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_forgot_password": { + "name": "core_users_forgot_password", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "core_users_forgot_password_userId_core_users_id_fk": { + "name": "core_users_forgot_password_userId_core_users_id_fk", + "tableFrom": "core_users_forgot_password", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "core_users_forgot_password_userId_unique": { + "name": "core_users_forgot_password_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "core_users_forgot_password_token_unique": { + "name": "core_users_forgot_password_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_secondary_roles": { + "name": "core_users_secondary_roles", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_secondary_roles_user_id_idx": { + "name": "core_users_secondary_roles_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "core_users_secondary_roles_role_id_idx": { + "name": "core_users_secondary_roles_role_id_idx", + "columns": [ + { + "expression": "roleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_secondary_roles_userId_core_users_id_fk": { + "name": "core_users_secondary_roles_userId_core_users_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "core_users_secondary_roles_roleId_core_roles_id_fk": { + "name": "core_users_secondary_roles_roleId_core_roles_id_fk", + "tableFrom": "core_users_secondary_roles", + "tableTo": "core_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "core_users_secondary_roles_userId_roleId_pk": { + "name": "core_users_secondary_roles_userId_roleId_pk", + "columns": [ + "userId", + "roleId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.core_users_sso": { + "name": "core_users_sso", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "core_users_sso_user_id_idx": { + "name": "core_users_sso_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "core_users_sso_userId_core_users_id_fk": { + "name": "core_users_sso_userId_core_users_id_fk", + "tableFrom": "core_users_sso", + "tableTo": "core_users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories": { + "name": "blog_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "color": { + "name": "color", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "blog_categories_created_at_idx": { + "name": "blog_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_categories_updated_at_idx": { + "name": "blog_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_categories_translations": { + "name": "blog_categories_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blog_categories_translations_language_id_idx": { + "name": "blog_categories_translations_language_id_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_categories_translations_itemId_blog_categories_id_fk": { + "name": "blog_categories_translations_itemId_blog_categories_id_fk", + "tableFrom": "blog_categories_translations", + "tableTo": "blog_categories", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_categories_translations_languageId_core_languages_id_fk": { + "name": "blog_categories_translations_languageId_core_languages_id_fk", + "tableFrom": "blog_categories_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_categories_translations_item_id_language_id_pk": { + "name": "blog_categories_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts": { + "name": "blog_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": { + "blog_posts_status_created_at_idx": { + "name": "blog_posts_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_created_at_idx": { + "name": "blog_posts_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_updated_at_idx": { + "name": "blog_posts_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_status_published_at_idx": { + "name": "blog_posts_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts_author_id": { + "name": "blog_posts_author_id", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "blog_posts_author_id_position_key": { + "name": "blog_posts_author_id_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_author_id_related_item_id_idx": { + "name": "blog_posts_author_id_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_posts_author_id_itemId_blog_posts_id_fk": { + "name": "blog_posts_author_id_itemId_blog_posts_id_fk", + "tableFrom": "blog_posts_author_id", + "tableTo": "blog_posts", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_posts_author_id_relatedItemId_core_users_id_fk": { + "name": "blog_posts_author_id_relatedItemId_core_users_id_fk", + "tableFrom": "blog_posts_author_id", + "tableTo": "core_users", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_posts_author_id_pk": { + "name": "blog_posts_author_id_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts_category_id": { + "name": "blog_posts_category_id", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "blog_posts_category_id_position_key": { + "name": "blog_posts_category_id_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_category_id_related_item_id_idx": { + "name": "blog_posts_category_id_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_posts_category_id_itemId_blog_posts_id_fk": { + "name": "blog_posts_category_id_itemId_blog_posts_id_fk", + "tableFrom": "blog_posts_category_id", + "tableTo": "blog_posts", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_posts_category_id_relatedItemId_blog_categories_id_fk": { + "name": "blog_posts_category_id_relatedItemId_blog_categories_id_fk", + "tableFrom": "blog_posts_category_id", + "tableTo": "blog_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_posts_category_id_pk": { + "name": "blog_posts_category_id_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.blog_posts_translations": { + "name": "blog_posts_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "friendlyUrl": { + "name": "friendlyUrl", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "blog_posts_translations_language_id_status_idx": { + "name": "blog_posts_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blog_posts_translations_language_id_friendly_url_key": { + "name": "blog_posts_translations_language_id_friendly_url_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "friendlyUrl", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blog_posts_translations_itemId_blog_posts_id_fk": { + "name": "blog_posts_translations_itemId_blog_posts_id_fk", + "tableFrom": "blog_posts_translations", + "tableTo": "blog_posts", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "blog_posts_translations_languageId_core_languages_id_fk": { + "name": "blog_posts_translations_languageId_core_languages_id_fk", + "tableFrom": "blog_posts_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "blog_posts_translations_item_id_language_id_pk": { + "name": "blog_posts_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles": { + "name": "example_advanced_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "syndicationIndexable": { + "name": "syndicationIndexable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "syndicationNoIndex": { + "name": "syndicationNoIndex", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "syndicationPriority": { + "name": "syndicationPriority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + } + }, + "indexes": { + "example_advanced_articles_syndication_priority_idx": { + "name": "example_advanced_articles_syndication_priority_idx", + "columns": [ + { + "expression": "syndicationPriority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_created_at_idx": { + "name": "example_advanced_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_updated_at_idx": { + "name": "example_advanced_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_status_published_at_idx": { + "name": "example_advanced_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_categories": { + "name": "example_advanced_articles_categories", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_categories_position_key": { + "name": "example_advanced_articles_categories_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_categories_related_item_id_idx": { + "name": "example_advanced_articles_categories_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_categories_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_categories_relatedItemId_example_categories_id_fk": { + "name": "example_advanced_articles_categories_relatedItemId_example_categories_id_fk", + "tableFrom": "example_advanced_articles_categories", + "tableTo": "example_categories", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_categories_pk": { + "name": "example_advanced_articles_categories_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_faq": { + "name": "example_advanced_articles_faq", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "question": { + "name": "question", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_advanced_articles_faq_position_key": { + "name": "example_advanced_articles_faq_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_faq_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_faq", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_related_articles": { + "name": "example_advanced_articles_related_articles", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "relatedItemId": { + "name": "relatedItemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "example_advanced_articles_related_articles_position_key": { + "name": "example_advanced_articles_related_articles_position_key", + "columns": [ + { + "expression": "itemId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_related_articles_related_item_id_idx": { + "name": "example_advanced_articles_related_articles_related_item_id_idx", + "columns": [ + { + "expression": "relatedItemId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_related_articles_relatedItemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_related_articles", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "relatedItemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_related_articles_pk": { + "name": "example_advanced_articles_related_articles_pk", + "columns": [ + "itemId", + "relatedItemId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_advanced_articles_translations": { + "name": "example_advanced_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "seoTitle": { + "name": "seoTitle", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "seoDescription": { + "name": "seoDescription", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "example_advanced_articles_translations_language_id_status_idx": { + "name": "example_advanced_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_advanced_articles_translations_language_id_slug_key": { + "name": "example_advanced_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk": { + "name": "example_advanced_articles_translations_itemId_example_advanced_articles_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "example_advanced_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_advanced_articles_translations_languageId_core_languages_id_fk": { + "name": "example_advanced_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_advanced_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_advanced_articles_translations_item_id_language_id_pk": { + "name": "example_advanced_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_articles": { + "name": "example_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "excerpt": { + "name": "excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "noIndex": { + "name": "noIndex", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_articles_status_created_at_idx": { + "name": "example_articles_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_slug_key": { + "name": "example_articles_slug_key", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_code_key": { + "name": "example_articles_code_key", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_author_idx": { + "name": "example_articles_author_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_category_idx": { + "name": "example_articles_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_created_at_idx": { + "name": "example_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_updated_at_idx": { + "name": "example_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_articles_status_published_at_idx": { + "name": "example_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_articles_author_core_users_id_fk": { + "name": "example_articles_author_core_users_id_fk", + "tableFrom": "example_articles", + "tableTo": "core_users", + "columnsFrom": [ + "author" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "example_articles_category_example_categories_id_fk": { + "name": "example_articles_category_example_categories_id_fk", + "tableFrom": "example_articles", + "tableTo": "example_categories", + "columnsFrom": [ + "category" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_categories": { + "name": "example_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_categories_created_at_idx": { + "name": "example_categories_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_categories_updated_at_idx": { + "name": "example_categories_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles": { + "name": "example_localized_articles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "featured": { + "name": "featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "example_localized_articles_created_at_idx": { + "name": "example_localized_articles_created_at_idx", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_updated_at_idx": { + "name": "example_localized_articles_updated_at_idx", + "columns": [ + { + "expression": "updatedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_status_published_at_idx": { + "name": "example_localized_articles_status_published_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "publishedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.example_localized_articles_translations": { + "name": "example_localized_articles_translations", + "schema": "", + "columns": { + "itemId": { + "name": "itemId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "languageId": { + "name": "languageId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "publishedAt": { + "name": "publishedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "example_localized_articles_translations_language_id_status_idx": { + "name": "example_localized_articles_translations_language_id_status_idx", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "example_localized_articles_translations_language_id_slug_key": { + "name": "example_localized_articles_translations_language_id_slug_key", + "columns": [ + { + "expression": "languageId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "example_localized_articles_translations_itemId_example_localized_articles_id_fk": { + "name": "example_localized_articles_translations_itemId_example_localized_articles_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "example_localized_articles", + "columnsFrom": [ + "itemId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "example_localized_articles_translations_languageId_core_languages_id_fk": { + "name": "example_localized_articles_translations_languageId_core_languages_id_fk", + "tableFrom": "example_localized_articles_translations", + "tableTo": "core_languages", + "columnsFrom": [ + "languageId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "example_localized_articles_translations_item_id_language_id_pk": { + "name": "example_localized_articles_translations_item_id_language_id_pk", + "columns": [ + "itemId", + "languageId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/docs/migrations/meta/_journal.json b/apps/docs/migrations/meta/_journal.json index 6838150d7..e302e9ce2 100644 --- a/apps/docs/migrations/meta/_journal.json +++ b/apps/docs/migrations/meta/_journal.json @@ -155,6 +155,118 @@ "when": 1785581778726, "tag": "0021_add_admin_dashboard", "breakpoints": true + }, + { + "idx": 22, + "version": "7", + "when": 1785706178516, + "tag": "0022_add_example_content", + "breakpoints": true + }, + { + "idx": 23, + "version": "7", + "when": 1785758740560, + "tag": "0023_add_publication_to_example_articles", + "breakpoints": true + }, + { + "idx": 24, + "version": "7", + "when": 1785764469085, + "tag": "0024_add_example_article_slug", + "breakpoints": true + }, + { + "idx": 25, + "version": "7", + "when": 1785955595563, + "tag": "0025_add_content_revisions", + "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1785957233989, + "tag": "0026_add_example_article_editorial", + "breakpoints": true + }, + { + "idx": 27, + "version": "7", + "when": 1786018313984, + "tag": "0027_add_content_schedules", + "breakpoints": true + }, + { + "idx": 28, + "version": "7", + "when": 1786024625069, + "tag": "0028_add_content_schedule_effects_error", + "breakpoints": true + }, + { + "idx": 29, + "version": "7", + "when": 1786034127995, + "tag": "0029_add_example_localized_articles", + "breakpoints": true + }, + { + "idx": 30, + "version": "7", + "when": 1786044755458, + "tag": "0030_add_translation_editorial", + "breakpoints": true + }, + { + "idx": 31, + "version": "7", + "when": 1786181800826, + "tag": "0031_add_example_advanced_articles", + "breakpoints": true + }, + { + "idx": 32, + "version": "7", + "when": 1786194085698, + "tag": "0032_add_content_slug_history", + "breakpoints": true + }, + { + "idx": 33, + "version": "7", + "when": 1786195724174, + "tag": "0033_add_example_article_no_index", + "breakpoints": true + }, + { + "idx": 34, + "version": "7", + "when": 1786292946013, + "tag": "0034_add_example_article_no_index_flag", + "breakpoints": true + }, + { + "idx": 35, + "version": "7", + "when": 1786350996229, + "tag": "0035_migrate_blog_to_content_engine", + "breakpoints": true + }, + { + "idx": 36, + "version": "7", + "when": 1786562844917, + "tag": "0036_add_core_secrets", + "breakpoints": true + }, + { + "idx": 37, + "version": "7", + "when": 1786804383682, + "tag": "0037_migrate_blog_to_many_categories_and_authors", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/apps/docs/package.json b/apps/docs/package.json index 28a4af6e7..dac4c657c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -30,6 +30,7 @@ "@hono/zod-openapi": "^1.5.1", "@hono/zod-validator": "^0.9.0", "@vitnode/blog": "workspace:*", + "@vitnode/example": "workspace:*", "@vitnode/core": "workspace:*", "drizzle-kit": "^0.31.10", "drizzle-orm": "^0.45.2", diff --git a/apps/docs/src/app/[locale]/(docs)/docs/error.tsx b/apps/docs/src/app/[locale]/(docs)/docs/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/(docs)/docs/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/[locale]/(main)/(home)/page.tsx b/apps/docs/src/app/[locale]/(main)/(home)/page.tsx index fdbcec642..0794e36b2 100644 --- a/apps/docs/src/app/[locale]/(main)/(home)/page.tsx +++ b/apps/docs/src/app/[locale]/(main)/(home)/page.tsx @@ -7,6 +7,7 @@ import { ChevronRight } from "lucide-react"; import { Suspense } from "react"; import { AnimatedBeamHome } from "../../../../components/animated-beam/animated-beam-home"; +import { AnimatedBeamHomeSkeleton } from "../../../../components/animated-beam/animated-beam-home-skeleton"; import { AdminSection } from "./sections/admin/admin"; import { CallToActionSection } from "./sections/call-to-action"; import { PoweringBySection } from "./sections/powering-by/powering-by"; @@ -78,7 +79,7 @@ export default function HomePage() { - + }> diff --git a/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/sso/[providerId]/loading.tsx b/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/sso/[providerId]/loading.tsx new file mode 100644 index 000000000..9e5f25c4c --- /dev/null +++ b/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/sso/[providerId]/loading.tsx @@ -0,0 +1,10 @@ +import { Loader } from "@vitnode/core/components/ui/loader"; + +export default function Loading() { + return ( +
+ + Loading +
+ ); +} diff --git a/apps/docs/src/app/[locale]/(main)/error.tsx b/apps/docs/src/app/[locale]/(main)/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/(main)/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-blog)/blog/categories/page.tsx b/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-blog)/blog/categories/page.tsx deleted file mode 100644 index 3dfeea398..000000000 --- a/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-blog)/blog/categories/page.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { Metadata } from "next"; - -import { I18nProvider } from "@vitnode/core/components/i18n-provider"; -import { DataTableSkeleton } from "@vitnode/core/components/table/data-table"; -import { HeaderContent } from "@vitnode/core/components/ui/header-content"; -import { checkAdminPermissionApi } from "@vitnode/core/lib/api/get-session-admin-api"; -import { getTranslations } from "next-intl/server"; -import dynamic from "next/dynamic"; -import { notFound } from "next/navigation"; -import React from "react"; - -import { CONFIG_PLUGIN } from "@vitnode/blog/const"; -import { ActionsCategoriesAdmin } from "@vitnode/blog/views/admin/categories/actions/actions"; - -const CategoriesAdminView = dynamic(async () => - import("@vitnode/blog/views/admin/categories/table/categories-admin-view").then(mod => ({ - default: mod.CategoriesAdminView, - })), -); - -export const generateMetadata = async (): Promise => { - const t = await getTranslations("@vitnode/blog.admin.nav"); - - return { - title: t("categories"), - }; -}; - -export default async function CategoriesPage( - params: React.ComponentProps, -) { - const [t, tNav, canView, canCreate] = await Promise.all([ - getTranslations("@vitnode/blog.admin.categories"), - getTranslations("@vitnode/blog.admin.nav"), - checkAdminPermissionApi({ - plugin: CONFIG_PLUGIN.pluginId, - module: "categories", - permission: "can_view", - }), - checkAdminPermissionApi({ - plugin: CONFIG_PLUGIN.pluginId, - module: "categories", - permission: "can_create", - }), - ]); - - if (!canView) { - notFound(); - } - - return ( - -
- - {canCreate && } - - - }> - - -
-
- ); -} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-blog)/blog/posts/page.tsx b/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-blog)/blog/posts/page.tsx deleted file mode 100644 index 99d58036d..000000000 --- a/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-blog)/blog/posts/page.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { Metadata } from "next"; - -import { I18nProvider } from "@vitnode/core/components/i18n-provider"; -import { DataTableSkeleton } from "@vitnode/core/components/table/data-table"; -import { HeaderContent } from "@vitnode/core/components/ui/header-content"; -import { checkAdminPermissionApi } from "@vitnode/core/lib/api/get-session-admin-api"; -import { getTranslations } from "next-intl/server"; -import dynamic from "next/dynamic"; -import { notFound } from "next/navigation"; -import React from "react"; - -import { CONFIG_PLUGIN } from "@vitnode/blog/const"; -import { ActionsPostsAdmin } from "@vitnode/blog/views/admin/posts/actions/actions"; - -const PostsAdminView = dynamic(async () => - import("@vitnode/blog/views/admin/posts/table/posts-admin-view").then(mod => ({ - default: mod.PostsAdminView, - })), -); - -export const generateMetadata = async (): Promise => { - const t = await getTranslations("@vitnode/blog.admin.nav"); - - return { - title: t("posts"), - }; -}; - -export default async function PostsPage( - params: React.ComponentProps, -) { - const [t, tNav, canView, canCreate] = await Promise.all([ - getTranslations("@vitnode/blog.admin.posts"), - getTranslations("@vitnode/blog.admin.nav"), - checkAdminPermissionApi({ - plugin: CONFIG_PLUGIN.pluginId, - module: "posts", - permission: "can_view", - }), - checkAdminPermissionApi({ - plugin: CONFIG_PLUGIN.pluginId, - module: "posts", - permission: "can_create", - }), - ]); - - if (!canView) { - notFound(); - } - - return ( - -
- - {canCreate && } - - - }> - - -
-
- ); -} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/content/[...slug]/page.tsx b/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/content/[...slug]/page.tsx new file mode 100644 index 000000000..c30d6692d --- /dev/null +++ b/apps/docs/src/app/[locale]/admin/(auth)/(plugins)/(vitnode-core)/content/[...slug]/page.tsx @@ -0,0 +1,23 @@ +import type { Metadata } from "next/dist/types"; + +import { + ContentAdminView, + type ContentAdminViewProps, + getContentLabels, + resolveContentType, +} from "@vitnode/core/views/admin/views/content/content-admin-view"; + +export const generateMetadata = async ({ + params, +}: ContentAdminViewProps): Promise => { + const entry = await resolveContentType(params); + if (!entry) return {}; + + const labels = await getContentLabels(entry); + + return { description: labels.desc, title: labels.title }; +}; + +export default function ContentAdminPage(props: ContentAdminViewProps) { + return ; +} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/blog/categories/page.tsx b/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/blog/categories/page.tsx deleted file mode 100644 index b4680017f..000000000 --- a/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/blog/categories/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { BreadcrumbAdmin } from "@vitnode/core/views/admin/layouts/breadcrumb/breadcrumb-admin"; - -export default function BreadcrumbSlot() { - return ; -} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/blog/posts/page.tsx b/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/blog/posts/page.tsx deleted file mode 100644 index 6aad0fb44..000000000 --- a/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/blog/posts/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { BreadcrumbAdmin } from "@vitnode/core/views/admin/layouts/breadcrumb/breadcrumb-admin"; - -export default function BreadcrumbSlot() { - return ; -} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/content/[...slug]/page.tsx b/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/content/[...slug]/page.tsx new file mode 100644 index 000000000..4e0f3b6a8 --- /dev/null +++ b/apps/docs/src/app/[locale]/admin/(auth)/@breadcrumb/content/[...slug]/page.tsx @@ -0,0 +1,54 @@ +import { getTranslations } from "next-intl/server"; + +import { contentTypeName } from "@vitnode/core/content/admin/labels"; +import { + CONTENT_ADMIN_CREATE_SEGMENT, + CONTENT_ADMIN_EDIT_SEGMENT, +} from "@vitnode/core/content/const"; +import { contentAdminHref } from "@vitnode/core/content/registry"; +import { BreadcrumbAdmin } from "@vitnode/core/views/admin/layouts/breadcrumb/breadcrumb-admin"; +import { + getContentLabels, + resolveContentRoute, +} from "@vitnode/core/views/admin/views/content/content-admin-view"; + +export default async function BreadcrumbSlot({ + params, +}: { + params: Promise<{ slug: string[] }>; +}) { + const { slug } = await params; + const route = await resolveContentRoute(params); + const labels = route ? await getContentLabels(route.entry) : undefined; + + if (!route || route.action === "list") { + return ( + + ); + } + + const t = await getTranslations("core.content"); + const { definition } = route.entry; + + return ( + + ); +} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/error.tsx b/apps/docs/src/app/[locale]/admin/(auth)/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/admin/(auth)/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/[locale]/error.tsx b/apps/docs/src/app/[locale]/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/api/vitnode/content/revalidate/route.ts b/apps/docs/src/app/api/vitnode/content/revalidate/route.ts new file mode 100644 index 000000000..983263af6 --- /dev/null +++ b/apps/docs/src/app/api/vitnode/content/revalidate/route.ts @@ -0,0 +1 @@ +export { POST } from "@vitnode/core/content/next/revalidate-route"; diff --git a/apps/docs/src/app/global-error.tsx b/apps/docs/src/app/global-error.tsx index 786d47b66..ccb9d5ed2 100644 --- a/apps/docs/src/app/global-error.tsx +++ b/apps/docs/src/app/global-error.tsx @@ -9,6 +9,18 @@ const geist = Geist({ subsets: ["latin"], }); -export default function GlobalError() { - return ; +export default function GlobalError({ + error, + retry, +}: { + error: Error & { digest?: string }; + retry: () => void; +}) { + return ( + + ); } diff --git a/apps/docs/src/components/animated-beam/animated-beam-home-skeleton.tsx b/apps/docs/src/components/animated-beam/animated-beam-home-skeleton.tsx new file mode 100644 index 000000000..1d806011a --- /dev/null +++ b/apps/docs/src/components/animated-beam/animated-beam-home-skeleton.tsx @@ -0,0 +1,24 @@ +import { Skeleton } from "@vitnode/core/components/ui/skeleton"; + +const Row = ({ center }: { center?: boolean }) => ( +
+ + + +
+); + +export const AnimatedBeamHomeSkeleton = () => ( +
+
+ + + +
+ + Loading +
+); diff --git a/apps/docs/src/examples/data-table.tsx b/apps/docs/src/examples/data-table.tsx index d0d86a2c0..a1c091107 100644 --- a/apps/docs/src/examples/data-table.tsx +++ b/apps/docs/src/examples/data-table.tsx @@ -73,8 +73,8 @@ export default function DataTableExample() { pageInfo={{ hasNextPage: false, hasPreviousPage: false, - startCursor: 1, - endCursor: 1, + startCursor: null, + endCursor: null, count: 1, totalCount: 1, }} diff --git a/apps/docs/src/examples/roles.tsx b/apps/docs/src/examples/roles.tsx new file mode 100644 index 000000000..2f83cc113 --- /dev/null +++ b/apps/docs/src/examples/roles.tsx @@ -0,0 +1,64 @@ +"use client"; + +import type { RoleOption } from "@vitnode/core/components/form/fields/search-roles.action.server"; + +import { AutoForm } from "@vitnode/core/components/form/auto-form"; +import { AutoFormRoles } from "@vitnode/core/components/form/fields/input-roles"; +import { z } from "zod"; + +const formSchema = z.object({ + roleId: z.number(), + roleIds: z.array(z.number()), +}); + +const ROLES: RoleOption[] = [ + { + color: "#ef4444", + id: 1, + name: [{ languageCode: "en", name: "Administrator" }], + }, + { color: "#3b82f6", id: 2, name: [{ languageCode: "en", name: "Editor" }] }, + { color: null, id: 3, name: [{ languageCode: "en", name: "Member" }] }, +]; + +const search = async (value: string) => + Promise.resolve( + ROLES.filter(role => + role.name[0].name.toLowerCase().includes(value.toLowerCase()), + ), + ); + +export default function RolesExample() { + return ( + ( + + ), + }, + { + id: "roleIds", + component: props => ( + + ), + }, + ]} + formSchema={formSchema} + /> + ); +} diff --git a/apps/docs/src/examples/user.tsx b/apps/docs/src/examples/user.tsx new file mode 100644 index 000000000..6763ac803 --- /dev/null +++ b/apps/docs/src/examples/user.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { AutoForm } from "@vitnode/core/components/form/auto-form"; +import { + AutoFormUser, + type UserOption, +} from "@vitnode/core/components/form/fields/input-users"; +import { z } from "zod"; + +const formSchema = z.object({ + authorId: z.number(), +}); + +const PEOPLE: UserOption[] = [ + { avatarColor: "3b82f6", id: 1, name: "Ada Lovelace", nameCode: "ada" }, + { avatarColor: "ef4444", id: 2, name: "Grace Hopper", nameCode: "grace" }, + { avatarColor: "22c55e", id: 3, name: "Alan Turing", nameCode: "alan" }, +]; + +export default function UserExample() { + return ( + ( + + Promise.resolve( + PEOPLE.filter(person => + person.name.toLowerCase().includes(value.toLowerCase()), + ), + ) + } + /> + ), + }, + ]} + formSchema={formSchema} + /> + ); +} diff --git a/apps/docs/src/locales/@vitnode/blog/pl.json b/apps/docs/src/locales/@vitnode/blog/pl.json index c4dd50874..59f71db9c 100644 --- a/apps/docs/src/locales/@vitnode/blog/pl.json +++ b/apps/docs/src/locales/@vitnode/blog/pl.json @@ -1,89 +1,61 @@ { "@vitnode/blog": { "title": "Blog", - "admin": { - "nav": { - "posts": "Wpisy", - "categories": "Kategorie" - }, - "categories": { - "desc": "Zarządzaj kategoriami wpisów na blogu.", - "table": { + "content": { + "post": { + "label": "{count, plural, one {Artykuł} few {Artykuły} many {Artykułów} other {Artykułu}}", + "title": "Artykuły", + "desc": "Pisz artykuły na blogu i zarządzaj nimi.", + "fields": { "title": "Tytuł", + "friendlyUrl": "Przyjazny adres URL", + "content": "Treść", + "categoryId": "Kategoria", + "authorId": "Autor", + "status": "Status", + "publishedAt": "Opublikowano", + "updatedAt": "Zaktualizowano" + } + }, + "category": { + "label": "{count, plural, one {Kategoria} few {Kategorie} many {Kategorii} other {Kategorii}}", + "title": "Kategorie", + "desc": "Grupuj artykuły razem.", + "fields": { + "name": "Nazwa", "color": "Kolor", - "updated_at": "Zaktualizowano" - }, - "delete": { - "title": "Usuń kategorię", - "desc": "Czy na pewno chcesz usunąć kategorię ? Tej akcji nie można cofnąć.", - "confirm": "Tak, usuń tę kategorię", - "success": "Kategoria została pomyślnie usunięta." - }, - "create": { - "title": "Utwórz kategorię", - "desc": "Nowa kategoria dla wpisów na blogu.", - "form": { - "title": { - "label": "Tytuł", - "already_exists": "Kategoria o tym tytule już istnieje." - }, - "color": "Kolor" - }, - "submit": "Utwórz", - "success": "Kategoria została pomyślnie utworzona." + "updatedAt": "Zaktualizowano" + } + } + }, + "admin": { + "article": { + "content": { + "label": "Treść" }, - "edit": { - "title": "Edytuj kategorię", - "submit": "Zapisz zmiany", - "success": "Kategoria została pomyślnie zaktualizowana." + "form": { + "publish": "Publikacja", + "settings": { + "title": "Ustawienia artykułu" + } } }, - "posts": { - "desc": "Twórz wpisy na blogu i zarządzaj nimi.", - "table": { - "title": "Tytuł", - "category": "Kategoria", - "author": "Autor", - "updated_at": "Zaktualizowano" - }, - "create": { - "title": "Utwórz wpis", - "desc": "Napisz nowy artykuł na swój blog.", - "form": { - "title": { - "label": "Tytuł", - "already_exists": "Wpis o tym tytule już istnieje." - }, - "friendly_url": { - "label": "Przyjazny adres URL", - "desc": "Używany w adresie wpisu. Wypełniany automatycznie na podstawie tytułu.", - "already_exists": "Taki przyjazny adres URL już istnieje." - }, - "content": "Treść", - "category": "Kategoria" - }, - "submit": "Utwórz wpis", - "success": "Wpis został pomyślnie utworzony." - }, - "edit": { - "title": "Edytuj wpis", - "submit": "Zapisz zmiany", - "success": "Wpis został pomyślnie zaktualizowany." - }, - "delete": { - "title": "Usuń wpis", - "desc": "Czy na pewno chcesz usunąć wpis ? Tej akcji nie można cofnąć.", - "confirm": "Tak, usuń ten wpis", - "success": "Wpis został pomyślnie usunięty." + "category": { + "color": { + "label": "Kolor", + "desc": "Wyświetlany obok kategorii na listach.", + "none": "Brak koloru" } } } }, - "@vitnode/blog:posts": "Wpisy", - "@vitnode/blog:posts:can_view": "Wyświetlanie listy wpisów", - "@vitnode/blog:posts:can_create": "Tworzenie wpisów", - "@vitnode/blog:posts:can_edit": "Edytowanie wpisów", - "@vitnode/blog:posts:can_delete": "Usuwanie wpisów", + "@vitnode/blog:posts": "Artykuły", + "@vitnode/blog:posts:can_view": "Wyświetlanie listy artykułów", + "@vitnode/blog:posts:can_create": "Tworzenie artykułów", + "@vitnode/blog:posts:can_edit": "Edytowanie artykułów", + "@vitnode/blog:posts:can_delete": "Usuwanie artykułów", + "@vitnode/blog:posts:can_publish": "Publikowanie i cofanie publikacji artykułów", + "@vitnode/blog:posts:can_restore": "Przywracanie wcześniejszej wersji artykułu", "@vitnode/blog:categories": "Kategorie", "@vitnode/blog:categories:can_view": "Wyświetlanie listy kategorii", "@vitnode/blog:categories:can_create": "Tworzenie kategorii", diff --git a/apps/docs/src/locales/@vitnode/core/pl.json b/apps/docs/src/locales/@vitnode/core/pl.json index f87acb19a..6c1886d04 100644 --- a/apps/docs/src/locales/@vitnode/core/pl.json +++ b/apps/docs/src/locales/@vitnode/core/pl.json @@ -220,6 +220,8 @@ "desc": "Przepraszamy, występują problemy techniczne po stronie serwera." }, "title": "Ups! Coś poszło nie tak.", + "try_again": "Spróbuj ponownie", + "reference": "Identyfikator błędu: {digest}", "internal_server_error": "Wewnętrzny błąd serwera.", "field_required": "To pole jest wymagane.", "field_min_length": "To pole musi mieć co najmniej {min} znaków.", @@ -409,8 +411,6 @@ "drop_here": "Upuść tutaj widżet", "empty_title": "Twój pulpit jest pusty", "empty_desc": "Dodaj widżety, aby mieć ważne dane zawsze pod ręką.", - "saved_title": "Pulpit zapisany", - "saved_desc": "Ten układ należy tylko do Ciebie - inni administratorzy mają swój własny.", "error_title": "Nie udało się zapisać pulpitu", "error_desc": "Coś poszło nie tak po drodze do serwera. Spróbuj ponownie.", "refresh_error": "Nie udało się odświeżyć tego widżetu. Odśwież stronę, aby zobaczyć zmiany.", @@ -433,8 +433,6 @@ "open": "Skonfiguruj {title}", "title": "Skonfiguruj {title}", "desc": "Te ustawienia dotyczą tylko tej karty - inni administratorzy mają swoje własne.", - "saved_title": "Ustawienia zapisane", - "saved_desc": "Karta użyje ich, gdy skończysz układać pulpit.", "error_title": "Nie udało się zapisać ustawień", "error_desc": "Coś poszło nie tak po drodze do serwera. Spróbuj ponownie.", "load_error": "Nie udało się wczytać tych ustawień. Zamknij i spróbuj ponownie." diff --git a/apps/docs/src/vitnode.api.config.ts b/apps/docs/src/vitnode.api.config.ts index a146553c9..0b63906c9 100644 --- a/apps/docs/src/vitnode.api.config.ts +++ b/apps/docs/src/vitnode.api.config.ts @@ -4,6 +4,7 @@ import { DiscordSSOApiPlugin } from "@vitnode/core/api/adapters/sso/discord"; import { FacebookSSOApiPlugin } from "@vitnode/core/api/adapters/sso/facebook"; import { GoogleSSOApiPlugin } from "@vitnode/core/api/adapters/sso/google"; import { buildApiConfig } from "@vitnode/core/vitnode.config"; +import { exampleApiPlugin } from "@vitnode/example/config.api"; import { NodeCronAdapter } from "@vitnode/node-cron"; import { NodemailerEmailAdapter } from "@vitnode/nodemailer"; // import { LocalStorageAdapter } from "@vitnode/core/api/adapters/storage/local"; @@ -29,7 +30,7 @@ export const vitNodeApiConfig = buildApiConfig({ title: "VitNode API", shortTitle: "VitNode", }, - plugins: [blogApiPlugin()], + plugins: [blogApiPlugin(), exampleApiPlugin()], dbProvider: drizzle({ connection: POSTGRES_URL, casing: "camelCase", diff --git a/apps/docs/src/vitnode.config.ts b/apps/docs/src/vitnode.config.ts index 40b227c47..0c360d790 100644 --- a/apps/docs/src/vitnode.config.ts +++ b/apps/docs/src/vitnode.config.ts @@ -1,5 +1,6 @@ import { blogPlugin } from "@vitnode/blog/config"; import { buildConfig, handleRequestConfig } from "@vitnode/core/vitnode.config"; +import { examplePlugin } from "@vitnode/example/config"; import { getRequestConfig } from "next-intl/server"; import { i18n } from "./i18n"; @@ -9,7 +10,7 @@ export const vitNodeConfig = buildConfig({ title: "VitNode", shortTitle: "VitNode", }, - plugins: [blogPlugin()], + plugins: [blogPlugin(), examplePlugin()], debug: false, i18n, theme: { diff --git a/package.json b/package.json index 81e733438..d38312371 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "lint": "turbo lint", "lint:fix": "turbo lint:fix", "test": "turbo test", + "test:types": "turbo test:types", "test:e2e": "turbo test:e2e", "i18n:create": "turbo i18n:create", "i18n:check": "turbo i18n:check", diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/api/vitnode/content/revalidate/route.ts b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/api/vitnode/content/revalidate/route.ts new file mode 100644 index 000000000..983263af6 --- /dev/null +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/api/vitnode/content/revalidate/route.ts @@ -0,0 +1 @@ +export { POST } from "@vitnode/core/content/next/revalidate-route"; diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error copy.tsx b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error copy.tsx deleted file mode 100644 index 786d47b66..000000000 --- a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error copy.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { GlobalErrorView } from "@vitnode/core/views/error/global-error-view"; -import { Geist } from "next/font/google"; - -import "./global.css"; - -const geist = Geist({ - subsets: ["latin"], -}); - -export default function GlobalError() { - return ; -} diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx index 786d47b66..ccb9d5ed2 100644 --- a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx @@ -9,6 +9,18 @@ const geist = Geist({ subsets: ["latin"], }); -export default function GlobalError() { - return ; +export default function GlobalError({ + error, + retry, +}: { + error: Error & { digest?: string }; + retry: () => void; +}) { + return ( + + ); } diff --git a/packages/elasticsearch/package.json b/packages/elasticsearch/package.json index 4f63f4d29..f7b819da1 100644 --- a/packages/elasticsearch/package.json +++ b/packages/elasticsearch/package.json @@ -27,9 +27,7 @@ "build:plugins": "vitnode build", "dev:plugins": "vitnode dev", "lint": "eslint .", - "lint:fix": "eslint . --fix", - "test": "vitest run", - "test:watch": "vitest" + "lint:fix": "eslint . --fix" }, "dependencies": { "@elastic/elasticsearch": "^9.4.2" @@ -41,7 +39,6 @@ "@vitnode/core": "workspace:*", "eslint": "^10.7.0", "tsc-alias": "^1.9.1", - "typescript": "^6.0.3", - "vitest": "^4.1.10" + "typescript": "^6.0.3" } } diff --git a/packages/elasticsearch/src/index.test.ts b/packages/elasticsearch/src/index.test.ts deleted file mode 100644 index ad217f6bd..000000000 --- a/packages/elasticsearch/src/index.test.ts +++ /dev/null @@ -1,375 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const { - Client, - ResponseError, - index, - deleteByQuery, - ping, - search, - exists, - create, -} = vi.hoisted(() => { - const index = vi.fn(); - const bulk = vi.fn(); - const del = vi.fn(); - const deleteByQuery = vi.fn(); - const ping = vi.fn(); - const search = vi.fn(); - const exists = vi.fn(); - const create = vi.fn(); - const Client = vi.fn(function () { - return { - index, - bulk, - delete: del, - deleteByQuery, - ping, - search, - indices: { exists, create }, - }; - }); - class ResponseError extends Error { - constructor(body: unknown) { - super("elasticsearch response error"); - this.body = body; - } - body: unknown; - } - - return { - Client, - ResponseError, - index, - deleteByQuery, - ping, - search, - exists, - create, - }; -}); - -vi.mock("@elastic/elasticsearch", () => ({ - Client, - errors: { ResponseError }, -})); - -const alreadyExistsError = () => - new ResponseError({ error: { type: "resource_already_exists_exception" } }); - -import { ElasticsearchSearchAdapter } from "./index"; - -const c = {} as never; -const config = { node: "http://localhost:9200", index: "test" }; -const doc = { - itemType: "blog_post", - itemId: 1, - title: "Hi", - content: "Hello world", - authorId: 5, - createdAt: new Date("2026-01-01T00:00:00.000Z"), -}; - -beforeEach(() => { - vi.clearAllMocks(); - exists.mockReset().mockResolvedValue(true); - create.mockReset().mockResolvedValue(undefined); -}); - -describe("ElasticsearchSearchAdapter configuration", () => { - it("throws when neither node nor cloudId is provided", async () => { - await expect( - ElasticsearchSearchAdapter({}).delete(c, "blog_post", 1), - ).rejects.toThrow("Missing Elasticsearch configuration"); - expect(Client).not.toHaveBeenCalled(); - }); - - it("constructs the client lazily and reuses it", async () => { - const adapter = ElasticsearchSearchAdapter(config); - expect(Client).not.toHaveBeenCalled(); - - await adapter.delete(c, "blog_post", 1); - await adapter.delete(c, "blog_post", 2); - - expect(Client).toHaveBeenCalledTimes(1); - }); -}); - -describe("ElasticsearchSearchAdapter.index", () => { - it("indexes a document with a deterministic id and ISO date", async () => { - await ElasticsearchSearchAdapter(config).index(c, doc); - - expect(index).toHaveBeenCalledWith({ - index: "test", - id: "blog_post:1:", - document: expect.objectContaining({ - itemType: "blog_post", - itemId: 1, - authorId: 5, - title: "Hi", - content: "Hello world", - isPublic: true, - createdAt: "2026-01-01T00:00:00.000Z", - }), - }); - }); - - it("creates the index with a mapping when it does not exist", async () => { - exists.mockResolvedValue(false); - - await ElasticsearchSearchAdapter(config).index(c, doc); - - expect(create).toHaveBeenCalledWith( - expect.objectContaining({ index: "test" }), - ); - }); - - it("defaults languageCode to an empty string when omitted", async () => { - await ElasticsearchSearchAdapter(config).index(c, doc); - - expect(index).toHaveBeenCalledWith( - expect.objectContaining({ - document: expect.objectContaining({ languageCode: "" }), - }), - ); - }); - - it("indexes the document languageCode and scopes the id by language", async () => { - await ElasticsearchSearchAdapter(config).index(c, { - ...doc, - languageCode: "pl", - }); - - expect(index).toHaveBeenCalledWith( - expect.objectContaining({ - id: "blog_post:1:pl", - document: expect.objectContaining({ languageCode: "pl" }), - }), - ); - }); - - it("keeps language variants of one item as distinct documents", async () => { - const adapter = ElasticsearchSearchAdapter(config); - - await adapter.index(c, { ...doc, languageCode: "en" }); - await adapter.index(c, { ...doc, languageCode: "pl" }); - - const ids = index.mock.calls.map(call => call[0].id); - expect(ids).toEqual(["blog_post:1:en", "blog_post:1:pl"]); - }); -}); - -describe("ElasticsearchSearchAdapter index initialization", () => { - it("creates the index only once under concurrent calls", async () => { - exists.mockResolvedValue(false); - const adapter = ElasticsearchSearchAdapter(config); - - await Promise.all([ - adapter.index(c, doc), - adapter.index(c, doc), - adapter.bulkIndex(c, [doc]), - ]); - - expect(exists).toHaveBeenCalledTimes(1); - expect(create).toHaveBeenCalledTimes(1); - }); - - it("does not re-check the index after it is ensured", async () => { - exists.mockResolvedValue(true); - const adapter = ElasticsearchSearchAdapter(config); - - await adapter.index(c, doc); - await adapter.index(c, doc); - - expect(exists).toHaveBeenCalledTimes(1); - }); - - it("swallows resource_already_exists_exception from a concurrent creator", async () => { - exists.mockResolvedValue(false); - create.mockRejectedValue(alreadyExistsError()); - - await expect( - ElasticsearchSearchAdapter(config).index(c, doc), - ).resolves.toBeUndefined(); - - expect(index).toHaveBeenCalledTimes(1); - }); - - it("propagates unexpected errors from create", async () => { - exists.mockResolvedValue(false); - create.mockRejectedValue(new Error("cluster unavailable")); - - await expect( - ElasticsearchSearchAdapter(config).index(c, doc), - ).rejects.toThrow("cluster unavailable"); - }); - - it("retries initialization after a transient failure", async () => { - exists.mockRejectedValueOnce(new Error("network")).mockResolvedValue(true); - const adapter = ElasticsearchSearchAdapter(config); - - await expect(adapter.index(c, doc)).rejects.toThrow("network"); - await expect(adapter.index(c, doc)).resolves.toBeUndefined(); - - expect(exists).toHaveBeenCalledTimes(2); - expect(index).toHaveBeenCalledTimes(1); - }); -}); - -describe("ElasticsearchSearchAdapter.delete", () => { - it("removes every language variant of an item by query", async () => { - await ElasticsearchSearchAdapter(config).delete(c, "blog_post", 1); - - expect(deleteByQuery).toHaveBeenCalledWith( - expect.objectContaining({ - query: { - bool: { - filter: [ - { term: { itemType: "blog_post" } }, - { term: { itemId: 1 } }, - ], - }, - }, - }), - { ignore: [404] }, - ); - }); -}); - -describe("ElasticsearchSearchAdapter.clear", () => { - it("clears one type with a term query", async () => { - await ElasticsearchSearchAdapter(config).clear(c, "blog_post"); - - expect(deleteByQuery).toHaveBeenCalledWith( - expect.objectContaining({ query: { term: { itemType: "blog_post" } } }), - { ignore: [404] }, - ); - }); - - it("clears everything with match_all", async () => { - await ElasticsearchSearchAdapter(config).clear(c); - - expect(deleteByQuery).toHaveBeenCalledWith( - expect.objectContaining({ query: { match_all: {} } }), - { ignore: [404] }, - ); - }); -}); - -describe("ElasticsearchSearchAdapter.search", () => { - beforeEach(() => { - search.mockResolvedValue({ - hits: { - total: { value: 1 }, - hits: [ - { - _id: "blog_post:1", - _score: 1.23, - _source: { - pluginId: "core", - itemType: "blog_post", - itemId: 1, - languageCode: "en", - authorId: 5, - title: "Hi", - content: "Hello world", - containerType: null, - containerId: null, - url: "/blog/1/hi", - isPublic: true, - metadata: {}, - createdAt: "2026-01-01T00:00:00.000Z", - }, - }, - ], - }, - }); - }); - - it("maps hits and pagination, leaving author for the model to hydrate", async () => { - const result = await ElasticsearchSearchAdapter(config).search(c, { - term: "hello", - sort: "relevance", - first: 20, - }); - - expect(result.pageInfo.totalCount).toBe(1); - expect(result.edges[0]).toMatchObject({ - itemId: 1, - languageCode: "en", - authorId: 5, - score: 1.23, - author: null, - url: "/blog/1/hi", - }); - }); - - it("filters by languageCode, matching the locale and language-agnostic rows", async () => { - await ElasticsearchSearchAdapter(config).search(c, { - term: "hello", - sort: "relevance", - languageCode: "en", - }); - - const arg = search.mock.calls[0][0]; - expect(arg.query.bool.filter).toContainEqual({ - terms: { languageCode: ["en", ""] }, - }); - }); - - it("omits the languageCode filter when no locale is requested", async () => { - await ElasticsearchSearchAdapter(config).search(c, { - term: "hello", - sort: "relevance", - }); - - const arg = search.mock.calls[0][0]; - expect(arg.query.bool.filter).not.toContainEqual( - expect.objectContaining({ - terms: expect.objectContaining({ languageCode: expect.anything() }), - }), - ); - }); - - it("builds a multi_match query for a term", async () => { - await ElasticsearchSearchAdapter(config).search(c, { - term: "hello", - sort: "relevance", - }); - - const arg = search.mock.calls[0][0]; - expect(arg.query.bool.must[0].multi_match.query).toBe("hello"); - }); - - it("wraps the query in function_score when ranking is configured", async () => { - await ElasticsearchSearchAdapter({ - ...config, - ranking: { timeDecay: { scale: "30d" } }, - }).search(c, { term: "hello", sort: "relevance" }); - - const arg = search.mock.calls[0][0]; - expect(arg.query.function_score).toBeDefined(); - expect(arg.query.function_score.functions[0].gauss).toBeDefined(); - }); - - it("sorts by date without a term", async () => { - await ElasticsearchSearchAdapter(config).search(c, { sort: "newest" }); - - const arg = search.mock.calls[0][0]; - expect(arg.sort).toEqual([{ createdAt: { order: "desc" } }]); - }); -}); - -describe("ElasticsearchSearchAdapter.ping", () => { - it("returns the client ping result", async () => { - ping.mockResolvedValue(true); - - expect(await ElasticsearchSearchAdapter(config).ping?.(c)).toBe(true); - }); - - it("returns false when ping throws", async () => { - ping.mockRejectedValue(new Error("down")); - - expect(await ElasticsearchSearchAdapter(config).ping?.(c)).toBe(false); - }); -}); diff --git a/packages/elasticsearch/src/index.ts b/packages/elasticsearch/src/index.ts index e1dddb680..08fee597f 100644 --- a/packages/elasticsearch/src/index.ts +++ b/packages/elasticsearch/src/index.ts @@ -60,7 +60,10 @@ const isIndexAlreadyExistsError = (error: unknown): boolean => "resource_already_exists_exception"; const toSource = (doc: SearchDocument): EsSource => ({ - pluginId: "core", + // `SearchModel` resolves ownership before any provider sees the document, so + // this fallback is only for a provider called directly - it must never be the + // reason a mirrored document disagrees with the canonical row. + pluginId: doc.pluginId ?? "core", itemType: doc.itemType, itemId: doc.itemId, languageCode: doc.languageCode ?? "", @@ -281,7 +284,48 @@ export const ElasticsearchSearchAdapter = ( return { name: "elasticsearch", - capabilities: { facets: true, timeDecay: true, authorBoost: true }, + // `languageScopedDelete`: `delete` below filters the delete-by-query on + // `languageCode` when it is given one, so taking the Polish translation down + // leaves the English document in place. + capabilities: { + facets: true, + timeDecay: true, + authorBoost: true, + languageScopedDelete: true, + }, + + /** + * How many documents this index holds for one collection. + * + * `_count` rather than a search: it returns a number without fetching a + * single document, so a diagnostic over a large index costs the same as one + * over an empty one. `languageCode` narrows it to a single translation, + * which is what makes per-locale drift visible - "Polish is missing forty + * documents" is not something a total can say. + * + * A missing index means zero rather than an error: an install that has never + * rebuilt has no index yet, and that is drift to report, not a crash. + */ + count: async (_c, { itemType, languageCode }) => { + const response = await getClient().count( + { + index, + query: { + bool: { + filter: [ + { term: { itemType } }, + ...(languageCode === undefined + ? [] + : [{ term: { languageCode } }]), + ], + }, + }, + }, + { ignore: [404] }, + ); + + return response.count ?? 0; + }, index: async (_c, doc) => { await ensureIndex(); @@ -310,13 +354,23 @@ export const ElasticsearchSearchAdapter = ( }, // One document per language shares an (itemType, itemId), so remove every - // language variant with a query rather than a single id. - delete: async (_c, itemType, itemId) => { + // language variant with a query rather than a single id - unless the caller + // named one, which is how a single translation is taken down without + // touching the others. + delete: async (_c, itemType, itemId, languageCode) => { await getClient().deleteByQuery( { index, query: { - bool: { filter: [{ term: { itemType } }, { term: { itemId } }] }, + bool: { + filter: [ + { term: { itemType } }, + { term: { itemId } }, + ...(languageCode === undefined + ? [] + : [{ term: { languageCode } }]), + ], + }, }, }, { ignore: [404] }, diff --git a/packages/s3/package.json b/packages/s3/package.json index de11e6359..86ec514c7 100644 --- a/packages/s3/package.json +++ b/packages/s3/package.json @@ -28,9 +28,7 @@ "build:plugins": "vitnode build", "dev:plugins": "vitnode dev", "lint": "eslint .", - "lint:fix": "eslint . --fix", - "test": "vitest run", - "test:watch": "vitest" + "lint:fix": "eslint . --fix" }, "dependencies": { "@aws-sdk/client-s3": "^3.1092.0" @@ -42,7 +40,6 @@ "@vitnode/core": "workspace:*", "eslint": "^10.7.0", "tsc-alias": "^1.9.1", - "typescript": "^6.0.3", - "vitest": "^4.1.10" + "typescript": "^6.0.3" } } diff --git a/packages/s3/src/index.test.ts b/packages/s3/src/index.test.ts deleted file mode 100644 index b922c4141..000000000 --- a/packages/s3/src/index.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const { DeleteObjectCommand, PutObjectCommand, S3Client, send } = vi.hoisted( - () => { - const send = vi.fn(); - const S3Client = vi.fn(function () { - return { send }; - }); - const DeleteObjectCommand = vi.fn(function ( - input: Record, - ) { - return { input }; - }); - const PutObjectCommand = vi.fn(function (input: Record) { - return { input }; - }); - - return { DeleteObjectCommand, PutObjectCommand, S3Client, send }; - }, -); - -vi.mock("@aws-sdk/client-s3", () => ({ - DeleteObjectCommand, - PutObjectCommand, - S3Client, -})); - -import { S3StorageAdapter } from "./index"; - -const config = { - accessKeyId: "AKIAEXAMPLE", - bucket: "media", - secretAccessKey: "secret", -}; - -beforeEach(() => { - vi.clearAllMocks(); - send.mockResolvedValue({}); -}); - -describe("S3StorageAdapter configuration", () => { - it.each([ - ["bucket", { ...config, bucket: "" }], - ["accessKeyId", { ...config, accessKeyId: "" }], - ["secretAccessKey", { ...config, secretAccessKey: "" }], - ])("rejects from delete when %s is missing", async (_, partial) => { - await expect(S3StorageAdapter(partial).delete("photo.png")).rejects.toThrow( - "Missing S3 configuration", - ); - }); - - it("rejects from upload when configuration is incomplete", async () => { - await expect( - S3StorageAdapter({ ...config, bucket: "" }).upload({ - body: Buffer.from("x"), - key: "photo.png", - }), - ).rejects.toThrow("Missing S3 configuration"); - }); - - it("never constructs a client when configuration is incomplete", async () => { - await expect( - S3StorageAdapter({ ...config, accessKeyId: "" }).delete("photo.png"), - ).rejects.toThrow(); - expect(S3Client).not.toHaveBeenCalled(); - }); -}); - -describe("S3StorageAdapter client", () => { - it("passes region and credentials to the client", async () => { - await S3StorageAdapter({ ...config, region: "us-east-1" }).delete( - "photo.png", - ); - - expect(S3Client).toHaveBeenCalledWith({ - region: "us-east-1", - endpoint: undefined, - forcePathStyle: false, - credentials: { - accessKeyId: "AKIAEXAMPLE", - secretAccessKey: "secret", - }, - }); - }); - - it("defaults the region to auto", async () => { - await S3StorageAdapter(config).delete("photo.png"); - - expect(S3Client).toHaveBeenCalledWith( - expect.objectContaining({ region: "auto" }), - ); - }); - - it("enables path-style addressing when a custom endpoint is set", async () => { - await S3StorageAdapter({ - ...config, - endpoint: "https://account.r2.cloudflarestorage.com", - }).delete("photo.png"); - - expect(S3Client).toHaveBeenCalledWith( - expect.objectContaining({ - endpoint: "https://account.r2.cloudflarestorage.com", - forcePathStyle: true, - }), - ); - }); - - it("honors an explicit forcePathStyle over the endpoint default", async () => { - await S3StorageAdapter({ - ...config, - endpoint: "https://account.r2.cloudflarestorage.com", - forcePathStyle: false, - }).delete("photo.png"); - - expect(S3Client).toHaveBeenCalledWith( - expect.objectContaining({ forcePathStyle: false }), - ); - }); - - it("constructs the client lazily and reuses it across calls", async () => { - const adapter = S3StorageAdapter(config); - expect(S3Client).not.toHaveBeenCalled(); - - await adapter.delete("a.png"); - await adapter.delete("b.png"); - - expect(S3Client).toHaveBeenCalledTimes(1); - }); -}); - -describe("S3StorageAdapter.getUrl", () => { - it("uses publicUrl when set, stripping a trailing slash", () => { - const url = S3StorageAdapter({ - ...config, - publicUrl: "https://cdn.example.com/", - }).getUrl("photo.png"); - - expect(url).toBe("https://cdn.example.com/photo.png"); - }); - - it("uses the endpoint and bucket when no publicUrl is set", () => { - const url = S3StorageAdapter({ - ...config, - endpoint: "https://account.r2.cloudflarestorage.com/", - }).getUrl("photo.png"); - - expect(url).toBe( - "https://account.r2.cloudflarestorage.com/media/photo.png", - ); - }); - - it("prefers publicUrl over the endpoint", () => { - const url = S3StorageAdapter({ - ...config, - endpoint: "https://account.r2.cloudflarestorage.com", - publicUrl: "https://cdn.example.com", - }).getUrl("photo.png"); - - expect(url).toBe("https://cdn.example.com/photo.png"); - }); - - it("falls back to the regional AWS url", () => { - const url = S3StorageAdapter({ ...config, region: "eu-west-1" }).getUrl( - "photo.png", - ); - - expect(url).toBe("https://media.s3.eu-west-1.amazonaws.com/photo.png"); - }); - - it("does not construct a client", () => { - S3StorageAdapter(config).getUrl("photo.png"); - - expect(S3Client).not.toHaveBeenCalled(); - }); -}); - -describe("S3StorageAdapter.delete", () => { - it("sends a DeleteObjectCommand for the key", async () => { - await S3StorageAdapter(config).delete("photo.png"); - - expect(DeleteObjectCommand).toHaveBeenCalledWith({ - Bucket: "media", - Key: "photo.png", - }); - expect(PutObjectCommand).not.toHaveBeenCalled(); - expect(send).toHaveBeenCalledTimes(1); - }); - - it("propagates errors from the client", async () => { - const error = new Error("access denied"); - send.mockRejectedValue(error); - - await expect(S3StorageAdapter(config).delete("photo.png")).rejects.toBe( - error, - ); - }); -}); - -describe("S3StorageAdapter.upload", () => { - it("sends a PutObjectCommand and returns the key and url", async () => { - const body = Buffer.from("file-contents"); - - const result = await S3StorageAdapter(config).upload({ - body, - contentType: "image/png", - key: "photo.png", - }); - - expect(PutObjectCommand).toHaveBeenCalledWith({ - Bucket: "media", - Key: "photo.png", - Body: body, - ContentType: "image/png", - }); - expect(send).toHaveBeenCalledTimes(1); - expect(result).toEqual({ - key: "photo.png", - url: "https://media.s3.auto.amazonaws.com/photo.png", - }); - }); - - it("returns a publicUrl-based url when configured", async () => { - const result = await S3StorageAdapter({ - ...config, - publicUrl: "https://cdn.example.com", - }).upload({ body: Buffer.from("x"), key: "photo.png" }); - - expect(result.url).toBe("https://cdn.example.com/photo.png"); - }); - - it("propagates errors from the client", async () => { - const error = new Error("upload failed"); - send.mockRejectedValue(error); - - await expect( - S3StorageAdapter(config).upload({ - body: Buffer.from("x"), - key: "photo.png", - }), - ).rejects.toBe(error); - }); -}); diff --git a/packages/s3/vitest.config.ts b/packages/s3/vitest.config.ts deleted file mode 100644 index 28e3aea5c..000000000 --- a/packages/s3/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - environment: "node", - exclude: ["**/node_modules/**", "**/dist/**"], - }, -}); diff --git a/packages/supabase-storage/package.json b/packages/supabase-storage/package.json index 728b0f006..f937c4bb4 100644 --- a/packages/supabase-storage/package.json +++ b/packages/supabase-storage/package.json @@ -27,9 +27,7 @@ "build:plugins": "vitnode build", "dev:plugins": "vitnode dev", "lint": "eslint .", - "lint:fix": "eslint . --fix", - "test": "vitest run", - "test:watch": "vitest" + "lint:fix": "eslint . --fix" }, "dependencies": { "@supabase/storage-js": "^2.110.8" @@ -41,7 +39,6 @@ "@vitnode/core": "workspace:*", "eslint": "^10.7.0", "tsc-alias": "^1.9.1", - "typescript": "^6.0.3", - "vitest": "^4.1.10" + "typescript": "^6.0.3" } } diff --git a/packages/supabase-storage/src/index.test.ts b/packages/supabase-storage/src/index.test.ts deleted file mode 100644 index b0fe26c13..000000000 --- a/packages/supabase-storage/src/index.test.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const { StorageClient, from, getPublicUrl, remove, upload } = vi.hoisted(() => { - const remove = vi.fn(); - const getPublicUrl = vi.fn(); - const upload = vi.fn(); - const from = vi.fn(() => ({ getPublicUrl, remove, upload })); - const StorageClient = vi.fn(function () { - return { from }; - }); - - return { StorageClient, from, getPublicUrl, remove, upload }; -}); - -vi.mock("@supabase/storage-js", () => ({ StorageClient })); - -import { SupabaseStorageAdapter } from "./index"; - -const config = { - bucket: "media", - secretKey: "sb_secret_123", - url: "https://project.supabase.co", -}; - -beforeEach(() => { - vi.clearAllMocks(); - remove.mockResolvedValue({ error: null }); - upload.mockResolvedValue({ error: null }); - getPublicUrl.mockReturnValue({ - data: { publicUrl: "https://project.supabase.co/public/media/photo.png" }, - }); -}); - -describe("SupabaseStorageAdapter configuration", () => { - it.each([ - ["bucket", { ...config, bucket: "" }], - ["secretKey", { ...config, secretKey: "" }], - ["url", { ...config, url: "" }], - ])("throws from getUrl when %s is missing", (_, partial) => { - expect(() => SupabaseStorageAdapter(partial).getUrl("photo.png")).toThrow( - "Missing Supabase Storage configuration", - ); - }); - - it("rejects from delete when configuration is incomplete", async () => { - await expect( - SupabaseStorageAdapter({ ...config, url: "" }).delete("photo.png"), - ).rejects.toThrow("Missing Supabase Storage configuration"); - }); - - it("rejects from upload when configuration is incomplete", async () => { - await expect( - SupabaseStorageAdapter({ ...config, secretKey: "" }).upload({ - body: Buffer.from("x"), - key: "photo.png", - }), - ).rejects.toThrow("Missing Supabase Storage configuration"); - }); - - it("never constructs a client when configuration is incomplete", () => { - expect(() => - SupabaseStorageAdapter({ ...config, bucket: "" }).getUrl("photo.png"), - ).toThrow(); - expect(StorageClient).not.toHaveBeenCalled(); - }); -}); - -describe("SupabaseStorageAdapter client", () => { - it("builds the storage endpoint and auth headers from config", () => { - SupabaseStorageAdapter(config).getUrl("photo.png"); - - expect(StorageClient).toHaveBeenCalledWith( - "https://project.supabase.co/storage/v1", - { - apikey: "sb_secret_123", - Authorization: "Bearer sb_secret_123", - }, - ); - }); - - it("strips a trailing slash from the url", () => { - SupabaseStorageAdapter({ - ...config, - url: "https://project.supabase.co/", - }).getUrl("photo.png"); - - expect(StorageClient).toHaveBeenCalledWith( - "https://project.supabase.co/storage/v1", - expect.anything(), - ); - }); - - it("constructs the client lazily and reuses it across calls", () => { - const adapter = SupabaseStorageAdapter(config); - expect(StorageClient).not.toHaveBeenCalled(); - - adapter.getUrl("a.png"); - adapter.getUrl("b.png"); - - expect(StorageClient).toHaveBeenCalledTimes(1); - }); - - it("isolates the client between adapter instances", () => { - SupabaseStorageAdapter(config).getUrl("a.png"); - SupabaseStorageAdapter(config).getUrl("b.png"); - - expect(StorageClient).toHaveBeenCalledTimes(2); - }); -}); - -describe("SupabaseStorageAdapter.getUrl", () => { - it("returns the public url for the key from the configured bucket", () => { - const url = SupabaseStorageAdapter(config).getUrl("photo.png"); - - expect(from).toHaveBeenCalledWith("media"); - expect(getPublicUrl).toHaveBeenCalledWith("photo.png"); - expect(url).toBe("https://project.supabase.co/public/media/photo.png"); - }); -}); - -describe("SupabaseStorageAdapter.delete", () => { - it("removes the key from the configured bucket", async () => { - await SupabaseStorageAdapter(config).delete("photo.png"); - - expect(from).toHaveBeenCalledWith("media"); - expect(remove).toHaveBeenCalledWith(["photo.png"]); - }); - - it("throws the error returned by the storage client", async () => { - const error = new Error("remove failed"); - remove.mockResolvedValue({ error }); - - await expect( - SupabaseStorageAdapter(config).delete("photo.png"), - ).rejects.toBe(error); - }); -}); - -describe("SupabaseStorageAdapter.upload", () => { - it("uploads with upsert and returns the key and public url", async () => { - const body = Buffer.from("file-contents"); - - const result = await SupabaseStorageAdapter(config).upload({ - body, - contentType: "image/png", - key: "photo.png", - }); - - expect(from).toHaveBeenCalledWith("media"); - expect(upload).toHaveBeenCalledWith("photo.png", body, { - contentType: "image/png", - upsert: true, - }); - expect(result).toEqual({ - key: "photo.png", - url: "https://project.supabase.co/public/media/photo.png", - }); - }); - - it("passes an undefined contentType through untouched", async () => { - await SupabaseStorageAdapter(config).upload({ - body: Buffer.from("x"), - key: "photo.png", - }); - - expect(upload).toHaveBeenCalledWith("photo.png", expect.any(Buffer), { - contentType: undefined, - upsert: true, - }); - }); - - it("throws the error returned by the storage client", async () => { - const error = new Error("upload failed"); - upload.mockResolvedValue({ error }); - - await expect( - SupabaseStorageAdapter(config).upload({ - body: Buffer.from("x"), - key: "photo.png", - }), - ).rejects.toBe(error); - }); - - it("does not build a public url when the upload fails", async () => { - upload.mockResolvedValue({ error: new Error("upload failed") }); - - await expect( - SupabaseStorageAdapter(config).upload({ - body: Buffer.from("x"), - key: "photo.png", - }), - ).rejects.toThrow(); - expect(getPublicUrl).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/supabase-storage/vitest.config.ts b/packages/supabase-storage/vitest.config.ts deleted file mode 100644 index 28e3aea5c..000000000 --- a/packages/supabase-storage/vitest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - environment: "node", - exclude: ["**/node_modules/**", "**/dist/**"], - }, -}); diff --git a/packages/vitnode/.swcrc b/packages/vitnode/.swcrc index 8f099dc7a..91fc9a4b0 100644 --- a/packages/vitnode/.swcrc +++ b/packages/vitnode/.swcrc @@ -1,6 +1,6 @@ { "$schema": "https://swc.rs/schema.json", - "exclude": ["\\.test\\.tsx?$"], + "exclude": ["\\.test\\.tsx?$", "\\.test-d\\.ts$", "^src/tests/"], "minify": true, "jsc": { "baseUrl": "./", diff --git a/packages/vitnode/config/next.config.ts b/packages/vitnode/config/next.config.ts index 90cd4de8d..9317acb61 100644 --- a/packages/vitnode/config/next.config.ts +++ b/packages/vitnode/config/next.config.ts @@ -8,6 +8,6 @@ export const vitNodeNextConfig = (config: NextConfig): NextConfig => ...config, serverExternalPackages: [ ...(config.serverExternalPackages ?? []), - "ioredis", + "redis", ], }); diff --git a/packages/vitnode/package.json b/packages/vitnode/package.json index 4c63edb60..7c91a77b0 100644 --- a/packages/vitnode/package.json +++ b/packages/vitnode/package.json @@ -43,8 +43,6 @@ "@react-email/ui": "^6.9.0", "@swc/cli": "^0.8.1", "@swc/core": "^1.15.46", - "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.2", "@types/node": "^26.1.1", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", @@ -81,6 +79,31 @@ "vitnode": "./cli.mjs" }, "exports": { + "./content": { + "import": "./dist/src/content/index.js", + "types": "./dist/src/content/index.d.ts", + "default": "./dist/src/content/index.js" + }, + "./content/server": { + "import": "./dist/src/content/server/index.js", + "types": "./dist/src/content/server/index.d.ts", + "default": "./dist/src/content/server/index.js" + }, + "./content/next": { + "import": "./dist/src/content/next/index.js", + "types": "./dist/src/content/next/index.d.ts", + "default": "./dist/src/content/next/index.js" + }, + "./content/next/revalidate-route": { + "import": "./dist/src/content/next/revalidate-route.server.js", + "types": "./dist/src/content/next/revalidate-route.server.d.ts", + "default": "./dist/src/content/next/revalidate-route.server.js" + }, + "./content/admin-form": { + "import": "./dist/src/views/admin/views/content/form/index.js", + "types": "./dist/src/views/admin/views/content/form/index.d.ts", + "default": "./dist/src/views/admin/views/content/form/index.js" + }, "./api/config": { "import": "./dist/src/api/config.js", "types": "./dist/src/api/config.d.ts", @@ -104,6 +127,7 @@ "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", + "test:types": "vitest run --typecheck.only", "lint": "eslint .", "lint:fix": "eslint . --fix" }, @@ -131,7 +155,6 @@ "dotenv": "^17.4.2", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", - "ioredis": "^5.11.1", "motion": "^12.42.2", "next-themes": "^0.4.6", "postgres": "^3.4.9", @@ -141,6 +164,7 @@ "react-resizable-panels": "^4.12.2", "react-scan": "^0.5.7", "recharts": "^3.10.0", + "redis": "^6.2.1", "server-only": "^0.0.1", "shadcn": "^4.14.0", "sharp": "^0.35.3", diff --git a/packages/vitnode/scripts/build.test.ts b/packages/vitnode/scripts/build.test.ts deleted file mode 100644 index ac13c04bc..000000000 --- a/packages/vitnode/scripts/build.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -const { runInteractiveShellCommand } = vi.hoisted(() => ({ - runInteractiveShellCommand: vi.fn(), -})); - -vi.mock("./run-interactive-shell-command.js", () => ({ - runInteractiveShellCommand, -})); - -import { buildPlugin } from "./build"; - -describe("buildPlugin", () => { - beforeEach(() => { - runInteractiveShellCommand.mockReset(); - runInteractiveShellCommand.mockResolvedValue(true); - }); - - it("runs tsc, swc and tsc-alias in order", async () => { - await buildPlugin(); - - expect(runInteractiveShellCommand.mock.calls.map(call => call[0])).toEqual([ - "tsc", - "swc", - "tsc-alias", - ]); - }); - - it("passes the build tsconfig and swc config to the tools", async () => { - await buildPlugin(); - - expect(runInteractiveShellCommand).toHaveBeenNthCalledWith(1, "tsc", [ - "-p", - "tsconfig.build.json", - ]); - expect(runInteractiveShellCommand).toHaveBeenNthCalledWith(2, "swc", [ - "src", - "-d", - "dist", - "--config-file", - ".swcrc", - "--copy-files", - ]); - expect(runInteractiveShellCommand).toHaveBeenNthCalledWith(3, "tsc-alias", [ - "-p", - "tsconfig.build.json", - ]); - }); - - it("stops on the first failing step", async () => { - runInteractiveShellCommand.mockReset(); - runInteractiveShellCommand.mockRejectedValueOnce(new Error("tsc failed")); - - await expect(buildPlugin()).rejects.toThrow("tsc failed"); - expect(runInteractiveShellCommand).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/vitnode/src/api/adapters/search/postgres.ts b/packages/vitnode/src/api/adapters/search/postgres.ts index 9f8f3ec43..3a4ee863f 100644 --- a/packages/vitnode/src/api/adapters/search/postgres.ts +++ b/packages/vitnode/src/api/adapters/search/postgres.ts @@ -75,7 +75,19 @@ const buildFilters = (params: SearchQueryParams): SQL | undefined => { export const PostgresSearchAdapter = (): SearchProviderApiPlugin => ({ name: "postgres", - capabilities: { authorBoost: false, facets: false, timeDecay: false }, + // Two capabilities that are both true for the same reason: this provider's + // store *is* `core_search_index`. `languageScopedDelete` because + // `SearchModel.delete` already narrows that table by `languageCode` before it + // gets here, and `canonicalStorage` because there is no second copy to drift + // from - so diagnostics can report the canonical count as the provider count + // rather than asking the same table twice. + capabilities: { + authorBoost: false, + canonicalStorage: true, + facets: false, + languageScopedDelete: true, + timeDecay: false, + }, // The SearchModel owns the canonical `core_search_index` table, which is this // provider's store, so the write methods are intentionally no-ops. diff --git a/packages/vitnode/src/api/config.ts b/packages/vitnode/src/api/config.ts index 90b1683ef..90bfc81c6 100644 --- a/packages/vitnode/src/api/config.ts +++ b/packages/vitnode/src/api/config.ts @@ -83,6 +83,7 @@ export function VitNodeAPI({ authorization: vitNodeApiConfig.authorization, dbProvider: vitNodeApiConfig.dbProvider, captcha: vitNodeApiConfig.captcha, + content: vitNodeApiConfig.content, cron: vitNodeApiConfig.cron, events: vitNodeApiConfig.events, search: vitNodeApiConfig.search, diff --git a/packages/vitnode/src/api/lib/cache-client.ts b/packages/vitnode/src/api/lib/cache-client.ts index 4abb0bd6c..1bfa310f3 100644 --- a/packages/vitnode/src/api/lib/cache-client.ts +++ b/packages/vitnode/src/api/lib/cache-client.ts @@ -1,17 +1,14 @@ -import { Redis } from "ioredis"; +import { createClient } from "redis"; -import type { CacheConfig } from "./cache"; +import type { CacheClient, CacheConfig } from "./cache"; -export const createCacheClient = (config?: CacheConfig): null | Redis => { +export const createCacheClient = (config?: CacheConfig): CacheClient | null => { if (!config) return null; - const { url, ...options } = config; - // `enableOfflineQueue: false` makes commands fail fast (instead of queueing) + // `disableOfflineQueue: true` makes commands fail fast (instead of queueing) // when Redis is unreachable, so `remember` falls through to its loader rather // than hanging. Callers can override it via the config. - const client = url - ? new Redis(url, { enableOfflineQueue: false, ...options }) - : new Redis({ enableOfflineQueue: false, ...options }); + const client = createClient({ disableOfflineQueue: true, ...config }); // Without a listener, an "error" event (e.g. Redis down) is thrown as an // unhandled exception and crashes the process. Swallow it here. @@ -19,5 +16,13 @@ export const createCacheClient = (config?: CacheConfig): null | Redis => { /* cache methods handle failures individually */ }); + // node-redis does not connect on construction. Kick the connection off here + // and let the built-in reconnect strategy keep retrying in the background - + // until the socket is ready every command rejects, which the cache and the + // rate limiter already treat as "no Redis". + void client.connect().catch(() => { + /* the reconnect strategy keeps retrying; commands fail fast meanwhile */ + }); + return client; }; diff --git a/packages/vitnode/src/api/lib/cache.test.ts b/packages/vitnode/src/api/lib/cache.test.ts deleted file mode 100644 index e57cf4ed1..000000000 --- a/packages/vitnode/src/api/lib/cache.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { Context } from "hono"; -import type { Redis } from "ioredis"; - -import { describe, expect, it, vi } from "vitest"; - -import { CacheModel } from "./cache"; - -const fakeContext = { get: () => undefined } as unknown as Context; -const LOCK_KEY = "vitnode:cache:__system__:lock:queue:process"; - -describe("CacheModel locks", () => { - describe("without Redis", () => { - const cache = new CacheModel(null, fakeContext); - - it("acquireLock returns true so cache-less deployments still proceed", async () => { - await expect(cache.acquireLock("queue:process", 55)).resolves.toBe(true); - }); - - it("releaseLock is a no-op", async () => { - await expect(cache.releaseLock("queue:process")).resolves.toBeUndefined(); - }); - }); - - describe("with Redis", () => { - it("acquires the lock with SET NX EX in the system namespace", async () => { - const set = vi.fn().mockResolvedValue("OK"); - const cache = new CacheModel({ set } as unknown as Redis, fakeContext); - - await expect(cache.acquireLock("queue:process", 55)).resolves.toBe(true); - expect(set).toHaveBeenCalledWith(LOCK_KEY, "1", "EX", 55, "NX"); - }); - - it("returns false when the lock is already held", async () => { - const set = vi.fn().mockResolvedValue(null); - const cache = new CacheModel({ set } as unknown as Redis, fakeContext); - - await expect(cache.acquireLock("queue:process", 55)).resolves.toBe(false); - }); - - it("returns false when Redis errors, to skip rather than double-run", async () => { - const set = vi.fn().mockRejectedValue(new Error("down")); - const cache = new CacheModel({ set } as unknown as Redis, fakeContext); - - await expect(cache.acquireLock("queue:process", 55)).resolves.toBe(false); - }); - - it("releaseLock deletes the lock key", async () => { - const del = vi.fn().mockResolvedValue(1); - const cache = new CacheModel({ del } as unknown as Redis, fakeContext); - - await cache.releaseLock("queue:process"); - expect(del).toHaveBeenCalledWith(LOCK_KEY); - }); - }); -}); diff --git a/packages/vitnode/src/api/lib/cache.ts b/packages/vitnode/src/api/lib/cache.ts index 95f710859..9564dd6f1 100644 --- a/packages/vitnode/src/api/lib/cache.ts +++ b/packages/vitnode/src/api/lib/cache.ts @@ -1,7 +1,11 @@ import type { Context } from "hono"; -import type { Redis, RedisOptions } from "ioredis"; +import type { RedisClientOptions, RedisClientType } from "redis"; -export type CacheConfig = RedisOptions & { url?: string }; +/** Connection options accepted by `redis` - `url` plus any client option. */ +export type CacheConfig = RedisClientOptions; + +/** The connected `node-redis` client shared by the cache, rate limiter and ws. */ +export type CacheClient = RedisClientType; /** * Root prefix applied to every key VitNode writes, so the cache can be flushed @@ -21,13 +25,13 @@ const SYSTEM_NAMESPACE = "__system__"; * never break a request. */ export class CacheModel { - constructor(client: null | Redis, c: Context) { + constructor(client: CacheClient | null, c: Context) { this.c = c; this.client = client; } protected readonly c: Context; - protected readonly client: null | Redis; + protected readonly client: CacheClient | null; private key(key: string): string { return `${this.prefix()}${key}`; @@ -61,7 +65,7 @@ export class CacheModel { if (!this.client) return; try { - if (fullKeys.length > 0) await this.client.del(...fullKeys); + if (fullKeys.length > 0) await this.client.del(fullKeys); } catch { /* swallow */ } @@ -81,7 +85,9 @@ export class CacheModel { try { const raw = JSON.stringify(value); if (ttlSeconds && ttlSeconds > 0) { - await this.client.set(fullKey, raw, "EX", ttlSeconds); + await this.client.set(fullKey, raw, { + expiration: { type: "EX", value: ttlSeconds }, + }); } else { await this.client.set(fullKey, raw); } @@ -102,13 +108,10 @@ export class CacheModel { if (!this.client) return true; try { - const result = await this.client.set( - this.systemKey(`lock:${key}`), - "1", - "EX", - ttlSeconds, - "NX", - ); + const result = await this.client.set(this.systemKey(`lock:${key}`), "1", { + condition: "NX", + expiration: { type: "EX", value: ttlSeconds }, + }); return result === "OK"; } catch { @@ -140,14 +143,14 @@ export class CacheModel { if (!this.client) return; try { - const stream = this.client.scanStream({ - match: `${this.prefix()}*`, - count: 100, + // `scanIterator` yields a batch of keys per SCAN round-trip. + const scan = this.client.scanIterator({ + MATCH: `${this.prefix()}*`, + COUNT: 100, }); - for await (const keys of stream) { - const batch = keys as string[]; - if (batch.length > 0) await this.client.del(...batch); + for await (const keys of scan) { + if (keys.length > 0) await this.client.del(keys); } } catch { /* swallow */ diff --git a/packages/vitnode/src/api/lib/module.ts b/packages/vitnode/src/api/lib/module.ts index 1c8bcbf61..00fcd8246 100644 --- a/packages/vitnode/src/api/lib/module.ts +++ b/packages/vitnode/src/api/lib/module.ts @@ -1,5 +1,9 @@ import { OpenAPIHono } from "@hono/zod-openapi"; +import type { AnyContentModel } from "@/content/server/model"; +import type { AnyContentTypeDefinition } from "@/content/types"; + +import type { SearchIndexer } from "../models/search"; import type { BuildCronReturn } from "./cron"; import type { BuildEventListenerReturn } from "./events"; import type { BuildQueueTaskReturn } from "./queue"; @@ -16,6 +20,23 @@ export interface BaseBuildModuleReturn< M extends string = string, Routes extends Route

[] = Route

[], > { + /** + * The models behind those content types - table, columns, schemas and + * services, not just the definition. + * + * Collected recursively like `contentTypes`, and exposed on the request + * context so background work can find the model for a content type id. The + * scheduled-publication queue task is the reason it exists: it runs in a cron + * request that has no idea which plugin owns the record it is publishing. + */ + contentModels?: AnyContentModel[]; + /** + * Content types whose CRUD routes this module serves. Unlike `events` and + * `cronJobs`, these are collected recursively by `buildApiPlugin`, so a + * generated content module can sit wherever it reads best in the tree - + * usually nested inside the plugin's own `admin` module. + */ + contentTypes?: AnyContentTypeDefinition[]; cronJobs: BuildCronReturn[]; events: BuildEventListenerReturn[]; hono: OpenAPIHono; @@ -24,6 +45,7 @@ export interface BaseBuildModuleReturn< pluginId: P; queueTasks: BuildQueueTaskReturn[]; routes: Routes; + searchIndexers?: SearchIndexer[]; webSockets: BuildWebSocketReturn[]; } @@ -46,11 +68,16 @@ export function buildModule< pluginId, name, modules, + contentModels, + contentTypes, cronJobs = [], events = [], queueTasks = [], + searchIndexers, webSockets = [], }: { + contentModels?: AnyContentModel[]; + contentTypes?: AnyContentTypeDefinition[]; cronJobs?: BuildCronReturn[]; events?: BuildEventListenerReturn[]; modules?: Modules; @@ -58,6 +85,7 @@ export function buildModule< pluginId: P; queueTasks?: BuildQueueTaskReturn[]; routes: Routes; + searchIndexers?: SearchIndexer[]; webSockets?: BuildWebSocketReturn[]; }): BuildModuleReturn { const hono = new OpenAPIHono(); @@ -80,9 +108,12 @@ export function buildModule< hono, name, modules, + contentModels, + contentTypes, cronJobs, events, queueTasks, + searchIndexers, webSockets, }; } diff --git a/packages/vitnode/src/api/lib/pagination-cursor.test.ts b/packages/vitnode/src/api/lib/pagination-cursor.test.ts new file mode 100644 index 000000000..a6286b2f7 --- /dev/null +++ b/packages/vitnode/src/api/lib/pagination-cursor.test.ts @@ -0,0 +1,445 @@ +import { bigint, date, pgTable, time, timestamp } from "drizzle-orm/pg-core"; +// @vitest-environment node +import { HTTPException } from "hono/http-exception"; +import { describe, expect, it } from "vitest"; + +import { core_users } from "@/database/users"; + +import { + cursorValueForColumn, + cursorValueIsCanonicalText, + cursorValueOf, + decodePaginationCursor, + encodePaginationCursor, + isCursorSortableColumn, +} from "./pagination-cursor"; + +const probes = pgTable("cursor_probes", { + big: bigint({ mode: "bigint" }), + clock: time(), + clockTz: time({ withTimezone: true }), + day: date(), + moment: timestamp(), +}); + +const statusOf = (error: unknown): number => + error instanceof HTTPException ? error.status : 0; + +describe("encoding", () => { + it("round-trips a cursor", () => { + const cursor = { + column: "updatedAt", + id: 42, + value: "2026-08-08T12:00:00.000Z", + }; + + expect( + decodePaginationCursor(encodePaginationCursor(cursor), { + column: "updatedAt", + primaryKey: "id", + }), + ).toEqual(cursor); + }); + + it("is opaque, so nothing downstream starts parsing it", () => { + const encoded = encodePaginationCursor({ + column: "updatedAt", + id: 42, + value: "2026-08-08T12:00:00.000Z", + }); + + expect(encoded).toMatch(/^[A-Za-z0-9_-]+$/); + expect(Number.isNaN(Number(encoded))).toBe(true); + }); + + it("round-trips a null order value, which is a real position", () => { + const cursor = { column: "publishedAt", id: 9, value: null }; + + expect( + decodePaginationCursor(encodePaginationCursor(cursor), { + column: "publishedAt", + primaryKey: "id", + }), + ).toEqual(cursor); + }); + + it.each([ + ["a string", "Zebra"], + ["a number", 12], + ["a boolean", true], + ])("round-trips %s value", (_why, value) => { + const cursor = { column: "name", id: 3, value }; + + expect( + decodePaginationCursor(encodePaginationCursor(cursor), { + column: "name", + primaryKey: "id", + }).value, + ).toEqual(value); + }); +}); + +describe("decoding refuses what it cannot trust", () => { + const decode = (raw: string, column = "updatedAt") => + decodePaginationCursor(raw, { column, primaryKey: "id" }); + + it.each([ + ["garbage", "not-a-cursor!!"], + ["an empty string", " "], + [ + "valid base64 that is not JSON", + Buffer.from("hello").toString("base64url"), + ], + [ + "JSON that is not an object", + Buffer.from("[1,2,3]").toString("base64url"), + ], + [ + "an object with no id", + Buffer.from(JSON.stringify({ column: "updatedAt", value: 1 })).toString( + "base64url", + ), + ], + [ + "a non-integer id", + Buffer.from( + JSON.stringify({ column: "updatedAt", id: 1.5, value: 1 }), + ).toString("base64url"), + ], + [ + "a zero id", + Buffer.from( + JSON.stringify({ column: "updatedAt", id: 0, value: 1 }), + ).toString("base64url"), + ], + [ + "an object value", + Buffer.from( + JSON.stringify({ column: "updatedAt", id: 1, value: { a: 1 } }), + ).toString("base64url"), + ], + ])("answers 400 for %s", (_why, raw) => { + expect(() => decode(raw)).toThrow(HTTPException); + try { + decode(raw); + } catch (error) { + expect(statusOf(error)).toBe(400); + } + }); + + it("refuses a cursor minted for another ordering", () => { + const encoded = encodePaginationCursor({ + column: "updatedAt", + id: 42, + value: "2026-08-08T12:00:00.000Z", + }); + + expect(() => decode(encoded, "title")).toThrow(/different ordering/); + }); +}); + +describe("legacy numeric cursors", () => { + it("still works when the list is ordered by its identifier", () => { + expect( + decodePaginationCursor("42", { column: "id", primaryKey: "id" }), + ).toEqual({ column: "id", id: 42, value: 42 }); + }); + + it("is refused for any other ordering rather than guessed at", () => { + expect(() => + decodePaginationCursor("42", { column: "updatedAt", primaryKey: "id" }), + ).toThrow(/cannot be used with the "updatedAt" ordering/); + }); +}); + +describe("column values", () => { + it("keeps a timestamp as text on both sides", () => { + const flattened = cursorValueOf( + core_users.createdAt, + "2026-08-08 12:00:00.123456", + ); + + expect(flattened).toBe("2026-08-08 12:00:00.123456"); + expect(cursorValueForColumn(core_users.createdAt, flattened)).toBe( + "2026-08-08 12:00:00.123456", + ); + }); + + it("keeps a string a string", () => { + expect(cursorValueOf(core_users.name, "Ada")).toBe("Ada"); + expect(cursorValueForColumn(core_users.name, "Ada")).toBe("Ada"); + }); + + it("keeps a number a number", () => { + expect(cursorValueOf(core_users.id, 7)).toBe(7); + expect(cursorValueForColumn(core_users.id, 7)).toBe(7); + }); + + it("treats null as null on both sides", () => { + expect(cursorValueOf(core_users.createdAt, null)).toBeNull(); + expect(cursorValueForColumn(core_users.createdAt, null)).toBeNull(); + }); + + it("refuses an unparseable date rather than letting Postgres fail the cast", () => { + expect(() => + cursorValueForColumn(core_users.createdAt, "not-a-date"), + ).toThrow(HTTPException); + }); + + it("accepts the sortable column kinds", () => { + expect(isCursorSortableColumn(core_users.id)).toBe(true); + expect(isCursorSortableColumn(core_users.name)).toBe(true); + expect(isCursorSortableColumn(core_users.createdAt)).toBe(true); + expect(isCursorSortableColumn(core_users.newsletter)).toBe(true); + expect(isCursorSortableColumn(probes.big)).toBe(true); + }); +}); + +describe("a tampered cursor value is refused, never coerced", () => { + const refuses = ( + column: Parameters[0], + value: unknown, + ) => { + expect(() => cursorValueForColumn(column, value as never)).toThrow( + HTTPException, + ); + try { + cursorValueForColumn(column, value as never); + } catch (error) { + expect(statusOf(error)).toBe(400); + } + }; + + describe("boolean", () => { + it('refuses the string "false", which coercion would read as true', () => { + refuses(core_users.newsletter, "false"); + }); + + it.each([ + ["a number", 0], + ["a string", "true"], + ["an empty string", ""], + ])("refuses %s", (_why, value) => { + refuses(core_users.newsletter, value); + }); + + it("accepts a real boolean, and null", () => { + expect(cursorValueForColumn(core_users.newsletter, false)).toBe(false); + expect(cursorValueForColumn(core_users.newsletter, true)).toBe(true); + expect(cursorValueForColumn(core_users.newsletter, null)).toBeNull(); + }); + }); + + describe("number", () => { + it.each([ + ["a numeric string", "42"], + ["an empty string", ""], + ["a boolean", true], + ["nonsense", "not-a-number"], + ])("refuses %s", (_why, value) => { + refuses(core_users.id, value); + }); + + it("accepts a finite number, and null", () => { + expect(cursorValueForColumn(core_users.id, 42)).toBe(42); + expect(cursorValueForColumn(core_users.id, null)).toBeNull(); + }); + }); + + describe("bigint", () => { + it("refuses a value that would make BigInt() throw", () => { + refuses(probes.big, "not-a-bigint"); + }); + + it.each([ + ["a fractional string", "1.5"], + ["an empty string", ""], + ["a number", 12], + ["a boolean", false], + ["whitespace", " 12 "], + ])("refuses %s", (_why, value) => { + refuses(probes.big, value); + }); + + it("accepts a decimal integer string, signed or not, and null", () => { + expect(cursorValueForColumn(probes.big, "9007199254740993")).toBe( + 9007199254740993n, + ); + expect(cursorValueForColumn(probes.big, "-4")).toBe(-4n); + expect(cursorValueForColumn(probes.big, null)).toBeNull(); + }); + }); + + describe("timestamp", () => { + it.each([ + ["nonsense", "not-a-date"], + ["a number", 1_700_000_000], + ["a boolean", true], + ["a half-written date", "2026-08"], + ["an injection attempt", "2026-08-09'; DROP TABLE users; --"], + ])("refuses %s", (_why, value) => { + refuses(core_users.createdAt, value); + }); + + it.each([ + ["a Postgres timestamp", "2026-08-09 10:00:00.123456"], + ["a Postgres timestamptz", "2026-08-09 10:00:00.123456+00"], + ["a plain date", "2026-08-09"], + ["an ISO string", "2026-08-09T10:00:00.123Z"], + ])("accepts %s, unchanged", (_why, value) => { + expect(cursorValueForColumn(core_users.createdAt, value)).toBe(value); + }); + + it("is the one kind bound as text plus a cast", () => { + expect(cursorValueIsCanonicalText(core_users.createdAt)).toBe(true); + expect(cursorValueIsCanonicalText(core_users.id)).toBe(false); + expect(cursorValueIsCanonicalText(core_users.name)).toBe(false); + }); + + describe("impossible values that still look like timestamps", () => { + it.each([ + ["month 13", "2026-13-01"], + ["month 0", "2026-00-01"], + ["a day past the end of the month", "2026-02-30"], + ["a day past the end of a short month", "2026-04-31"], + ["29 February in a common year", "2025-02-29"], + ["29 February in a century that is not a leap year", "1900-02-29"], + ["day 0", "2026-08-00"], + ["day 32", "2026-01-32"], + ["hour 24", "2026-08-09 24:00:00"], + ["hour 99", "2026-08-09 99:00:00"], + ["minute 60", "2026-08-09 23:60:00"], + ["second 61", "2026-08-09 23:59:61"], + ["year 0", "0000-01-01"], + ])("refuses %s", (_why, value) => { + refuses(core_users.createdAt, value); + }); + + it.each([ + ["an offset past the maximum", "2026-08-09 10:00:00+25:00"], + ["an offset with 99 minutes", "2026-08-09 10:00:00+12:99"], + ["an offset that is not a number", "2026-08-09 10:00:00+ab"], + ["a seven-digit fraction", "2026-08-09 10:00:00.1234567"], + ["trailing rubbish", "2026-08-09 10:00:00 OR 1=1"], + [ + "an era suffix, which is outside the supported domain", + "2026-08-09 BC", + ], + ])("refuses %s", (_why, value) => { + refuses(core_users.createdAt, value); + }); + + it.each([ + ["29 February in a leap year", "2024-02-29"], + ["29 February in a leap century", "2000-02-29"], + ["the last second of a day", "2026-08-09 23:59:59"], + ["the first second of a day", "2026-08-09 00:00:00"], + ["31 December", "2026-12-31"], + ["microseconds", "2026-08-09 10:00:00.123456"], + ["a whole-hour offset", "2026-08-09 10:00:00+02"], + ["a half-hour offset", "2026-08-09 10:00:00+05:30"], + ["a compact offset", "2026-08-09 10:00:00-0400"], + ["a negative offset", "2026-08-09 10:00:00-04"], + ])("accepts %s", (_why, value) => { + expect(cursorValueForColumn(core_users.createdAt, value)).toBe(value); + }); + + it("preserves microseconds through validation, digit for digit", () => { + const value = "2026-08-09 10:00:00.000001"; + + expect(cursorValueForColumn(core_users.createdAt, value)).toBe(value); + }); + }); + }); + + describe("other temporal columns", () => { + it("holds a date column to a date, and nothing more", () => { + expect(cursorValueForColumn(probes.day, "2026-08-09")).toBe("2026-08-09"); + refuses(probes.day, "2026-08-09 10:00:00"); + refuses(probes.day, "2026-02-30"); + refuses(probes.day, "not-a-date"); + }); + + it("holds a time column to a time, and refuses a zone it has not got", () => { + expect(cursorValueForColumn(probes.clock, "10:00:00")).toBe("10:00:00"); + expect(cursorValueForColumn(probes.clock, "10:00:00.123456")).toBe( + "10:00:00.123456", + ); + refuses(probes.clock, "10:00:00+02"); + refuses(probes.clock, "24:00:00"); + refuses(probes.clock, "2026-08-09"); + }); + + it("lets a time-with-zone column carry its zone", () => { + expect(cursorValueForColumn(probes.clockTz, "10:00:00+02")).toBe( + "10:00:00+02", + ); + refuses(probes.clockTz, "10:00:00+25"); + }); + + it("binds every temporal column as text plus a cast", () => { + for (const column of [probes.day, probes.clock, probes.clockTz]) { + expect(cursorValueIsCanonicalText(column)).toBe(true); + expect(isCursorSortableColumn(column)).toBe(true); + } + }); + }); + + describe("string", () => { + it.each([ + ["a number", 12], + ["a boolean", true], + ])("refuses %s", (_why, value) => { + refuses(core_users.name, value); + }); + + it("accepts a string, and null", () => { + expect(cursorValueForColumn(core_users.name, "Ada")).toBe("Ada"); + expect(cursorValueForColumn(core_users.name, null)).toBeNull(); + }); + }); + + it("never lets a native parser error escape", () => { + const hostile = [ + [probes.big, "nope"], + [core_users.createdAt, "nope"], + [core_users.newsletter, "nope"], + [core_users.id, "nope"], + ] as const; + + for (const [column, value] of hostile) { + try { + cursorValueForColumn(column, value); + throw new Error(`Expected ${column.name} to refuse ${value}.`); + } catch (error) { + expect(error).toBeInstanceOf(HTTPException); + } + } + }); +}); + +describe("minting keeps the database's own representation", () => { + it("keeps a Postgres timestamp string exactly as it was read", () => { + expect( + cursorValueOf(core_users.createdAt, "2026-08-09 10:00:00.123456"), + ).toBe("2026-08-09 10:00:00.123456"); + }); + + it("rewrites a Date into the form the column would have been read in", () => { + expect( + cursorValueOf(core_users.createdAt, new Date("2026-08-09T10:00:00.123Z")), + ).toBe("2026-08-09 10:00:00.123"); + }); + + it("refuses a Date that is not a moment, rather than minting nonsense", () => { + expect(() => + cursorValueOf(core_users.createdAt, new Date("not-a-date")), + ).toThrow(/invalid date/i); + }); + + it("carries a bigint as a decimal string, which JSON can hold", () => { + expect(cursorValueOf(probes.big, 9007199254740993n)).toBe( + "9007199254740993", + ); + }); +}); diff --git a/packages/vitnode/src/api/lib/pagination-cursor.ts b/packages/vitnode/src/api/lib/pagination-cursor.ts new file mode 100644 index 000000000..289222d30 --- /dev/null +++ b/packages/vitnode/src/api/lib/pagination-cursor.ts @@ -0,0 +1,488 @@ +import type { PgColumn } from "drizzle-orm/pg-core"; + +import { HTTPException } from "hono/http-exception"; + +/** + * The opaque cursor a paginated list hands out, and takes back. + * + * Two properties, and both of them are load-bearing. + * + * **It is the ordered tuple.** A cursor has to describe a position in an + * ordering, and an ordering is `(orderColumn, id)` - so the cursor is that pair. + * An identifier on its own is only a position when the list is ordered by the + * identifier; for any other column it names a row whose place in the sequence + * nobody knows. + * + * **It is self-contained.** The value it carries *is* the boundary, and nothing + * re-reads the row it came from. That is the difference between a cursor and a + * pointer: a cursor is the position as it stood when the page was generated, and + * editing or deleting the row that happened to sit on the boundary must not move + * it. Re-reading would mean an edit to one row silently skips every row the + * ordering used to have between the old position and the new one. + * + * The wire form is `base64url(JSON)`: opaque, so no client starts depending on + * the shape, and self-describing, so a cursor minted for one order column is + * refused by a request that has since changed to another. + * + * It is **not signed**, so every field is treated as hostile input and validated + * against the column it claims to describe - see {@link cursorValueForColumn}. + */ + +/** What an order column's value can be, once it has been through JSON. */ +export type PaginationCursorValue = boolean | null | number | string; + +export interface PaginationCursor { + /** The order column this cursor was minted for. */ + column: string; + /** The row's primary key - the tiebreaker half of the ordered tuple. */ + id: number; + /** The order column's value on that row. `null` is a real position. */ + value: PaginationCursorValue; +} + +/** + * How one column's values travel in a cursor. + * + * Named per kind rather than inferred, because "how do I serialise this" and + * "what am I willing to accept back" are the same question asked twice, and + * answering it in one place is what stops the second answer being looser than + * the first. + */ +type CursorKind = "bigint" | "boolean" | "number" | "string" | "temporal"; + +const KIND_BY_DATA_TYPE: Record = { + bigint: "bigint", + boolean: "boolean", + date: "temporal", + number: "number", + string: "string", +}; + +const badRequest = (message: string): HTTPException => + new HTTPException(400, { message }); + +/** The one message a tampered or stale cursor ever produces. */ +const INVALID_CURSOR = "Invalid pagination cursor."; + +/** + * The three shapes a temporal value comes in, keyed by what Postgres will parse. + * + * Classified from the **SQL** type rather than the JavaScript one, because the + * two disagree in exactly the case that matters: `date()` and `time()` hand back + * plain strings, so `dataType` calls them `"string"` - and a string cursor bound + * straight into `column > $1` would reach Postgres as `'nonsense'::date` and + * come back as a 500 rather than a 400. + */ +type TemporalType = "date" | "time" | "timestamp"; + +const temporalTypeOf = (column: PgColumn): null | TemporalType => { + const sqlType = column.getSQLType().toLowerCase(); + + // Order matters: "timestamp with time zone" also starts with "time". + if (sqlType.startsWith("timestamp")) return "timestamp"; + if (sqlType.startsWith("time")) return "time"; + if (sqlType.startsWith("date")) return "date"; + + return null; +}; + +const hasTimeZone = (column: PgColumn): boolean => + column.getSQLType().toLowerCase().includes("with time zone"); + +/** + * Whether a column can be paged through at all. + * + * A `json`, `array` or custom column has no total order Postgres and JavaScript + * agree on, so a cursor over one would be a value the next page cannot compare + * against. Refused rather than approximated. + */ +export const isCursorSortableColumn = (column: PgColumn): boolean => + temporalTypeOf(column) !== null || column.dataType in KIND_BY_DATA_TYPE; + +const kindOf = (column: PgColumn): CursorKind => { + if (temporalTypeOf(column)) return "temporal"; + + const kind = KIND_BY_DATA_TYPE[column.dataType]; + if (!kind) { + throw badRequest( + `The "${column.name}" column cannot be used as a pagination cursor.`, + ); + } + + return kind; +}; + +/** + * The grammar of a Postgres temporal value, as `::text` renders it. + * + * One pattern per SQL type, because a `date` column and a `timestamp` column do + * not accept the same strings and pretending they do is how a cursor for one + * ends up being parsed as the other: + * + * | SQL type | accepted | + * | --------------------------- | ------------------------------------------- | + * | `date` | `2026-08-09` | + * | `time` | `10:00:00`, `10:00:00.123456` | + * | `time with time zone` | the above, optionally `+02` / `Z` | + * | `timestamp` | a date, optionally a time, optionally a zone | + * | `timestamp with time zone` | the same, and that is what `::text` writes | + * + * A `T` separator and a `Z` designator are accepted alongside the space-and- + * offset form Postgres writes, because a JavaScript `Date` is the one input this + * module takes that has no database text behind it. + * + * Matching the shape is only half of it. These patterns cannot tell `2026-02-30` + * from `2026-02-28`, so every capture is range-checked afterwards - see + * {@link isRealTemporal}. + */ +const TEMPORAL_GRAMMAR: Record = { + date: /^(?\d{4,6})-(?\d{2})-(?\d{2})$/, + time: /^(?\d{2}):(?\d{2}):(?\d{2})(?:\.\d{1,6})?(?.*)$/, + timestamp: + /^(?\d{4,6})-(?\d{2})-(?\d{2})(?:[ T](?\d{2}):(?\d{2}):(?\d{2})(?:\.\d{1,6})?(?.*))?$/, +}; + +/** + * Whatever the trailing group swallowed, checked rather than trusted. + * + * `(?.*)` is deliberately greedy: it catches a seventh fractional digit, + * an era suffix and `OR 1=1` alike, and hands all of them here to be refused. + */ +type TemporalParts = Partial< + Record< + "day" | "hour" | "minute" | "month" | "second" | "year" | "zone", + string + > +>; + +/** `Z`, or `±HH`, `±HH:MM`, `±HHMM`, `±HH:MM:SS` - the forms Postgres writes. */ +const ZONE = /^([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?$/; + +const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +const isLeapYear = (year: number): boolean => + (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + +const isRealZone = (raw: string, allowed: boolean): boolean => { + if (raw === "") return true; + if (!allowed) return false; + if (raw === "Z") return true; + + const match = ZONE.exec(raw); + if (!match) return false; + + const [, , hours, minutes = "0", seconds = "0"] = match; + + // Postgres refuses anything past ±15:59:59, and so does this. + return Number(hours) <= 15 && Number(minutes) <= 59 && Number(seconds) <= 59; +}; + +/** + * Whether a shaped temporal string is a moment that exists. + * + * The reason a pattern is not enough: `2026-02-30`, `2025-02-29`, `2026-13-01` + * and `2026-08-09 23:60:00` all match the shape and all make Postgres raise + * `invalid input syntax`, which is a 500 arriving from a query string. Every one + * of them is refused here instead, before anything is bound. + * + * Deliberately stricter than Postgres in one place: Postgres reads `24:00:00` as + * the following midnight, but its own `::text` never writes it, so a cursor + * carrying one did not come from a row. + */ +const isRealTemporal = ( + column: PgColumn, + temporal: TemporalType, + value: string, +): boolean => { + const match = TEMPORAL_GRAMMAR[temporal].exec(value); + if (!match) return false; + + const { day, hour, minute, month, second, year, zone } = + (match.groups as TemporalParts | undefined) ?? {}; + + if (year !== undefined) { + const [y, m, d] = [Number(year), Number(month), Number(day)]; + if (y < 1 || y > 294276 || m < 1 || m > 12) return false; + + const limit = m === 2 && isLeapYear(y) ? 29 : DAYS_IN_MONTH[m - 1]; + if (d < 1 || d > limit) return false; + } + + // A bare `date`, or a `timestamp` written as one: no time to check. + if (hour === undefined) return true; + + if (Number(hour) > 23 || Number(minute) > 59 || Number(second) > 59) { + return false; + } + + // A `timestamp` takes an offset and throws it away, which is what lets a + // `Date`-minted value carry `+00`. A `time` without a zone does not. + return isRealZone( + zone ?? "", + temporal === "timestamp" || hasTimeZone(column), + ); +}; + +const DECIMAL_INTEGER = /^-?\d+$/; + +const pad = (value: number, width = 2): string => + String(value).padStart(width, "0"); + +/** + * A `Date` written the way Postgres writes the column it belongs to. + * + * Only reachable when a caller mints a cursor from a value it already holds + * rather than from a row - the paginated path selects `::text` and never sees a + * `Date`. Even so it goes through the same grammar as everything else, so a + * minted cursor and an accepted cursor can never disagree about what a value + * looks like. + */ +const canonicalFromDate = (column: PgColumn, value: Date): string => { + const time = value.getTime(); + if (!Number.isFinite(time)) { + throw new Error( + `Cannot build a pagination cursor from an invalid date on "${column.name}".`, + ); + } + + const zone = hasTimeZone(column) ? "+00" : ""; + const date = `${pad(value.getUTCFullYear(), 4)}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}`; + const clock = `${pad(value.getUTCHours())}:${pad(value.getUTCMinutes())}:${pad(value.getUTCSeconds())}.${pad(value.getUTCMilliseconds(), 3)}`; + + switch (temporalTypeOf(column)) { + case "date": + return date; + case "time": + return `${clock}${zone}`; + default: + return `${date} ${clock}${zone}`; + } +}; + +/** + * One column value, flattened into the cursor's canonical representation. + * + * Per kind, and deliberately not a generic coercion: + * + * | kind | carried as | + * | --------- | --------------------------------------------- | + * | number | a JSON number | + * | boolean | a JSON boolean | + * | string | a JSON string | + * | bigint | a decimal string, because JSON has no bigint | + * | temporal | the database's own `::text`, microseconds and all | + * | null | `null` | + * + * The temporal row is the one worth reading twice. A Postgres `timestamp` keeps + * microseconds and a JavaScript `Date` keeps milliseconds, so a value that has + * been through a `Date` is *strictly smaller* than the one still in the table - + * and comparing against it would exclude the entire millisecond it came from. + * Since `now()` stamps every row in one statement identically, that is not an + * edge case: it would end a bulk-imported collection's walk after page one. So + * the page query selects `column::text` and this function keeps it exactly as + * Postgres wrote it. + */ +export const cursorValueOf = ( + column: PgColumn, + value: unknown, +): PaginationCursorValue => { + if (value === null || value === undefined) return null; + + switch (kindOf(column)) { + case "bigint": { + if (typeof value === "bigint") return value.toString(); + if (typeof value === "number") return String(value); + break; + } + case "boolean": { + if (typeof value === "boolean") return value; + break; + } + case "number": { + if (typeof value === "number") return value; + break; + } + case "temporal": { + // Already `::text` from the database on the paginated path. A `Date` only + // reaches here when a caller mints from a value it is holding, and it is + // rewritten into the form Postgres would have written. + if (value instanceof Date) return canonicalFromDate(column, value); + if (typeof value === "string") return value; + break; + } + default: { + if (typeof value === "string") return value; + break; + } + } + + // The value came off a row of the very column it is being minted for, so + // anything else is a wiring bug rather than bad input - and quietly writing + // `"[object Object]"` into a cursor would hide it until somebody turned a + // page. + throw new Error( + `Cannot build a pagination cursor from a ${typeof value} value of "${column.name}".`, + ); +}; + +/** + * The cursor value, validated against the column it claims to describe. + * + * Validation rather than coercion, because the cursor is opaque but not signed: + * a client can edit it. `Boolean("false")` is `true`, `Number("")` is `0`, and + * `BigInt("nonsense")` throws a `SyntaxError` that would surface as a 500 - so + * every kind checks the shape it expects and refuses anything else with a 400. + * + * Returns the value in the form the SQL comparison needs: a real `boolean`, + * `number` or `bigint` for those kinds, and for a temporal column the + * **canonical text**, which the predicate binds with an explicit cast so + * Postgres parses it at full precision. + * + * A temporal value is checked for more than shape. `2026-02-30` and + * `2026-08-09 23:60:00` look like timestamps and are not moments, and Postgres + * answers a cast of either with `invalid input syntax` - a 500 produced by a + * query string. Both are refused here, so nothing impossible is ever bound. + */ +export const cursorValueForColumn = ( + column: PgColumn, + value: PaginationCursorValue, +): unknown => { + if (value === null) return null; + + switch (kindOf(column)) { + case "bigint": { + // A decimal string, and nothing else: `BigInt("1.5")` and `BigInt("")` + // are a `SyntaxError` and a `0` respectively, and neither is an answer. + if (typeof value !== "string" || !DECIMAL_INTEGER.test(value)) { + throw badRequest(INVALID_CURSOR); + } + + return BigInt(value); + } + case "boolean": { + if (typeof value !== "boolean") throw badRequest(INVALID_CURSOR); + + return value; + } + case "number": { + if (typeof value !== "number" || !Number.isFinite(value)) { + throw badRequest(INVALID_CURSOR); + } + + return value; + } + case "temporal": { + const temporal = temporalTypeOf(column); + if ( + typeof value !== "string" || + !temporal || + !isRealTemporal(column, temporal, value) + ) { + throw badRequest(INVALID_CURSOR); + } + + // Kept as text. The predicate casts it back to the column's own type, so + // Postgres does the parsing - at the precision it stored. + return value; + } + default: { + if (typeof value !== "string") throw badRequest(INVALID_CURSOR); + + return value; + } + } +}; + +/** + * Whether this column's value travels as canonical text plus a cast. + * + * True for every temporal type, and it decides two things at once: the page + * query selects `column::text` rather than the column, and the predicate binds + * the cursor back with an explicit cast. Both halves exist so the microseconds + * Postgres stored survive a round trip that JavaScript's millisecond `Date` + * would otherwise truncate. + */ +export const cursorValueIsCanonicalText = (column: PgColumn): boolean => + kindOf(column) === "temporal"; + +export const encodePaginationCursor = (cursor: PaginationCursor): string => + Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url"); + +/** A bare integer, which is what every cursor was before this. */ +const LEGACY_CURSOR = /^[1-9]\d{0,14}$/; + +const isCursorValue = (value: unknown): value is PaginationCursorValue => + value === null || + typeof value === "boolean" || + typeof value === "number" || + typeof value === "string"; + +/** + * Reads a cursor, or refuses the request. + * + * Three ways this says no, and each of them is a `400` rather than a page of + * wrong rows: + * + * 1. **garbage** - not base64url, not JSON, or not the shape; + * 2. **the wrong column** - a cursor minted while the list was ordered by + * `updatedAt`, replayed against a list now ordered by `title`. The two + * describe different sequences, so the position means nothing; + * 3. **a legacy numeric cursor on a non-primary-key ordering** - the exact case + * that used to skip rows. A bare number is still accepted when the list is + * ordered by its identifier, because there it really is the whole tuple. + * + * The *value* is checked separately, against the column - see + * {@link cursorValueForColumn} - because only the caller knows which column this + * request is ordered by. + */ +export const decodePaginationCursor = ( + raw: string, + { column, primaryKey }: { column: string; primaryKey: string }, +): PaginationCursor => { + const trimmed = raw.trim(); + if (trimmed === "") throw badRequest(INVALID_CURSOR); + + if (LEGACY_CURSOR.test(trimmed)) { + if (column !== primaryKey) { + throw badRequest( + `This cursor cannot be used with the "${column}" ordering. Start from the first page.`, + ); + } + const id = Number(trimmed); + + return { column, id, value: id }; + } + + let parsed: unknown; + try { + parsed = JSON.parse( + Buffer.from(trimmed, "base64url").toString("utf8"), + ) as unknown; + } catch { + throw badRequest(INVALID_CURSOR); + } + + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + throw badRequest(INVALID_CURSOR); + } + + const candidate = parsed as Record; + const id = candidate.id; + if ( + typeof candidate.column !== "string" || + typeof id !== "number" || + !Number.isSafeInteger(id) || + id <= 0 || + !isCursorValue(candidate.value) + ) { + throw badRequest(INVALID_CURSOR); + } + + if (candidate.column !== column) { + throw badRequest( + `This cursor was issued for a different ordering. Start from the first page.`, + ); + } + + return { column, id, value: candidate.value }; +}; diff --git a/packages/vitnode/src/api/lib/plugin.test.ts b/packages/vitnode/src/api/lib/plugin.test.ts new file mode 100644 index 000000000..e00296f1a --- /dev/null +++ b/packages/vitnode/src/api/lib/plugin.test.ts @@ -0,0 +1,219 @@ +// @vitest-environment node +import { describe, expect, it } from "vitest"; + +import { + testArticleContentType, + testCategoryContentType, +} from "@/tests/content-fixtures"; + +import type { SearchIndexer } from "../models/search"; + +import { validateSearchIndexers } from "../models/search"; +import { buildModule } from "./module"; +import { buildApiPlugin } from "./plugin"; + +const contentModule = buildModule({ + pluginId: "@vitnode/example", + name: "content", + routes: [], + contentTypes: [testArticleContentType, testCategoryContentType], +}); + +const adminModule = buildModule({ + pluginId: "@vitnode/example", + name: "admin", + routes: [], + modules: [contentModule], +}); + +describe("buildApiPlugin content types", () => { + it("collects content types from nested modules", () => { + const plugin = buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [adminModule], + }); + + expect(plugin.contentTypes?.map(item => item.id)).toEqual([ + "test.article", + "test.category", + ]); + }); + + it("derives staff permissions for each collected content type", () => { + const plugin = buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [adminModule], + }); + + expect(plugin.permissionStaff?.admin?.article).toEqual([ + "can_view", + { dependsOn: ["can_view"], permission: "can_create" }, + { dependsOn: ["can_view"], permission: "can_edit" }, + { dependsOn: ["can_view"], permission: "can_delete" }, + ]); + expect(plugin.permissionStaff?.admin?.category).toBeDefined(); + }); + + it("keeps hand-declared permissions and other modules intact", () => { + const plugin = buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [adminModule], + permissionStaff: { + admin: { article: ["can_view"], posts: ["can_view"] }, + moderator: { posts: ["can_edit"] }, + }, + }); + + expect(plugin.permissionStaff?.admin?.article).toEqual(["can_view"]); + expect(plugin.permissionStaff?.admin?.posts).toEqual(["can_view"]); + expect(plugin.permissionStaff?.moderator?.posts).toEqual(["can_edit"]); + }); + + it("leaves permissionStaff untouched for a plugin with no content types", () => { + const plugin = buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [], + }); + + expect(plugin.contentTypes).toEqual([]); + expect(plugin.permissionStaff).toBeUndefined(); + }); + + it("rejects two content types sharing a table inside one plugin", () => { + const duplicate = buildModule({ + pluginId: "@vitnode/example", + name: "content", + routes: [], + contentTypes: [testArticleContentType, testArticleContentType], + }); + + expect(() => + buildApiPlugin({ pluginId: "@vitnode/example", modules: [duplicate] }), + ).toThrow(/Duplicate content type id/); + }); +}); + +const indexer = (itemType: string): SearchIndexer => ({ + itemType, + load: async () => await Promise.resolve({ documents: [], itemsRead: 0 }), +}); + +describe("buildApiPlugin search indexers", () => { + it("collects indexers from nested modules", () => { + const nested = buildModule({ + pluginId: "@vitnode/example", + name: "content", + routes: [], + searchIndexers: [indexer("test.article")], + }); + + const plugin = buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [ + buildModule({ + pluginId: "@vitnode/example", + name: "admin", + routes: [], + modules: [nested], + }), + ], + }); + + expect(plugin.searchIndexers?.map(item => item.itemType)).toEqual([ + "test.article", + ]); + }); + + it("merges root-level indexers with collected ones", () => { + const plugin = buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [ + buildModule({ + pluginId: "@vitnode/example", + name: "content", + routes: [], + searchIndexers: [indexer("test.article")], + }), + ], + searchIndexers: [indexer("blog_post")], + }); + + expect(plugin.searchIndexers?.map(item => item.itemType)).toEqual([ + "blog_post", + "test.article", + ]); + }); + + it("leaves a plugin with no indexers alone", () => { + const plugin = buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [adminModule], + }); + + expect(plugin.searchIndexers).toEqual([]); + }); + + it("rejects the same item type registered twice", () => { + expect(() => + buildApiPlugin({ + pluginId: "@vitnode/example", + modules: [ + buildModule({ + pluginId: "@vitnode/example", + name: "content", + routes: [], + searchIndexers: [indexer("test.article")], + }), + ], + searchIndexers: [indexer("test.article")], + }), + ).toThrow(/Duplicate search indexer for item type "test.article"/); + }); +}); + +describe("validateSearchIndexers", () => { + it("retains each plugin's ownership after collection", () => { + // What the AdminCP reports a collection's owner from, and what the rebuild + // stamps on a legacy document. + const collected = [ + ...(buildApiPlugin({ + pluginId: "@vitnode/example", + searchIndexers: [indexer("test.article")], + }).searchIndexers ?? []), + ].map(item => ({ ...item, pluginId: "@vitnode/example" })); + const other = [ + ...(buildApiPlugin({ + pluginId: "@vitnode/blog", + searchIndexers: [indexer("blog_post")], + }).searchIndexers ?? []), + ].map(item => ({ ...item, pluginId: "@vitnode/blog" })); + + expect( + validateSearchIndexers([...collected, ...other]).map(item => [ + item.itemType, + item.pluginId, + ]), + ).toEqual([ + ["test.article", "@vitnode/example"], + ["blog_post", "@vitnode/blog"], + ]); + }); + + it("names both owners of a collision", () => { + expect(() => + validateSearchIndexers([ + { ...indexer("blog_post"), pluginId: "@vitnode/blog" }, + { ...indexer("blog_post"), pluginId: "@vitnode/other" }, + ]), + ).toThrow(/both "@vitnode\/blog" and "@vitnode\/other"/); + }); + + it("passes distinct item types through", () => { + expect( + validateSearchIndexers([ + { ...indexer("blog_post"), pluginId: "@vitnode/blog" }, + { ...indexer("test.article"), pluginId: "@vitnode/example" }, + ]).map(item => item.itemType), + ).toEqual(["blog_post", "test.article"]); + }); +}); diff --git a/packages/vitnode/src/api/lib/plugin.ts b/packages/vitnode/src/api/lib/plugin.ts index 24e165a69..c559072e5 100644 --- a/packages/vitnode/src/api/lib/plugin.ts +++ b/packages/vitnode/src/api/lib/plugin.ts @@ -1,18 +1,29 @@ import { OpenAPIHono } from "@hono/zod-openapi"; +import type { RegisteredContentType } from "@/content/registry"; +import type { AnyContentModel } from "@/content/server/model"; +import type { AnyContentTypeDefinition } from "@/content/types"; import type { LocaleMessagesMap } from "@/lib/i18n/types"; +import { + validateContentTypes, + withContentPermissions, +} from "@/content/registry"; + import type { SearchIndexer } from "../models/search"; import type { CronJobConfig } from "./cron"; import type { EventListenerConfig } from "./events"; -import type { BuildModuleReturn } from "./module"; +import type { BaseBuildModuleReturn, BuildModuleReturn } from "./module"; import type { PermissionStaffConfig } from "./permission-staff"; import type { QueueTaskConfig } from "./queue"; import type { WebSocketConfig } from "./websocket"; +import { validateSearchIndexers } from "../models/search"; import { checkPluginId } from "./check-plugin-id"; export interface BuildPluginApiReturn { + contentModels?: AnyContentModel[]; + contentTypes?: AnyContentTypeDefinition[]; cronJobs?: Omit[]; events?: Omit[]; hono: OpenAPIHono; @@ -47,13 +58,20 @@ export function buildApiPlugin

({ checkPluginId(pluginId); const hono = new OpenAPIHono(); + const contentModels: AnyContentModel[] = []; + const contentTypes: AnyContentTypeDefinition[] = []; const cronJobs: BuildPluginApiReturn["cronJobs"] = []; const events: BuildPluginApiReturn["events"] = []; + const indexers: SearchIndexer[] = [...(searchIndexers ?? [])]; const queueTasks: BuildPluginApiReturn["queueTasks"] = []; const webSockets: BuildPluginApiReturn["webSockets"] = []; modules.forEach(handler => { hono.route(`/${handler.name}`, handler.hono); + contentModels.push(...collectContentModels(handler)); + contentTypes.push(...collectContentTypes(handler)); + indexers.push(...collectSearchIndexers(handler)); + handler.cronJobs?.forEach(cron => { cronJobs.push({ ...cron, module: handler.name }); }); @@ -71,15 +89,57 @@ export function buildApiPlugin

({ }); }); + const registered: RegisteredContentType[] = validateContentTypes( + contentTypes.map(definition => ({ definition, pluginId })), + ); + + validateSearchIndexers(indexers.map(indexer => ({ ...indexer, pluginId }))); + return { pluginId, messages, hono, + contentModels, + contentTypes: registered.map(entry => entry.definition), cronJobs, events, queueTasks, - searchIndexers, + searchIndexers: indexers, webSockets, - permissionStaff, + // Every content type contributes can_view/can_create/can_edit/can_delete + // unless the plugin declared that module itself. + permissionStaff: withContentPermissions(permissionStaff, registered), }; } + +/** + * Walks the whole module tree. Content types are collected recursively - unlike + * `events`, `cronJobs` and friends, which only come from top-level modules - so + * a generated content module can be nested inside the plugin's `admin` module + * and still register its permissions. + */ +function collectContentTypes( + module: BaseBuildModuleReturn, +): AnyContentTypeDefinition[] { + return [ + ...(module.contentTypes ?? []), + ...(module.modules ?? []).flatMap(collectContentTypes), + ]; +} + +/** Same walk as {@link collectContentTypes}, and for the same reason. */ +function collectContentModels( + module: BaseBuildModuleReturn, +): AnyContentModel[] { + return [ + ...(module.contentModels ?? []), + ...(module.modules ?? []).flatMap(collectContentModels), + ]; +} + +function collectSearchIndexers(module: BaseBuildModuleReturn): SearchIndexer[] { + return [ + ...(module.searchIndexers ?? []), + ...(module.modules ?? []).flatMap(collectSearchIndexers), + ]; +} diff --git a/packages/vitnode/src/api/lib/route.test.ts b/packages/vitnode/src/api/lib/route.test.ts new file mode 100644 index 000000000..e9272f91e --- /dev/null +++ b/packages/vitnode/src/api/lib/route.test.ts @@ -0,0 +1,88 @@ +// @vitest-environment node +import type { MiddlewareHandler } from "hono"; + +import { OpenAPIHono, z } from "@hono/zod-openapi"; +import { describe, expect, it } from "vitest"; + +import { buildRoute } from "./route"; + +const okResponse = { + 200: { + content: { + "application/json": { schema: z.object({ plugin: z.string() }) }, + }, + description: "ok", + }, +} as const; + +const mount = (route: ReturnType) => { + const app = new OpenAPIHono(); + app.openapi(route.route, route.handler); + + return app; +}; + +describe("buildRoute", () => { + it("keeps pluginMiddleware when the route brings its own middleware", async () => { + const marks: string[] = []; + const custom: MiddlewareHandler = async (_c, next) => { + marks.push("custom"); + await next(); + }; + + const route = buildRoute({ + pluginId: "@vitnode/test", + route: { + method: "get", + path: "/", + middleware: [custom], + responses: okResponse, + }, + handler: c => c.json({ plugin: c.get("plugin").id }, 200), + }); + + const res = await mount(route).request("/"); + + expect(res.status).toBe(200); + // Without the fix the `...route` spread replaced the composed array and + // `c.get("plugin")` was undefined. + expect(await res.json()).toEqual({ plugin: "@vitnode/test" }); + expect(marks).toEqual(["custom"]); + }); + + it("composes pluginMiddleware, the permission guard and route middleware in order", () => { + const custom: MiddlewareHandler = async (_c, next) => next(); + + const { route } = buildRoute({ + pluginId: "@vitnode/test", + adminStaffPermission: { module: "articles", permission: "can_view" }, + route: { + method: "get", + path: "/", + middleware: [custom], + responses: okResponse, + }, + handler: c => c.json({ plugin: c.get("plugin").id }, 200), + }); + + const middleware = route.middleware; + + expect(middleware).toHaveLength(3); + expect(middleware.at(-1)).toBe(custom); + }); + + it("prepends the plugin tag and keeps route tags", () => { + const { route } = buildRoute({ + pluginId: "@vitnode/test_plugin", + route: { + method: "get", + path: "/", + tags: ["Articles"], + responses: okResponse, + }, + handler: c => c.json({ plugin: c.get("plugin").id }, 200), + }); + + expect(route.tags).toEqual(["@vitnode/test Plugin", "Articles"]); + }); +}); diff --git a/packages/vitnode/src/api/lib/route.ts b/packages/vitnode/src/api/lib/route.ts index e4e4f7830..c40ddb664 100644 --- a/packages/vitnode/src/api/lib/route.ts +++ b/packages/vitnode/src/api/lib/route.ts @@ -67,9 +67,12 @@ export const buildRoute = < return { route: createRouteHono({ + // `route` is spread first on purpose: `tags` and `middleware` already + // merge the route's own values, so letting the spread win would drop + // `pluginMiddleware` and the staff-permission guard. + ...route, tags, middleware, - ...route, }), handler: handler as Route["handler"], pluginId, diff --git a/packages/vitnode/src/api/lib/save-language-words.test.ts b/packages/vitnode/src/api/lib/save-language-words.test.ts deleted file mode 100644 index 9247a8c0b..000000000 --- a/packages/vitnode/src/api/lib/save-language-words.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -// @vitest-environment node -import type { Context } from "hono"; - -import { describe, expect, it, vi } from "vitest"; - -import { core_languages_words } from "@/database/languages"; - -import { saveLanguageWords } from "./save-language-words"; - -const createDbMock = () => { - const insertValues = vi.fn(); - const where = vi.fn(); - const tx = { - delete: vi.fn(() => ({ where })), - insert: vi.fn(() => ({ values: insertValues })), - }; - const db = { - transaction: vi.fn(async (cb: (t: typeof tx) => Promise) => cb(tx)), - }; - - return { db, tx, insertValues, where }; -}; - -const createContext = (db: unknown): Context => - ({ get: (key: string) => (key === "db" ? db : undefined) }) as Context; - -describe("saveLanguageWords", () => { - it("replaces the tuple rows: delete then insert the mapped values", async () => { - const { db, tx, insertValues } = createDbMock(); - - await saveLanguageWords(createContext(db), { - pluginCode: "blog", - tableName: "blog_categories", - variable: "name", - itemId: 7, - values: [ - { languageCode: "en", value: "News" }, - { languageCode: "pl", value: "Aktualności" }, - ], - }); - - expect(db.transaction).toHaveBeenCalledOnce(); - expect(tx.delete).toHaveBeenCalledWith(core_languages_words); - expect(tx.insert).toHaveBeenCalledWith(core_languages_words); - expect(insertValues).toHaveBeenCalledWith([ - { - languageCode: "en", - pluginCode: "blog", - itemId: 7, - value: "News", - tableName: "blog_categories", - variable: "name", - }, - { - languageCode: "pl", - pluginCode: "blog", - itemId: 7, - value: "Aktualności", - tableName: "blog_categories", - variable: "name", - }, - ]); - }); - - it("deletes but does not insert when values is empty", async () => { - const { db, tx, insertValues } = createDbMock(); - - await saveLanguageWords(createContext(db), { - pluginCode: "blog", - tableName: "blog_categories", - variable: "name", - itemId: 7, - values: [], - }); - - expect(tx.delete).toHaveBeenCalledOnce(); - expect(tx.insert).not.toHaveBeenCalled(); - expect(insertValues).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/vitnode/src/api/lib/with-pagination.ts b/packages/vitnode/src/api/lib/with-pagination.ts index 5a0964d8f..fbaf2d2c4 100644 --- a/packages/vitnode/src/api/lib/with-pagination.ts +++ b/packages/vitnode/src/api/lib/with-pagination.ts @@ -8,65 +8,222 @@ import type { import type { Context } from "hono"; import { z } from "@hono/zod-openapi"; -import { and, asc, count, desc, gt, ilike, lt, or } from "drizzle-orm"; +import { + and, + asc, + count, + desc, + eq, + gt, + ilike, + isNotNull, + isNull, + lt, + or, + sql, +} from "drizzle-orm"; +import { HTTPException } from "hono/http-exception"; +import type { PaginationCursor } from "./pagination-cursor"; + +import { + cursorValueForColumn, + cursorValueIsCanonicalText, + cursorValueOf, + decodePaginationCursor, + encodePaginationCursor, + isCursorSortableColumn, +} from "./pagination-cursor"; + +/** Nobody may ask for more than this in one page, whatever they send. */ +const MAX_PAGE_SIZE = 100; + +/** + * The column a page query carries purely so its rows can be turned into cursors. + * + * Selected by the **same statement** that returns the rows, and removed again + * before anything leaves this module. It exists because a cursor has to describe + * the position the returned row actually occupied, and the only way to be + * certain of that is to read the two out of one snapshot. + * + * Prefixed so it cannot collide with a column name, and stripped rather than + * documented, because it is pagination's business and nobody else's. + */ +export const PAGINATION_CURSOR_FIELD = "__cursorValue"; + +/** What a page query must spread into its projection. */ +export type PaginationCursorSelection = Record< + typeof PAGINATION_CURSOR_FIELD, + PgColumn | SQL +>; + +/** + * Reads `first`, `last` and `cursor`, or refuses the request with a 400. + * + * Refusing rather than repairing is the change worth noting. `first=0` used to + * clamp its way into a one-row page that reported `hasNextPage: true`, and + * `first=abc` became `NaN` and fell through to the default page size - both of + * them a request nobody made, answered as if they had. Every one of these is now + * a stable 400, and the route schema rejects most of them a step earlier. + */ function parsePaginationParams(params: { query: { cursor?: string; first?: string; last?: string }; -}): { cursor?: number; first?: number; last?: number } { - const cursor = params.query.cursor - ? parseInt(params.query.cursor, 10) - : undefined; - const first = params.query.first - ? Math.min(parseInt(params.query.first, 10), 100) - : undefined; - const last = params.query.last - ? Math.min(parseInt(params.query.last, 10), 100) - : undefined; +}): { cursor?: string; first?: number; last?: number } { + const size = (raw: string | undefined, name: string): number | undefined => { + if (raw === undefined || raw === "") return undefined; + + const parsed = Number(raw); + if (!Number.isSafeInteger(parsed) || parsed < 1) { + throw new HTTPException(400, { + message: `"${name}" must be a whole number greater than zero.`, + }); + } + + return Math.min(parsed, MAX_PAGE_SIZE); + }; + + const first = size(params.query.first, "first"); + const last = size(params.query.last, "last"); if (first !== undefined && last !== undefined) { - throw new Error("Cannot specify both first and last"); - } - if (first !== undefined && first < 0) { - throw new Error("first must be positive"); - } - if (last !== undefined && last < 0) { - throw new Error("last must be positive"); + throw new HTTPException(400, { + message: 'Use either "first" or "last", not both.', + }); } - return { cursor, first, last }; + const cursor = params.query.cursor?.trim(); + + return { cursor: cursor === "" ? undefined : cursor, first, last }; } -function getOrderFn( +/** + * Which way the rows really come back. + * + * Backward pagination runs the query in reverse and flips the page afterwards, + * so the *effective* SQL direction is not the one the caller asked for - and + * the cursor predicate has to describe the effective one, or it would be reading + * a sequence the `ORDER BY` is not producing. + */ +function effectiveDirection( isForward: boolean, order: "asc" | "desc", -): typeof asc | typeof desc { - if (isForward) { - return order === "asc" ? asc : desc; - } +): "asc" | "desc" { + if (isForward) return order; - return order === "asc" ? desc : asc; + return order === "asc" ? "desc" : "asc"; } -function buildWhereWithCursor< - Primary extends ColumnBaseConfig<"number", string>, ->( - baseWhere: SQL | undefined, - cursor: number | undefined, - isForward: boolean, - order: "asc" | "desc", - table: PgTable, - primaryCursor: PgColumn, -): SQL | undefined { - if (!cursor) return baseWhere; +/** + * `and`/`or` given at least one defined condition always produce SQL. + * + * Stated as a check rather than a non-null assertion: the assertion would be a + * claim about code somewhere else, and this is a claim about the two lines above + * it - which is the kind that stays true. + */ +function required(value: SQL | undefined): SQL { + if (!value) throw new Error("Expected a pagination condition."); + + return value; +} + +/** + * "Strictly after this position, in this direction." + * + * The whole keyset, written out. `(column, id)` is the ordered tuple, so the + * predicate is the tuple comparison - not a comparison of one half of it: + * + * ```sql + * column > :value OR (column = :value AND id > :id) -- ascending + * column < :value OR (column = :value AND id < :id) -- descending + * ``` + * + * `:value` comes from the **cursor** and nowhere else. That is the invariant a + * cursor exists to provide: it is the position as it stood when the page was + * generated, so editing the row that happened to sit on the boundary must not + * move it. Reading the row's current value instead would mean one edit silently + * skips every row the ordering used to have between the old position and the + * new one - and deleting it would leave no position at all. + * + * The `NULL` branches are the part that is easy to get wrong. Postgres sorts + * `NULLS LAST` for `ASC` and `NULLS FIRST` for `DESC`, and `column > NULL` is + * `NULL` rather than true - so a nullable order column needs the null block + * named explicitly, or a page boundary landing on it would end the walk early + * and silently. + */ +function buildCursorCondition({ + column, + cursor, + direction, + isPrimaryOrder, + primary, +}: { + column: PgColumn; + cursor: PaginationCursor; + direction: "asc" | "desc"; + isPrimaryOrder: boolean; + primary: PgColumn; +}): SQL { + const after = direction === "asc" ? gt : lt; + + // The identifier is the whole tuple when the list is ordered by it, so there + // is no second half to compare and no null block to worry about. + if (isPrimaryOrder) return after(primary, cursor.id); + + const boundary = boundaryValue(column, cursor); + + if (direction === "asc") { + // NULLS LAST: a null cursor is inside the trailing block, and everything + // that is not null is already behind us. + if (cursor.value === null) { + return required(and(isNull(column), gt(primary, cursor.id))); + } + + return required( + or( + gt(column, boundary), + and(eq(column, boundary), gt(primary, cursor.id)), + isNull(column), + ), + ); + } + + // NULLS FIRST: a null cursor is inside the *leading* block, so the rest of + // that block comes first and every non-null row follows it. + if (cursor.value === null) { + return required( + or(and(isNull(column), lt(primary, cursor.id)), isNotNull(column)), + ); + } + + return required( + or(lt(column, boundary), and(eq(column, boundary), lt(primary, cursor.id))), + ); +} - const cursorFilter = - (isForward && order === "asc") || (!isForward && order === "desc") - ? gt - : lt; +/** + * The cursor's own value, bound so Postgres compares it at full precision. + * + * Two shapes, because two kinds of value survive a round trip differently: + * + * - a **temporal** value travels as the database's own `::text` and is bound + * back with an explicit cast, so Postgres parses the microseconds it wrote. + * Binding a JavaScript `Date` here would silently truncate to milliseconds and + * exclude the whole millisecond the cursor came from. + * - **everything else** - a number, a string, a boolean, a bigint - is exact in + * JavaScript already, so it goes through the column's own encoder. + * + * `getSQLType()` is derived from the schema rather than from the request, which + * is what makes `sql.raw` safe here; the value itself is always a bound + * parameter. + */ +function boundaryValue(column: PgColumn, cursor: PaginationCursor): SQL { + const value = cursorValueForColumn(column, cursor.value); - const cursorWhere = cursorFilter(table[primaryCursor.name], cursor); + if (cursorValueIsCanonicalText(column)) { + return sql`${String(value)}::${sql.raw(column.getSQLType())}`; + } - return baseWhere ? and(baseWhere, cursorWhere) : cursorWhere; + return sql`${sql.param(value, column)}`; } function buildSearchWhere( @@ -122,6 +279,14 @@ export async function withPagination< }; primaryCursor: PgColumn; query: (args: { + /** + * Spread this into the projection: `.select({ ...fields, ...cursorSelection })`. + * + * Not optional in practice. It is how the cursor value is read out of the + * same statement as the row, and a query that omits it can only be paged by + * a column it happens to have selected itself. + */ + cursorSelection: PaginationCursorSelection; limit: number | Placeholder; orderBy: SQL; where: SQL | undefined; @@ -130,21 +295,57 @@ export async function withPagination< table: Omit, "enableRLS">; where?: SQL; }): Promise<{ - edges: QueryMin[]; + edges: Omit[]; pageInfo: { count: number; - endCursor: null | number; + /** An opaque cursor. Hand it back as `cursor`; never parse it. */ + endCursor: null | string; hasNextPage: boolean; hasPreviousPage: boolean; - startCursor: null | number; + startCursor: null | string; totalCount: number; }; }> { - const { cursor, first, last } = parsePaginationParams(params); + const { cursor: rawCursor, first, last } = parsePaginationParams(params); const isForward = last === undefined; - const orderFn = getOrderFn(isForward, orderByFromParams.order); - const orderBy: SQL = orderFn(table[orderByFromParams.column.name]); + const direction = effectiveDirection(isForward, orderByFromParams.order); + const orderFn = direction === "asc" ? asc : desc; + + const primary = table[primaryCursor.name]; + const orderName = orderByFromParams.column.name; + const orderColumn = table[orderName] as PgColumn; + const isPrimaryOrder = orderName === primaryCursor.name; + + // A column with no total order Postgres and JavaScript agree on cannot be + // paged at all, so it is refused rather than served for one page and then + // quietly wrong on the next. + if (!isCursorSortableColumn(orderColumn)) { + throw new HTTPException(400, { + message: `Results cannot be ordered by "${orderName}".`, + }); + } + + /** + * The ordered tuple, `(requested column, identifier)`. + * + * The tiebreaker is not decoration: without it the ordering is partial, so + * every row sharing an `updatedAt` sits wherever Postgres feels like putting + * it, and a page boundary landing inside a tie skips or repeats rows. With it + * the ordering is total - and the cursor predicate below compares the *same* + * tuple, which is the invariant this whole module rests on. + */ + const orderBy: SQL = isPrimaryOrder + ? orderFn(primary) + : sql`${orderFn(orderColumn)}, ${orderFn(primary)}`; + + const cursor = + rawCursor === undefined + ? undefined + : decodePaginationCursor(rawCursor, { + column: orderName, + primaryKey: primaryCursor.name, + }); const searchWhere = buildSearchWhere(search, params.query.search); const baseWhere = @@ -152,54 +353,196 @@ export async function withPagination< ? and(whereFromParams, searchWhere) : (whereFromParams ?? searchWhere); - const where = buildWhereWithCursor( - baseWhere, - cursor, - isForward, - orderByFromParams.order, - table, - primaryCursor, - ); + const cursorWhere = cursor + ? buildCursorCondition({ + column: orderColumn, + cursor, + direction, + isPrimaryOrder, + primary, + }) + : undefined; + const where = + baseWhere && cursorWhere + ? and(baseWhere, cursorWhere) + : (baseWhere ?? cursorWhere); const totalCount = await fetchTotalCount(c, table, baseWhere); + /** + * The cursor value, projected by the page query itself. + * + * A temporal column goes through `::text` so no microsecond is lost on the way + * out; everything else is exact in JavaScript already and is selected as it + * is. Either way it rides along with the row, which is the point: a cursor + * minted from a *second* read would describe wherever the boundary row had got + * to by then, not where it was when it was chosen for this page. + */ + const cursorSelection: PaginationCursorSelection = { + [PAGINATION_CURSOR_FIELD]: cursorValueIsCanonicalText(orderColumn) + ? sql`${orderColumn}::text` + : orderColumn, + }; + const limit = (first ?? last ?? 50) + 1; - const edges = await query({ limit, where, orderBy }); + const edges = await query({ cursorSelection, limit, where, orderBy }); const requested = first ?? last ?? edges.length; const hasMore = edges.length > requested; const slicedEdges = edges.slice(0, requested); const finalEdges = isForward ? slicedEdges : slicedEdges.reverse(); - const startCursor: null | number = - (finalEdges[0]?.[primaryCursor.name] as number) ?? null; - const endCursor: null | number = - (finalEdges.at(-1)?.[primaryCursor.name] as number) ?? null; + const boundaries = cursorsFrom({ + edges: finalEdges, + orderColumn, + orderName, + primaryName: primaryCursor.name, + }); return { pageInfo: { totalCount, count: finalEdges.length, - hasNextPage: isForward ? hasMore : !!cursor, - hasPreviousPage: isForward ? !!cursor : hasMore, - startCursor, - endCursor, + // An empty page has nothing to page from, so it never advertises a + // neighbour it cannot hand out a cursor for. + hasNextPage: + finalEdges.length === 0 ? false : isForward ? hasMore : Boolean(cursor), + hasPreviousPage: + finalEdges.length === 0 ? false : isForward ? Boolean(cursor) : hasMore, + ...boundaries, }, - edges: finalEdges, + edges: finalEdges.map(withoutCursorField), }; } +/** + * The row as the caller asked for it, with pagination's own column taken back. + * + * The internal value is projected for one purpose and has no business in an + * admin response, a public response, an OpenAPI schema, a search document or a + * revision snapshot - all of which are built from what this returns. + */ +function withoutCursorField>( + row: QueryMin, +): Omit { + if (!(PAGINATION_CURSOR_FIELD in row)) return row; + + const rest: Partial = { ...row }; + delete rest[PAGINATION_CURSOR_FIELD]; + + return rest as Omit; +} + +/** + * The two cursors a page hands back, read off the page itself. + * + * No query. That is the entire design: the value and the row come out of one + * `SELECT`, so the tuple a cursor names is the tuple that actually decided where + * the row sat. + * + * It used to be a second `SELECT` of the boundary rows by id, which looked + * harmless and was not. Between the page query and that lookup another writer + * can move the boundary row - so a row chosen at `(10:00, 42)` would be handed + * back as a cursor saying `(14:00, 42)`, and the next page would start after + * 14:00 and skip everything in between. A `DELETE` in the same window was worse: + * the lookup returned nothing, the value became `null`, and for a nullable + * ordering `null` is a *real* position inside the null block - so the walk + * jumped there and abandoned the rest of the collection. Both are gone by + * construction rather than by locking. + */ +function cursorsFrom({ + edges, + orderColumn, + orderName, + primaryName, +}: { + edges: readonly Record[]; + orderColumn: PgColumn; + orderName: string; + primaryName: string; +}): { endCursor: null | string; startCursor: null | string } { + const first = edges[0]; + const last = edges.at(-1); + if (!first || !last) return { endCursor: null, startCursor: null }; + + /** + * Where the boundary value comes from, in order of preference. + * + * The projected field is the answer for every query built through this module. + * A query that omits it can still be paged by a column it selected itself - + * exact for a number, a string or a boolean, and from the same statement, so + * the invariant holds. A temporal column is the one case with no safe + * fallback: the row carries a `Date` that has already dropped the microseconds + * the next comparison needs, so minting from it would hand out a cursor that + * silently re-reads part of the page it came from. + */ + const valueOf = (row: Record): unknown => { + if (PAGINATION_CURSOR_FIELD in row) return row[PAGINATION_CURSOR_FIELD]; + if (!cursorValueIsCanonicalText(orderColumn) && orderName in row) { + return row[orderName]; + } + + throw new Error( + `The page query for "${orderName}" must spread \`cursorSelection\` into its projection, so the cursor value is read from the same statement as the row.`, + ); + }; + + const mint = (row: Record): string => + encodePaginationCursor({ + column: orderName, + id: Number(row[primaryName]), + value: cursorValueOf(orderColumn, valueOf(row)), + }); + + return { endCursor: mint(last), startCursor: mint(first) }; +} + +/** A positive whole number, as a query string carries it. */ +const zodPageSize = z + .string() + .regex(/^\d+$/, "Must be a whole number.") + .refine(value => Number(value) >= 1, "Must be greater than zero.") + .refine(value => Number.isSafeInteger(Number(value)), "Too large."); + export const zodPaginationPageInfo = z.object({ totalCount: z.number(), count: z.number(), hasNextPage: z.boolean(), hasPreviousPage: z.boolean(), - startCursor: z.number().nullable(), - endCursor: z.number().nullable(), + /** + * Opaque. It encodes the ordered tuple the next page continues from, so it is + * meaningless outside the ordering that produced it - hand it back unchanged. + */ + startCursor: z.string().nullable(), + endCursor: z.string().nullable(), }); -export const zodPaginationQuery = z.object({ - cursor: z.string().optional(), - first: z.string().optional(), - last: z.string().optional(), -}); +/** + * The pagination half of a list route's query, validated at the edge. + * + * Every rule that can be stated here is stated here rather than left to the + * internals, so a bad page size is a 400 from the route's own contract - and + * appears in the OpenAPI document - instead of something the handler discovers + * later. `parsePaginationParams` re-checks all of it, because a service can be + * called directly and a plugin can build a route without this schema. + * + * The cursor is only shape-checked here: it is opaque, so "looks like a cursor" + * is all a request schema can honestly say. Whether it decodes, and whether it + * belongs to *this* ordering, is decided where the ordering is known. + */ +export const zodPaginationQuery = z + .object({ + cursor: z + .string() + .min(1) + .max(512) + // base64url, or a legacy numeric cursor. Anything else cannot be one. + .regex(/^[A-Za-z0-9_-]+$/, "Invalid cursor.") + .optional(), + first: zodPageSize.optional(), + last: zodPageSize.optional(), + }) + .refine( + query => query.first === undefined || query.last === undefined, + 'Use either "first" or "last", not both.', + ); diff --git a/packages/vitnode/src/api/middlewares/global.middleware.ts b/packages/vitnode/src/api/middlewares/global.middleware.ts index 0a6e738e3..9dd4fc79e 100644 --- a/packages/vitnode/src/api/middlewares/global.middleware.ts +++ b/packages/vitnode/src/api/middlewares/global.middleware.ts @@ -1,8 +1,10 @@ import type { Context, Env, Next } from "hono"; -import type { Redis } from "ioredis"; import { HTTPException } from "hono/http-exception"; +import type { CacheClient } from "@/api/lib/cache"; +import type { RegisteredContentType } from "@/content/registry"; +import type { RegisteredContentModel } from "@/content/server/model"; import type { LocaleConfig, MessagesSource } from "@/lib/i18n/types"; import type { VitNodeApiConfig, VitNodeConfig } from "@/vitnode.config"; import type { VitNodeRealtime } from "@/ws/registry"; @@ -15,10 +17,18 @@ import { EmailModel } from "@/api/models/email"; import { EventsModel } from "@/api/models/events"; import { I18nModel } from "@/api/models/i18n"; import { QueueModel } from "@/api/models/queue"; -import { SearchModel } from "@/api/models/search"; +import { + assertSearchProviderCapabilities, + SearchModel, + validateSearchIndexers, +} from "@/api/models/search"; import { SessionModel } from "@/api/models/session"; import { SessionAdminModel } from "@/api/models/session-admin"; import { StorageModel } from "@/api/models/storage"; +import { validateContentTypes } from "@/content/registry"; +import { ensureContentLocalizationLanguages } from "@/content/server/language-resolver"; +import { warnAboutContentPreviewConfig } from "@/content/server/preview-config"; +import { ensureContentPreviewSecret } from "@/content/server/preview-secret"; import { CONFIG } from "@/lib/config"; import { collectLocaleCodes } from "@/lib/i18n/load-messages"; import { buildApiMessagesSources } from "@/lib/i18n/sources"; @@ -83,6 +93,25 @@ export interface EnvVariablesVitNode { ssoAdapters: SSOApiPlugin[]; }; captcha?: Pick["captcha"]; + /** + * Every registered content type's *model*, with the plugin that owns it. + * + * Background work has only a content type id to go on - a queue handler + * runs in a cron request with no plugin context at all - so the lookup from + * id to table, service and owner has to live somewhere it can reach. + */ + contentModels: RegisteredContentModel[]; + /** + * Signs content preview links. + * + * Generated by the install and stored in `core_secrets`. Absent only when + * no content type has `editorial.preview` enabled, i.e. when there is + * nothing to sign. + */ + contentPreviewSecret?: string; + /** Web origins the background cache bridge posts to. */ + contentRevalidateOrigins?: string[]; + contentTypes: RegisteredContentType[]; cron: (BuildCronReturn & { module: string; pluginId: string })[]; cronSecret?: string; email?: VitNodeApiConfig["email"]; @@ -139,6 +168,7 @@ export interface EnvVariablesVitNode { export const globalMiddleware = ({ ai, authorization, + content, metadata, email, dbProvider, @@ -155,6 +185,7 @@ export const globalMiddleware = ({ | "ai" | "authorization" | "captcha" + | "content" | "cron" | "dbProvider" | "email" @@ -164,7 +195,7 @@ export const globalMiddleware = ({ | "search" | "storage" > & - Pick & { cacheClient: null | Redis }) => { + Pick & { cacheClient: CacheClient | null }) => { const pluginsMetadata = plugins.map(plugin => ({ id: plugin.pluginId, })); @@ -212,14 +243,75 @@ export const globalMiddleware = ({ })), ); - const searchIndexersMetadata: SearchIndexerConfig[] = plugins.flatMap( - plugin => + // Validated across *all* plugins, for the same reason content types are: + // `buildApiPlugin` can only catch collisions inside a single plugin. + const searchIndexersMetadata: SearchIndexerConfig[] = validateSearchIndexers( + plugins.flatMap(plugin => (plugin.searchIndexers ?? []).map(indexer => ({ ...indexer, pluginId: plugin.pluginId, })), + ), + ); + + // Validated once more across *all* plugins: `buildApiPlugin` can only catch + // collisions inside a single plugin. + const contentTypesMetadata: RegisteredContentType[] = validateContentTypes( + plugins.flatMap(plugin => + (plugin.contentTypes ?? []).map(definition => ({ + definition, + pluginId: plugin.pluginId, + })), + ), + ); + + // Once, here, because "does anything have preview enabled" is only answerable + // after every plugin's content types are in. A warning, never a boot failure: + // preview is one content type's opt-in feature, not a prerequisite for the + // API. + warnAboutContentPreviewConfig({ contentTypes: contentTypesMetadata }); + + // Whether anything can mint a preview link at all - and so whether this + // install has any reason to hold a signing key. An install with no previewable + // content type never touches `core_secrets` because of this. + const hasPreviewableContentTypes = contentTypesMetadata.some( + entry => entry.definition.editorial.preview.enabled, + ); + + // Not validated: a model carries the definition that `contentTypesMetadata` + // already checked, so a second pass would only repeat the same errors. + const contentModelsMetadata: RegisteredContentModel[] = plugins.flatMap( + plugin => + (plugin.contentModels ?? []).map(model => ({ + model, + pluginId: plugin.pluginId, + })), ); + // Computed at boot, outside the request: "does anything need the languages + // table" is a property of the installed plugins, not of a request. + const hasLocalizedContentTypes = contentTypesMetadata.some( + entry => entry.definition.localization.enabled, + ); + + // Resolved once rather than per request - the adapter cannot change between + // them - which is also what makes the capability check below a boot-time fact. + const searchAdapter = search?.adapter ?? PostgresSearchAdapter(); + + // A localized searchable content type is indexed once per translation, so + // taking one translation down has to remove one document. A provider that + // ignores the language would remove them all and say nothing, so the pairing + // is refused here rather than discovered by whoever deletes a translation. + assertSearchProviderCapabilities(searchAdapter, { + localizedSearchContentTypes: contentTypesMetadata + .filter( + entry => + entry.definition.localization.enabled && + entry.definition.search.enabled, + ) + .map(entry => entry.definition.id), + }); + const permissionStaffMetadata: PermissionStaffCatalogEntry[] = plugins.map( plugin => ({ pluginId: plugin.pluginId, @@ -274,6 +366,13 @@ export const globalMiddleware = ({ c.set("storage", new StorageModel(c)); c.set("realtime", realtime); + // Resolved before `core` is set rather than per mint, so the integrations + // panel and the routes read the same value. Memoised, so this is one query + // on the first request of the process and nothing afterwards. + const contentPreviewSecret = hasPreviewableContentTypes + ? await ensureContentPreviewSecret(dbProvider) + : undefined; + c.set("core", { ai, i18n: i18nMetadata, @@ -283,7 +382,7 @@ export const globalMiddleware = ({ adapter: events?.adapter ?? LocalEventsAdapter(), listeners: eventsMetadata, }, - search: { adapter: search?.adapter ?? PostgresSearchAdapter() }, + search: { adapter: searchAdapter }, searchIndexers: searchIndexersMetadata, storage, authorization: { @@ -300,6 +399,7 @@ export const globalMiddleware = ({ cookieSecure: authorization?.cookieSecure ?? true, }, captcha, + contentPreviewSecret, cronSecret: CONFIG.cronJobSecret, hasCronAdapter: !!cron, plugins: pluginsMetadata, @@ -307,8 +407,21 @@ export const globalMiddleware = ({ queue: queueMetadata, webSockets: webSocketsMetadata, permissionStaff: permissionStaffMetadata, + contentModels: contentModelsMetadata, + contentRevalidateOrigins: content?.revalidateOrigins, + contentTypes: contentTypesMetadata, }); + // Whether a localized content type's `defaultLocale` names a row in + // `core_languages` is a fact about the *installation*, so it cannot be + // checked when the definition is built - there is no connection yet. This is + // that check, run at most once per process and skipped entirely when nothing + // is localized, so an install with no localized content types never touches + // the languages table because of this. + if (hasLocalizedContentTypes) { + await ensureContentLocalizationLanguages(c, contentTypesMetadata); + } + const user = await new SessionModel(c).getUser(); c.set("user", user); c.set("admin", null); diff --git a/packages/vitnode/src/api/middlewares/rate-limiter.middleware.ts b/packages/vitnode/src/api/middlewares/rate-limiter.middleware.ts index 47e09f4a7..e832f3674 100644 --- a/packages/vitnode/src/api/middlewares/rate-limiter.middleware.ts +++ b/packages/vitnode/src/api/middlewares/rate-limiter.middleware.ts @@ -1,5 +1,4 @@ import type { Context, Next } from "hono"; -import type { Redis } from "ioredis"; import { type IRateLimiterOptions, @@ -9,6 +8,8 @@ import { type RateLimiterRes, } from "rate-limiter-flexible"; +import type { CacheClient } from "@/api/lib/cache"; + import { CONFIG } from "../../lib/config"; const createRateLimiter = ({ @@ -17,7 +18,7 @@ const createRateLimiter = ({ ...options }: Omit & { keyPrefix: string; - storeClient?: null | Redis; + storeClient?: CacheClient | null; }): RateLimiterAbstract => { // With a Redis client the counters are shared across all instances, so rate // limits hold up behind a load balancer. `insuranceLimiter` falls back to @@ -25,6 +26,10 @@ const createRateLimiter = ({ if (storeClient) { return new RateLimiterRedis({ storeClient, + // `rate-limiter-flexible` sniffs the client library from the store + // client's constructor name, which node-redis does not expose. Without + // this it assumes ioredis and calls a command that doesn't exist. + useRedisPackage: true, keyPrefix, ...options, insuranceLimiter: new RateLimiterMemory({ keyPrefix, ...options }), @@ -39,7 +44,7 @@ const createRateLimiter = ({ export const rateLimiterMiddleware = ( options?: Omit, - storeClient?: null | Redis, + storeClient?: CacheClient | null, ) => { if (CONFIG.node_development) { // In development, we disable the rate limiter for easier testing diff --git a/packages/vitnode/src/api/models/events.test.ts b/packages/vitnode/src/api/models/events.test.ts index f9b834a32..95cbee53e 100644 --- a/packages/vitnode/src/api/models/events.test.ts +++ b/packages/vitnode/src/api/models/events.test.ts @@ -210,6 +210,42 @@ describe("EventsModel.emit envelope", () => { expect(publish.mock.calls[0][1].pluginId).toBe("@vitnode/blog"); }); + it("an explicit owner wins over the context plugin", async () => { + // The queue case: core owns the handler, so the context says core, but the + // domain event belongs to whoever owns the thing it happened to. + const { adapter, publish } = captureEnvelope(); + const { ctx } = makeCtx({ adapter, plugin: { id: "@vitnode/core" } }); + + await new EventsModel(ctx).emit("user.created", PAYLOAD, { + pluginId: "@vitnode/example", + }); + + expect(publish.mock.calls[0][1].pluginId).toBe("@vitnode/example"); + }); + + it("an omitted override changes nothing for existing callers", async () => { + const { adapter, publish } = captureEnvelope(); + const { ctx } = makeCtx({ adapter, plugin: { id: "@vitnode/blog" } }); + + await new EventsModel(ctx).emit("user.created", PAYLOAD, {}); + + expect(publish.mock.calls[0][1].pluginId).toBe("@vitnode/blog"); + }); + + it("does not impersonate the plugin on the shared context", async () => { + // Overriding by swapping `c.get("plugin")` would change the logger, the + // permission checks and every other model on the request to fix one field. + const { adapter, publish } = captureEnvelope(); + const { ctx } = makeCtx({ adapter, plugin: { id: "@vitnode/core" } }); + + await new EventsModel(ctx).emit("user.created", PAYLOAD, { + pluginId: "@vitnode/example", + }); + + expect(publish.mock.calls[0][1].pluginId).toBe("@vitnode/example"); + expect(ctx.get("plugin").id).toBe("@vitnode/core"); + }); + it("derives the actor: admin wins over user, then user, then system", async () => { const { adapter, publish } = captureEnvelope(); diff --git a/packages/vitnode/src/api/models/events.ts b/packages/vitnode/src/api/models/events.ts index 4b5426581..96ad07aa2 100644 --- a/packages/vitnode/src/api/models/events.ts +++ b/packages/vitnode/src/api/models/events.ts @@ -100,6 +100,26 @@ export interface EventsApiPlugin { publish: (c: Context, envelope: EventEnvelope) => Promise; } +export interface EventEmitOptions { + /** + * Who owns the *domain event*, when that is not the plugin handling the + * request. + * + * Ownership normally comes from `c.get("plugin")`, which is right for a route: + * whoever handled the request emitted the event. It is wrong for anything that + * runs on someone else's behalf. A queue handler is the clear case - core owns + * the handler, so the context says `@vitnode/core`, but a scheduled + * `content.example.article.published` is the example plugin's event and always + * was. + * + * Pass it explicitly rather than swapping `c.get("plugin")` for the duration. + * The context is shared with the logger, the permission checks and every other + * model on the request; impersonating a plugin inside it would change all of + * them to fix one field. + */ + pluginId?: string; +} + export class EventsModel { constructor(c: Context) { this.c = c; @@ -117,10 +137,17 @@ export class EventsModel { * AFTER the writes the event describes have committed - after your awaited * inserts/updates, and after any enclosing `db.transaction` callback has * returned. + * + * **Not throwing is the contract, not an oversight.** An interactive mutation + * has already committed by the time this runs, and a listener that fell over + * is not a reason to tell the person their save failed. A caller that *does* + * need delivery to be retried - the scheduled-effects task is the one in + * core - reads `failures` and decides for itself. */ async emit( name: K, payload: VitNodeEvents[K], + options?: EventEmitOptions, ): Promise { const admin = this.c.get("admin"); const user = this.c.get("user"); @@ -129,7 +156,8 @@ export class EventsModel { name, payload, emittedAt: new Date(), - pluginId: this.c.get("plugin")?.id ?? "@vitnode/core", + pluginId: + options?.pluginId ?? this.c.get("plugin")?.id ?? "@vitnode/core", actor: admin ? { type: "admin", id: admin.user.id } : user diff --git a/packages/vitnode/src/api/models/queue.test.ts b/packages/vitnode/src/api/models/queue.test.ts index c0cd10117..382f9eb89 100644 --- a/packages/vitnode/src/api/models/queue.test.ts +++ b/packages/vitnode/src/api/models/queue.test.ts @@ -1,86 +1,126 @@ +// @vitest-environment node import type { Context } from "hono"; import { describe, expect, it, vi } from "vitest"; import { QueueModel } from "./queue"; -const makeCtx = ( - overrides: { - plugin?: { id: string }; - queue?: { maxAttempts?: number; name: string; pluginId: string }[]; - } = {}, -): { - ctx: Context; - values: ReturnType; -} => { - const values = vi.fn().mockReturnValue({ - returning: vi.fn().mockResolvedValue([{ id: 1 }]), +/** Records what was inserted, and through which handle. */ +const harness = ({ plugin }: { plugin?: string } = {}) => { + const inserts: { handle: string; values: Record }[] = []; + + const handle = (name: string) => ({ + insert: () => ({ + values: (values: Record) => ({ + returning: async () => { + inserts.push({ handle: name, values }); + + return await Promise.resolve([{ id: 1 }]); + }, + }), + }), }); - const store: Record = { - db: { insert: vi.fn().mockReturnValue({ values }) }, - core: { queue: overrides.queue ?? [] }, - plugin: overrides.plugin, - }; - - return { - ctx: { get: (k: string) => store[k] } as unknown as Context, - values, - }; + + const c = { + get: (key: string) => + key === "db" + ? handle("request") + : key === "core" + ? { queue: [{ maxAttempts: 7, name: "known", pluginId: plugin }] } + : key === "plugin" + ? plugin + ? { id: plugin } + : undefined + : undefined, + } as unknown as Context; + + return { c, inserts, tx: handle("transaction") }; }; describe("QueueModel.dispatch", () => { - it("uses the explicit maxAttempts when provided", async () => { - const { ctx, values } = makeCtx({ - queue: [{ name: "job", pluginId: "@vitnode/core", maxAttempts: 5 }], - }); + it("stamps the requesting plugin by default", async () => { + const { c, inserts } = harness({ plugin: "@vitnode/example" }); - await new QueueModel(ctx).dispatch({ name: "job", maxAttempts: 7 }); + await new QueueModel(c).dispatch({ name: "do-something" }); - expect(values.mock.calls[0][0]).toMatchObject({ maxAttempts: 7 }); + expect(inserts[0].values.pluginId).toBe("@vitnode/example"); }); - it("falls back to the registered task maxAttempts", async () => { - const { ctx, values } = makeCtx({ - queue: [{ name: "job", pluginId: "@vitnode/core", maxAttempts: 5 }], - }); + it("falls back to core when no plugin is handling the request", async () => { + const { c, inserts } = harness(); - await new QueueModel(ctx).dispatch({ name: "job" }); + await new QueueModel(c).dispatch({ name: "do-something" }); - expect(values.mock.calls[0][0]).toMatchObject({ maxAttempts: 5 }); + expect(inserts[0].values.pluginId).toBe("@vitnode/core"); }); - it("defaults to 3 when the registered task has no maxAttempts", async () => { - const { ctx, values } = makeCtx({ - queue: [{ name: "job", pluginId: "@vitnode/core" }], - }); + it("stamps an explicit plugin instead", async () => { + // The case that makes scheduled publication work at all: a plugin's route + // dispatches a task core owns. The worker resolves handlers by + // `${pluginId}:${name}`, so the plugin's own id would leave the row + // unclaimable forever. + const { c, inserts } = harness({ plugin: "@vitnode/example" }); - await new QueueModel(ctx).dispatch({ name: "job" }); + await new QueueModel(c).dispatch({ + name: "content-schedule", + pluginId: "@vitnode/core", + }); - expect(values.mock.calls[0][0]).toMatchObject({ maxAttempts: 3 }); + expect(inserts[0].values.pluginId).toBe("@vitnode/core"); }); - it("defaults to 3 when the task is not registered", async () => { - const { ctx, values } = makeCtx({ queue: [] }); + it("uses the request handle when no transaction is given", async () => { + const { c, inserts } = harness(); - await new QueueModel(ctx).dispatch({ name: "job" }); + await new QueueModel(c).dispatch({ name: "do-something" }); - expect(values.mock.calls[0][0]).toMatchObject({ maxAttempts: 3 }); + expect(inserts[0].handle).toBe("request"); }); - it("scopes the task lookup by pluginId", async () => { - const { ctx, values } = makeCtx({ - plugin: { id: "@vitnode/blog" }, - queue: [ - { name: "job", pluginId: "@vitnode/core", maxAttempts: 5 }, - { name: "job", pluginId: "@vitnode/blog", maxAttempts: 9 }, - ], + it("joins a transaction when one is given", async () => { + // Without this the queue row can commit while the row it points at rolls + // back, and the task wakes up to find nothing there. + const { c, inserts, tx } = harness(); + + await new QueueModel(c).dispatch({ + name: "do-something", + tx: tx as never, }); - await new QueueModel(ctx).dispatch({ name: "job" }); + expect(inserts[0].handle).toBe("transaction"); + }); + + it("still reads the registered task's maxAttempts", async () => { + const { c, inserts } = harness({ plugin: "@vitnode/example" }); - expect(values.mock.calls[0][0]).toMatchObject({ - pluginId: "@vitnode/blog", - maxAttempts: 9, + await new QueueModel(c).dispatch({ name: "known" }); + + expect(inserts[0].values.maxAttempts).toBe(7); + }); + + it("looks the task up under the plugin it is dispatched for", async () => { + // `known` is registered under `@vitnode/example`, so dispatching it as core + // finds no registration and falls back to the default. + const { c, inserts } = harness({ plugin: "@vitnode/example" }); + + await new QueueModel(c).dispatch({ + name: "known", + pluginId: "@vitnode/core", }); + + expect(inserts[0].values.maxAttempts).toBe(3); + }); + + it("defaults availableAt to now, so a task runs on the next tick", async () => { + const now = new Date("2026-08-05T10:00:00.000Z"); + vi.useFakeTimers(); + vi.setSystemTime(now); + + const { c, inserts } = harness(); + await new QueueModel(c).dispatch({ name: "do-something" }); + + expect(inserts[0].values.availableAt).toEqual(now); + + vi.useRealTimers(); }); }); diff --git a/packages/vitnode/src/api/models/queue.ts b/packages/vitnode/src/api/models/queue.ts index 38f66309f..7cd68b19c 100644 --- a/packages/vitnode/src/api/models/queue.ts +++ b/packages/vitnode/src/api/models/queue.ts @@ -7,8 +7,26 @@ export interface QueueDispatchArgs { maxAttempts?: number; name: string; payload?: Record; + /** + * Who owns the handler, when that is not the plugin handling the request. + * + * The worker resolves a handler by `` `${pluginId}:${name}` ``, so a task + * registered by core but dispatched from a plugin's route needs to say so - + * otherwise the row is stamped with the plugin's id and nothing will ever + * claim it. Defaults to the requesting plugin, which is right for the + * ordinary case where a plugin dispatches its own task. + */ + pluginId?: string; priority?: number; queue?: string; + /** + * Join an existing transaction instead of using the request handle. + * + * Needed whenever the row that the task refers to is written in the same + * unit of work: without it, the queue row can commit while the row it points + * at rolls back, and the task wakes up to find nothing there. + */ + tx?: Omit; } /** @@ -27,19 +45,21 @@ export class QueueModel { async dispatch({ name, payload = {}, + pluginId: explicitPluginId, queue = "default", priority = 0, maxAttempts, availableAt, + tx, }: QueueDispatchArgs): Promise<{ id: number }> { - const pluginId = this.c.get("plugin")?.id ?? "@vitnode/core"; + const pluginId = + explicitPluginId ?? this.c.get("plugin")?.id ?? "@vitnode/core"; const registeredTask = this.c .get("core") .queue.find(task => task.pluginId === pluginId && task.name === name); - const [row] = await this.c - .get("db") + const [row] = await (tx ?? this.c.get("db")) .insert(core_queue) .values({ pluginId, diff --git a/packages/vitnode/src/api/models/search.test-d.ts b/packages/vitnode/src/api/models/search.test-d.ts new file mode 100644 index 000000000..20234211b --- /dev/null +++ b/packages/vitnode/src/api/models/search.test-d.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-deprecated -- Asserting that the + deprecated result shape still compiles is the point of this file. */ +import { assertType, describe, expectTypeOf, it } from "vitest"; + +import type { ContentSearchIndexer } from "@/content/server"; + +import type { + LegacySearchIndexerPage, + SearchDocument, + SearchIndexer, + SearchIndexerLoadResult, + SearchIndexerPage, +} from "./search"; + +const document: SearchDocument = { + content: "body", + createdAt: new Date("2026-01-01T00:00:00.000Z"), + itemId: 1, + itemType: "custom_item", + title: "Hello", +}; + +describe("SearchIndexer.load", () => { + it("accepts the preferred page result", () => { + assertType({ + itemType: "custom_item", + load: async (_c, _offset, limit) => + await Promise.resolve({ documents: [document], itemsRead: limit }), + }); + }); + + it("still accepts the deprecated array result", () => { + // Exactly what a plugin written before Stage 3 looks like. It has to keep + // compiling: `SearchIndexer` is a documented public import. + const legacyIndexer: SearchIndexer = { + itemType: "legacy.item", + load: async () => await Promise.resolve([]), + }; + + assertType(legacyIndexer); + assertType({ + itemType: "legacy.item", + load: async () => await Promise.resolve([document]), + }); + }); + + it("accepts an indexer that returns either shape", () => { + assertType({ + count: async () => await Promise.resolve(1), + itemType: "either", + load: async (_c, offset) => + await Promise.resolve( + offset === 0 ? { documents: [document], itemsRead: 1 } : [], + ), + }); + }); + + it("rejects a result that is neither shape", () => { + assertType({ + itemType: "wrong", + // @ts-expect-error - a bare document is not a page and not an array. + load: async () => await Promise.resolve(document), + }); + }); + + it("rejects a page missing its source count", () => { + assertType({ + itemType: "wrong", + // @ts-expect-error - `itemsRead` is what the rebuild pages by. + load: async () => await Promise.resolve({ documents: [document] }), + }); + }); +}); + +describe("SearchIndexerLoadResult", () => { + it("is the union of the page and the deprecated array", () => { + expectTypeOf().toExtend(); + expectTypeOf().toExtend(); + expectTypeOf().toEqualTypeOf(); + }); +}); + +describe("ContentSearchIndexer", () => { + it("is a SearchIndexer pinned to the page result", () => { + expectTypeOf().toExtend(); + expectTypeOf< + Awaited> + >().toEqualTypeOf(); + }); +}); diff --git a/packages/vitnode/src/api/models/search.test.ts b/packages/vitnode/src/api/models/search.test.ts index 74323a5b9..f9f9a034c 100644 --- a/packages/vitnode/src/api/models/search.test.ts +++ b/packages/vitnode/src/api/models/search.test.ts @@ -5,9 +5,14 @@ import { describe, expect, it, vi } from "vitest"; import { core_search_index } from "@/database/search"; -import type { SearchProviderApiPlugin } from "./search"; +import type { SearchDocument, SearchProviderApiPlugin } from "./search"; -import { SearchModel } from "./search"; +import { PostgresSearchAdapter } from "../adapters/search/postgres"; +import { + assertSearchProviderCapabilities, + normalizeSearchIndexerPage, + SearchModel, +} from "./search"; const createProvider = (): SearchProviderApiPlugin => ({ name: "postgres", @@ -28,7 +33,10 @@ const createProvider = (): SearchProviderApiPlugin => ({ }), }); -const createContext = (provider: SearchProviderApiPlugin) => { +const createContext = ( + provider: SearchProviderApiPlugin, + requestPluginId?: string, +) => { const onConflictDoUpdate = vi.fn().mockResolvedValue(undefined); const values = vi.fn< (row: { content: string; isPublic: boolean; pluginId: string }) => { @@ -44,7 +52,9 @@ const createContext = (provider: SearchProviderApiPlugin) => { get: (key: string) => { if (key === "db") return db; if (key === "core") return { search: { adapter: provider } }; - if (key === "plugin") return undefined; + if (key === "plugin") { + return requestPluginId ? { id: requestPluginId } : undefined; + } return undefined; }, @@ -75,6 +85,112 @@ describe("SearchModel", () => { expect(provider.index).toHaveBeenCalledWith(c, { ...doc, content: "Hello world", + pluginId: "core", + }); + }); + + describe("plugin ownership", () => { + const doc = { + content: "body", + createdAt: new Date("2026-01-01"), + itemId: 1, + itemType: "example.article", + title: "Hello", + }; + + it("prefers an explicit document owner over the request", async () => { + // A rebuild runs inside the core cron request, so the request's plugin is + // not the owner - the document has to win, or the same record would be + // stored differently depending on which path wrote it. + const provider = createProvider(); + const { c, values } = createContext(provider, "@vitnode/core"); + + await new SearchModel(c).index({ ...doc, pluginId: "@vitnode/example" }); + + expect(values.mock.calls[0][0].pluginId).toBe("@vitnode/example"); + expect(provider.index).toHaveBeenCalledWith( + c, + expect.objectContaining({ pluginId: "@vitnode/example" }), + ); + }); + + it("falls back to the request's plugin", async () => { + const provider = createProvider(); + const { c, values } = createContext(provider, "@vitnode/example"); + + await new SearchModel(c).index(doc); + + expect(values.mock.calls[0][0].pluginId).toBe("@vitnode/example"); + expect(provider.index).toHaveBeenCalledWith( + c, + expect.objectContaining({ pluginId: "@vitnode/example" }), + ); + }); + + it("treats a blank owner as absent", async () => { + // `pluginId` is public input, so an empty or whitespace-only string is a + // missing owner - not a collection called "". + const provider = createProvider(); + const { c, values } = createContext(provider, "@vitnode/example"); + + await new SearchModel(c).index({ ...doc, pluginId: " " }); + + expect(values.mock.calls[0][0].pluginId).toBe("@vitnode/example"); + }); + + it("falls back to core for a blank owner outside a plugin request", async () => { + const provider = createProvider(); + const { c, values } = createContext(provider); + + await new SearchModel(c).index({ ...doc, pluginId: "" }); + + expect(values.mock.calls[0][0].pluginId).toBe("core"); + }); + + it("falls back to core outside a plugin request", async () => { + const provider = createProvider(); + const { c, values } = createContext(provider); + + await new SearchModel(c).index(doc); + + expect(values.mock.calls[0][0].pluginId).toBe("core"); + }); + + it("resolves every document in a bulk write", async () => { + const provider = createProvider(); + const { c, values } = createContext(provider, "@vitnode/core"); + + await new SearchModel(c).bulkIndex([ + { ...doc, itemId: 1, pluginId: "@vitnode/example" }, + { ...doc, itemId: 2, pluginId: "@vitnode/blog" }, + // No owner declared: the request's plugin stands in. + { ...doc, itemId: 3 }, + ]); + + expect(values.mock.calls.map(call => call[0].pluginId)).toEqual([ + "@vitnode/example", + "@vitnode/blog", + "@vitnode/core", + ]); + expect(provider.bulkIndex).toHaveBeenCalledWith(c, [ + expect.objectContaining({ itemId: 1, pluginId: "@vitnode/example" }), + expect.objectContaining({ itemId: 2, pluginId: "@vitnode/blog" }), + expect.objectContaining({ itemId: 3, pluginId: "@vitnode/core" }), + ]); + }); + + it("rewrites the owner of an existing row on conflict", async () => { + // Otherwise a row written before its indexer declared an owner would keep + // the first writer's guess forever, and a rebuild could not repair it. + const provider = createProvider(); + const { c, values } = createContext(provider, "@vitnode/example"); + + await new SearchModel(c).index(doc); + + const { onConflictDoUpdate } = values.mock.results[0].value; + expect(onConflictDoUpdate.mock.calls[0][0].set).toMatchObject({ + pluginId: "@vitnode/example", + }); }); }); @@ -85,7 +201,20 @@ describe("SearchModel", () => { await new SearchModel(c).delete("blog_post", 5); expect(deleteFn).toHaveBeenCalledWith(core_search_index); - expect(provider.delete).toHaveBeenCalledWith(c, "blog_post", 5); + // No language: deleting the record means every language of it. + expect(provider.delete).toHaveBeenCalledWith(c, "blog_post", 5, undefined); + }); + + it("deletes one language without touching the others", async () => { + const provider = createProvider(); + const { c, deleteFn } = createContext(provider); + + // Multi-language content is one row per `(itemType, itemId, languageCode)`, + // so taking the Polish translation down must leave the English one indexed. + await new SearchModel(c).delete("blog_post", 5, "pl"); + + expect(deleteFn).toHaveBeenCalledWith(core_search_index); + expect(provider.delete).toHaveBeenCalledWith(c, "blog_post", 5, "pl"); }); it("delegates search to the provider", async () => { @@ -100,3 +229,197 @@ describe("SearchModel", () => { }); }); }); + +describe("normalizeSearchIndexerPage", () => { + const document: SearchDocument = { + content: "body", + createdAt: new Date("2026-01-01"), + itemId: 1, + itemType: "legacy.item", + title: "Hello", + }; + + it("passes a modern page through untouched", () => { + const page = { documents: [document], itemsRead: 7 }; + + expect(normalizeSearchIndexerPage(page, 200)).toBe(page); + }); + + it("keeps a modern page that read rows but produced nothing", () => { + // The whole reason the object form exists: this must not read as exhausted. + expect( + normalizeSearchIndexerPage({ documents: [], itemsRead: 200 }, 200), + ).toEqual({ documents: [], itemsRead: 200 }); + }); + + it("reports the requested limit for a non-empty legacy array", () => { + // Not `documents.length`: a legacy indexer may emit several documents per + // source row, so the array length would skip rows on every page. + expect(normalizeSearchIndexerPage([document], 200)).toEqual({ + documents: [document], + itemsRead: 200, + }); + }); + + it("reports the requested limit however many documents a page holds", () => { + expect( + normalizeSearchIndexerPage([document, document, document, document], 200) + .itemsRead, + ).toBe(200); + }); + + it("treats an empty legacy array as an exhausted source", () => { + expect(normalizeSearchIndexerPage([], 200)).toEqual({ + documents: [], + itemsRead: 0, + }); + }); +}); + +/** + * The one provider capability that is not a nicety. + * + * `delete(c, itemType, itemId, languageCode)` is a JavaScript call: a provider + * written before per-locale content accepts the fourth argument and drops it, so + * taking one translation down removes every language from that provider's store + * while the canonical `core_search_index` removes one. Nothing throws, nothing is + * logged, and the two disagree from then on - which is why the pairing is refused + * at boot instead of being discovered by whoever deletes a translation. + */ +describe("assertSearchProviderCapabilities", () => { + const legacy = (): SearchProviderApiPlugin => ({ + ...createProvider(), + name: "legacy-engine", + }); + + const scoped = (): SearchProviderApiPlugin => ({ + ...createProvider(), + name: "scoped-engine", + capabilities: { + authorBoost: false, + facets: false, + languageScopedDelete: true, + timeDecay: false, + }, + }); + + it("allows a provider that declares nothing when nothing is localized", () => { + expect(() => + assertSearchProviderCapabilities(legacy(), { + localizedSearchContentTypes: [], + }), + ).not.toThrow(); + }); + + it("refuses a provider that cannot scope a delete to one language", () => { + expect(() => + assertSearchProviderCapabilities(legacy(), { + localizedSearchContentTypes: ["example.article"], + }), + ).toThrow(/legacy-engine/); + }); + + it("names the content type and the missing capability", () => { + // A boot failure is only useful if it says what to change. + expect(() => + assertSearchProviderCapabilities(legacy(), { + localizedSearchContentTypes: ["example.article"], + }), + ).toThrow(/example\.article/); + expect(() => + assertSearchProviderCapabilities(legacy(), { + localizedSearchContentTypes: ["example.article"], + }), + ).toThrow(/languageScopedDelete/); + }); + + it("refuses a provider that declares the other capabilities but not this one", () => { + // Declaring `capabilities` is not the same as declaring this capability. + const partial: SearchProviderApiPlugin = { + ...createProvider(), + name: "facets-only", + capabilities: { authorBoost: true, facets: true, timeDecay: true }, + }; + + expect(() => + assertSearchProviderCapabilities(partial, { + localizedSearchContentTypes: ["example.article"], + }), + ).toThrow(/facets-only/); + }); + + it("allows a provider that declares it", () => { + expect(() => + assertSearchProviderCapabilities(scoped(), { + localizedSearchContentTypes: ["example.article", "example.page"], + }), + ).not.toThrow(); + }); + + it("lists every offending content type, not just the first", () => { + expect(() => + assertSearchProviderCapabilities(legacy(), { + localizedSearchContentTypes: ["example.article", "example.page"], + }), + ).toThrow(/example\.article", "example\.page/); + }); + + it("says yes to the bundled Postgres provider", async () => { + // Its store *is* `core_search_index`, which `SearchModel.delete` already + // narrows by language before the provider is reached. + const { PostgresSearchAdapter } = + await import("@/api/adapters/search/postgres"); + + expect(() => + assertSearchProviderCapabilities(PostgresSearchAdapter(), { + localizedSearchContentTypes: ["example.article"], + }), + ).not.toThrow(); + }); +}); + +/** + * The provider half of a search diagnostic. + * + * `SearchModel.index` writes the canonical row and *then* hands the document to + * the provider, so the two can disagree - and a diagnostic that cannot ask the + * provider would report the canonical table's health as the whole story. + */ +describe("provider diagnostics", () => { + const modelFor = (provider: SearchProviderApiPlugin) => + new SearchModel({ + get: (key: string) => + key === "core" ? { search: { adapter: provider } } : undefined, + } as never); + + it("reports the bundled Postgres provider as canonical storage", () => { + // Its store *is* `core_search_index`, so a diagnostic can use the canonical + // count rather than paying for a second one over the same rows. + expect(modelFor(PostgresSearchAdapter()).isCanonicalStorage()).toBe(true); + }); + + it("reports a mirroring provider as not canonical", () => { + expect(modelFor(createProvider()).isCanonicalStorage()).toBe(false); + }); + + it("answers null when the provider offers no count", async () => { + // `null` is not zero and not healthy - it means nobody looked, and the + // caller has to report that as unverified. + await expect( + modelFor(createProvider()).countDocuments({ itemType: "blog_post" }), + ).resolves.toBeNull(); + }); + + it("passes the item type and language straight through", async () => { + const count = vi.fn().mockResolvedValue(12); + const model = modelFor({ ...createProvider(), count }); + + await expect( + model.countDocuments({ itemType: "blog_post", languageCode: "pl" }), + ).resolves.toBe(12); + expect(count.mock.calls[0][1]).toEqual({ + itemType: "blog_post", + languageCode: "pl", + }); + }); +}); diff --git a/packages/vitnode/src/api/models/search.ts b/packages/vitnode/src/api/models/search.ts index 7a29d88fa..4d3db855b 100644 --- a/packages/vitnode/src/api/models/search.ts +++ b/packages/vitnode/src/api/models/search.ts @@ -19,6 +19,11 @@ export interface SearchDocument { // language; single-language content may leave it empty. languageCode?: string; metadata?: Record; + // The plugin that owns this item. Omit it and {@link SearchModel} falls back + // to the request's plugin - which is only right while the request *is* the + // owning plugin's, so a rebuild (it runs in the core cron request) must set it + // explicitly. + pluginId?: string; title: string; updatedAt?: Date; url?: string; @@ -81,14 +86,80 @@ export interface SearchResult { export interface SearchProviderCapabilities { authorBoost: boolean; + /** + * Whether the provider's store **is** `core_search_index`. + * + * True only for the bundled Postgres provider, which queries the canonical + * table directly rather than mirroring it. Diagnostics use this to skip a + * second count of the same rows: canonical and provider are one storage, so + * asking twice would cost a query to learn something already known. + * + * A mirroring provider - anything with its own store - must leave it unset. + */ + canonicalStorage?: boolean; facets: boolean; + /** + * Whether {@link SearchProviderApiPlugin.delete} honours its `languageCode`. + * + * Declared rather than inferred, because JavaScript cannot tell the difference: + * a provider written as `delete(c, itemType, itemId)` accepts the fourth + * argument at runtime and silently ignores it, so taking down one translation + * would remove every language from that provider's store while the canonical + * `core_search_index` removed one. The two would then disagree forever, and + * nothing would say so. + * + * Optional, so a provider written before localized content still compiles and + * still serves single-language content. Absent means "no", and + * {@link assertSearchProviderCapabilities} refuses to boot an install that + * pairs such a provider with a localized searchable content type. + */ + languageScopedDelete?: boolean; timeDecay: boolean; } +/** + * One page of a rebuild. + * + * The two counts are separate on purpose. An indexer may emit several documents + * per item (one per language, say) or none at all (a row whose data cannot be + * projected), so a document count can never stand in for a source count - using + * it would either skip items or end the rebuild while rows remain. + */ +export interface SearchIndexerPage { + documents: SearchDocument[]; + /** Source rows this page read. `0` means the source is exhausted. */ + itemsRead: number; +} + +/** + * The pre-{@link SearchIndexerPage} result: documents with no source count. + * + * @deprecated Return a {@link SearchIndexerPage}. An array cannot say how many + * source rows produced it, so the rebuild has to assume a full page was read and + * wait for an empty one to stop - which means a page that reads rows and projects + * none of them (every row on it malformed, say) ends the rebuild early and the + * rows behind it are never indexed. Supported for now; removed in a future major + * release. + */ +export type LegacySearchIndexerPage = SearchDocument[]; + +export type SearchIndexerLoadResult = + // The one intentional use of the deprecated shape: this union is what keeps + // pre-Stage-3 indexers compiling, so the lint rule has nothing to warn about + // here. Every *other* reference should be flagged. + // eslint-disable-next-line @typescript-eslint/no-deprecated + LegacySearchIndexerPage | SearchIndexerPage; + /** * Streams every existing item of one content type so the whole index can be - * rebuilt (e.g. after switching engines). `load` returns one page at a time; - * return fewer than `limit` rows to signal the end. + * rebuilt (e.g. after switching engines). + * + * `load` is called with `offset` advanced by the previous page's `itemsRead`. + * Report `itemsRead: 0` to end the rebuild; an empty `documents` array does not, + * because a page can legitimately read rows and project none of them. + * + * Returning a bare `SearchDocument[]` still works - see + * {@link LegacySearchIndexerPage} for what it gives up. */ export interface SearchIndexer { // Total number of source items available to index for this type. Powers the @@ -100,13 +171,120 @@ export interface SearchIndexer { c: Context, offset: number, limit: number, - ) => Promise; + ) => Promise; } +/** + * A declared document owner, or `undefined` when there is not really one. + * + * `pluginId` is public input, so an empty or whitespace-only string is a missing + * owner rather than a collection named `""`. Every place that resolves ownership + * goes through this, so the fallback chains cannot drift apart. + */ +export const searchDocumentOwner = ( + pluginId: null | string | undefined, +): string | undefined => { + const trimmed = pluginId?.trim(); + + return trimmed === "" ? undefined : trimmed; +}; + +/** + * Turns either `load` result into a page, so the rebuild has one shape to reason + * about and the compatibility rule lives in exactly one place. + * + * A non-empty legacy array reports `requestedLimit` rather than + * `documents.length`, because that is what the old rebuild advanced by: an + * indexer may emit several documents per source row (one per language), so a + * document count would skip rows on every page. An empty array is the only end + * signal it has. + */ +export const normalizeSearchIndexerPage = ( + result: SearchIndexerLoadResult, + requestedLimit: number, +): SearchIndexerPage => { + if (!Array.isArray(result)) return result; + + return { + documents: result, + itemsRead: result.length === 0 ? 0 : requestedLimit, + }; +}; + export interface SearchIndexerConfig extends SearchIndexer { pluginId: string; } +/** + * Rejects two indexers claiming the same `itemType`. + * + * `itemType` is the index's only namespace, so a collision is not a cosmetic + * problem: both indexers would `load` on every rebuild, writing over each + * other's documents whenever their item ids overlap, and the admin coverage + * report would silently describe only the first one. Failing at boot is the only + * place this is cheap to notice. + * + * Called once per plugin by `buildApiPlugin` and again across every plugin by + * the global middleware, which is the only place that sees them all. + */ +export const validateSearchIndexers = ( + indexers: readonly SearchIndexerConfig[], +): SearchIndexerConfig[] => { + const seen = new Map(); + + for (const indexer of indexers) { + const owner = seen.get(indexer.itemType); + if (owner !== undefined) { + throw new Error( + `[Search] Duplicate search indexer for item type "${indexer.itemType}": registered by both "${owner}" and "${indexer.pluginId}". An item type may only be indexed by one indexer.`, + ); + } + + seen.set(indexer.itemType, indexer.pluginId); + } + + return [...indexers]; +}; + +/** + * Refuses to boot a provider that cannot express what the installed content + * types need. + * + * Only one requirement so far, and it is narrow on purpose: a content type that + * is both localized and searchable is indexed once per published translation, so + * unpublishing or deleting one of them has to remove exactly one document. A + * provider that ignores `languageCode` would take every language out instead, and + * because the extra argument is simply dropped there is no error, no log line and + * no way to notice until somebody searches for content that should still be + * there. + * + * Fails at boot rather than at the delete for the obvious reason: the delete is + * the moment the damage happens, and by then the install has been running. + * + * Content types are passed as plain ids so this stays where the rest of the + * search contract lives, with no dependency on the Content Engine. + */ +export const assertSearchProviderCapabilities = ( + provider: SearchProviderApiPlugin, + { + localizedSearchContentTypes, + }: { + /** Ids of content types indexed once per translation. */ + localizedSearchContentTypes: readonly string[]; + }, +): void => { + if (localizedSearchContentTypes.length === 0) return; + if (provider.capabilities?.languageScopedDelete === true) return; + + throw new Error( + `[Search] The "${provider.name}" search provider does not support language-scoped deletion, but ${localizedSearchContentTypes.length === 1 ? "the content type" : "the content types"} ${localizedSearchContentTypes + .map(id => `"${id}"`) + .join( + ", ", + )} ${localizedSearchContentTypes.length === 1 ? "is" : "are"} localized and searchable - each publishes one search document per translation. Taking one translation down must remove one document, and a provider that ignores the "languageCode" argument of "delete" would remove every language instead. Declare "capabilities: { languageScopedDelete: true }" on the provider once its "delete" honours that argument, or turn "search" off for ${localizedSearchContentTypes.length === 1 ? "that content type" : "those content types"}.`, + ); +}; + /** * A pluggable search engine. The {@link SearchModel} owns the canonical * `core_search_index` table for every provider, so a provider that queries that @@ -118,7 +296,43 @@ export interface SearchProviderApiPlugin { bulkIndex: (c: Context, docs: SearchDocument[]) => Promise; capabilities?: SearchProviderCapabilities; clear: (c: Context, itemType?: string) => Promise; - delete: (c: Context, itemType: string, itemId: number) => Promise; + /** + * How many documents the provider holds for one collection. + * + * Optional, and its absence is meaningful: a provider that cannot be counted + * is reported as **unverified** rather than healthy, because "we did not look" + * and "we looked and it was fine" are different answers and only one of them + * is worth acting on. + * + * It must count rather than fetch - `_count` on Elasticsearch, `COUNT(*)` on a + * table - and honour `languageCode` where the provider stores one document per + * translation. Omitting the language means every language. + */ + count?: ( + c: Context, + args: { itemType: string; languageCode?: string }, + ) => Promise; + /** + * Removes one item's documents. + * + * `languageCode` narrows it to a single language, for content that is indexed + * once per translation: unpublishing the Polish copy of an article must not + * take the English one out of the index. Omit it and every language goes, which + * is what deleting the record itself means. + * + * Optional on the signature so a provider written before per-locale content + * still compiles - but ignoring it is **not** silently tolerated. A provider + * that honours it says so with + * `capabilities: { languageScopedDelete: true }`, and an install that pairs one + * that does not with a localized searchable content type refuses to boot. See + * {@link assertSearchProviderCapabilities}. + */ + delete: ( + c: Context, + itemType: string, + itemId: number, + languageCode?: string, + ) => Promise; index: (c: Context, doc: SearchDocument) => Promise; name: string; ping?: (c: Context) => Promise; @@ -126,7 +340,7 @@ export interface SearchProviderApiPlugin { } const toRow = (doc: SearchDocument) => ({ - pluginId: "core", + pluginId: doc.pluginId ?? "core", itemType: doc.itemType, itemId: doc.itemId, languageCode: doc.languageCode ?? "", @@ -186,8 +400,26 @@ export class SearchModel { return this.c.get("core").search.adapter; } + /** + * Fills in the document's owner, once, for every write path. + * + * The request's plugin is only a *fallback*: it is the owner when a mutation + * route indexes its own content, and it is `@vitnode/core` during a rebuild, + * which runs inside the core cron request. So an explicit `pluginId` always + * wins - that is how a rebuild reproduces the same ownership a live write + * produced. Resolving it here rather than in each adapter is what keeps the + * canonical row and the mirrored document from disagreeing. + */ + private resolveOwner(doc: SearchDocument): SearchDocument { + return { + ...doc, + pluginId: + searchDocumentOwner(doc.pluginId) ?? this.c.get("plugin")?.id ?? "core", + }; + } + private async upsertRow(doc: SearchDocument): Promise { - const row = { ...toRow(doc), pluginId: this.c.get("plugin")?.id ?? "core" }; + const row = toRow(doc); await this.c .get("db") @@ -200,6 +432,9 @@ export class SearchModel { core_search_index.languageCode, ], set: { + // Included so a rebuild corrects the owner of a row written before the + // indexer declared one, rather than leaving the first writer's guess. + pluginId: row.pluginId, authorId: row.authorId, title: row.title, content: row.content, @@ -216,10 +451,9 @@ export class SearchModel { } async bulkIndex(docs: SearchDocument[]): Promise { - const clean = docs.map(doc => ({ - ...doc, - content: stripHtml(doc.content), - })); + const clean = docs.map(doc => + this.resolveOwner({ ...doc, content: stripHtml(doc.content) }), + ); for (const doc of clean) { await this.upsertRow(doc); @@ -237,7 +471,36 @@ export class SearchModel { await this.provider().clear(this.c, itemType); } - async delete(itemType: string, itemId: number): Promise { + /** + * How many documents the **provider** holds, or `null` when it cannot say. + * + * `null` is not zero and not healthy: it means the provider offers no + * diagnostics, and a caller has to report that as unverified rather than + * turning an absence of evidence into a clean bill of health. + */ + async countDocuments(args: { + itemType: string; + languageCode?: string; + }): Promise { + const provider = this.provider(); + if (!provider.count) return null; + + return await provider.count(this.c, args); + } + + /** + * Removes one item from the index, in one language or in all of them. + * + * `languageCode` is the whole point of the overload: multi-language content is + * one row per `(itemType, itemId, languageCode)`, so taking the Polish + * translation down must leave the English document exactly where it is. + * Omitting it removes every language, which is what deleting the record means. + */ + async delete( + itemType: string, + itemId: number, + languageCode?: string, + ): Promise { await this.c .get("db") .delete(core_search_index) @@ -245,20 +508,36 @@ export class SearchModel { and( eq(core_search_index.itemType, itemType), eq(core_search_index.itemId, itemId), + languageCode === undefined + ? undefined + : eq(core_search_index.languageCode, languageCode), ), ); - await this.provider().delete(this.c, itemType, itemId); + await this.provider().delete(this.c, itemType, itemId, languageCode); } /** Canonical projection lives in `core_search_index`; the provider mirrors it. */ async index(doc: SearchDocument): Promise { - const clean = { ...doc, content: stripHtml(doc.content) }; + const clean = this.resolveOwner({ + ...doc, + content: stripHtml(doc.content), + }); await this.upsertRow(clean); await this.provider().index(this.c, clean); } + /** + * Whether the active provider's store is the canonical table itself. + * + * Diagnostics ask this before counting twice - see + * {@link SearchProviderCapabilities.canonicalStorage}. + */ + isCanonicalStorage(): boolean { + return this.provider().capabilities?.canonicalStorage === true; + } + name(): string { return this.provider().name; } diff --git a/packages/vitnode/src/api/modules/admin/advanced/cron/routes/get.route.ts b/packages/vitnode/src/api/modules/admin/advanced/cron/routes/get.route.ts index b49a25c9c..d12a9d01a 100644 --- a/packages/vitnode/src/api/modules/admin/advanced/cron/routes/get.route.ts +++ b/packages/vitnode/src/api/modules/admin/advanced/cron/routes/get.route.ts @@ -1,3 +1,4 @@ +import { getTableColumns } from "drizzle-orm"; import z from "zod"; import { buildRoute } from "@/api/lib/route"; @@ -56,10 +57,10 @@ export const getCronsRoute = buildRoute({ }, c, primaryCursor: core_cron.id, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") - .select() + .select({ ...getTableColumns(core_cron), ...cursorSelection }) .from(core_cron) .where(where) .orderBy(orderBy) diff --git a/packages/vitnode/src/api/modules/admin/advanced/queue/routes/get.route.ts b/packages/vitnode/src/api/modules/admin/advanced/queue/routes/get.route.ts index 40181afa9..affd8a4ae 100644 --- a/packages/vitnode/src/api/modules/admin/advanced/queue/routes/get.route.ts +++ b/packages/vitnode/src/api/modules/admin/advanced/queue/routes/get.route.ts @@ -1,4 +1,4 @@ -import { inArray } from "drizzle-orm"; +import { getTableColumns, inArray } from "drizzle-orm"; import z from "zod"; import { buildRoute } from "@/api/lib/route"; @@ -73,10 +73,10 @@ export const getQueueTasksRoute = buildRoute({ c, primaryCursor: core_queue.id, where: statuses.length ? inArray(core_queue.status, statuses) : undefined, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") - .select() + .select({ ...getTableColumns(core_queue), ...cursorSelection }) .from(core_queue) .where(where) .orderBy(orderBy) diff --git a/packages/vitnode/src/api/modules/admin/debug/debug.admin.module.ts b/packages/vitnode/src/api/modules/admin/debug/debug.admin.module.ts index bf3a6e73b..dd9af296c 100644 --- a/packages/vitnode/src/api/modules/admin/debug/debug.admin.module.ts +++ b/packages/vitnode/src/api/modules/admin/debug/debug.admin.module.ts @@ -1,5 +1,7 @@ import { CONFIG_PLUGIN } from "../../../../config"; import { buildModule } from "../../../lib/module"; +import { clearSearchDebugAdminRoute } from "./routes/clear-search.route"; +import { contentStatusDebugAdminRoute } from "./routes/content-status.route"; import { integrationsDebugAdminRoute } from "./routes/integrations.route"; import { logsDebugAdminRoute } from "./routes/logs.route"; import { queueDebugAdminRoute } from "./routes/queue.route"; @@ -18,6 +20,8 @@ export const debugAdminModule = buildModule({ queueDebugAdminRoute, searchStatusDebugAdminRoute, rebuildSearchDebugAdminRoute, + clearSearchDebugAdminRoute, + contentStatusDebugAdminRoute, sendTestEmailDebugAdminRoute, testAiDebugAdminRoute, testStorageUploadDebugAdminRoute, diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/clear-search.route.ts b/packages/vitnode/src/api/modules/admin/debug/routes/clear-search.route.ts new file mode 100644 index 000000000..404097a12 --- /dev/null +++ b/packages/vitnode/src/api/modules/admin/debug/routes/clear-search.route.ts @@ -0,0 +1,89 @@ +import { HTTPException } from "hono/http-exception"; +import { z } from "zod"; + +import { buildRoute } from "@/api/lib/route"; +import { CONFIG_PLUGIN } from "@/config"; + +export const zodClearSearchSchema = z.object({ + itemType: z.string().min(1), +}); + +/** + * Deletes the documents of one collection that has no registered rebuild + * indexer. + * + * Deliberately not part of `/search/rebuild`: this removes documents and puts + * nothing back, so it must not hide behind an action called "reindex". It is + * refused for a collection that *does* have an indexer - that one has a rebuild, + * which is the non-destructive way to get the same freshness. + * + * What it does **not** mean is that the collection is abandoned. Registering an + * indexer is optional, and a plugin that writes through `search.index()` keeps + * its collection current without one - so a cleared collection can reappear on + * that plugin's next write. This clears the current indexed state; it does not + * stop anything from writing again. + * + * `itemType` is required and non-empty, so there is no payload that clears the + * whole index by omission. A full rebuild is the only thing that does that, and + * it refills what it can. + */ +export const clearSearchDebugAdminRoute = buildRoute({ + pluginId: CONFIG_PLUGIN.pluginId, + adminStaffPermission: { module: "system", permission: "can_view" }, + route: { + method: "post", + description: + "Permanently remove the currently indexed documents of one collection that has no registered rebuild indexer.", + path: "/search/clear", + request: { + body: { + required: true, + content: { + "application/json": { + schema: zodClearSearchSchema, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: z.object({ cleared: z.boolean() }), + }, + }, + description: "Collection cleared", + }, + 409: { + description: "The collection has a registered rebuild indexer", + }, + }, + }, + handler: async c => { + const { itemType } = c.req.valid("json"); + + if (c.get("core").searchIndexers.some(i => i.itemType === itemType)) { + throw new HTTPException(409, { + message: `"${itemType}" has a registered rebuild indexer. Rebuild it instead of deleting its documents.`, + }); + } + + await c.get("search").clear(itemType); + + // The documents are already gone, so the audit trail is best effort: the + // logger writes to the database and can fail on its own, and reporting a + // failed cleanup for a cleanup that happened would send an administrator + // looking for documents that are not there. + const message = `[Search] Removed the indexed documents of unmanaged collection "${itemType}".`; + try { + await c.get("log").warn(message); + } catch { + // eslint-disable-next-line no-console + console.warn( + `[VitNode] Failed to persist search cleanup audit: ${message}`, + ); + } + + return c.json({ cleared: true }); + }, +}); diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/content-status.route.ts b/packages/vitnode/src/api/modules/admin/debug/routes/content-status.route.ts new file mode 100644 index 000000000..fe7c2ae66 --- /dev/null +++ b/packages/vitnode/src/api/modules/admin/debug/routes/content-status.route.ts @@ -0,0 +1,112 @@ +import { z } from "zod"; + +import { buildRoute } from "@/api/lib/route"; +import { CONFIG_PLUGIN } from "@/config"; +import { contentEngineDiagnostics } from "@/content/server/diagnostics"; + +const localeDriftSchema = z.object({ + /** Documents `core_search_index` holds for this locale. */ + canonicalIndexed: z.number(), + canonicalHealthy: z.boolean(), + /** Published rows - or published translations - the database holds. */ + expected: z.number(), + /** `""` for a content type that is not localized. */ + locale: z.string(), + /** `null` when the provider offers no diagnostics - unverified, not healthy. */ + providerHealthy: z.boolean().nullable(), + providerIndexed: z.number().nullable(), +}); + +const contentTypeSchema = z.object({ + contentTypeId: z.string(), + features: z.object({ + editorial: z.boolean(), + localization: z.boolean(), + publicApi: z.boolean(), + publication: z.boolean(), + scheduling: z.boolean(), + search: z.boolean(), + }), + pluginId: z.string(), + /** `null` for a content type without `search`. */ + search: z + .object({ + canonicalHealthy: z.boolean(), + /** Documents `core_search_index` holds, every locale. */ + canonicalIndexedTotal: z.number(), + contentTypeId: z.string(), + /** Published rows - or translations - the database holds, every locale. */ + expectedTotal: z.number(), + /** Canonical **and** provider both agree. Unverified is not healthy. */ + healthy: z.boolean(), + locales: z.array(localeDriftSchema), + provider: z.object({ + /** Why the provider could not be counted, when that is the answer. */ + error: z.string().optional(), + healthy: z.boolean().nullable(), + /** + * Every document the provider holds, in any locale. + * + * The guard against a document left behind in a locale the database no + * longer knows about, which per-locale counts can never ask for. + */ + indexedTotal: z.number().nullable(), + name: z.string(), + /** Whether the provider was actually asked. */ + verified: z.boolean(), + }), + }) + .nullable(), + /** `null` for a content type without scheduling. */ + schedules: z + .object({ + /** Transitions that committed but were never announced. */ + failedEffects: z.number(), + pending: z.number(), + withErrors: z.number(), + }) + .nullable(), +}); + +/** + * What the Content Engine looks like from the outside, right now. + * + * Sits beside `/search/status` under the same `system: can_view` permission, + * and answers the questions that one cannot: `/search/status` reports what is + * *in* the index, and this reports what the **database** says should be there. + * A collection can be 100% covered by the first and still be missing every + * Polish document, because coverage is measured against the indexer's own count + * and drift is measured against the rows. + * + * Aggregates only - two counts per content type - so it is safe to open on an + * install with a large table. Nothing here mutates anything; repairing drift is + * `/search/rebuild`, which is a separate decision and a separate route. + */ +export const contentStatusDebugAdminRoute = buildRoute({ + pluginId: CONFIG_PLUGIN.pluginId, + adminStaffPermission: { module: "system", permission: "can_view" }, + route: { + method: "get", + description: + "Report every registered content type, its search index drift per locale, and its outstanding scheduled-effect failures.", + path: "/content/status", + responses: { + 200: { + content: { + "application/json": { + schema: z.object({ + contentTypes: z.array(contentTypeSchema), + /** No scheduled transition committed without being announced. */ + effectsHealthy: z.boolean(), + /** `searchHealthy && effectsHealthy`. */ + healthy: z.boolean(), + searchHealthy: z.boolean(), + }), + }, + }, + description: "Content Engine status", + }, + }, + }, + handler: async c => c.json(await contentEngineDiagnostics(c)), +}); diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/integrations.route.ts b/packages/vitnode/src/api/modules/admin/debug/routes/integrations.route.ts index 000a41381..30d8dfb14 100644 --- a/packages/vitnode/src/api/modules/admin/debug/routes/integrations.route.ts +++ b/packages/vitnode/src/api/modules/admin/debug/routes/integrations.route.ts @@ -38,6 +38,13 @@ export const integrationsDebugAdminRoute = buildRoute({ .enum(["cloudflare_turnstile", "recaptcha_v3"]) .nullable(), }), + contentPreview: z.object({ + // `true` when at least one content type has + // `editorial.preview.enabled`, i.e. the preview routes exist. + active: z.boolean(), + // How many content types can mint preview links. + contentTypes: z.number(), + }), cron: z.object({ // `true` when a cron adapter is configured, i.e. an in-process // scheduler is running the registered jobs automatically. @@ -133,6 +140,9 @@ export const integrationsDebugAdminRoute = buildRoute({ cronActivity?.lastActivity ? new Date(cronActivity.lastActivity) : null, ); const cronActive = core.hasCronAdapter; + const previewContentTypes = core.contentTypes.filter( + entry => entry.definition.editorial.preview.enabled, + ).length; const queueStatus = getQueueStatus({ cronActive, cronStale, @@ -152,6 +162,10 @@ export const integrationsDebugAdminRoute = buildRoute({ active: !!(captcha?.secretKey && captcha.siteKey), type: captcha?.type ?? null, }, + contentPreview: { + active: previewContentTypes > 0, + contentTypes: previewContentTypes, + }, cron: { active: cronActive, jobs: core.cron.length, diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/logs.route.ts b/packages/vitnode/src/api/modules/admin/debug/routes/logs.route.ts index 74266f343..0ec2eb213 100644 --- a/packages/vitnode/src/api/modules/admin/debug/routes/logs.route.ts +++ b/packages/vitnode/src/api/modules/admin/debug/routes/logs.route.ts @@ -66,10 +66,11 @@ export const logsDebugAdminRoute = buildRoute({ query, }, primaryCursor: core_logs.id, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_logs.id, pluginId: core_logs.pluginId, type: core_logs.type, diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/rebuild-search.route.ts b/packages/vitnode/src/api/modules/admin/debug/routes/rebuild-search.route.ts index 48bbeebd3..7e8f0516f 100644 --- a/packages/vitnode/src/api/modules/admin/debug/routes/rebuild-search.route.ts +++ b/packages/vitnode/src/api/modules/admin/debug/routes/rebuild-search.route.ts @@ -1,3 +1,4 @@ +import { HTTPException } from "hono/http-exception"; import { z } from "zod"; import { buildRoute } from "@/api/lib/route"; @@ -35,11 +36,24 @@ export const rebuildSearchDebugAdminRoute = buildRoute({ }, description: "Rebuild queued", }, + 404: { description: "No indexer is registered for that collection" }, }, }, handler: async c => { const { itemType } = c.req.valid("json") ?? {}; + // A rebuild of a collection with no indexer would clear it and refill + // nothing, so it is refused here rather than queued and discovered later. + // The task repeats the check for callers that bypass this route. + if ( + itemType && + !c.get("core").searchIndexers.some(i => i.itemType === itemType) + ) { + throw new HTTPException(404, { + message: `No search indexer is registered for "${itemType}".`, + }); + } + await c.get("queue").dispatch({ name: "rebuild-search-index", payload: itemType ? { itemType } : {}, diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/search-collections.route.test.ts b/packages/vitnode/src/api/modules/admin/debug/routes/search-collections.route.test.ts new file mode 100644 index 000000000..4f9bdc463 --- /dev/null +++ b/packages/vitnode/src/api/modules/admin/debug/routes/search-collections.route.test.ts @@ -0,0 +1,279 @@ +// @vitest-environment node +import { HTTPException } from "hono/http-exception"; +import { describe, expect, it, vi } from "vitest"; + +import type { SearchIndexerConfig } from "@/api/models/search"; + +import { clearSearchDebugAdminRoute } from "./clear-search.route"; +import { rebuildSearchDebugAdminRoute } from "./rebuild-search.route"; + +const indexer = (itemType: string): SearchIndexerConfig => ({ + itemType, + load: async () => await Promise.resolve({ documents: [], itemsRead: 0 }), + pluginId: "@vitnode/example", +}); + +interface Dispatched { + name: string; + payload?: Record; +} + +/** + * Both handlers are called directly, the way the queue-task tests call theirs: + * `Route.handler` is deliberately erased, and neither route needs Hono for + * anything but reading a body these tests can hand it outright. + */ +const harness = ({ + body, + clearFails = false, + indexers = [], + logFails = false, +}: { + body?: Record; + clearFails?: boolean; + indexers?: SearchIndexerConfig[]; + logFails?: boolean; +} = {}) => { + const dispatched: Dispatched[] = []; + const cleared: (string | undefined)[] = []; + const warnings: string[] = []; + + const clear = vi.fn(async (itemType?: string) => { + if (clearFails) throw new Error("engine unavailable"); + cleared.push(itemType); + await Promise.resolve(); + }); + + const c = { + get: (key: string) => { + if (key === "core") return { searchIndexers: indexers }; + if (key === "queue") { + return { + dispatch: async (task: Dispatched) => { + dispatched.push(task); + await Promise.resolve(); + }, + }; + } + if (key === "search") { + return { + clear: clear as unknown, + }; + } + if (key === "log") { + return { + warn: async (content: string) => { + warnings.push(content); + await Promise.resolve(); + if (logFails) throw new Error("core_logs unavailable"); + }, + }; + } + + return undefined; + }, + json: (value: unknown) => new Response(JSON.stringify(value)), + req: { valid: () => body }, + }; + + return { c, clear, cleared, dispatched, warnings }; +}; + +const statusOf = async (run: () => Promise) => { + try { + await run(); + } catch (error) { + if (error instanceof HTTPException) return error.status; + throw error; + } + + return 200; +}; + +describe("POST /search/rebuild", () => { + it("queues a scoped rebuild for a registered collection", async () => { + const { c, dispatched } = harness({ + body: { itemType: "example.article" }, + indexers: [indexer("example.article")], + }); + + await rebuildSearchDebugAdminRoute.handler(c); + + expect(dispatched).toEqual([ + { + name: "rebuild-search-index", + payload: { itemType: "example.article" }, + }, + ]); + }); + + it("rejects a scoped rebuild for a collection with no indexer", async () => { + // Queuing this would clear the collection and refill nothing, so the button + // that offers it must fail before anything is dispatched. + const { c, dispatched } = harness({ + body: { itemType: "removed.collection" }, + indexers: [indexer("example.article")], + }); + + await expect( + statusOf(async () => await rebuildSearchDebugAdminRoute.handler(c)), + ).resolves.toBe(404); + expect(dispatched).toEqual([]); + }); + + it("queues a full rebuild with no item type", async () => { + const { c, dispatched } = harness({ + indexers: [indexer("example.article")], + }); + + await rebuildSearchDebugAdminRoute.handler(c); + + expect(dispatched).toEqual([{ name: "rebuild-search-index", payload: {} }]); + }); + + it("queues a full rebuild even with no indexers at all", async () => { + // The guard is about *scoped* rebuilds; a full one is allowed to clear an + // index it cannot fully refill, which is how documents with no indexer get + // removed. + const { c, dispatched } = harness({ body: {} }); + + await rebuildSearchDebugAdminRoute.handler(c); + + expect(dispatched).toHaveLength(1); + }); +}); + +describe("POST /search/clear", () => { + it("clears only the requested collection", async () => { + const { c, cleared, warnings } = harness({ + body: { itemType: "removed.collection" }, + indexers: [indexer("example.article")], + }); + + await clearSearchDebugAdminRoute.handler(c); + + expect(cleared).toEqual(["removed.collection"]); + expect(warnings[0]).toContain("removed.collection"); + }); + + it("refuses a collection that still has an indexer", async () => { + // That one has a rebuild, which gets the same freshness without deleting. + const { c, cleared } = harness({ + body: { itemType: "example.article" }, + indexers: [indexer("example.article")], + }); + + await expect( + statusOf(async () => await clearSearchDebugAdminRoute.handler(c)), + ).resolves.toBe(409); + expect(cleared).toEqual([]); + }); + + it("never clears the whole index", async () => { + // `itemType` is required and non-empty in the schema, so there is no payload + // that reaches `clear(undefined)` through this route. + const { c, cleared } = harness({ + body: { itemType: "removed.collection" }, + }); + + await clearSearchDebugAdminRoute.handler(c); + + expect(cleared).not.toContain(undefined); + }); + + it("writes a neutral audit warning", async () => { + const { c, warnings } = harness({ + body: { itemType: "live.only" }, + }); + + await clearSearchDebugAdminRoute.handler(c); + + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain("unmanaged collection"); + expect(warnings[0]).toContain("live.only"); + // Nothing claims the plugin is gone: registering an indexer is optional. + expect(warnings[0]).not.toContain("orphan"); + }); + + it("stays successful when the audit log fails", async () => { + // The documents are already gone. Reporting a failure would send an + // administrator looking for documents that are not there. + const consoleWarn = vi + .spyOn(console, "warn") + .mockImplementation(() => undefined); + + try { + const { c, clear, cleared, warnings } = harness({ + body: { itemType: "live.only" }, + logFails: true, + }); + + const res = await clearSearchDebugAdminRoute.handler(c); + + await expect(new Response(res.body).json()).resolves.toEqual({ + cleared: true, + }); + expect(cleared).toEqual(["live.only"]); + // Attempted once, and not retried because the log failed. + expect(clear).toHaveBeenCalledTimes(1); + expect(warnings).toHaveLength(1); + expect(consoleWarn).toHaveBeenCalledTimes(1); + expect(String(consoleWarn.mock.calls[0][0])).toContain( + "Failed to persist search cleanup audit", + ); + } finally { + consoleWarn.mockRestore(); + } + }); + + it("does not reach the console when the audit log succeeds", async () => { + const consoleWarn = vi + .spyOn(console, "warn") + .mockImplementation(() => undefined); + + try { + const { c } = harness({ body: { itemType: "live.only" } }); + + await clearSearchDebugAdminRoute.handler(c); + + expect(consoleWarn).not.toHaveBeenCalled(); + } finally { + consoleWarn.mockRestore(); + } + }); + + it("propagates a failed clear and writes no success audit", async () => { + const { c, warnings } = harness({ + body: { itemType: "live.only" }, + clearFails: true, + }); + + await expect(clearSearchDebugAdminRoute.handler(c)).rejects.toThrow( + "engine unavailable", + ); + expect(warnings).toEqual([]); + }); + + it("rejects an empty item type at the schema", () => { + expect( + zodBody(clearSearchDebugAdminRoute).safeParse({ itemType: "" }).success, + ).toBe(false); + expect(zodBody(clearSearchDebugAdminRoute).safeParse({}).success).toBe( + false, + ); + expect( + zodBody(clearSearchDebugAdminRoute).safeParse({ itemType: "a.b" }) + .success, + ).toBe(true); + }); +}); + +/** Reaches the body schema the route declared, so the test asserts on the real one. */ +function zodBody(route: typeof clearSearchDebugAdminRoute) { + const body = route.route.request?.body; + if (!body || !("content" in body)) throw new Error("No body schema."); + + return body.content["application/json"].schema as { + safeParse: (value: unknown) => { success: boolean }; + }; +} diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.test.ts b/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.test.ts new file mode 100644 index 000000000..0dc884621 --- /dev/null +++ b/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.test.ts @@ -0,0 +1,263 @@ +// @vitest-environment node +import { describe, expect, it } from "vitest"; + +import type { SearchIndexerConfig } from "@/api/models/search"; + +import { searchStatusDebugAdminRoute } from "./search-status.route"; + +interface IndexedRow { + indexed: number; + itemType: string; + lastIndexedAt: Date | null; + pluginId: null | string; +} + +interface Collection { + hasIndexer: boolean; + indexed: number; + itemType: string; + pluginId: string; + total: null | number; +} + +const indexer = ( + itemType: string, + pluginId: string, + total?: number, +): SearchIndexerConfig => ({ + itemType, + ...(total === undefined + ? {} + : { count: async () => await Promise.resolve(total) }), + load: async () => await Promise.resolve({ documents: [], itemsRead: 0 }), + pluginId, +}); + +/** + * The handler is called directly, the way the queue-task tests call theirs: it + * takes no request input, so routing it through Hono would only add a cast - + * `Route.handler` is deliberately erased to `(...args: unknown[])`. + * + * The stub answers the coverage query with the given rows and every later query + * (the sync-error panel) with nothing. + */ +const harness = ({ + indexers = [], + rows = [], +}: { + indexers?: SearchIndexerConfig[]; + rows?: IndexedRow[]; +} = {}) => { + const results: unknown[][] = [rows, []]; + let call = 0; + + const chain = (value: unknown[]) => { + const builder: Record = {}; + for (const op of ["from", "groupBy", "where", "orderBy", "limit"]) { + builder[op] = () => builder; + } + builder.then = async (resolve: (rows: unknown[]) => TResult) => + await Promise.resolve(value).then(resolve); + + return builder; + }; + + const db = { + select: () => { + const value = results[call] ?? []; + call++; + + return chain(value); + }, + }; + + let body: undefined | { collections: Collection[] }; + + const c = { + get: (key: string) => { + if (key === "db") return db; + if (key === "search") { + return { + name: () => "postgres", + ping: async () => await Promise.resolve(true), + }; + } + if (key === "core") { + return { hasCronAdapter: true, searchIndexers: indexers }; + } + + return undefined; + }, + json: (value: { collections: Collection[] }) => { + body = value; + + return new Response(); + }, + }; + + return { + collections: async (): Promise => { + await searchStatusDebugAdminRoute.handler(c); + + if (!body) throw new Error("The handler returned no body."); + + return body.collections; + }, + }; +}; + +const indexedRow = ( + itemType: string, + pluginId: null | string, + indexed: number, +): IndexedRow => ({ indexed, itemType, lastIndexedAt: null, pluginId }); + +describe("search status collection ownership", () => { + it("uses the registered indexer's plugin", async () => { + const { collections } = harness({ + indexers: [indexer("example.article", "@vitnode/example", 3)], + rows: [indexedRow("example.article", "@vitnode/example", 3)], + }); + + await expect(collections()).resolves.toEqual([ + expect.objectContaining({ + hasIndexer: true, + indexed: 3, + itemType: "example.article", + pluginId: "@vitnode/example", + total: 3, + }), + ]); + }); + + it("falls back to the stored owner when no indexer is registered", async () => { + // No rebuild indexer, for any reason - never registered, or its plugin is + // gone - but the rows still say who wrote them. Reassigning them to core + // would be a lie. + const { collections } = harness({ + rows: [indexedRow("example.article", "@vitnode/example", 3)], + }); + + const [collection] = await collections(); + + expect(collection.pluginId).toBe("@vitnode/example"); + expect(collection.hasIndexer).toBe(false); + // No indexer to ask for a source count, so there is none to report. + expect(collection.total).toBeNull(); + }); + + it("reports `unknown` when neither source names an owner", async () => { + const { collections } = harness({ + rows: [indexedRow("mystery.item", null, 2)], + }); + + const [collection] = await collections(); + + expect(collection.pluginId).toBe("unknown"); + }); + + it("treats a blank stored owner as unknown", async () => { + const { collections } = harness({ + rows: [indexedRow("mystery.item", " ", 2)], + }); + + const [collection] = await collections(); + + expect(collection.pluginId).toBe("unknown"); + }); + + it("keeps the registered owner when the stored one disagrees", async () => { + // The next rebuild rewrites the rows, so the live indexer is canonical. + const { collections } = harness({ + indexers: [indexer("example.article", "@vitnode/example", 3)], + rows: [indexedRow("example.article", "@vitnode/old-example", 3)], + }); + + const [collection] = await collections(); + + expect(collection.pluginId).toBe("@vitnode/example"); + }); + + it("keeps an over-indexed collection with no indexer truthful", async () => { + // The regression: `total` used to fall back to `indexed`, so a collection + // with no indexer reported 11/11 and read as fully indexed. There is no + // source to count, so there is no total - and it must still not be called + // core. + const { collections } = harness({ + rows: [indexedRow("example.article", "@vitnode/example", 11)], + }); + + await expect(collections()).resolves.toEqual([ + expect.objectContaining({ + hasIndexer: false, + indexed: 11, + pluginId: "@vitnode/example", + total: null, + }), + ]); + }); + + it("reports the real counts of an over-indexed registered collection", async () => { + const { collections } = harness({ + indexers: [indexer("example.article", "@vitnode/example", 9)], + rows: [indexedRow("example.article", "@vitnode/example", 11)], + }); + + const [collection] = await collections(); + + // Neither number is rewritten to hide the extra documents, so the AdminCP + // still reads this as stale. + expect(collection).toMatchObject({ + hasIndexer: true, + indexed: 11, + pluginId: "@vitnode/example", + total: 9, + }); + }); + + it("lists a registered collection with nothing indexed yet", async () => { + const { collections } = harness({ + indexers: [indexer("example.article", "@vitnode/example", 5)], + }); + + await expect(collections()).resolves.toEqual([ + expect.objectContaining({ + hasIndexer: true, + indexed: 0, + pluginId: "@vitnode/example", + total: 5, + }), + ]); + }); + + it("reports an indexer with no `count` against the indexed total", async () => { + // `count` is optional, and leaving it out is documented as "assume covered". + // That is still a registered collection, so `hasIndexer` stays true. + const { collections } = harness({ + indexers: [indexer("example.article", "@vitnode/example")], + rows: [indexedRow("example.article", "@vitnode/example", 4)], + }); + + const [collection] = await collections(); + + expect(collection).toMatchObject({ + hasIndexer: true, + indexed: 4, + total: 4, + }); + }); + + it("does not infer an indexer from a stored owner", async () => { + // The rule the field exists for: rows knowing who wrote them says nothing + // about whether a rebuild indexer is registered. It says nothing the other + // way either - a plugin may be installed, active, and writing live. + const { collections } = harness({ + rows: [indexedRow("example.article", "@vitnode/example", 3)], + }); + + const [collection] = await collections(); + + expect(collection.pluginId).toBe("@vitnode/example"); + expect(collection.hasIndexer).toBe(false); + }); +}); diff --git a/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.ts b/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.ts index 629145a46..20229f4e6 100644 --- a/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.ts +++ b/packages/vitnode/src/api/modules/admin/debug/routes/search-status.route.ts @@ -1,16 +1,67 @@ -import { countDistinct, max } from "drizzle-orm"; +import { + and, + count, + countDistinct, + desc, + eq, + like, + max, + ne, +} from "drizzle-orm"; import { z } from "zod"; import { buildRoute } from "@/api/lib/route"; +import { searchDocumentOwner } from "@/api/models/search"; import { CONFIG_PLUGIN } from "@/config"; +import { core_logs } from "@/database/logs"; import { core_search_index } from "@/database/search"; +const CONTENT_SEARCH_LOG_PREFIX = "[content-search]"; + +const SYNC_ERROR_LIMIT = 10; + const collectionSchema = z.object({ + /** + * Index rows, counting one per language. + * + * Separate from `indexed`, which counts distinct items: multi-language content + * is indexed once per translation, so its coverage has to be measured in + * documents or a fully-indexed collection would read as 33%. + */ + documents: z.number(), + /** + * Whether an indexer is registered for this item type *right now*. A stored + * plugin owner does not imply one: the plugin may be uninstalled, renamed, or + * simply not loaded in this process. + */ + hasIndexer: z.boolean(), indexed: z.number(), itemType: z.string(), + /** + * One entry per language present in the index, newest first by count. + * + * Empty for a collection that is entirely language-agnostic. It is what makes + * "Polish is missing 40 documents" visible at all - a single total cannot say + * which language a rebuild failed halfway through. + */ + languages: z.array( + z.object({ + documents: z.number(), + languageCode: z.string(), + lastIndexedAt: z.date().nullable(), + }), + ), lastIndexedAt: z.date().nullable(), pluginId: z.string(), - total: z.number(), + /** Source items the indexer reports. `null` when there is no indexer to ask. */ + total: z.number().nullable(), +}); + +const syncErrorSchema = z.object({ + content: z.string(), + createdAt: z.date(), + id: z.number(), + pluginId: z.string(), }); export const searchStatusDebugAdminRoute = buildRoute({ @@ -31,6 +82,7 @@ export const searchStatusDebugAdminRoute = buildRoute({ hasCronAdapter: z.boolean(), healthy: z.boolean(), lastIndexedAt: z.date().nullable(), + syncErrors: z.array(syncErrorSchema), total: z.number(), }), }, @@ -46,17 +98,73 @@ export const searchStatusDebugAdminRoute = buildRoute({ // One item can emit several index rows (e.g. one per language), so coverage // is measured in distinct items - not documents. + // + // `pluginId` comes along so a collection whose indexer is gone can still name + // its owner. An item type has one owner, so the aggregate is a formality - + // `max` picks deterministically if rows ever disagree mid-rebuild. const indexedByType = await db .select({ itemType: core_search_index.itemType, + documents: count(), indexed: countDistinct(core_search_index.itemId), lastIndexedAt: max(core_search_index.indexedAt), + pluginId: max(core_search_index.pluginId), }) .from(core_search_index) .groupBy(core_search_index.itemType); const statsByType = new Map(indexedByType.map(row => [row.itemType, row])); + // Per language, so a rebuild that stopped halfway through one locale is + // visible as that locale rather than as a slightly-low total. The + // language-agnostic rows (`""`) are dropped: they are not a language, and + // listing them as one would put an unnamed row in every collection. + const byLanguage = await db + .select({ + itemType: core_search_index.itemType, + documents: count(), + languageCode: core_search_index.languageCode, + lastIndexedAt: max(core_search_index.indexedAt), + }) + .from(core_search_index) + .where(ne(core_search_index.languageCode, "")) + .groupBy(core_search_index.itemType, core_search_index.languageCode) + .orderBy(desc(count())); + + const languagesByType = new Map< + string, + { documents: number; languageCode: string; lastIndexedAt: Date | null }[] + >(); + for (const row of byLanguage) { + const entries = languagesByType.get(row.itemType) ?? []; + entries.push({ + documents: row.documents, + languageCode: row.languageCode, + lastIndexedAt: row.lastIndexedAt, + }); + languagesByType.set(row.itemType, entries); + } + + // Newest first, and bounded: this is a "what went wrong lately" panel, not a + // log viewer. `LIKE 'prefix%'` needs no escaping - the prefix contains + // neither `%` nor `_`. + const syncErrors = await db + .select({ + id: core_logs.id, + pluginId: core_logs.pluginId, + content: core_logs.content, + createdAt: core_logs.createdAt, + }) + .from(core_logs) + .where( + and( + eq(core_logs.type, "error"), + like(core_logs.content, `${CONTENT_SEARCH_LOG_PREFIX}%`), + ), + ) + .orderBy(desc(core_logs.id)) + .limit(SYNC_ERROR_LIMIT); + // Start from every registered indexer so a collection with nothing indexed // yet still appears; then fold in any indexed type without a live indexer. const itemTypes = [ @@ -71,15 +179,34 @@ export const searchStatusDebugAdminRoute = buildRoute({ const indexer = core.searchIndexers.find(i => i.itemType === itemType); const stats = statsByType.get(itemType); const indexed = stats?.indexed ?? 0; - const total = indexer?.count ? await indexer.count(c) : indexed; + // No indexer, no source count - and inventing `total = indexed` would + // report a collection nothing can rebuild as fully covered. An indexer + // without the optional `count` still falls back to the indexed count, + // which is the documented behaviour of leaving `count` out. + // + // `hasIndexer` says only whether a rebuild indexer exists. A plugin may + // keep its collection current through `search.index()` and register none, + // so this is not a statement about the plugin. + const total = indexer ? ((await indexer.count?.(c)) ?? indexed) : null; return { + hasIndexer: indexer !== undefined, itemType, - pluginId: indexer?.pluginId ?? "core", + // The registered indexer is canonical - it is what the next rebuild + // will stamp on the rows. Falling back to the stored owner is what + // stops a collection with no indexer being reassigned to core, and + // `"unknown"` is honest when neither source knows. + pluginId: + indexer?.pluginId ?? + searchDocumentOwner(stats?.pluginId) ?? + "unknown", + documents: stats?.documents ?? 0, indexed, - // A source count below the indexed count (e.g. items deleted since the - // last rebuild) would break the coverage bar; never report less. - total: Math.max(total, indexed), + languages: languagesByType.get(itemType) ?? [], + // Reported as measured, even when it is below `indexed`: more documents + // than source records is a stale index, and raising the source count to + // hide it is how that goes unnoticed. The UI clamps the bar instead. + total, lastIndexedAt: stats?.lastIndexedAt ?? null, }; }), @@ -97,6 +224,7 @@ export const searchStatusDebugAdminRoute = buildRoute({ hasCronAdapter: core.hasCronAdapter, healthy: await search.ping(), lastIndexedAt, + syncErrors, total: collections.reduce((sum, row) => sum + row.indexed, 0), }); }, diff --git a/packages/vitnode/src/api/modules/admin/files/routes/list.route.ts b/packages/vitnode/src/api/modules/admin/files/routes/list.route.ts index 873605bf3..5ce24159c 100644 --- a/packages/vitnode/src/api/modules/admin/files/routes/list.route.ts +++ b/packages/vitnode/src/api/modules/admin/files/routes/list.route.ts @@ -90,10 +90,11 @@ export const listFilesAdminRoute = buildRoute({ c, primaryCursor: core_files.id, search: [core_files.name], - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_files.id, name: core_files.name, key: core_files.key, diff --git a/packages/vitnode/src/api/modules/admin/roles/routes/list.route.ts b/packages/vitnode/src/api/modules/admin/roles/routes/list.route.ts index efe9f5138..4985eba3b 100644 --- a/packages/vitnode/src/api/modules/admin/roles/routes/list.route.ts +++ b/packages/vitnode/src/api/modules/admin/roles/routes/list.route.ts @@ -100,10 +100,11 @@ export const listRolesAdminRoute = buildRoute({ ) : undefined, primaryCursor: core_roles.id, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_roles.id, color: core_roles.color, protected: core_roles.protected, diff --git a/packages/vitnode/src/api/modules/admin/staff/routes/admins.route.ts b/packages/vitnode/src/api/modules/admin/staff/routes/admins.route.ts index f1bfbd19f..1b5b4d786 100644 --- a/packages/vitnode/src/api/modules/admin/staff/routes/admins.route.ts +++ b/packages/vitnode/src/api/modules/admin/staff/routes/admins.route.ts @@ -38,10 +38,11 @@ export const listAdminsStaffAdminRoute = buildRoute({ query, }, primaryCursor: core_admin_permissions.id, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_admin_permissions.id, roleId: core_admin_permissions.roleId, userId: core_admin_permissions.userId, diff --git a/packages/vitnode/src/api/modules/admin/staff/routes/moderators.route.ts b/packages/vitnode/src/api/modules/admin/staff/routes/moderators.route.ts index 1ac4f1eb7..b0bcb0353 100644 --- a/packages/vitnode/src/api/modules/admin/staff/routes/moderators.route.ts +++ b/packages/vitnode/src/api/modules/admin/staff/routes/moderators.route.ts @@ -38,10 +38,11 @@ export const listModeratorsStaffAdminRoute = buildRoute({ query, }, primaryCursor: core_moderators_permissions.id, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_moderators_permissions.id, roleId: core_moderators_permissions.roleId, userId: core_moderators_permissions.userId, diff --git a/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts b/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts index bd04bd9dc..6ddc5104a 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/list.route.ts @@ -90,10 +90,11 @@ export const listUsersAdminRoute = buildRoute({ search: [core_users.name, core_users.email, core_users.nameCode], where: roleIds.length ? inArray(core_users.roleId, roleIds) : undefined, primaryCursor: core_users.id, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_users.id, name: core_users.name, email: core_users.email, diff --git a/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts b/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts index a2e6fe2cc..cb9f70af2 100644 --- a/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts +++ b/packages/vitnode/src/api/modules/admin/users/routes/users.route.ts @@ -59,10 +59,11 @@ export const usersAdminRoute = buildRoute({ query, }, primaryCursor: core_users.id, - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_users.id, name: core_users.name, email: core_users.email, diff --git a/packages/vitnode/src/api/modules/content/content.module.ts b/packages/vitnode/src/api/modules/content/content.module.ts new file mode 100644 index 000000000..682152133 --- /dev/null +++ b/packages/vitnode/src/api/modules/content/content.module.ts @@ -0,0 +1,33 @@ +import { buildModule } from "@/api/lib/module"; +import { CONFIG_PLUGIN } from "@/config"; + +import { contentEditorialCleanupCron } from "./cron/content-editorial-cleanup.cron"; +import { contentScheduleEffectsQueueTask } from "./tasks/content-schedule-effects.task"; +import { contentScheduleQueueTask } from "./tasks/content-schedule.task"; + +/** + * Core's own Content Engine module: the background half. + * + * It serves no routes. It exists because `queueTasks` and `cronJobs` are + * collected from **top-level** modules only, while `buildContentAdminModule` is + * nested inside a plugin's `admin` module - so a task registered there would be + * silently dropped, with no error and no handler. + * + * One task for every schedulable content type in the install, rather than one + * per type. The handler resolves the model from `c.get("core").contentModels`, + * so adding a content type adds no task, no name to collide with, and no + * registration to forget. + * + * Two tasks rather than one, because a scheduled publication is two units of + * work with two different failure meanings: `content-schedule` moves the + * database and either commits or does not, and `content-schedule-effects` + * announces what committed and can be retried on its own without ever + * republishing. + */ +export const contentModule = buildModule({ + pluginId: CONFIG_PLUGIN.pluginId, + name: "content", + routes: [], + cronJobs: [contentEditorialCleanupCron], + queueTasks: [contentScheduleQueueTask, contentScheduleEffectsQueueTask], +}); diff --git a/packages/vitnode/src/api/modules/content/cron/content-editorial-cleanup.cron.ts b/packages/vitnode/src/api/modules/content/cron/content-editorial-cleanup.cron.ts new file mode 100644 index 000000000..3130c3358 --- /dev/null +++ b/packages/vitnode/src/api/modules/content/cron/content-editorial-cleanup.cron.ts @@ -0,0 +1,59 @@ +import { buildCron } from "@/api/lib/cron"; +import { + CONTENT_REVISION_MAX_RETENTION, + CONTENT_SCHEDULE_RETENTION_DAYS, +} from "@/content/const"; +import { pruneContentRevisions } from "@/content/server/revisions-model"; +import { pruneContentSchedules } from "@/content/server/schedules-model"; + +const DAY_MS = 24 * 60 * 60 * 1000; + +/** + * Sweeps up editorial rows that no longer describe anything. + * + * Revision retention is enforced inline, in the same transaction as the write, + * so this is **not** the thing that keeps the table bounded on a healthy + * install - an install with no cron adapter must not grow forever, and it does + * not. What this handles is the case inline pruning structurally cannot: rows + * whose content type stopped existing, so nothing will ever write to them again + * and trigger a prune. + * + * Daily rather than hourly. Nothing here is urgent, and a plugin removed at + * lunchtime does not need its history gone by teatime. + */ +export const contentEditorialCleanupCron = buildCron({ + name: "content-editorial-cleanup", + description: + "Remove revisions and schedules for content types that are no longer registered, and settled schedules past their retention window.", + // 03:20 daily, off the hour so it does not pile onto every other daily job. + schedule: "20 3 * * *", + handler: async c => { + const known = c + .get("core") + .contentTypes.filter(entry => entry.definition.editorial.enabled) + .map(entry => entry.definition.id); + + const schedules = await pruneContentSchedules({ + db: c.get("db"), + knownContentTypeIds: known, + olderThan: new Date( + Date.now() - CONTENT_SCHEDULE_RETENTION_DAYS * DAY_MS, + ), + }); + + const revisions = await pruneContentRevisions({ + db: c.get("db"), + knownContentTypeIds: known, + }); + + if (schedules.orphaned + revisions.orphaned === 0) return; + + // Worth saying out loud: an unexpected number here usually means a plugin + // id or a content type id was renamed without the documented UPDATE. + await c + .get("log") + .debug( + `[content-editorial-cleanup] removed ${revisions.orphaned} orphaned revisions and ${schedules.orphaned} orphaned schedules (${schedules.settled} settled schedules aged out; revision retention stays capped at ${CONTENT_REVISION_MAX_RETENTION} per record).`, + ); + }, +}); diff --git a/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.test.ts b/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.test.ts new file mode 100644 index 000000000..7f2b98ac8 --- /dev/null +++ b/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.test.ts @@ -0,0 +1,402 @@ +// @vitest-environment node +import type { Context } from "hono"; + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { testEditorialPostContentType } from "@/tests/content-fixtures"; + +const claimContentSchedule = vi.fn(); +const settleContentSchedule = vi.fn(); + +vi.mock("@/content/server/schedules-model", () => ({ + claimContentSchedule: (...args: unknown[]) => claimContentSchedule(...args), + settleContentSchedule: (...args: unknown[]) => settleContentSchedule(...args), +})); + +const { executeContentSchedule } = await import("./execute-content-schedule"); + +const PLUGIN_ID = "@vitnode/example"; + +const claimed = { + action: "publish" as const, + contentTypeId: testEditorialPostContentType.id, + createdBy: 3, + id: 55, + itemId: 7, + pluginId: PLUGIN_ID, +}; + +const row = { + createdAt: new Date("2026-08-01T09:00:00.000Z"), + id: 7, + publishedAt: new Date("2026-08-05T12:00:00.000Z"), + slug: "hello-world", + status: "published", + title: "Hello world", + updatedAt: new Date("2026-08-05T12:00:00.000Z"), + version: 4, +}; + +const outcome = { + changed: true, + changedFields: [], + operation: "publish" as const, + previousSlug: "hello-world", + restoredFromRevisionId: null, + revisionId: 90, + row, + version: 4, +}; + +const harness = ({ + editorial, + registered = true, +}: { + editorial?: Partial>; + registered?: boolean; +} = {}) => { + const publish = vi.fn().mockResolvedValue(outcome); + const unpublish = vi.fn().mockResolvedValue(outcome); + + const model = { + definition: testEditorialPostContentType, + editorialService: () => ({ publish, unpublish, ...editorial }), + }; + + const dispatch = vi.fn().mockResolvedValue({ id: 1 }); + let committed = false; + + const db = { + transaction: async (fn: (tx: unknown) => Promise) => { + const result = await fn({ tx: true }); + committed = true; + + return result; + }, + }; + + const c = { + get: (key: string) => + key === "db" + ? db + : key === "queue" + ? { dispatch } + : key === "core" + ? { + contentModels: registered + ? [{ model, pluginId: PLUGIN_ID }] + : [], + } + : undefined, + } as unknown as Context; + + return { c, committed: () => committed, dispatch, publish, unpublish }; +}; + +/** The single argument every effects dispatch carries. */ +const dispatchedPayload = (dispatch: ReturnType) => + dispatch.mock.calls[0][0] as { + name: string; + payload: Record; + pluginId: string; + tx?: unknown; + }; + +beforeEach(() => { + vi.clearAllMocks(); + settleContentSchedule.mockResolvedValue(true); +}); + +describe("executeContentSchedule", () => { + it("publishes, settles the schedule, and queues the announcements", async () => { + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch, publish } = harness(); + + const result = await executeContentSchedule(c, { + generation: 1, + scheduleId: 55, + }); + + expect(result.status).toBe("executed"); + expect(publish).toHaveBeenCalledTimes(1); + expect(dispatch).toHaveBeenCalledTimes(1); + expect(dispatchedPayload(dispatch).name).toBe("content-schedule-effects"); + }); + + describe("one transaction, from the claim to the commit", () => { + it("claims, transitions, settles and dispatches on the same handle", async () => { + // The whole point of the fix. Every one of these ran against the same + // `tx`, so the row lock `claimContentSchedule` takes is still held when + // the transition commits - which is what makes a concurrent cancel wait + // rather than succeed and then be ignored. + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch, publish } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + const tx = { tx: true }; + expect(claimContentSchedule).toHaveBeenCalledWith(tx, expect.anything()); + expect(publish.mock.calls[0][1]).toMatchObject({ tx }); + expect(settleContentSchedule).toHaveBeenCalledWith( + tx, + 55, + expect.anything(), + ); + expect(dispatchedPayload(dispatch).tx).toEqual(tx); + }); + + it("dispatches the effects before the transaction commits", async () => { + // If the queue row could land after the commit, a crash in between would + // leave a published record nobody was ever told about. + claimContentSchedule.mockResolvedValue(claimed); + const { c, committed, dispatch } = harness(); + + dispatch.mockImplementation(async () => { + expect(committed()).toBe(false); + + return Promise.resolve({ id: 1 }); + }); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + expect(dispatch).toHaveBeenCalledTimes(1); + }); + + it("settles only while the schedule is still pending", async () => { + // The guard that stops a stale worker overwriting `cancelled` with + // `completed`. + claimContentSchedule.mockResolvedValue(claimed); + const { c } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + expect(settleContentSchedule).toHaveBeenCalledWith( + expect.anything(), + 55, + { + expectedStatus: "pending", + lastError: null, + status: "completed", + }, + ); + }); + + it("rolls the transition back when the schedule is no longer pending", async () => { + // Structurally impossible while the lock is held - so if it happens the + // lock was not held, and publishing a cancelled plan is the worse of the + // two outcomes. + claimContentSchedule.mockResolvedValue(claimed); + settleContentSchedule.mockResolvedValue(false); + const { c, dispatch } = harness(); + + await expect( + executeContentSchedule(c, { generation: 1, scheduleId: 55 }), + ).rejects.toThrow(/no longer pending/); + + expect(dispatch).not.toHaveBeenCalled(); + }); + }); + + it("runs as the system, never as a made-up user", async () => { + claimContentSchedule.mockResolvedValue(claimed); + const { c, publish } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + expect(publish.mock.calls[0][1]).toMatchObject({ + actor: { type: "system", userId: null }, + }); + }); + + describe("the effects payload", () => { + it("names the person who booked it", async () => { + // The actor is genuinely the system, so "on whose instruction" has to + // come from somewhere else - and it is the whole point of the audit + // trail. + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + expect(dispatchedPayload(dispatch).payload).toMatchObject({ + contentTypeId: testEditorialPostContentType.id, + itemId: 7, + operation: "publish", + pluginId: PLUGIN_ID, + revisionId: 90, + scheduleId: 55, + scheduledBy: 3, + version: 4, + }); + }); + + it("says the record was private before a publish", async () => { + // Derived from the transition's own guard rather than read back outside + // the lock: `publish` only changes a row that was not published. + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + expect(dispatchedPayload(dispatch).payload.wasPublic).toBe(false); + }); + + it("says the record was public before an unpublish", async () => { + claimContentSchedule.mockResolvedValue({ + ...claimed, + action: "unpublish", + }); + const { c, dispatch } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + expect(dispatchedPayload(dispatch).payload.wasPublic).toBe(true); + }); + + it("is JSON, so the queue can store and replay it", async () => { + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + const { row: stored } = dispatchedPayload(dispatch).payload as { + row: Record; + }; + expect(stored.publishedAt).toBe("2026-08-05T12:00:00.000Z"); + expect(stored.title).toBe("Hello world"); + }); + + it("is stamped with core, so the worker can find the handler", async () => { + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch } = harness(); + + await executeContentSchedule(c, { generation: 1, scheduleId: 55 }); + + expect(dispatchedPayload(dispatch).pluginId).toBe("@vitnode/core"); + }); + }); + + describe("no-ops", () => { + it("does nothing when the row is cancelled, superseded or not yet due", async () => { + // All four guards collapse to the same answer from `claim`, so this is + // one test rather than four identical ones. + claimContentSchedule.mockResolvedValue(null); + const { c, dispatch, publish } = harness(); + + const result = await executeContentSchedule(c, { + generation: 1, + scheduleId: 55, + }); + + expect(result.status).toBe("skipped"); + expect(publish).not.toHaveBeenCalled(); + // The load-bearing part: a superseded task must not touch search or the + // cache, or a cancelled plan would still expire a live page. + expect(dispatch).not.toHaveBeenCalled(); + expect(settleContentSchedule).not.toHaveBeenCalled(); + }); + + it("does nothing more when the record is already published", async () => { + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch } = harness({ + editorial: { + publish: vi.fn().mockResolvedValue({ ...outcome, changed: false }), + }, + }); + + const result = await executeContentSchedule(c, { + generation: 1, + scheduleId: 55, + }); + + expect(result.status).toBe("skipped"); + expect(dispatch).not.toHaveBeenCalled(); + // Still settled, or it would be retried forever for a record that is + // already in the state the schedule wanted. + expect(settleContentSchedule).toHaveBeenCalledWith( + expect.anything(), + 55, + { + expectedStatus: "pending", + lastError: null, + status: "completed", + }, + ); + }); + + it("does nothing when the record was deleted first", async () => { + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch } = harness({ + editorial: { publish: vi.fn().mockResolvedValue(null) }, + }); + + const result = await executeContentSchedule(c, { + generation: 1, + scheduleId: 55, + }); + + expect(result.status).toBe("skipped"); + expect(dispatch).not.toHaveBeenCalled(); + }); + }); + + it("cancels rather than retrying when the content type is gone", async () => { + // A plugin removed, or `editorial` turned off. An error every ten minutes + // forever is not a useful way to report a config change. + claimContentSchedule.mockResolvedValue(claimed); + const { c, dispatch } = harness({ registered: false }); + + const result = await executeContentSchedule(c, { + generation: 1, + scheduleId: 55, + }); + + expect(result.status).toBe("unregistered"); + expect(settleContentSchedule).toHaveBeenCalledWith( + expect.anything(), + 55, + expect.objectContaining({ + expectedStatus: "pending", + status: "cancelled", + }), + ); + expect(dispatch).not.toHaveBeenCalled(); + }); + + it("records the error and rethrows a real failure", async () => { + // This one *is* worth retrying, and the queue's backoff is the policy. + claimContentSchedule.mockResolvedValue(claimed); + const { c } = harness({ + editorial: { + publish: vi.fn().mockRejectedValue(new Error("deadlock detected")), + }, + }); + + await expect( + executeContentSchedule(c, { generation: 1, scheduleId: 55 }), + ).rejects.toThrow("deadlock detected"); + + expect(settleContentSchedule).toHaveBeenCalledWith(expect.anything(), 55, { + expectedStatus: "pending", + lastError: "deadlock detected", + }); + // Left pending, so the AdminCP shows it as overdue rather than done. + expect(settleContentSchedule).not.toHaveBeenCalledWith( + expect.anything(), + 55, + expect.objectContaining({ status: "completed" }), + ); + }); + + it("passes the generation straight through to the claim", async () => { + claimContentSchedule.mockResolvedValue(null); + const { c } = harness(); + + await executeContentSchedule(c, { generation: 4, scheduleId: 55 }); + + expect(claimContentSchedule).toHaveBeenCalledWith(expect.anything(), { + generation: 4, + scheduleId: 55, + }); + }); +}); diff --git a/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.ts b/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.ts new file mode 100644 index 000000000..b76415c59 --- /dev/null +++ b/packages/vitnode/src/api/modules/content/helpers/execute-content-schedule.ts @@ -0,0 +1,242 @@ +import type { Context } from "hono"; + +import type { ContentEditorialOutcome } from "@/content/server/editorial-service"; +import type { ContentScheduleEffectsPayload } from "@/content/server/schedule-effects"; +import type { AnyContentTypeDefinition } from "@/content/types"; + +import { CONTENT_QUEUE_TASK_SCHEDULE_EFFECTS } from "@/content/const"; +import { CONTENT_SYSTEM_ACTOR } from "@/content/server/actor"; +import { findContentModel } from "@/content/server/model"; +import { + claimContentSchedule, + settleContentSchedule, +} from "@/content/server/schedules-model"; + +/** What the run decided, so the task logs something worth reading. */ +export interface ContentScheduleOutcome { + reason?: string; + status: "executed" | "skipped" | "unregistered"; +} + +/** + * Thrown when a claimed schedule is no longer `pending` at settlement time. + * + * Structurally impossible: the row is locked `FOR UPDATE` from the claim to the + * commit, so nothing else can have moved it. If it ever happens the lock was + * not held, and rolling the whole transition back is the only safe answer - + * publishing a record whose schedule somebody cancelled is worse than not + * publishing it. + */ +class ContentScheduleSettlementError extends Error { + constructor(scheduleId: number) { + super( + `Schedule ${scheduleId} was no longer pending at settlement time. Rolling the transition back.`, + ); + + this.name = "ContentScheduleSettlementError"; + } +} + +type ScheduleTransaction = + | { contentTypeId: string; kind: "unregistered" } + | { effects: ContentScheduleEffectsPayload; kind: "executed" } + | { kind: "skipped"; reason: string }; + +const slugOf = ( + definition: AnyContentTypeDefinition, + row: null | Record | undefined, +): null | string => { + if (!definition.publicApi.enabled) return null; + + const value = row?.[definition.publicApi.slugField]; + + return typeof value === "string" ? value : null; +}; + +/** + * Everything the announcements need, frozen at the moment the transition + * committed. + * + * `wasPublic` is derived rather than read back: the transition guards on the + * state it is leaving (`status <> 'published'` to publish, `= 'published'` to + * unpublish), so a *changed* publish came from a non-public row and a changed + * unpublish from a public one. That removes the extra `SELECT` the old code did + * outside the lock, and removes with it the window where the answer could have + * been someone else's write. + */ +const effectsPayload = ({ + claimed, + definition, + outcome, + pluginId, +}: { + claimed: { + action: "publish" | "unpublish"; + createdBy: null | number; + id: number; + itemId: number; + }; + definition: AnyContentTypeDefinition; + outcome: ContentEditorialOutcome; + pluginId: string; +}): ContentScheduleEffectsPayload => { + const row = outcome.row as unknown as Record; + + return { + changedFields: [...outcome.changedFields] as string[], + contentTypeId: definition.id, + itemId: claimed.itemId, + operation: claimed.action, + pluginId, + // A publish and an unpublish move `status`, never a field value, so the + // slug the record answered to before is the one it answers to now. Carried + // anyway, because the cache bridge takes a list and a future action that + // *does* move it should not need this file to change. + previousSlug: outcome.previousSlug ?? slugOf(definition, row), + revisionId: outcome.revisionId, + row: JSON.parse(JSON.stringify(row)) as Record, + scheduleId: claimed.id, + scheduledBy: claimed.createdBy, + version: outcome.version, + wasPublic: claimed.action === "unpublish", + }; +}; + +/** + * Runs one scheduled transition, or decides not to. + * + * **One transaction, from the claim to the commit.** The old shape claimed in a + * short transaction of its own and released the row lock before publishing, + * which left a real window: an administrator could cancel, be told it worked, + * and watch the article go live anyway. Now the `FOR UPDATE` taken by + * `claimContentSchedule` is held until the transition, its revision, the + * settlement *and* the effects task have all committed - so a concurrent cancel + * either wins outright (before the claim) or waits and then finds the schedule + * already `completed`, which is a truthful 404 rather than a lie. + * + * What is deliberately **not** in the transaction: the event, the search write + * and the cache bridge. They talk to systems a rollback cannot reach, so they + * are handed to `content-schedule-effects` - a queue row written in this same + * transaction, and therefore present exactly when the transition committed. + * + * Almost every guard here is a **silent no-op**, and that is the design rather + * than laziness: each one describes a schedule that is no longer the plan - + * cancelled, rescheduled, or already run. Throwing would send the queue into a + * retry loop over a decision that is never going to change. + */ +export const executeContentSchedule = async ( + c: Context, + { generation, scheduleId }: { generation: number; scheduleId: number }, +): Promise => { + const db = c.get("db"); + + let result: ScheduleTransaction; + try { + result = await db.transaction(async (tx): Promise => { + const claimed = await claimContentSchedule(tx, { + generation, + scheduleId, + }); + + if (!claimed) { + return { + kind: "skipped", + reason: "not pending, superseded, or not yet due", + }; + } + + const entry = findContentModel( + c.get("core").contentModels, + claimed.contentTypeId, + ); + const editorialService = entry?.model.editorialService; + + // The plugin was removed, or the content type dropped its editorial + // block. There is nothing to publish and there never will be, so cancel + // rather than retrying until the queue gives up - an error every ten + // minutes forever is not a useful way to report a config change. + if (!entry || !editorialService) { + await settleContentSchedule(tx, claimed.id, { + expectedStatus: "pending", + lastError: `Content type "${claimed.contentTypeId}" is no longer registered with an editorial workflow.`, + status: "cancelled", + }); + + return { contentTypeId: claimed.contentTypeId, kind: "unregistered" }; + } + + const { model, pluginId } = entry; + + const outcome = await editorialService(c, { pluginId })[claimed.action]( + claimed.itemId, + { + // No fake user id anywhere. Who *asked* for this is on the schedule + // row and travels in the event as `scheduledBy`. + actor: CONTENT_SYSTEM_ACTOR, + tx, + }, + ); + + // Settled whatever happened. A record that was deleted first, or is + // already in the state the schedule wanted, is still a schedule that + // has had its answer - leaving it pending would retry it forever. + if ( + !(await settleContentSchedule(tx, claimed.id, { + expectedStatus: "pending", + lastError: null, + status: "completed", + })) + ) { + throw new ContentScheduleSettlementError(claimed.id); + } + + if (!outcome) { + return { kind: "skipped", reason: "record no longer exists" }; + } + if (!outcome.changed) { + return { kind: "skipped", reason: "already in that state" }; + } + + const effects = effectsPayload({ + claimed, + definition: model.definition, + outcome, + pluginId, + }); + + // In the transaction, so the announcement task exists if and only if + // the transition it announces committed. A crash a millisecond later + // loses nothing: the row is durable and the queue will drain it. + await c.get("queue").dispatch({ + name: CONTENT_QUEUE_TASK_SCHEDULE_EFFECTS, + payload: effects, + // Core owns the handler. Without this the row would be stamped with + // the requesting plugin's id and nothing would ever claim it. + pluginId: "@vitnode/core", + tx, + }); + + return { effects, kind: "executed" }; + }); + } catch (error) { + // Outside the rolled-back transaction, and guarded on `pending`: by now the + // lock is gone, so a cancel may legitimately have won the row. + await settleContentSchedule(db, scheduleId, { + expectedStatus: "pending", + lastError: error instanceof Error ? error.message : "Unknown error", + }); + + // Rethrown on purpose: this one *is* worth retrying, and the queue's + // backoff is the retry policy. + throw error; + } + + if (result.kind === "unregistered") { + return { reason: result.contentTypeId, status: "unregistered" }; + } + if (result.kind === "skipped") { + return { reason: result.reason, status: "skipped" }; + } + + return { status: "executed" }; +}; diff --git a/packages/vitnode/src/api/modules/content/tasks/content-schedule-effects.task.ts b/packages/vitnode/src/api/modules/content/tasks/content-schedule-effects.task.ts new file mode 100644 index 000000000..4583100f7 --- /dev/null +++ b/packages/vitnode/src/api/modules/content/tasks/content-schedule-effects.task.ts @@ -0,0 +1,38 @@ +import { buildQueueTask } from "@/api/lib/queue"; +import { CONTENT_QUEUE_TASK_SCHEDULE_EFFECTS } from "@/content/const"; +import { + contentScheduleEffectsPayloadSchema, + runContentScheduleEffects, +} from "@/content/server/schedule-effects"; + +/** + * Announces a scheduled transition that has already committed. + * + * Unlike `content-schedule`, the payload here **is** data rather than a pointer, + * and deliberately so: the record may have been edited again by the time this + * runs, and an event describing the record's current state would announce + * something other than the publication it is reporting. Everything travels + * frozen from the transaction that wrote it. + * + * Five attempts rather than three. The failures this retries are transient by + * nature - a search node restarting, a web app redeploying - and the backoff + * (10s, 20s, 40s, 80s) is a far better fit for those than for a deadlock. + */ +export const contentScheduleEffectsQueueTask = buildQueueTask({ + name: CONTENT_QUEUE_TASK_SCHEDULE_EFFECTS, + description: + "Emit the event, sync search and expire the cache for a scheduled publish or unpublish that has already committed. Never republishes.", + maxAttempts: 5, + handler: async (c, payload) => { + const input = contentScheduleEffectsPayloadSchema.parse(payload); + const outcome = await runContentScheduleEffects(c, input); + + if (outcome.status === "unregistered") { + await c + .get("log") + .warn( + `[content-schedule-effects] ${input.scheduleId}: ${input.contentTypeId} is no longer registered, so nothing was announced.`, + ); + } + }, +}); diff --git a/packages/vitnode/src/api/modules/content/tasks/content-schedule.task.ts b/packages/vitnode/src/api/modules/content/tasks/content-schedule.task.ts new file mode 100644 index 000000000..206152bd4 --- /dev/null +++ b/packages/vitnode/src/api/modules/content/tasks/content-schedule.task.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; + +import { buildQueueTask } from "@/api/lib/queue"; +import { CONTENT_QUEUE_TASK_SCHEDULE } from "@/content/const"; + +import { executeContentSchedule } from "../helpers/execute-content-schedule"; + +/** + * The payload is a **pointer**, not data. + * + * Everything that matters - which record, which action, whether it is still + * wanted - is re-read from the schedule row under a lock. A payload carrying + * the action would go stale the moment somebody rescheduled, and a payload + * carrying the item id would be a way to publish an arbitrary record by + * inserting a queue row. + */ +export const contentSchedulePayloadSchema = z.object({ + generation: z.number().int().positive(), + scheduleId: z.number().int().positive(), +}); + +export const contentScheduleQueueTask = buildQueueTask({ + name: CONTENT_QUEUE_TASK_SCHEDULE, + description: + "Publish or unpublish a content record at its scheduled time. A cancelled, rescheduled or already-executed schedule is a no-op.", + handler: async (c, payload) => { + const { generation, scheduleId } = + contentSchedulePayloadSchema.parse(payload); + + const outcome = await executeContentSchedule(c, { generation, scheduleId }); + if (outcome.status === "executed") return; + + const message = `[content-schedule] ${scheduleId}: ${outcome.status}${outcome.reason ? ` (${outcome.reason})` : ""}`; + + // A skip is the normal, healthy outcome for a superseded task, so it is + // `debug` - but silence would make "the schedule never fired" impossible to + // tell from "it fired and correctly did nothing". An unregistered content + // type is a real misconfiguration, so that one is a warning. + await (outcome.status === "unregistered" + ? c.get("log").warn(message) + : c.get("log").debug(message)); + }, +}); diff --git a/packages/vitnode/src/api/modules/search/routes/search.route.ts b/packages/vitnode/src/api/modules/search/routes/search.route.ts index ee4433064..0256d91ed 100644 --- a/packages/vitnode/src/api/modules/search/routes/search.route.ts +++ b/packages/vitnode/src/api/modules/search/routes/search.route.ts @@ -3,10 +3,24 @@ import { z } from "@hono/zod-openapi"; import { CONFIG_PLUGIN } from "@/config"; import { buildRoute } from "../../../lib/route"; -import { - zodPaginationPageInfo, - zodPaginationQuery, -} from "../../../lib/with-pagination"; +import { zodPaginationQuery } from "../../../lib/with-pagination"; + +/** + * The search index's own page info. + * + * Deliberately not `zodPaginationPageInfo`: that one describes a keyset walk + * over a table and hands out an opaque cursor for the ordered tuple. A search + * page is not that - a relevance-sorted one walks by offset and an ordinary one + * by row id - so it keeps the numeric cursors it has always had. + */ +const zodSearchPageInfo = z.object({ + totalCount: z.number(), + count: z.number(), + hasNextPage: z.boolean(), + hasPreviousPage: z.boolean(), + startCursor: z.number().nullable(), + endCursor: z.number().nullable(), +}); export const zodSearchHitSchema = z.object({ id: z.number(), @@ -57,7 +71,11 @@ export const searchRoute = buildRoute({ "application/json": { schema: z.object({ edges: z.array(zodSearchHitSchema), - pageInfo: zodPaginationPageInfo, + // The search index has its own pagination - a relevance-sorted + // page walks by offset, and an ordinary one by row id - so it + // keeps the numeric cursors it has always had rather than the + // opaque keyset cursor `withPagination` mints for a table. + pageInfo: zodSearchPageInfo, }), }, }, diff --git a/packages/vitnode/src/api/modules/search/tasks/rebuild-index.task.test.ts b/packages/vitnode/src/api/modules/search/tasks/rebuild-index.task.test.ts new file mode 100644 index 000000000..ea7e488be --- /dev/null +++ b/packages/vitnode/src/api/modules/search/tasks/rebuild-index.task.test.ts @@ -0,0 +1,518 @@ +// @vitest-environment node +import type { Context } from "hono"; + +import { describe, expect, it, vi } from "vitest"; + +import type { EnvVitNode } from "@/api/middlewares/global.middleware"; +import type { + SearchDocument, + SearchIndexerConfig, + SearchIndexerLoadResult, +} from "@/api/models/search"; + +import { rebuildSearchIndexTask } from "./rebuild-index.task"; + +const document = ( + itemType: string, + itemId: number, + extra: Partial = {}, +): SearchDocument => ({ + content: "body", + createdAt: new Date("2026-01-01T00:00:00.000Z"), + itemId, + itemType, + title: `Item ${itemId}`, + ...extra, +}); + +/** + * An indexer that replays a fixed list of pages, recording the offsets it was + * asked for. Page contents are what each test is about; the offsets are what the + * rebuild is supposed to derive from them. + */ +const scriptedIndexer = ({ + itemType, + pages, + pluginId, +}: { + itemType: string; + /** + * Either result shape, so the same script drives a modern and a legacy + * indexer. Past the end, each keeps returning its own "no more rows" signal. + */ + pages: SearchIndexerLoadResult[]; + pluginId: string; +}) => { + const offsets: number[] = []; + const exhausted: SearchIndexerLoadResult = Array.isArray(pages[0]) + ? [] + : { documents: [], itemsRead: 0 }; + let call = 0; + + const config: SearchIndexerConfig = { + itemType, + load: async (_c, offset) => { + offsets.push(offset); + const page = pages[call] ?? exhausted; + call++; + + return await Promise.resolve(page); + }, + pluginId, + }; + + return { config, offsets }; +}; + +const harness = (indexers: SearchIndexerConfig[]) => { + const cleared: (string | undefined)[] = []; + const indexed: SearchDocument[][] = []; + + const search = { + bulkIndex: vi.fn(async (docs: SearchDocument[]) => { + indexed.push(docs); + await Promise.resolve(); + }), + clear: vi.fn(async (itemType?: string) => { + cleared.push(itemType); + await Promise.resolve(); + }), + }; + + const c = { + get: (key: string) => { + if (key === "search") return search; + if (key === "core") return { searchIndexers: indexers }; + + return undefined; + }, + } as unknown as Context; + + return { c, cleared, indexed, search }; +}; + +describe("rebuild-search-index", () => { + it("keeps paging after a page that produced no documents", async () => { + // The regression: every row on page 1 is rejected by the mapper. Treating an + // empty document array as end-of-source would stop here and never index the + // valid row on page 2. + const { config, offsets } = scriptedIndexer({ + itemType: "test.searchable", + pages: [ + { documents: [], itemsRead: 200 }, + { documents: [document("test.searchable", 201)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/example", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(offsets).toEqual([0, 200, 201]); + expect(indexed).toHaveLength(1); + expect(indexed[0]?.[0]?.itemId).toBe(201); + }); + + it("stops when a page reads no source rows", async () => { + const { config, offsets } = scriptedIndexer({ + itemType: "test.searchable", + pages: [ + { documents: [document("test.searchable", 1)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/example", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(offsets).toEqual([0, 1]); + expect(indexed).toHaveLength(1); + }); + + it("never calls the engine for an empty document page", async () => { + const { config } = scriptedIndexer({ + itemType: "test.searchable", + pages: [ + { documents: [], itemsRead: 5 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/example", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(indexed).toEqual([]); + }); + + it("advances by source items, not by documents", async () => { + // A multi-language indexer emits several documents per item. Advancing by + // document count would skip items on every page. + const { config, offsets } = scriptedIndexer({ + itemType: "blog_post", + pages: [ + { + documents: [ + document("blog_post", 1, { languageCode: "en" }), + document("blog_post", 1, { languageCode: "pl" }), + document("blog_post", 2, { languageCode: "en" }), + document("blog_post", 2, { languageCode: "pl" }), + ], + itemsRead: 2, + }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/blog", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(offsets).toEqual([0, 2]); + expect(indexed[0]).toHaveLength(4); + }); + + describe("plugin ownership", () => { + it("keeps an owner the document already declared", async () => { + const { config } = scriptedIndexer({ + itemType: "test.searchable", + pages: [ + { + documents: [ + document("test.searchable", 1, { pluginId: "@vitnode/example" }), + ], + itemsRead: 1, + }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/example", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(indexed[0]?.[0]?.pluginId).toBe("@vitnode/example"); + }); + + it("stamps the registering plugin on a legacy document", async () => { + // A hand-written indexer that predates `SearchDocument.pluginId`. Without + // this the rebuild would store it as core, because the queue drains inside + // the core cron request. + const { config } = scriptedIndexer({ + itemType: "blog_post", + pages: [ + { documents: [document("blog_post", 7)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/blog", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(indexed[0]?.[0]?.pluginId).toBe("@vitnode/blog"); + }); + + it("preserves ownership in a single-collection rebuild", async () => { + const example = scriptedIndexer({ + itemType: "test.searchable", + pages: [ + { + documents: [ + document("test.searchable", 1, { pluginId: "@vitnode/example" }), + ], + itemsRead: 1, + }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/example", + }); + const blog = scriptedIndexer({ + itemType: "blog_post", + pages: [{ documents: [document("blog_post", 1)], itemsRead: 1 }], + pluginId: "@vitnode/blog", + }); + const { c, cleared, indexed } = harness([example.config, blog.config]); + + await rebuildSearchIndexTask.handler(c, { + itemType: "test.searchable", + }); + + // Scoped: the other plugin's collection is neither cleared nor reloaded. + expect(cleared).toEqual(["test.searchable"]); + expect(blog.offsets).toEqual([]); + expect(indexed.flat().map(doc => doc.pluginId)).toEqual([ + "@vitnode/example", + ]); + }); + }); + + it("clears the whole index for a full rebuild and each indexer runs", async () => { + const first = scriptedIndexer({ + itemType: "a.one", + pages: [ + { documents: [document("a.one", 1)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/a", + }); + const second = scriptedIndexer({ + itemType: "b.two", + pages: [ + { documents: [document("b.two", 1)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/b", + }); + const { c, cleared, indexed } = harness([first.config, second.config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(cleared).toEqual([undefined]); + expect(indexed.flat().map(doc => doc.pluginId)).toEqual([ + "@vitnode/a", + "@vitnode/b", + ]); + }); + + describe("the deprecated array result", () => { + it("indexes a legacy page and stops on the empty one", async () => { + const { config, offsets } = scriptedIndexer({ + itemType: "legacy.item", + pages: [[document("legacy.item", 1)], []], + pluginId: "@vitnode/legacy", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + // No source count to advance by, so the cursor moves a whole page - which + // is exactly what the old rebuild did. + expect(offsets).toEqual([0, 200]); + expect(indexed).toHaveLength(1); + expect(indexed[0]?.[0]?.itemId).toBe(1); + }); + + it("stamps the registering plugin on a legacy document", async () => { + const { config } = scriptedIndexer({ + itemType: "legacy.item", + pages: [[document("legacy.item", 1)], []], + pluginId: "@vitnode/legacy", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(indexed[0]?.[0]?.pluginId).toBe("@vitnode/legacy"); + }); + + it("keeps an owner a legacy document declared itself", async () => { + const { config } = scriptedIndexer({ + itemType: "legacy.item", + pages: [ + [document("legacy.item", 1, { pluginId: "@vitnode/elsewhere" })], + [], + ], + pluginId: "@vitnode/legacy", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(indexed[0]?.[0]?.pluginId).toBe("@vitnode/elsewhere"); + }); + + it("treats a blank declared owner as absent", async () => { + const { config } = scriptedIndexer({ + itemType: "legacy.item", + pages: [[document("legacy.item", 1, { pluginId: " " })], []], + pluginId: "@vitnode/legacy", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(indexed[0]?.[0]?.pluginId).toBe("@vitnode/legacy"); + }); + + it("does not mistake a multilingual document count for a source count", async () => { + // Four documents, two source items, two languages. Advancing by the array + // length would jump to offset 4 and skip most of a 200-row page. + const { config, offsets } = scriptedIndexer({ + itemType: "legacy.multilingual", + pages: [ + [ + document("legacy.multilingual", 1, { languageCode: "en" }), + document("legacy.multilingual", 1, { languageCode: "pl" }), + document("legacy.multilingual", 2, { languageCode: "en" }), + document("legacy.multilingual", 2, { languageCode: "pl" }), + ], + [], + ], + pluginId: "@vitnode/legacy", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(offsets).toEqual([0, 200]); + expect(offsets).not.toContain(4); + expect(indexed[0]).toHaveLength(4); + }); + + it("indexes an empty first page as an exhausted source", async () => { + // The ambiguity the modern contract exists to remove: an array cannot say + // whether rows were read and all filtered out, so this ends the rebuild. + const { config, offsets } = scriptedIndexer({ + itemType: "legacy.item", + pages: [[], [document("legacy.item", 1)]], + pluginId: "@vitnode/legacy", + }); + const { c, indexed } = harness([config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(offsets).toEqual([0]); + expect(indexed).toEqual([]); + }); + + it("runs alongside a modern indexer", async () => { + const legacy = scriptedIndexer({ + itemType: "legacy.item", + pages: [[document("legacy.item", 1)], []], + pluginId: "@vitnode/legacy", + }); + const modern = scriptedIndexer({ + itemType: "modern.item", + pages: [ + { documents: [], itemsRead: 200 }, + { documents: [document("modern.item", 2)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/modern", + }); + const { c, indexed } = harness([legacy.config, modern.config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(legacy.offsets).toEqual([0, 200]); + expect(modern.offsets).toEqual([0, 200, 201]); + expect(indexed.flat().map(doc => [doc.itemId, doc.pluginId])).toEqual([ + [1, "@vitnode/legacy"], + [2, "@vitnode/modern"], + ]); + }); + }); + + describe("a collection with no rebuild indexer", () => { + it("refuses a scoped rebuild before clearing anything", async () => { + // The action offering this is called "reindex", so it must not be a delete: + // clearing here would remove the documents and refill nothing. + const other = scriptedIndexer({ + itemType: "example.article", + pages: [{ documents: [], itemsRead: 0 }], + pluginId: "@vitnode/example", + }); + const { c, indexed, search } = harness([other.config]); + + await expect( + rebuildSearchIndexTask.handler(c, { itemType: "removed.collection" }), + ).rejects.toThrow(/no search indexer is registered/i); + + expect(search.clear).not.toHaveBeenCalled(); + expect(other.offsets).toEqual([]); + expect(indexed).toEqual([]); + }); + + it("refuses even when no indexer is registered at all", async () => { + const { c, search } = harness([]); + + await expect( + rebuildSearchIndexTask.handler(c, { itemType: "removed.collection" }), + ).rejects.toThrow(/removed.collection/); + + expect(search.clear).not.toHaveBeenCalled(); + }); + + it("names the collection it refused", async () => { + const { c } = harness([]); + + await expect( + rebuildSearchIndexTask.handler(c, { itemType: "removed.collection" }), + ).rejects.toThrow(/Cannot rebuild collection "removed.collection"/); + }); + + it("still lets a full rebuild clear the whole index", async () => { + // A full rebuild refills only what has an indexer, so documents without one + // are removed by it. That is the documented behaviour, and it must not be + // blocked by the scoped guard. + const registered = scriptedIndexer({ + itemType: "example.article", + pages: [ + { documents: [document("example.article", 1)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/example", + }); + const { c, cleared, indexed } = harness([registered.config]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(cleared).toEqual([undefined]); + expect(indexed.flat().map(doc => doc.itemId)).toEqual([1]); + }); + + it("still lets a full rebuild run with no indexers registered", async () => { + const { c, cleared, indexed } = harness([]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(cleared).toEqual([undefined]); + expect(indexed).toEqual([]); + }); + }); + + it("clears and rebuilds only the scoped collection when it has an indexer", async () => { + const target = scriptedIndexer({ + itemType: "example.article", + pages: [ + { documents: [document("example.article", 1)], itemsRead: 1 }, + { documents: [], itemsRead: 0 }, + ], + pluginId: "@vitnode/example", + }); + const other = scriptedIndexer({ + itemType: "blog_post", + pages: [{ documents: [document("blog_post", 9)], itemsRead: 1 }], + pluginId: "@vitnode/blog", + }); + const { c, cleared, indexed } = harness([target.config, other.config]); + + await rebuildSearchIndexTask.handler(c, { itemType: "example.article" }); + + expect(cleared).toEqual(["example.article"]); + expect(other.offsets).toEqual([]); + expect(indexed.flat().map(doc => doc.itemId)).toEqual([1]); + }); + + it("does not loop forever on a broken indexer", async () => { + // A page that reports rows but never advances past them would spin. The + // cursor is the indexer's own `itemsRead`, so this asserts the loop is driven + // by data rather than by a fixed page size. + const load = vi.fn( + async (_c: Context, offset: number) => + await Promise.resolve( + offset === 0 + ? { documents: [], itemsRead: 3 } + : { documents: [], itemsRead: 0 }, + ), + ); + const { c } = harness([{ itemType: "x.y", load, pluginId: "@vitnode/x" }]); + + await rebuildSearchIndexTask.handler(c, {}); + + expect(load).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/vitnode/src/api/modules/search/tasks/rebuild-index.task.ts b/packages/vitnode/src/api/modules/search/tasks/rebuild-index.task.ts index 03d396dda..ddd07ad58 100644 --- a/packages/vitnode/src/api/modules/search/tasks/rebuild-index.task.ts +++ b/packages/vitnode/src/api/modules/search/tasks/rebuild-index.task.ts @@ -1,4 +1,8 @@ import { buildQueueTask } from "@/api/lib/queue"; +import { + normalizeSearchIndexerPage, + searchDocumentOwner, +} from "@/api/models/search"; const PAGE_SIZE = 200; @@ -16,20 +20,50 @@ export const rebuildSearchIndexTask = buildQueueTask({ indexer => !itemType || indexer.itemType === itemType, ); + // Clearing a collection nothing can rebuild is a delete, not a rebuild. The + // route rejects this too, but the check has to be here as well: a queue task + // can be dispatched directly, and a job queued while an indexer was still + // registered can drain after the plugin is gone. + if (itemType && indexers.length === 0) { + throw new Error( + `[Search] Cannot rebuild collection "${itemType}": no search indexer is registered. Its documents were left alone - they may still be maintained by live writes, so remove them explicitly if that is what you meant.`, + ); + } + // Scope the clear to the target collection so a single-collection reindex // never wipes the rest of the index. await search.clear(itemType); for (const indexer of indexers) { - // Offset advances by whole pages of items, not by document count: an - // indexer may emit several documents per item (e.g. one per language), so - // the two are not interchangeable. A page that yields no documents ends - // the loop. - for (let page = 0; ; page++) { - const docs = await indexer.load(c, page * PAGE_SIZE, PAGE_SIZE); - if (docs.length === 0) break; + // The cursor advances by source rows read, never by documents produced: an + // indexer may emit several documents per item, or none for a page whose + // rows it cannot project. Ending on an empty document array would stop the + // rebuild at the first such page and never reach the rows after it. + // + // A legacy indexer returning a bare array is normalized to the same shape, + // where a non-empty page reports the requested limit - the only cursor the + // old contract ever had. + for (let offset = 0; ;) { + const page = normalizeSearchIndexerPage( + await indexer.load(c, offset, PAGE_SIZE), + PAGE_SIZE, + ); + if (page.itemsRead === 0) break; + + if (page.documents.length > 0) { + // This task runs inside the core cron request, so the request's plugin + // is not the owner. Stamp the registering plugin on any document that + // did not name one, or a rebuild would relabel it as core. + await search.bulkIndex( + page.documents.map(document => ({ + ...document, + pluginId: + searchDocumentOwner(document.pluginId) ?? indexer.pluginId, + })), + ); + } - await search.bulkIndex(docs); + offset += page.itemsRead; } } }, diff --git a/packages/vitnode/src/api/modules/users/files/routes/list.route.ts b/packages/vitnode/src/api/modules/users/files/routes/list.route.ts index d9a40c7d4..dce44b888 100644 --- a/packages/vitnode/src/api/modules/users/files/routes/list.route.ts +++ b/packages/vitnode/src/api/modules/users/files/routes/list.route.ts @@ -72,10 +72,11 @@ export const listUserFilesRoute = buildRoute({ primaryCursor: core_files.id, search: [core_files.name], where: eq(core_files.userId, user.id), - query: async ({ limit, where, orderBy }) => + query: async ({ cursorSelection, limit, where, orderBy }) => await c .get("db") .select({ + ...cursorSelection, id: core_files.id, name: core_files.name, key: core_files.key, diff --git a/packages/vitnode/src/api/plugin.ts b/packages/vitnode/src/api/plugin.ts index 9119a04f9..45cd3deb4 100644 --- a/packages/vitnode/src/api/plugin.ts +++ b/packages/vitnode/src/api/plugin.ts @@ -2,6 +2,7 @@ import { CONFIG_PLUGIN } from "@/config"; import { buildApiPlugin } from "./lib/plugin"; import { adminModule } from "./modules/admin/admin.module"; +import { contentModule } from "./modules/content/content.module"; import { cronModule } from "./modules/cron/cron.module"; import { middlewareModule } from "./modules/middleware/middleware.module"; import { queueModule } from "./modules/queue/queue.module"; @@ -14,6 +15,7 @@ export const newBuildPluginApiCore = buildApiPlugin({ middlewareModule, usersModule, adminModule, + contentModule, cronModule, queueModule, searchModule, diff --git a/packages/vitnode/src/components/confirm-action/confirm-action-alert-dialog.tsx b/packages/vitnode/src/components/confirm-action/confirm-action-alert-dialog.tsx index b8ad44b19..1cee0c08a 100644 --- a/packages/vitnode/src/components/confirm-action/confirm-action-alert-dialog.tsx +++ b/packages/vitnode/src/components/confirm-action/confirm-action-alert-dialog.tsx @@ -24,22 +24,33 @@ export const ConfirmActionAlertDialog = ({ children, title, description, + finalFocus, + submitVariant, textSubmit, onSubmit, ...props }: Omit, "children"> & React.ComponentProps & { - children: React.ReactElement; + /** + * The element that opens the dialog. + * + * Optional, for a caller that owns `open` itself - a confirmation opened from + * a menu item has no trigger to render, because the item is gone by the time + * the dialog is on screen. + */ + children?: React.ReactElement; description?: React.ReactNode; + /** Where focus goes on close, for a dialog whose trigger no longer exists. */ + finalFocus?: React.ComponentProps["finalFocus"]; title?: React.ReactNode; }) => { const t = useTranslations("core.global.confirm_action"); return ( - + {children ? : null} - + {title ?? t("title")} @@ -48,7 +59,11 @@ export const ConfirmActionAlertDialog = ({ }> - + diff --git a/packages/vitnode/src/components/confirm-action/content.tsx b/packages/vitnode/src/components/confirm-action/content.tsx index 85158bace..b3dd88e70 100644 --- a/packages/vitnode/src/components/confirm-action/content.tsx +++ b/packages/vitnode/src/components/confirm-action/content.tsx @@ -10,9 +10,15 @@ import { Button } from "../ui/button"; export const ContentConfirmAction = ({ onSubmit, + submitVariant = "destructive", textSubmit, }: { onSubmit: (props: { onClose: () => void }) => Promise | void; + /** + * Defaults to `destructive`, which is right for the deletes this dialog was + * built for - and wrong for a confirmation that publishes something. + */ + submitVariant?: React.ComponentProps["variant"]; textSubmit?: string; }) => { const t = useTranslations("core.global.confirm_action"); @@ -27,7 +33,7 @@ export const ContentConfirmAction = ({

{t("cancel")} - diff --git a/packages/vitnode/src/components/form/auto-form.tsx b/packages/vitnode/src/components/form/auto-form.tsx index 79eb3a58d..aca27e168 100644 --- a/packages/vitnode/src/components/form/auto-form.tsx +++ b/packages/vitnode/src/components/form/auto-form.tsx @@ -10,7 +10,9 @@ import { type FieldValues, type Mode, useForm, + useFormContext, type UseFormReturn, + useFormState, } from "react-hook-form"; import z from "zod"; @@ -63,6 +65,15 @@ export interface ItemAutoFormComponentProps { itemParams?: InputParams; label?: React.ReactNode; labelRight?: React.ReactNode; + /** + * Whether this field holds one value per language. + * + * Set by whoever builds the field list - the Content Engine reads it off + * `localized: true` - so a custom component can pass it straight through to + * `AutoFormInput`, `AutoFormTextarea` or `AutoFormEditor` and get the language + * switcher without knowing why the field has one. + */ + multiLang?: boolean; otherProps: { ["aria-invalid"]?: boolean; enum?: string[]; @@ -98,6 +109,40 @@ function AutoFormField({ return ; } +/** + * The submit button of the surrounding `AutoForm`, for a `layout` that has to + * place it itself. + * + * Reads the form through context rather than taking props, so it stays in step + * with validity and submission exactly like the built-in one - and so a layout + * cannot wire up a button that submits a different form. + */ +export const AutoFormSubmitButton = ({ + children, + className, + variant, +}: { + children?: React.ReactNode; + className?: string; + variant?: React.ComponentProps["variant"]; +}) => { + const t = useTranslations("core.global"); + const { control } = useFormContext(); + const { isSubmitting, isValid } = useFormState({ control }); + + return ( + + ); +}; + export type AutoFormOnSubmit< T extends z.ZodObject, TContext = unknown, @@ -118,6 +163,7 @@ export function AutoForm< onSubmit: onSubmitProp, captcha, fields, + layout, tabs, submitButtonProps, children, @@ -126,6 +172,19 @@ export function AutoForm< captcha?: z.infer["captcha"]; fields: ItemAutoFormProps[]; formSchema: T; + /** + * Places the fields yourself instead of stacking them in declaration order. + * + * Called with every field already rendered and keyed by its `id`, so a layout + * puts an element where it wants it and each one stays wired into this form's + * validation, dirty state and error display. One ``, one schema, one + * submit - a layout cannot accidentally create a second of any of them. + * + * The automatic submit button is **not** rendered in this mode: a layout that + * decides where the fields go has to decide where the button goes too. + * Mutually exclusive with `tabs`. + */ + layout?: (renderedFields: Record) => React.ReactNode; mode?: Mode; onSubmit?: AutoFormOnSubmit; submitButtonProps?: Omit< @@ -272,6 +331,24 @@ export function AutoForm< ); + if (layout) { + return ( + + {layout( + Object.fromEntries( + fields + .filter(isFieldVisible) + .map(item => [item.id, renderField(item)]), + ), + )} + + {children} + + {captcha &&
} + + ); + } + return (
{tabs?.length ? ( diff --git a/packages/vitnode/src/components/form/common/async-picker.tsx b/packages/vitnode/src/components/form/common/async-picker.tsx new file mode 100644 index 000000000..6fc4d333d --- /dev/null +++ b/packages/vitnode/src/components/form/common/async-picker.tsx @@ -0,0 +1,165 @@ +"use client"; + +import { ChevronsUpDownIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; +import React from "react"; +import { useDebouncedCallback } from "use-debounce"; + +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Spinner } from "@/components/ui/spinner"; +import { cn } from "@/lib/utils"; + +/** Anything this picker can offer: an identity and whatever renders it. */ +export interface AsyncPickerOption { + id: number; +} + +/** + * A search-as-you-type picker over a server-side list. + * + * The shape every "choose a user", "choose a role" control in the AdminCP had + * grown its own copy of. Extracted so the ones that matter - the debounce, the + * spinner that only shows while there is nothing to show, and re-running the + * empty search each time it opens so the list is never yesterday's - are + * decided once. + * + * **Not** built on `Combobox`: an option here is a row with an avatar or a + * colour swatch in it, and the combobox's async mode renders `{ label, value }` + * strings. Rendering is the caller's job through `renderOption`. + * + * Deliberately uncontrolled about *selection*: it reports what was picked and + * nothing else, so the same component serves a single-value field and a + * multi-value one without knowing which it is in. + */ +export function AsyncPicker({ + className, + disabled, + emptyLabel, + invalid, + onSelect, + renderOption, + search, + searchPlaceholder, + selectedIds = [], + trigger, +}: { + className?: string; + disabled?: boolean; + /** Shown when a search comes back with nothing. Defaults to the core string. */ + emptyLabel?: string; + invalid?: boolean; + onSelect: (option: TOption) => void; + renderOption: (option: TOption) => React.ReactNode; + search: (value: string) => Promise; + searchPlaceholder?: string; + /** Ticked in the list, so picking again to remove reads as a toggle. */ + selectedIds?: number[]; + trigger: React.ReactNode; +}) { + const t = useTranslations("core.global"); + const [open, setOpen] = React.useState(false); + const [options, setOptions] = React.useState([]); + const [isSearching, setIsSearching] = React.useState(false); + + const runSearch = React.useCallback( + async (value: string) => { + setIsSearching(true); + try { + setOptions(await search(value)); + } catch (error) { + // A search that fails is an empty list plus a console line, never a + // thrown error: this sits inside a form, and taking the page down + // because a lookup timed out loses whatever else was typed. + // eslint-disable-next-line no-console + console.error(error); + setOptions([]); + } finally { + setIsSearching(false); + } + }, + [search], + ); + const debouncedSearch = useDebouncedCallback(runSearch, 400); + const selected = new Set(selectedIds); + + return ( + { + setOpen(next); + // Cleared and re-run on every open rather than cached: the list is a + // live view of who exists, and a stale one offers somebody who was + // deleted since. + if (next) { + setOptions([]); + void runSearch(""); + } + }} + open={open} + > + + } + > + {trigger} + + + + + + + + {isSearching && options.length === 0 ? ( +
+ +
+ ) : ( + <> + + {emptyLabel ?? t("results_not_found")} + + + {options.map(option => ( + { + onSelect(option); + setOpen(false); + }} + value={String(option.id)} + > + {renderOption(option)} + + ))} + + + )} +
+
+
+
+ ); +} diff --git a/packages/vitnode/src/components/form/fields/checkbox.tsx b/packages/vitnode/src/components/form/fields/checkbox.tsx index ef1980333..939e40e9f 100644 --- a/packages/vitnode/src/components/form/fields/checkbox.tsx +++ b/packages/vitnode/src/components/form/fields/checkbox.tsx @@ -16,6 +16,10 @@ export const AutoFormCheckbox = ({ className, // eslint-disable-next-line @typescript-eslint/no-unused-vars itemParams, + // Only the language-aware inputs implement this - dropped here so it never + // lands on the DOM element the rest props spread into. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + multiLang, ...props }: ItemAutoFormComponentProps & Omit, "checked">) => { diff --git a/packages/vitnode/src/components/form/fields/color.test.tsx b/packages/vitnode/src/components/form/fields/color.test.tsx deleted file mode 100644 index 4dbb9979b..000000000 --- a/packages/vitnode/src/components/form/fields/color.test.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { type FieldValues, useForm } from "react-hook-form"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -import { Form, FormField } from "@/components/ui/form"; - -import { AutoFormColor } from "./color"; - -vi.mock("next-intl", () => ({ - useLocale: () => "en", - useTranslations: () => (key: string) => key, -})); - -vi.mock("react-colorful", () => ({ - HslStringColorPicker: ({ - color, - onChange, - }: { - color?: string; - onChange?: (value: string) => void; - }) => ( - - ), -})); - -const Harness = ({ - onSubmit = vi.fn(), - defaultValue = "hsl(240, 80%, 60%)", -}: { - defaultValue?: string; - onSubmit?: (values: FieldValues) => void; -}) => { - const form = useForm({ - defaultValues: { color: defaultValue } as FieldValues, - }); - - return ( - - ( - - )} - /> - - - ); -}; - -describe("AutoFormColor", () => { - beforeEach(() => { - Element.prototype.scrollIntoView = vi.fn(); - Element.prototype.hasPointerCapture = vi.fn(() => false); - Element.prototype.setPointerCapture = vi.fn(); - Element.prototype.releasePointerCapture = vi.fn(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it("renders the label and the current color on the trigger", () => { - render(); - - expect(screen.getByText("Color")).toBeDefined(); - expect(screen.getByText("hsl(240, 80%, 60%)")).toBeDefined(); - }); - - it("shows the pick-color placeholder when no color is set", () => { - render(); - - expect(screen.getByText("pick_color")).toBeDefined(); - }); - - it("writes the picked HSL color into the form and submits it", async () => { - const onSubmit = vi.fn(); - render(); - - fireEvent.click(screen.getByText("hsl(240, 80%, 60%)")); - fireEvent.click(await screen.findByTestId("picker")); - - fireEvent.click(screen.getByRole("button", { name: "submit" })); - - await waitFor(() => { - expect(onSubmit).toHaveBeenCalledWith( - { color: "hsl(200, 50%, 50%)" }, - expect.anything(), - ); - }); - }); - - it("lets the user type a color in any format via the text input", async () => { - const onSubmit = vi.fn(); - render(); - - fireEvent.click(screen.getByText("hsl(240, 80%, 60%)")); - const input = await screen.findByRole("textbox"); - fireEvent.change(input, { target: { value: "#00ff00" } }); - - fireEvent.click(screen.getByRole("button", { name: "submit" })); - - await waitFor(() => { - expect(onSubmit).toHaveBeenCalledWith( - { color: "#00ff00" }, - expect.anything(), - ); - }); - }); - - it("clears the value via the remove button when allowRemoveColor is set", async () => { - const onSubmit = vi.fn(); - render(); - - fireEvent.click(screen.getByText("hsl(240, 80%, 60%)")); - fireEvent.click(await screen.findByRole("button", { name: "remove" })); - - fireEvent.click(screen.getByRole("button", { name: "submit" })); - - await waitFor(() => { - expect(onSubmit).toHaveBeenCalledWith({ color: "" }, expect.anything()); - }); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/color.tsx b/packages/vitnode/src/components/form/fields/color.tsx index 360a35938..d57a9d3d7 100644 --- a/packages/vitnode/src/components/form/fields/color.tsx +++ b/packages/vitnode/src/components/form/fields/color.tsx @@ -16,6 +16,10 @@ export const AutoFormColor = ({ field, // eslint-disable-next-line @typescript-eslint/no-unused-vars itemParams, + // Only the language-aware inputs implement this - dropped here so it never + // lands on the DOM element the rest props spread into. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + multiLang, ...props }: ItemAutoFormComponentProps & Omit, "onChange" | "value">) => { diff --git a/packages/vitnode/src/components/form/fields/combobox.test.tsx b/packages/vitnode/src/components/form/fields/combobox.test.tsx deleted file mode 100644 index 36d77c39e..000000000 --- a/packages/vitnode/src/components/form/fields/combobox.test.tsx +++ /dev/null @@ -1,320 +0,0 @@ -import type { ReactElement } from "react"; - -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; - -import type { ItemAutoFormComponentProps } from "../auto-form"; - -import { AutoFormCombobox } from "./combobox"; - -vi.mock("next-intl", () => ({ - useTranslations: () => (key: string) => key, -})); - -vi.mock("@/components/ui/form", () => ({ - FormMessage: () => null, - useFormField: () => ({ - error: undefined, - formDescriptionId: "form-item-description", - formItemId: "form-item", - formMessageId: "form-item-message", - }), -})); - -afterEach(() => { - vi.useRealTimers(); -}); - -const createField = ( - value: unknown = null, -): ItemAutoFormComponentProps["field"] => { - return { - value, - onChange: vi.fn(), - onBlur: vi.fn(), - name: "field", - ref: vi.fn(), - }; -}; - -const renderWithClient = (ui: ReactElement) => { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }); - - return render( - {ui}, - ); -}; - -describe("AutoFormCombobox", () => { - it("uses the translated default placeholder when no placeholder is provided", () => { - renderWithClient( - , - ); - - expect(screen.getByPlaceholderText("select_option")).toBeTruthy(); - }); - - it("renders label and placeholder for static options", () => { - renderWithClient( - , - ); - - expect(screen.getByText("Type")).toBeTruthy(); - expect(screen.getByPlaceholderText("Pick a type")).toBeTruthy(); - }); - - it("renders optional marker, labelRight and description", () => { - renderWithClient( - Admin only} - otherProps={{ - enum: ["option-one"], - isOptional: true, - "aria-invalid": false, - }} - />, - ); - - expect(screen.getByText("Type")).toBeTruthy(); - expect(screen.getByText("optional")).toBeTruthy(); - expect(screen.getByText("Admin only")).toBeTruthy(); - expect(screen.getByText("Choose the primary item type.")).toBeTruthy(); - }); - - it("passes disabled and invalid state to the single input", () => { - renderWithClient( - , - ); - - const input = screen.getByPlaceholderText("Pick a type"); - - expect((input as HTMLInputElement).disabled).toBe(true); - expect(input.getAttribute("aria-invalid")).toBe("true"); - }); - - it("renders chips when multiple for static options", () => { - const { container } = renderWithClient( - , - ); - - expect( - container.querySelector("[data-slot='combobox-chips']"), - ).toBeTruthy(); - }); - - it("renders mapped labels for selected values", () => { - renderWithClient( - , - ); - - expect(screen.getByText("Option One")).toBeTruthy(); - }); - - it("falls back to raw selected values when labels are missing", () => { - renderWithClient( - , - ); - - expect(screen.getByText("unknown-option")).toBeTruthy(); - }); - - it("calls onInputValueChange for static input", () => { - const onInputValueChange = vi.fn(); - - renderWithClient( - , - ); - - fireEvent.change(screen.getByPlaceholderText("Pick a type"), { - target: { - value: "opt", - }, - }); - - expect(onInputValueChange).toHaveBeenCalled(); - }); - - it("calls field.onChange and onValueChange when a static option is selected", async () => { - const field = createField(); - const onValueChange = vi.fn(); - - renderWithClient( - , - ); - - const input = screen.getByPlaceholderText("Pick a type"); - - fireEvent.focus(input); - fireEvent.keyDown(input, { key: "ArrowDown" }); - fireEvent.keyDown(input, { key: "Enter" }); - - await waitFor(() => { - expect(field.onChange).toHaveBeenCalledWith("option-one"); - }); - expect(onValueChange).toHaveBeenCalledWith("option-one", expect.anything()); - }); - - it("loads async options and ignores multiple", async () => { - const fetchData = vi - .fn() - .mockResolvedValue([{ label: "Category", value: "1" }]); - const { container } = renderWithClient( - , - ); - - await waitFor(() => { - expect(fetchData).toHaveBeenCalledWith({ search: "" }); - }); - - expect(screen.getByPlaceholderText("Search categories")).toBeTruthy(); - expect(container.querySelector("[data-slot='combobox-chips']")).toBeNull(); - }); - - it("uses placeholder as async search placeholder fallback", async () => { - const fetchData = vi.fn().mockResolvedValue([]); - - renderWithClient( - , - ); - - await waitFor(() => { - expect(fetchData).toHaveBeenCalledWith({ search: "" }); - }); - - expect(screen.getByPlaceholderText("Search from placeholder")).toBeTruthy(); - }); - - it("forwards async input changes without refetching immediately", async () => { - const fetchData = vi.fn().mockResolvedValue([]); - const onInputValueChange = vi.fn(); - - renderWithClient( - , - ); - - await waitFor(() => { - expect(fetchData).toHaveBeenCalledWith({ search: "" }); - }); - - const input = screen.getByPlaceholderText("Search categories"); - - fireEvent.input(input, { - target: { - value: "cat", - }, - }); - - expect(onInputValueChange).toHaveBeenCalledWith("cat", expect.anything()); - expect(fetchData).not.toHaveBeenCalledWith({ search: "cat" }); - }); -}); diff --git a/packages/vitnode/src/components/form/fields/combobox.tsx b/packages/vitnode/src/components/form/fields/combobox.tsx index 1295ef1d2..f52319931 100644 --- a/packages/vitnode/src/components/form/fields/combobox.tsx +++ b/packages/vitnode/src/components/form/fields/combobox.tsx @@ -24,7 +24,7 @@ import type { ItemAutoFormComponentProps } from "../auto-form"; import { AutoFormDesc } from "../common/desc"; import { AutoFormLabel } from "../common/label"; -interface ComboboxAsyncItem { +export interface ComboboxAsyncItem { label: string; value: string; } @@ -37,6 +37,24 @@ type AutoFormComboboxProps = ItemAutoFormComponentProps & className?: string; labels?: { label: string; value: string }[]; placeholder?: string; + /** + * Where this field's results are cached, overriding the default of `[id]`. + * + * For a caller that needs to *expire* them from somewhere else: `id` names + * the field, and a key that says what the options are **of** can be matched + * by prefix from the screen that just changed them. + */ + queryKey?: readonly unknown[]; + /** + * The chip one selected item becomes, for a `multiple` async field. + * + * Defaults to its label. The people picker passes a face and a handle, which + * is the whole difference between a chip that says "Ada Lovelace" and one + * you can recognise at a glance. + */ + renderChip?: (item: ComboboxAsyncItem) => React.ReactNode; + /** The row one item becomes in the list. Defaults to its label. */ + renderItem?: (item: ComboboxAsyncItem) => React.ReactNode; showClear?: boolean; } & ( | { @@ -56,6 +74,10 @@ export const AutoFormCombobox = ({ field, // eslint-disable-next-line @typescript-eslint/no-unused-vars itemParams, + // Only the language-aware inputs implement this - dropped here so it never + // lands on the DOM element the rest props spread into. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + multiLang, description, placeholder, otherProps, @@ -69,17 +91,24 @@ export const AutoFormCombobox = ({ showClear, fetchData, id, + queryKey, searchPlaceholder, filter, + renderChip, + renderItem, ...props }: AutoFormComboboxProps) => { const t = useTranslations("core.global"); const anchor = useComboboxAnchor(); const isAsync = typeof fetchData === "function"; - const isMultiple = !isAsync && multiple; + // Async **and** multiple: the value is then a list of `{ label, value }` + // items rather than a list of strings, because a server-searched option + // carries its own label - the list it came from is a search result, not a + // fixed set the chips could look a name up in. + const isMultiple = multiple; const [search, setSearch] = React.useState(""); const { data, isLoading } = useQuery({ - queryKey: [id ?? "combobox", { search }], + queryKey: [...(queryKey ?? [id ?? "combobox"]), { search }], queryFn: async () => { if (!fetchData) return []; @@ -97,10 +126,6 @@ export const AutoFormCombobox = ({ ? (searchPlaceholder ?? placeholder ?? t("select_option")) : (placeholder ?? t("select_option")); const getComboboxValue = () => { - if (isAsync) { - return field.value ?? null; - } - if (isMultiple) { return field.value ?? []; } @@ -149,7 +174,7 @@ export const AutoFormCombobox = ({ {(item: ComboboxAsyncItem) => ( - {item.label} + {renderItem ? renderItem(item) : item.label} )} @@ -213,14 +238,40 @@ export const AutoFormCombobox = ({ <> - {values => ( + {(values: (ComboboxAsyncItem | string)[]) => ( <> - {values.map((value: string) => ( - - {labels.find(l => l.value === value)?.label ?? value} - - ))} - + {values.map(value => { + // A sync field's value is the option string and its label + // is looked up; an async one carries its own. + const item = + typeof value === "string" + ? { + label: + labels.find(l => l.value === value)?.label ?? + value, + value, + } + : value; + + return ( + + {renderChip ? renderChip(item) : item.label} + + ); + })} + )} diff --git a/packages/vitnode/src/components/form/fields/date-time.tsx b/packages/vitnode/src/components/form/fields/date-time.tsx new file mode 100644 index 000000000..026cec39c --- /dev/null +++ b/packages/vitnode/src/components/form/fields/date-time.tsx @@ -0,0 +1,75 @@ +import type React from "react"; + +import { FormControl, FormMessage } from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; + +import type { ItemAutoFormComponentProps } from "../auto-form"; + +import { AutoFormDesc } from "../common/desc"; +import { AutoFormLabel } from "../common/label"; + +/** `2026-08-02T10:00:00.000Z` -> `2026-08-02T10:00`, what the input expects. */ +const toInputValue = (value: unknown): string => { + if (typeof value !== "string" || value === "") return ""; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return ""; + + const pad = (part: number) => String(part).padStart(2, "0"); + + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`; +}; + +/** + * A date and time field backed by the platform's `datetime-local` input. + * + * The form value is an ISO 8601 string (or `null` for a nullable field), which + * is exactly what the generated API accepts - Zod v4 cannot turn `z.date()` + * into JSON Schema, and `AutoForm` runs `z.toJSONSchema` on every schema. + */ +export const AutoFormDateTime = ({ + label, + labelRight, + description, + field, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + itemParams, + // Only the language-aware inputs implement this - dropped here so it never + // lands on the DOM element the rest props spread into. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + multiLang, + otherProps: { isOptional }, + ...props +}: ItemAutoFormComponentProps & + Omit, "type" | "value">) => { + return ( + <> + {!!label && ( + + {label} + + )} + + + { + field.onBlur(); + props.onBlur?.(event); + }} + onChange={event => { + const { value } = event.target; + // An emptied input means "no value" - `null` when the field allows + // it, otherwise an empty string so validation reports it. + field.onChange(value === "" ? null : new Date(value).toISOString()); + props.onChange?.(event); + }} + type="datetime-local" + value={toInputValue(field.value)} + {...props} + /> + + + {!!description && {description}} + + + ); +}; diff --git a/packages/vitnode/src/components/form/fields/editor.test.tsx b/packages/vitnode/src/components/form/fields/editor.test.tsx deleted file mode 100644 index 252796926..000000000 --- a/packages/vitnode/src/components/form/fields/editor.test.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { type FieldValues, useForm } from "react-hook-form"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -import { LanguagesProvider } from "@/components/languages-provider"; -import { Form, FormField } from "@/components/ui/form"; - -import { AutoFormEditor } from "./editor"; - -vi.mock("next-intl", () => ({ - useLocale: () => "en", - useTranslations: () => (key: string) => key, -})); - -vi.mock("@/components/ui/editor", () => ({ - Editor: ({ - value, - onChange, - onBlur, - ...props - }: { - onBlur?: () => void; - onChange?: (value: string) => void; - value?: string; - }) => ( -