diff --git a/.gitea/workflows/build-next.yml b/.gitea/workflows/build-next.yml index e918a0e..bac982d 100644 --- a/.gitea/workflows/build-next.yml +++ b/.gitea/workflows/build-next.yml @@ -1,56 +1,131 @@ -name: Build and Push Next App +name: Build and Deploy Next App on: push: - branches: [main] + branches: + - main paths: - - 'apps/**' + - 'apps/next/**' - 'packages/**' - 'tools/**' - 'scripts/**' - 'docker/**' - '.gitea/workflows/build-next.yml' - - '.infisical.json' - 'package.json' - 'bun.lock' - 'turbo.json' +# One production deploy at a time; a newer push waits rather than racing. +concurrency: + group: build-next-production + cancel-in-progress: false + +# Production env comes from the Infisical `staging` environment. Each job logs +# in as the read-only `convexmonorepo-ci` machine identity and writes the env to a +# temp file (scripts/ci-export-env.mjs), so no prod values live in Gitea. jobs: + # Quality gate: workspace lint (sherif), eslint, typecheck and the unit, + # integration and component suites must pass before anything touches + # production. The local-stack e2e smoke check is skipped in CI. quality: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 with: - bun-version: 1.3.10 - - run: bun install --frozen-lockfile - - name: Lint, typecheck, and test + bun-version: 1.3.14 + + - name: Install dependencies + run: ./scripts/ci-install + + - name: Lint and typecheck env: - DOTENV_PROD: ${{ secrets.DOTENV_PROD }} + INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_CLIENT_ID }} + INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} run: | env_file="$(mktemp)" trap 'rm -f "$env_file"' EXIT - printf '%s\n' "$DOTENV_PROD" > "$env_file" + node scripts/ci-export-env.mjs staging > "$env_file" bunx dotenv -e "$env_file" -- env NODE_ENV=test SKIP_E2E=1 bun run ci:check - build-next: + # Pushes Convex functions and schema to prod before the image that depends on + # them is built. The env file must carry CONVEX_SELF_HOSTED_URL and + # CONVEX_SELF_HOSTED_ADMIN_KEY. Codegen is disabled: _generated is committed. + convex-deploy: needs: [quality] runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v4 - - name: Log in to container registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.gbrown.org -u "${{ secrets.REGISTRY_USER }}" --password-stdin - - name: Build image + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.14 + + - name: Install dependencies + run: ./scripts/ci-install + + - name: Deploy Convex functions env: - DOTENV_PROD: ${{ secrets.DOTENV_PROD }} + INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_CLIENT_ID }} + INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} run: | env_file="$(mktemp)" trap 'rm -f "$env_file"' EXIT - printf '%s\n' "$DOTENV_PROD" > "$env_file" + node scripts/ci-export-env.mjs staging > "$env_file" + cd packages/backend + bunx convex deploy --env-file "$env_file" --codegen disable --typecheck disable + + build-next: + needs: [convex-deploy] + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to container registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.gbrown.org -u "${{ secrets.REGISTRY_USER }}" --password-stdin + + - name: Build app using project script + env: + INFISICAL_CLIENT_ID: ${{ secrets.INFISICAL_CLIENT_ID }} + INFISICAL_CLIENT_SECRET: ${{ secrets.INFISICAL_CLIENT_SECRET }} + run: | + env_file="$(mktemp)" + trap 'rm -f "$env_file"' EXIT + node scripts/ci-export-env.mjs staging > "$env_file" CI_ENV_FILE="$env_file" ./scripts/build-next-app staging - - name: Tag and push image + + - name: Tag built image run: | docker tag convexmonorepo-next:latest git.gbrown.org/gib/convexmonorepo-next:${{ gitea.sha }} docker tag convexmonorepo-next:latest git.gbrown.org/gib/convexmonorepo-next:latest + + - name: Push image tags + run: | docker push git.gbrown.org/gib/convexmonorepo-next:${{ gitea.sha }} docker push git.gbrown.org/gib/convexmonorepo-next:latest + + # Asks the VPS to pull the new image and restart podman-convexmonorepo.service + # through the forced-command deploy key (~/Server/AGENTS.md ยง15). A + # failure only warns: the image is pushed and the midnight + # podman-update.timer deploys it anyway. + - name: Trigger immediate deploy + run: | + install -m600 /dev/null "$HOME/.deploy_key" + printf '%s\n' "${{ secrets.DEPLOY_SSH_KEY }}" > "$HOME/.deploy_key" + ssh -i "$HOME/.deploy_key" \ + -o BatchMode=yes \ + -o StrictHostKeyChecking=accept-new \ + -o ConnectTimeout=15 \ + gib@192.168.2.2 convexmonorepo \ + || echo "::warning::deploy trigger failed; nightly sweep will deploy instead" + rm -f "$HOME/.deploy_key" diff --git a/.gitignore b/.gitignore index 71c6ac5..17d259b 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,6 @@ apps/next/playwright-report/ # tanstack .tanstack + +# local command flags (machine-specific, e.g. which Infisical account to use) +scripts/flags/ diff --git a/.infisical.json b/.infisical.json index 96c2371..d5b4135 100644 --- a/.infisical.json +++ b/.infisical.json @@ -1,5 +1,6 @@ { "workspaceId": "b2760150-c581-4e0d-acb5-b4da51cee58d", "defaultEnvironment": "", - "gitBranchToEnvironmentMapping": null + "gitBranchToEnvironmentMapping": null, + "domain": "https://infisical.gbrown.org/api" } diff --git a/AGENTS.md b/AGENTS.md index 690fba2..86a4919 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,8 +7,8 @@ - `packages/backend/convex`: self-hosted Convex functions, schema, and auth. - `packages/ui`: shared shadcn-based UI components. - `tools`: shared ESLint, Prettier, Tailwind, TypeScript, and Vitest config. -- Local development uses host-run apps, local Convex on ports 3210/3211, its - own data volume, and Payload Postgres on port 5432. Convex does not use +- Local development uses host-run apps, local Convex on ports 3240/3241, its + own data volume, and Payload Postgres on port 5436. Convex does not use Postgres unless a cloned project explicitly enables the commented option. ## Protected and generated files @@ -29,8 +29,17 @@ - Run `infisical login` and `infisical init` before local development. - Machine-generated values belong in `.local/.generated.env`; never put the generated Convex admin key in shared Infisical. -- CI uses Gitea-injected secrets or `CI_ENV_FILE` and must not call Infisical. -- App code imports validated variables from `@/env`, never `process.env`. +- CI reads `staging` through `scripts/ci-export-env.mjs`, logged in as the + read-only `convexmonorepo-ci` machine identity (Gitea secrets + `INFISICAL_CLIENT_ID` and `INFISICAL_CLIENT_SECRET`). `scripts/export-env` + refuses to run in CI. +- `staging` holds the production values that CI builds and deploys with. +- If you are logged in to more than one Infisical account, put + `INFISICAL_EMAIL=...` in `scripts/flags/infisical.env` (gitignored) and the + scripts switch the CLI to that account before exporting. +- Server code imports validated variables from `@/env`. Client components and + `instrumentation-client.ts` import from `@/publicEnv`, which keeps zod out of + the browser bundle. Never read `process.env` elsewhere. - Add cache-relevant variables to `turbo.json` `globalEnv`. ## Code and dependency rules @@ -52,10 +61,12 @@ bun dev:next # host Next + deploy/watch local Convex functions bun db:down # stop and preserve both data volumes bun db:down:wipe # remove both volumes, generated admin key, and seed marker bun db:sync:payload # refresh and apply the local Payload seed snapshot +bun db:sync-env # re-push Infisical dev values into the local Convex env +bun dev:kill # stop dev servers that survived a Ctrl-C ``` -Local URLs: app `http://localhost:3000`, Convex `http://localhost:3210`, -dashboard `http://localhost:6791`, Payload Postgres `localhost:5432`. +Local URLs: app `http://localhost:3000`, Convex `http://localhost:3240`, +dashboard `http://localhost:6794`, Payload Postgres `localhost:5436`. Use `INFISICAL_ENV=staging bun dev:next` for staging services. `db:sync:payload` is an explicit, destructive local operation. It validates a @@ -65,6 +76,37 @@ only under `.local/`, and never changes Convex or production data. Normal when `.local/payload-seed-state.env` is absent or stale. Do not store seed markers in Payload Postgres; schema push treats unmanaged tables as drift. +## Payload schema changes + +Payload schema changes ship as migrations in `apps/next/src/migrations/`. +Locally, Payload pushes schema edits straight into the localhost database. When +a change is ready, run `bun with-env bunx payload migrate:create ` from +`apps/next` and commit the generated files. Production runs pending migrations +on startup (`prodMigrations` in `payload.config.ts`). Push only runs against a +database on `localhost`: a push leaves a `dev` row in `payload_migrations`, and +Payload's startup migration stops on a confirmation prompt when it finds one. +For the same reason, a local production build against the local dev database +hangs; point it at a database without that row. + +## Analytics and bundle size + +- Plausible (`NEXT_PUBLIC_PLAUSIBLE_SRC`, the per-site script URL) loads through + a same-origin proxy under `/pa`, clear of Payload's `/api` routes. PostHog + (`NEXT_PUBLIC_POSTHOG_KEY`, `NEXT_PUBLIC_POSTHOG_HOST`) loads after the page + is idle through `/ingest`. Both stay off when their env is unset. +- Session replay comes from PostHog. Sentry carries errors and traces only. +- `packages/ui/src/index.tsx` holds re-exports only (`cn` and `ccn` live in + `utils.ts`). With `optimizePackageImports` in `next.config.js`, each page + loads only the UI modules it imports. Declaring anything in the index breaks + that. + +## Deployment + +A push to `main` runs `.gitea/workflows/build-next.yml`: the quality gate, then +`convex deploy` to production, then the image build and push, then +`ssh gib@192.168.2.2 convexmonorepo` (the forced-command deploy on the VPS). A +push to `main` is a production deploy. + ## Validation Use `bun typecheck`, never a production build for routine validation. The full diff --git a/README.md b/README.md index 8760230..682b7c2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ Payload CMS, shared UI/config packages, Vitest, and Docker deployment. ## Local setup -Requirements: Bun 1.3.10, Docker or Podman, Node 22, and the Infisical CLI. +Requirements: Bun 1.3.14, Docker or Podman, Node 22, the Infisical CLI, `jq`, and +`flock`. ```sh bun install --frozen-lockfile @@ -22,9 +23,9 @@ files. Select staging with `INFISICAL_ENV=staging bun dev:next`. Local services: - Next.js: `http://localhost:3000` -- Convex API: `http://localhost:3210` -- Convex dashboard: `http://localhost:6791` -- Payload Postgres: `localhost:5432` +- Convex API: `http://localhost:3240` +- Convex dashboard: `http://localhost:6794` +- Payload Postgres: `localhost:5436` Next and Expo run on the host. Payload uses the local Postgres database. Convex uses a separate self-hosted data volume and does not use Postgres by default. @@ -54,7 +55,8 @@ Convex URL with the development host's LAN address when testing Expo on-device. - Local `dev` and `staging`: Infisical. - Generated local state: `.local/.generated.env`. -- CI/CD: Gitea `DOTENV_PROD`, materialized only as a temporary runner file. +- CI/CD: Infisical `staging`, read by the read-only `convexmonorepo-ci` machine + identity and materialized only as a temporary runner file. - Docker compilation: explicit Compose build args; `.env*` stays outside the image context. @@ -87,6 +89,14 @@ Shared dependency versions belong in root catalogs. Edit the root catalog, run Production Compose retains the self-hosted Convex backend/dashboard and accepts an external Payload `PAYLOAD_DB_URL`. Its commented Postgres service remains an -optional Payload database. Gitea runs the quality gate first, builds the Next -image from a temporary Gitea-secret env file, then pushes SHA and `latest` tags. -CI never installs or invokes Infisical. +optional Payload database. + +A push to `main` runs the quality gate, deploys the Convex functions, builds the +Next image, pushes SHA and `latest` tags, and asks the VPS to redeploy +`podman-convexmonorepo.service` over the forced-command deploy key. If that last +step fails, the nightly update timer deploys the image instead. Each job reads +Infisical `staging` through `scripts/ci-export-env.mjs` as the `convexmonorepo-ci` +machine identity (Gitea secrets `INFISICAL_CLIENT_ID` and +`INFISICAL_CLIENT_SECRET`). + +Payload schema changes ship as migrations; see `AGENTS.md`. diff --git a/apps/next/eslint.config.ts b/apps/next/eslint.config.ts index 37f3d3d..7bcbd94 100644 --- a/apps/next/eslint.config.ts +++ b/apps/next/eslint.config.ts @@ -6,7 +6,7 @@ import { reactConfig } from '@gib/eslint-config/react'; export default defineConfig( { - ignores: ['.next/**', 'payload-types.ts'], + ignores: ['.next/**', 'payload-types.ts', 'src/migrations/**'], }, baseConfig, reactConfig, diff --git a/apps/next/src/migrations/20261001_183341_baseline.json b/apps/next/src/migrations/20261001_183341_baseline.json new file mode 100644 index 0000000..0fc8719 --- /dev/null +++ b/apps/next/src/migrations/20261001_183341_baseline.json @@ -0,0 +1,4991 @@ +{ + "version": "7", + "dialect": "postgresql", + "tables": { + "public.users_sessions": { + "name": "users_sessions", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "users_sessions_order_idx": { + "name": "users_sessions_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_sessions_parent_id_idx": { + "name": "users_sessions_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_sessions_parent_id_fk": { + "name": "users_sessions_parent_id_fk", + "tableFrom": "users_sessions", + "tableTo": "users", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "email": { + "name": "email", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "reset_password_token": { + "name": "reset_password_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "reset_password_expiration": { + "name": "reset_password_expiration", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "salt": { + "name": "salt", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "hash": { + "name": "hash", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "reset_password_requested_at": { + "name": "reset_password_requested_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "login_attempts": { + "name": "login_attempts", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "lock_until": { + "name": "lock_until", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_updated_at_idx": { + "name": "users_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_kv": { + "name": "payload_kv", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "payload_kv_key_idx": { + "name": "payload_kv_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_locked_documents": { + "name": "payload_locked_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "global_slug": { + "name": "global_slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_locked_documents_global_slug_idx": { + "name": "payload_locked_documents_global_slug_idx", + "columns": [ + { + "expression": "global_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_updated_at_idx": { + "name": "payload_locked_documents_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_created_at_idx": { + "name": "payload_locked_documents_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_locked_documents_rels": { + "name": "payload_locked_documents_rels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "payload_locked_documents_rels_order_idx": { + "name": "payload_locked_documents_rels_order_idx", + "columns": [ + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_parent_idx": { + "name": "payload_locked_documents_rels_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_path_idx": { + "name": "payload_locked_documents_rels_path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_locked_documents_rels_users_id_idx": { + "name": "payload_locked_documents_rels_users_id_idx", + "columns": [ + { + "expression": "users_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "payload_locked_documents_rels_parent_fk": { + "name": "payload_locked_documents_rels_parent_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "payload_locked_documents", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_locked_documents_rels_users_fk": { + "name": "payload_locked_documents_rels_users_fk", + "tableFrom": "payload_locked_documents_rels", + "tableTo": "users", + "columnsFrom": ["users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_preferences": { + "name": "payload_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_preferences_key_idx": { + "name": "payload_preferences_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_updated_at_idx": { + "name": "payload_preferences_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_created_at_idx": { + "name": "payload_preferences_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_preferences_rels": { + "name": "payload_preferences_rels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "users_id": { + "name": "users_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "payload_preferences_rels_order_idx": { + "name": "payload_preferences_rels_order_idx", + "columns": [ + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_rels_parent_idx": { + "name": "payload_preferences_rels_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_rels_path_idx": { + "name": "payload_preferences_rels_path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_preferences_rels_users_id_idx": { + "name": "payload_preferences_rels_users_id_idx", + "columns": [ + { + "expression": "users_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "payload_preferences_rels_parent_fk": { + "name": "payload_preferences_rels_parent_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "payload_preferences", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "payload_preferences_rels_users_fk": { + "name": "payload_preferences_rels_users_fk", + "tableFrom": "payload_preferences_rels", + "tableTo": "users", + "columnsFrom": ["users_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payload_migrations": { + "name": "payload_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "batch": { + "name": "batch", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "payload_migrations_updated_at_idx": { + "name": "payload_migrations_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "payload_migrations_created_at_idx": { + "name": "payload_migrations_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_hero_highlights": { + "name": "landing_page_blocks_hero_highlights", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_hero_highlights_order_idx": { + "name": "landing_page_blocks_hero_highlights_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_hero_highlights_parent_id_idx": { + "name": "landing_page_blocks_hero_highlights_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_hero_highlights_parent_id_fk": { + "name": "landing_page_blocks_hero_highlights_parent_id_fk", + "tableFrom": "landing_page_blocks_hero_highlights", + "tableTo": "landing_page_blocks_hero", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_hero": { + "name": "landing_page_blocks_hero", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "badge_emoji": { + "name": "badge_emoji", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'๐Ÿš€'" + }, + "badge_text": { + "name": "badge_text", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Production-ready monorepo template'" + }, + "heading_prefix": { + "name": "heading_prefix", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Build Full-Stack Apps with'" + }, + "heading_highlight": { + "name": "heading_highlight", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'convex-monorepo'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A Turborepo starter with Next.js, Expo, and self-hosted Convex. Ship web and mobile apps faster with shared code, type-safe backend, and complete control over your infrastructure.'" + }, + "primary_cta_label": { + "name": "primary_cta_label", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'View Source Code'" + }, + "primary_cta_url": { + "name": "primary_cta_url", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'https://git.gbrown.org/gib/convex-monorepo'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_hero_order_idx": { + "name": "landing_page_blocks_hero_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_hero_parent_id_idx": { + "name": "landing_page_blocks_hero_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_hero_path_idx": { + "name": "landing_page_blocks_hero_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_hero_parent_id_fk": { + "name": "landing_page_blocks_hero_parent_id_fk", + "tableFrom": "landing_page_blocks_hero", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_logo_cloud_items": { + "name": "landing_page_blocks_logo_cloud_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "accent": { + "name": "accent", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_logo_cloud_items_order_idx": { + "name": "landing_page_blocks_logo_cloud_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_logo_cloud_items_parent_id_idx": { + "name": "landing_page_blocks_logo_cloud_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_logo_cloud_items_parent_id_fk": { + "name": "landing_page_blocks_logo_cloud_items_parent_id_fk", + "tableFrom": "landing_page_blocks_logo_cloud_items", + "tableTo": "landing_page_blocks_logo_cloud", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_logo_cloud": { + "name": "landing_page_blocks_logo_cloud", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Designed For Modern Product Teams'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_logo_cloud_order_idx": { + "name": "landing_page_blocks_logo_cloud_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_logo_cloud_parent_id_idx": { + "name": "landing_page_blocks_logo_cloud_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_logo_cloud_path_idx": { + "name": "landing_page_blocks_logo_cloud_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_logo_cloud_parent_id_fk": { + "name": "landing_page_blocks_logo_cloud_parent_id_fk", + "tableFrom": "landing_page_blocks_logo_cloud", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_features_items": { + "name": "landing_page_blocks_features_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_features_items_order_idx": { + "name": "landing_page_blocks_features_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_features_items_parent_id_idx": { + "name": "landing_page_blocks_features_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_features_items_parent_id_fk": { + "name": "landing_page_blocks_features_items_parent_id_fk", + "tableFrom": "landing_page_blocks_features_items", + "tableTo": "landing_page_blocks_features", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_features": { + "name": "landing_page_blocks_features", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Everything You Need to Ship Fast'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A complete monorepo template with all the tools and patterns you need for production-ready applications.'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_features_order_idx": { + "name": "landing_page_blocks_features_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_features_parent_id_idx": { + "name": "landing_page_blocks_features_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_features_path_idx": { + "name": "landing_page_blocks_features_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_features_parent_id_fk": { + "name": "landing_page_blocks_features_parent_id_fk", + "tableFrom": "landing_page_blocks_features", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_stats_items": { + "name": "landing_page_blocks_stats_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_stats_items_order_idx": { + "name": "landing_page_blocks_stats_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_stats_items_parent_id_idx": { + "name": "landing_page_blocks_stats_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_stats_items_parent_id_fk": { + "name": "landing_page_blocks_stats_items_parent_id_fk", + "tableFrom": "landing_page_blocks_stats_items", + "tableTo": "landing_page_blocks_stats", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_stats": { + "name": "landing_page_blocks_stats", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Built To Demo Well'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'These are the kinds of outcomes you can point to when showing the template to clients.'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_stats_order_idx": { + "name": "landing_page_blocks_stats_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_stats_parent_id_idx": { + "name": "landing_page_blocks_stats_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_stats_path_idx": { + "name": "landing_page_blocks_stats_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_stats_parent_id_fk": { + "name": "landing_page_blocks_stats_parent_id_fk", + "tableFrom": "landing_page_blocks_stats", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_tech_stack_categories_technologies": { + "name": "landing_page_blocks_tech_stack_categories_technologies", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_tech_stack_categories_technologies_order_idx": { + "name": "landing_page_blocks_tech_stack_categories_technologies_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_tech_stack_categories_technologies_parent_id_idx": { + "name": "landing_page_blocks_tech_stack_categories_technologies_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_tech_stack_categories_technologies_parent_id_fk": { + "name": "landing_page_blocks_tech_stack_categories_technologies_parent_id_fk", + "tableFrom": "landing_page_blocks_tech_stack_categories_technologies", + "tableTo": "landing_page_blocks_tech_stack_categories", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_tech_stack_categories": { + "name": "landing_page_blocks_tech_stack_categories", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_tech_stack_categories_order_idx": { + "name": "landing_page_blocks_tech_stack_categories_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_tech_stack_categories_parent_id_idx": { + "name": "landing_page_blocks_tech_stack_categories_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_tech_stack_categories_parent_id_fk": { + "name": "landing_page_blocks_tech_stack_categories_parent_id_fk", + "tableFrom": "landing_page_blocks_tech_stack_categories", + "tableTo": "landing_page_blocks_tech_stack", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_tech_stack": { + "name": "landing_page_blocks_tech_stack", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Modern Tech Stack'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Built with the latest and greatest tools for maximum productivity and performance.'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_tech_stack_order_idx": { + "name": "landing_page_blocks_tech_stack_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_tech_stack_parent_id_idx": { + "name": "landing_page_blocks_tech_stack_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_tech_stack_path_idx": { + "name": "landing_page_blocks_tech_stack_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_tech_stack_parent_id_fk": { + "name": "landing_page_blocks_tech_stack_parent_id_fk", + "tableFrom": "landing_page_blocks_tech_stack", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_testimonials_items": { + "name": "landing_page_blocks_testimonials_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "quote": { + "name": "quote", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "company": { + "name": "company", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "avatar_emoji": { + "name": "avatar_emoji", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_testimonials_items_order_idx": { + "name": "landing_page_blocks_testimonials_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_testimonials_items_parent_id_idx": { + "name": "landing_page_blocks_testimonials_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_testimonials_items_parent_id_fk": { + "name": "landing_page_blocks_testimonials_items_parent_id_fk", + "tableFrom": "landing_page_blocks_testimonials_items", + "tableTo": "landing_page_blocks_testimonials", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_testimonials": { + "name": "landing_page_blocks_testimonials", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'What Clients Actually Care About'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'The polished part is not just the frontend. Editors can shape the story, launch updates fast, and preview changes before they go live.'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_testimonials_order_idx": { + "name": "landing_page_blocks_testimonials_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_testimonials_parent_id_idx": { + "name": "landing_page_blocks_testimonials_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_testimonials_path_idx": { + "name": "landing_page_blocks_testimonials_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_testimonials_parent_id_fk": { + "name": "landing_page_blocks_testimonials_parent_id_fk", + "tableFrom": "landing_page_blocks_testimonials", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_pricing_plans_features": { + "name": "landing_page_blocks_pricing_plans_features", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_pricing_plans_features_order_idx": { + "name": "landing_page_blocks_pricing_plans_features_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_pricing_plans_features_parent_id_idx": { + "name": "landing_page_blocks_pricing_plans_features_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_pricing_plans_features_parent_id_fk": { + "name": "landing_page_blocks_pricing_plans_features_parent_id_fk", + "tableFrom": "landing_page_blocks_pricing_plans_features", + "tableTo": "landing_page_blocks_pricing_plans", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_pricing_plans": { + "name": "landing_page_blocks_pricing_plans", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "billing_period": { + "name": "billing_period", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "badge": { + "name": "badge", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "is_highlighted": { + "name": "is_highlighted", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cta_label": { + "name": "cta_label", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "cta_url": { + "name": "cta_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_pricing_plans_order_idx": { + "name": "landing_page_blocks_pricing_plans_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_pricing_plans_parent_id_idx": { + "name": "landing_page_blocks_pricing_plans_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_pricing_plans_parent_id_fk": { + "name": "landing_page_blocks_pricing_plans_parent_id_fk", + "tableFrom": "landing_page_blocks_pricing_plans", + "tableTo": "landing_page_blocks_pricing", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_pricing": { + "name": "landing_page_blocks_pricing", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A Great Way To Pitch Productized Delivery'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Use pricing blocks to show packaged service tiers, subscriptions, or implementation options right inside the landing page builder.'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_pricing_order_idx": { + "name": "landing_page_blocks_pricing_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_pricing_parent_id_idx": { + "name": "landing_page_blocks_pricing_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_pricing_path_idx": { + "name": "landing_page_blocks_pricing_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_pricing_parent_id_fk": { + "name": "landing_page_blocks_pricing_parent_id_fk", + "tableFrom": "landing_page_blocks_pricing", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_faq_items": { + "name": "landing_page_blocks_faq_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "question": { + "name": "question", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "answer": { + "name": "answer", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_faq_items_order_idx": { + "name": "landing_page_blocks_faq_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_faq_items_parent_id_idx": { + "name": "landing_page_blocks_faq_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_faq_items_parent_id_fk": { + "name": "landing_page_blocks_faq_items_parent_id_fk", + "tableFrom": "landing_page_blocks_faq_items", + "tableTo": "landing_page_blocks_faq", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_faq": { + "name": "landing_page_blocks_faq", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Questions You Will Probably Get In A Demo'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A good FAQ block makes the editor feel complete and gives clients another obvious content type they can manage themselves.'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_faq_order_idx": { + "name": "landing_page_blocks_faq_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_faq_parent_id_idx": { + "name": "landing_page_blocks_faq_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_faq_path_idx": { + "name": "landing_page_blocks_faq_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_faq_parent_id_fk": { + "name": "landing_page_blocks_faq_parent_id_fk", + "tableFrom": "landing_page_blocks_faq", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page_blocks_cta": { + "name": "landing_page_blocks_cta", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "varchar", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Ready to Build Something Amazing?'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Clone the repository and start building your next project with everything pre-configured.'" + }, + "command_label": { + "name": "command_label", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Quick Start'" + }, + "command": { + "name": "command", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'git clone https://git.gbrown.org/gib/convex-monorepo.git\ncd convex-monorepo\nbun i'" + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page_blocks_cta_order_idx": { + "name": "landing_page_blocks_cta_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_cta_parent_id_idx": { + "name": "landing_page_blocks_cta_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "landing_page_blocks_cta_path_idx": { + "name": "landing_page_blocks_cta_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "landing_page_blocks_cta_parent_id_fk": { + "name": "landing_page_blocks_cta_parent_id_fk", + "tableFrom": "landing_page_blocks_cta", + "tableTo": "landing_page", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.landing_page": { + "name": "landing_page", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "_status": { + "name": "_status", + "type": "enum_landing_page_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'draft'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "landing_page__status_idx": { + "name": "landing_page__status_idx", + "columns": [ + { + "expression": "_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_hero_highlights": { + "name": "_landing_page_v_blocks_hero_highlights", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_hero_highlights_order_idx": { + "name": "_landing_page_v_blocks_hero_highlights_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_hero_highlights_parent_id_idx": { + "name": "_landing_page_v_blocks_hero_highlights_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_hero_highlights_parent_id_fk": { + "name": "_landing_page_v_blocks_hero_highlights_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_hero_highlights", + "tableTo": "_landing_page_v_blocks_hero", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_hero": { + "name": "_landing_page_v_blocks_hero", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "badge_emoji": { + "name": "badge_emoji", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'๐Ÿš€'" + }, + "badge_text": { + "name": "badge_text", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Production-ready monorepo template'" + }, + "heading_prefix": { + "name": "heading_prefix", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Build Full-Stack Apps with'" + }, + "heading_highlight": { + "name": "heading_highlight", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'convex-monorepo'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A Turborepo starter with Next.js, Expo, and self-hosted Convex. Ship web and mobile apps faster with shared code, type-safe backend, and complete control over your infrastructure.'" + }, + "primary_cta_label": { + "name": "primary_cta_label", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'View Source Code'" + }, + "primary_cta_url": { + "name": "primary_cta_url", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'https://git.gbrown.org/gib/convex-monorepo'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_hero_order_idx": { + "name": "_landing_page_v_blocks_hero_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_hero_parent_id_idx": { + "name": "_landing_page_v_blocks_hero_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_hero_path_idx": { + "name": "_landing_page_v_blocks_hero_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_hero_parent_id_fk": { + "name": "_landing_page_v_blocks_hero_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_hero", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_logo_cloud_items": { + "name": "_landing_page_v_blocks_logo_cloud_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "accent": { + "name": "accent", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_logo_cloud_items_order_idx": { + "name": "_landing_page_v_blocks_logo_cloud_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_logo_cloud_items_parent_id_idx": { + "name": "_landing_page_v_blocks_logo_cloud_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_logo_cloud_items_parent_id_fk": { + "name": "_landing_page_v_blocks_logo_cloud_items_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_logo_cloud_items", + "tableTo": "_landing_page_v_blocks_logo_cloud", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_logo_cloud": { + "name": "_landing_page_v_blocks_logo_cloud", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Designed For Modern Product Teams'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_logo_cloud_order_idx": { + "name": "_landing_page_v_blocks_logo_cloud_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_logo_cloud_parent_id_idx": { + "name": "_landing_page_v_blocks_logo_cloud_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_logo_cloud_path_idx": { + "name": "_landing_page_v_blocks_logo_cloud_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_logo_cloud_parent_id_fk": { + "name": "_landing_page_v_blocks_logo_cloud_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_logo_cloud", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_features_items": { + "name": "_landing_page_v_blocks_features_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_features_items_order_idx": { + "name": "_landing_page_v_blocks_features_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_features_items_parent_id_idx": { + "name": "_landing_page_v_blocks_features_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_features_items_parent_id_fk": { + "name": "_landing_page_v_blocks_features_items_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_features_items", + "tableTo": "_landing_page_v_blocks_features", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_features": { + "name": "_landing_page_v_blocks_features", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Everything You Need to Ship Fast'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A complete monorepo template with all the tools and patterns you need for production-ready applications.'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_features_order_idx": { + "name": "_landing_page_v_blocks_features_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_features_parent_id_idx": { + "name": "_landing_page_v_blocks_features_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_features_path_idx": { + "name": "_landing_page_v_blocks_features_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_features_parent_id_fk": { + "name": "_landing_page_v_blocks_features_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_features", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_stats_items": { + "name": "_landing_page_v_blocks_stats_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_stats_items_order_idx": { + "name": "_landing_page_v_blocks_stats_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_stats_items_parent_id_idx": { + "name": "_landing_page_v_blocks_stats_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_stats_items_parent_id_fk": { + "name": "_landing_page_v_blocks_stats_items_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_stats_items", + "tableTo": "_landing_page_v_blocks_stats", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_stats": { + "name": "_landing_page_v_blocks_stats", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Built To Demo Well'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'These are the kinds of outcomes you can point to when showing the template to clients.'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_stats_order_idx": { + "name": "_landing_page_v_blocks_stats_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_stats_parent_id_idx": { + "name": "_landing_page_v_blocks_stats_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_stats_path_idx": { + "name": "_landing_page_v_blocks_stats_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_stats_parent_id_fk": { + "name": "_landing_page_v_blocks_stats_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_stats", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_tech_stack_categories_technologies": { + "name": "_landing_page_v_blocks_tech_stack_categories_technologies", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_tech_stack_categories_technologies_order_idx": { + "name": "_landing_page_v_blocks_tech_stack_categories_technologies_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_tech_stack_categories_technologies_parent_id_idx": { + "name": "_landing_page_v_blocks_tech_stack_categories_technologies_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_tech_stack_categories_technologies_parent_id_fk": { + "name": "_landing_page_v_blocks_tech_stack_categories_technologies_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_tech_stack_categories_technologies", + "tableTo": "_landing_page_v_blocks_tech_stack_categories", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_tech_stack_categories": { + "name": "_landing_page_v_blocks_tech_stack_categories", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_tech_stack_categories_order_idx": { + "name": "_landing_page_v_blocks_tech_stack_categories_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_tech_stack_categories_parent_id_idx": { + "name": "_landing_page_v_blocks_tech_stack_categories_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_tech_stack_categories_parent_id_fk": { + "name": "_landing_page_v_blocks_tech_stack_categories_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_tech_stack_categories", + "tableTo": "_landing_page_v_blocks_tech_stack", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_tech_stack": { + "name": "_landing_page_v_blocks_tech_stack", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Modern Tech Stack'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Built with the latest and greatest tools for maximum productivity and performance.'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_tech_stack_order_idx": { + "name": "_landing_page_v_blocks_tech_stack_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_tech_stack_parent_id_idx": { + "name": "_landing_page_v_blocks_tech_stack_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_tech_stack_path_idx": { + "name": "_landing_page_v_blocks_tech_stack_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_tech_stack_parent_id_fk": { + "name": "_landing_page_v_blocks_tech_stack_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_tech_stack", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_testimonials_items": { + "name": "_landing_page_v_blocks_testimonials_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "quote": { + "name": "quote", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "company": { + "name": "company", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "avatar_emoji": { + "name": "avatar_emoji", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_testimonials_items_order_idx": { + "name": "_landing_page_v_blocks_testimonials_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_testimonials_items_parent_id_idx": { + "name": "_landing_page_v_blocks_testimonials_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_testimonials_items_parent_id_fk": { + "name": "_landing_page_v_blocks_testimonials_items_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_testimonials_items", + "tableTo": "_landing_page_v_blocks_testimonials", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_testimonials": { + "name": "_landing_page_v_blocks_testimonials", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'What Clients Actually Care About'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'The polished part is not just the frontend. Editors can shape the story, launch updates fast, and preview changes before they go live.'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_testimonials_order_idx": { + "name": "_landing_page_v_blocks_testimonials_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_testimonials_parent_id_idx": { + "name": "_landing_page_v_blocks_testimonials_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_testimonials_path_idx": { + "name": "_landing_page_v_blocks_testimonials_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_testimonials_parent_id_fk": { + "name": "_landing_page_v_blocks_testimonials_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_testimonials", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_pricing_plans_features": { + "name": "_landing_page_v_blocks_pricing_plans_features", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_pricing_plans_features_order_idx": { + "name": "_landing_page_v_blocks_pricing_plans_features_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_pricing_plans_features_parent_id_idx": { + "name": "_landing_page_v_blocks_pricing_plans_features_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_pricing_plans_features_parent_id_fk": { + "name": "_landing_page_v_blocks_pricing_plans_features_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_pricing_plans_features", + "tableTo": "_landing_page_v_blocks_pricing_plans", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_pricing_plans": { + "name": "_landing_page_v_blocks_pricing_plans", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "billing_period": { + "name": "billing_period", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "badge": { + "name": "badge", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "is_highlighted": { + "name": "is_highlighted", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cta_label": { + "name": "cta_label", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "cta_url": { + "name": "cta_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_pricing_plans_order_idx": { + "name": "_landing_page_v_blocks_pricing_plans_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_pricing_plans_parent_id_idx": { + "name": "_landing_page_v_blocks_pricing_plans_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_pricing_plans_parent_id_fk": { + "name": "_landing_page_v_blocks_pricing_plans_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_pricing_plans", + "tableTo": "_landing_page_v_blocks_pricing", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_pricing": { + "name": "_landing_page_v_blocks_pricing", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A Great Way To Pitch Productized Delivery'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Use pricing blocks to show packaged service tiers, subscriptions, or implementation options right inside the landing page builder.'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_pricing_order_idx": { + "name": "_landing_page_v_blocks_pricing_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_pricing_parent_id_idx": { + "name": "_landing_page_v_blocks_pricing_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_pricing_path_idx": { + "name": "_landing_page_v_blocks_pricing_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_pricing_parent_id_fk": { + "name": "_landing_page_v_blocks_pricing_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_pricing", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_faq_items": { + "name": "_landing_page_v_blocks_faq_items", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "question": { + "name": "question", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "answer": { + "name": "answer", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_faq_items_order_idx": { + "name": "_landing_page_v_blocks_faq_items_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_faq_items_parent_id_idx": { + "name": "_landing_page_v_blocks_faq_items_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_faq_items_parent_id_fk": { + "name": "_landing_page_v_blocks_faq_items_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_faq_items", + "tableTo": "_landing_page_v_blocks_faq", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_faq": { + "name": "_landing_page_v_blocks_faq", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Questions You Will Probably Get In A Demo'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'A good FAQ block makes the editor feel complete and gives clients another obvious content type they can manage themselves.'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_faq_order_idx": { + "name": "_landing_page_v_blocks_faq_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_faq_parent_id_idx": { + "name": "_landing_page_v_blocks_faq_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_faq_path_idx": { + "name": "_landing_page_v_blocks_faq_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_faq_parent_id_fk": { + "name": "_landing_page_v_blocks_faq_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_faq", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v_blocks_cta": { + "name": "_landing_page_v_blocks_cta", + "schema": "", + "columns": { + "_order": { + "name": "_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_parent_id": { + "name": "_parent_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "_path": { + "name": "_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "heading": { + "name": "heading", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Ready to Build Something Amazing?'" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Clone the repository and start building your next project with everything pre-configured.'" + }, + "command_label": { + "name": "command_label", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'Quick Start'" + }, + "command": { + "name": "command", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'git clone https://git.gbrown.org/gib/convex-monorepo.git\ncd convex-monorepo\nbun i'" + }, + "_uuid": { + "name": "_uuid", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "block_name": { + "name": "block_name", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_blocks_cta_order_idx": { + "name": "_landing_page_v_blocks_cta_order_idx", + "columns": [ + { + "expression": "_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_cta_parent_id_idx": { + "name": "_landing_page_v_blocks_cta_parent_id_idx", + "columns": [ + { + "expression": "_parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_blocks_cta_path_idx": { + "name": "_landing_page_v_blocks_cta_path_idx", + "columns": [ + { + "expression": "_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "_landing_page_v_blocks_cta_parent_id_fk": { + "name": "_landing_page_v_blocks_cta_parent_id_fk", + "tableFrom": "_landing_page_v_blocks_cta", + "tableTo": "_landing_page_v", + "columnsFrom": ["_parent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public._landing_page_v": { + "name": "_landing_page_v", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version__status": { + "name": "version__status", + "type": "enum__landing_page_v_version_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'draft'" + }, + "version_updated_at": { + "name": "version_updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "version_created_at": { + "name": "version_created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp(3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "latest": { + "name": "latest", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "autosave": { + "name": "autosave", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "_landing_page_v_version_version__status_idx": { + "name": "_landing_page_v_version_version__status_idx", + "columns": [ + { + "expression": "version__status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_created_at_idx": { + "name": "_landing_page_v_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_updated_at_idx": { + "name": "_landing_page_v_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_latest_idx": { + "name": "_landing_page_v_latest_idx", + "columns": [ + { + "expression": "latest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "_landing_page_v_autosave_idx": { + "name": "_landing_page_v_autosave_idx", + "columns": [ + { + "expression": "autosave", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.enum_landing_page_status": { + "name": "enum_landing_page_status", + "schema": "public", + "values": ["draft", "published"] + }, + "public.enum__landing_page_v_version_status": { + "name": "enum__landing_page_v_version_status", + "schema": "public", + "values": ["draft", "published"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "id": "a49a70a0-f700-463b-a2a6-a46414ff2455", + "prevId": "00000000-0000-0000-0000-000000000000" +} diff --git a/apps/next/src/migrations/20261001_183341_baseline.ts b/apps/next/src/migrations/20261001_183341_baseline.ts new file mode 100644 index 0000000..dd2b607 --- /dev/null +++ b/apps/next/src/migrations/20261001_183341_baseline.ts @@ -0,0 +1,710 @@ +import { MigrateDownArgs, MigrateUpArgs, sql } from '@payloadcms/db-postgres'; + +export const up = async ({ + db, + payload, + req, +}: MigrateUpArgs): Promise => { + await db.execute(sql` + CREATE TYPE "public"."enum_landing_page_status" AS ENUM('draft', 'published'); + CREATE TYPE "public"."enum__landing_page_v_version_status" AS ENUM('draft', 'published'); + CREATE TABLE "users_sessions" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "created_at" timestamp(3) with time zone, + "expires_at" timestamp(3) with time zone NOT NULL + ); + + CREATE TABLE "users" ( + "id" serial PRIMARY KEY NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "email" varchar NOT NULL, + "reset_password_token" varchar, + "reset_password_expiration" timestamp(3) with time zone, + "salt" varchar, + "hash" varchar, + "reset_password_requested_at" timestamp(3) with time zone, + "login_attempts" numeric DEFAULT 0, + "lock_until" timestamp(3) with time zone + ); + + CREATE TABLE "payload_kv" ( + "id" serial PRIMARY KEY NOT NULL, + "key" varchar NOT NULL, + "data" jsonb NOT NULL + ); + + CREATE TABLE "payload_locked_documents" ( + "id" serial PRIMARY KEY NOT NULL, + "global_slug" varchar, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL + ); + + CREATE TABLE "payload_locked_documents_rels" ( + "id" serial PRIMARY KEY NOT NULL, + "order" integer, + "parent_id" integer NOT NULL, + "path" varchar NOT NULL, + "users_id" integer + ); + + CREATE TABLE "payload_preferences" ( + "id" serial PRIMARY KEY NOT NULL, + "key" varchar, + "value" jsonb, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL + ); + + CREATE TABLE "payload_preferences_rels" ( + "id" serial PRIMARY KEY NOT NULL, + "order" integer, + "parent_id" integer NOT NULL, + "path" varchar NOT NULL, + "users_id" integer + ); + + CREATE TABLE "payload_migrations" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar, + "batch" numeric, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL + ); + + CREATE TABLE "landing_page_blocks_hero_highlights" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "label" varchar + ); + + CREATE TABLE "landing_page_blocks_hero" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "badge_emoji" varchar DEFAULT '๐Ÿš€', + "badge_text" varchar DEFAULT 'Production-ready monorepo template', + "heading_prefix" varchar DEFAULT 'Build Full-Stack Apps with', + "heading_highlight" varchar DEFAULT 'convex-monorepo', + "description" varchar DEFAULT 'A Turborepo starter with Next.js, Expo, and self-hosted Convex. Ship web and mobile apps faster with shared code, type-safe backend, and complete control over your infrastructure.', + "primary_cta_label" varchar DEFAULT 'View Source Code', + "primary_cta_url" varchar DEFAULT 'https://git.gbrown.org/gib/convex-monorepo', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_logo_cloud_items" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar, + "accent" varchar + ); + + CREATE TABLE "landing_page_blocks_logo_cloud" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Designed For Modern Product Teams', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_features_items" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "icon" varchar, + "title" varchar, + "description" varchar + ); + + CREATE TABLE "landing_page_blocks_features" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Everything You Need to Ship Fast', + "description" varchar DEFAULT 'A complete monorepo template with all the tools and patterns you need for production-ready applications.', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_stats_items" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "value" varchar, + "label" varchar, + "description" varchar + ); + + CREATE TABLE "landing_page_blocks_stats" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Built To Demo Well', + "description" varchar DEFAULT 'These are the kinds of outcomes you can point to when showing the template to clients.', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_tech_stack_categories_technologies" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar, + "description" varchar + ); + + CREATE TABLE "landing_page_blocks_tech_stack_categories" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "category" varchar + ); + + CREATE TABLE "landing_page_blocks_tech_stack" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Modern Tech Stack', + "description" varchar DEFAULT 'Built with the latest and greatest tools for maximum productivity and performance.', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_testimonials_items" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "quote" varchar, + "name" varchar, + "role" varchar, + "company" varchar, + "avatar_emoji" varchar + ); + + CREATE TABLE "landing_page_blocks_testimonials" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'What Clients Actually Care About', + "description" varchar DEFAULT 'The polished part is not just the frontend. Editors can shape the story, launch updates fast, and preview changes before they go live.', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_pricing_plans_features" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "label" varchar + ); + + CREATE TABLE "landing_page_blocks_pricing_plans" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar, + "price" varchar, + "billing_period" varchar, + "description" varchar, + "badge" varchar, + "is_highlighted" boolean, + "cta_label" varchar, + "cta_url" varchar + ); + + CREATE TABLE "landing_page_blocks_pricing" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'A Great Way To Pitch Productized Delivery', + "description" varchar DEFAULT 'Use pricing blocks to show packaged service tiers, subscriptions, or implementation options right inside the landing page builder.', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_faq_items" ( + "_order" integer NOT NULL, + "_parent_id" varchar NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "question" varchar, + "answer" varchar + ); + + CREATE TABLE "landing_page_blocks_faq" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Questions You Will Probably Get In A Demo', + "description" varchar DEFAULT 'A good FAQ block makes the editor feel complete and gives clients another obvious content type they can manage themselves.', + "block_name" varchar + ); + + CREATE TABLE "landing_page_blocks_cta" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" varchar PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Ready to Build Something Amazing?', + "description" varchar DEFAULT 'Clone the repository and start building your next project with everything pre-configured.', + "command_label" varchar DEFAULT 'Quick Start', + "command" varchar DEFAULT 'git clone https://git.gbrown.org/gib/convex-monorepo.git + cd convex-monorepo + bun i', + "block_name" varchar + ); + + CREATE TABLE "landing_page" ( + "id" serial PRIMARY KEY NOT NULL, + "_status" "enum_landing_page_status" DEFAULT 'draft', + "updated_at" timestamp(3) with time zone, + "created_at" timestamp(3) with time zone + ); + + CREATE TABLE "_landing_page_v_blocks_hero_highlights" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "label" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_hero" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "badge_emoji" varchar DEFAULT '๐Ÿš€', + "badge_text" varchar DEFAULT 'Production-ready monorepo template', + "heading_prefix" varchar DEFAULT 'Build Full-Stack Apps with', + "heading_highlight" varchar DEFAULT 'convex-monorepo', + "description" varchar DEFAULT 'A Turborepo starter with Next.js, Expo, and self-hosted Convex. Ship web and mobile apps faster with shared code, type-safe backend, and complete control over your infrastructure.', + "primary_cta_label" varchar DEFAULT 'View Source Code', + "primary_cta_url" varchar DEFAULT 'https://git.gbrown.org/gib/convex-monorepo', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_logo_cloud_items" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "name" varchar, + "accent" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_logo_cloud" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Designed For Modern Product Teams', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_features_items" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "icon" varchar, + "title" varchar, + "description" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_features" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Everything You Need to Ship Fast', + "description" varchar DEFAULT 'A complete monorepo template with all the tools and patterns you need for production-ready applications.', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_stats_items" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "value" varchar, + "label" varchar, + "description" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_stats" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Built To Demo Well', + "description" varchar DEFAULT 'These are the kinds of outcomes you can point to when showing the template to clients.', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_tech_stack_categories_technologies" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "name" varchar, + "description" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_tech_stack_categories" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "category" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_tech_stack" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Modern Tech Stack', + "description" varchar DEFAULT 'Built with the latest and greatest tools for maximum productivity and performance.', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_testimonials_items" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "quote" varchar, + "name" varchar, + "role" varchar, + "company" varchar, + "avatar_emoji" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_testimonials" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'What Clients Actually Care About', + "description" varchar DEFAULT 'The polished part is not just the frontend. Editors can shape the story, launch updates fast, and preview changes before they go live.', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_pricing_plans_features" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "label" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_pricing_plans" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "name" varchar, + "price" varchar, + "billing_period" varchar, + "description" varchar, + "badge" varchar, + "is_highlighted" boolean, + "cta_label" varchar, + "cta_url" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_pricing" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'A Great Way To Pitch Productized Delivery', + "description" varchar DEFAULT 'Use pricing blocks to show packaged service tiers, subscriptions, or implementation options right inside the landing page builder.', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_faq_items" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "question" varchar, + "answer" varchar, + "_uuid" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_faq" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Questions You Will Probably Get In A Demo', + "description" varchar DEFAULT 'A good FAQ block makes the editor feel complete and gives clients another obvious content type they can manage themselves.', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v_blocks_cta" ( + "_order" integer NOT NULL, + "_parent_id" integer NOT NULL, + "_path" text NOT NULL, + "id" serial PRIMARY KEY NOT NULL, + "heading" varchar DEFAULT 'Ready to Build Something Amazing?', + "description" varchar DEFAULT 'Clone the repository and start building your next project with everything pre-configured.', + "command_label" varchar DEFAULT 'Quick Start', + "command" varchar DEFAULT 'git clone https://git.gbrown.org/gib/convex-monorepo.git + cd convex-monorepo + bun i', + "_uuid" varchar, + "block_name" varchar + ); + + CREATE TABLE "_landing_page_v" ( + "id" serial PRIMARY KEY NOT NULL, + "version__status" "enum__landing_page_v_version_status" DEFAULT 'draft', + "version_updated_at" timestamp(3) with time zone, + "version_created_at" timestamp(3) with time zone, + "created_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL, + "latest" boolean, + "autosave" boolean + ); + + ALTER TABLE "users_sessions" ADD CONSTRAINT "users_sessions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_locked_documents"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_preferences"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_hero_highlights" ADD CONSTRAINT "landing_page_blocks_hero_highlights_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_hero"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_hero" ADD CONSTRAINT "landing_page_blocks_hero_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_logo_cloud_items" ADD CONSTRAINT "landing_page_blocks_logo_cloud_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_logo_cloud"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_logo_cloud" ADD CONSTRAINT "landing_page_blocks_logo_cloud_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_features_items" ADD CONSTRAINT "landing_page_blocks_features_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_features"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_features" ADD CONSTRAINT "landing_page_blocks_features_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_stats_items" ADD CONSTRAINT "landing_page_blocks_stats_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_stats"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_stats" ADD CONSTRAINT "landing_page_blocks_stats_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_tech_stack_categories_technologies" ADD CONSTRAINT "landing_page_blocks_tech_stack_categories_technologies_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_tech_stack_categories"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_tech_stack_categories" ADD CONSTRAINT "landing_page_blocks_tech_stack_categories_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_tech_stack"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_tech_stack" ADD CONSTRAINT "landing_page_blocks_tech_stack_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_testimonials_items" ADD CONSTRAINT "landing_page_blocks_testimonials_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_testimonials"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_testimonials" ADD CONSTRAINT "landing_page_blocks_testimonials_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_pricing_plans_features" ADD CONSTRAINT "landing_page_blocks_pricing_plans_features_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_pricing_plans"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_pricing_plans" ADD CONSTRAINT "landing_page_blocks_pricing_plans_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_pricing"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_pricing" ADD CONSTRAINT "landing_page_blocks_pricing_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_faq_items" ADD CONSTRAINT "landing_page_blocks_faq_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page_blocks_faq"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_faq" ADD CONSTRAINT "landing_page_blocks_faq_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "landing_page_blocks_cta" ADD CONSTRAINT "landing_page_blocks_cta_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."landing_page"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_hero_highlights" ADD CONSTRAINT "_landing_page_v_blocks_hero_highlights_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_hero"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_hero" ADD CONSTRAINT "_landing_page_v_blocks_hero_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_logo_cloud_items" ADD CONSTRAINT "_landing_page_v_blocks_logo_cloud_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_logo_cloud"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_logo_cloud" ADD CONSTRAINT "_landing_page_v_blocks_logo_cloud_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_features_items" ADD CONSTRAINT "_landing_page_v_blocks_features_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_features"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_features" ADD CONSTRAINT "_landing_page_v_blocks_features_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_stats_items" ADD CONSTRAINT "_landing_page_v_blocks_stats_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_stats"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_stats" ADD CONSTRAINT "_landing_page_v_blocks_stats_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_tech_stack_categories_technologies" ADD CONSTRAINT "_landing_page_v_blocks_tech_stack_categories_technologies_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_tech_stack_categories"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_tech_stack_categories" ADD CONSTRAINT "_landing_page_v_blocks_tech_stack_categories_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_tech_stack"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_tech_stack" ADD CONSTRAINT "_landing_page_v_blocks_tech_stack_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_testimonials_items" ADD CONSTRAINT "_landing_page_v_blocks_testimonials_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_testimonials"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_testimonials" ADD CONSTRAINT "_landing_page_v_blocks_testimonials_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_pricing_plans_features" ADD CONSTRAINT "_landing_page_v_blocks_pricing_plans_features_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_pricing_plans"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_pricing_plans" ADD CONSTRAINT "_landing_page_v_blocks_pricing_plans_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_pricing"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_pricing" ADD CONSTRAINT "_landing_page_v_blocks_pricing_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_faq_items" ADD CONSTRAINT "_landing_page_v_blocks_faq_items_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v_blocks_faq"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_faq" ADD CONSTRAINT "_landing_page_v_blocks_faq_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "_landing_page_v_blocks_cta" ADD CONSTRAINT "_landing_page_v_blocks_cta_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_landing_page_v"("id") ON DELETE cascade ON UPDATE no action; + CREATE INDEX "users_sessions_order_idx" ON "users_sessions" USING btree ("_order"); + CREATE INDEX "users_sessions_parent_id_idx" ON "users_sessions" USING btree ("_parent_id"); + CREATE INDEX "users_updated_at_idx" ON "users" USING btree ("updated_at"); + CREATE INDEX "users_created_at_idx" ON "users" USING btree ("created_at"); + CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email"); + CREATE UNIQUE INDEX "payload_kv_key_idx" ON "payload_kv" USING btree ("key"); + CREATE INDEX "payload_locked_documents_global_slug_idx" ON "payload_locked_documents" USING btree ("global_slug"); + CREATE INDEX "payload_locked_documents_updated_at_idx" ON "payload_locked_documents" USING btree ("updated_at"); + CREATE INDEX "payload_locked_documents_created_at_idx" ON "payload_locked_documents" USING btree ("created_at"); + CREATE INDEX "payload_locked_documents_rels_order_idx" ON "payload_locked_documents_rels" USING btree ("order"); + CREATE INDEX "payload_locked_documents_rels_parent_idx" ON "payload_locked_documents_rels" USING btree ("parent_id"); + CREATE INDEX "payload_locked_documents_rels_path_idx" ON "payload_locked_documents_rels" USING btree ("path"); + CREATE INDEX "payload_locked_documents_rels_users_id_idx" ON "payload_locked_documents_rels" USING btree ("users_id"); + CREATE INDEX "payload_preferences_key_idx" ON "payload_preferences" USING btree ("key"); + CREATE INDEX "payload_preferences_updated_at_idx" ON "payload_preferences" USING btree ("updated_at"); + CREATE INDEX "payload_preferences_created_at_idx" ON "payload_preferences" USING btree ("created_at"); + CREATE INDEX "payload_preferences_rels_order_idx" ON "payload_preferences_rels" USING btree ("order"); + CREATE INDEX "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" USING btree ("parent_id"); + CREATE INDEX "payload_preferences_rels_path_idx" ON "payload_preferences_rels" USING btree ("path"); + CREATE INDEX "payload_preferences_rels_users_id_idx" ON "payload_preferences_rels" USING btree ("users_id"); + CREATE INDEX "payload_migrations_updated_at_idx" ON "payload_migrations" USING btree ("updated_at"); + CREATE INDEX "payload_migrations_created_at_idx" ON "payload_migrations" USING btree ("created_at"); + CREATE INDEX "landing_page_blocks_hero_highlights_order_idx" ON "landing_page_blocks_hero_highlights" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_hero_highlights_parent_id_idx" ON "landing_page_blocks_hero_highlights" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_hero_order_idx" ON "landing_page_blocks_hero" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_hero_parent_id_idx" ON "landing_page_blocks_hero" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_hero_path_idx" ON "landing_page_blocks_hero" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_logo_cloud_items_order_idx" ON "landing_page_blocks_logo_cloud_items" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_logo_cloud_items_parent_id_idx" ON "landing_page_blocks_logo_cloud_items" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_logo_cloud_order_idx" ON "landing_page_blocks_logo_cloud" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_logo_cloud_parent_id_idx" ON "landing_page_blocks_logo_cloud" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_logo_cloud_path_idx" ON "landing_page_blocks_logo_cloud" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_features_items_order_idx" ON "landing_page_blocks_features_items" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_features_items_parent_id_idx" ON "landing_page_blocks_features_items" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_features_order_idx" ON "landing_page_blocks_features" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_features_parent_id_idx" ON "landing_page_blocks_features" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_features_path_idx" ON "landing_page_blocks_features" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_stats_items_order_idx" ON "landing_page_blocks_stats_items" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_stats_items_parent_id_idx" ON "landing_page_blocks_stats_items" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_stats_order_idx" ON "landing_page_blocks_stats" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_stats_parent_id_idx" ON "landing_page_blocks_stats" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_stats_path_idx" ON "landing_page_blocks_stats" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_tech_stack_categories_technologies_order_idx" ON "landing_page_blocks_tech_stack_categories_technologies" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_tech_stack_categories_technologies_parent_id_idx" ON "landing_page_blocks_tech_stack_categories_technologies" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_tech_stack_categories_order_idx" ON "landing_page_blocks_tech_stack_categories" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_tech_stack_categories_parent_id_idx" ON "landing_page_blocks_tech_stack_categories" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_tech_stack_order_idx" ON "landing_page_blocks_tech_stack" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_tech_stack_parent_id_idx" ON "landing_page_blocks_tech_stack" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_tech_stack_path_idx" ON "landing_page_blocks_tech_stack" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_testimonials_items_order_idx" ON "landing_page_blocks_testimonials_items" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_testimonials_items_parent_id_idx" ON "landing_page_blocks_testimonials_items" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_testimonials_order_idx" ON "landing_page_blocks_testimonials" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_testimonials_parent_id_idx" ON "landing_page_blocks_testimonials" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_testimonials_path_idx" ON "landing_page_blocks_testimonials" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_pricing_plans_features_order_idx" ON "landing_page_blocks_pricing_plans_features" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_pricing_plans_features_parent_id_idx" ON "landing_page_blocks_pricing_plans_features" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_pricing_plans_order_idx" ON "landing_page_blocks_pricing_plans" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_pricing_plans_parent_id_idx" ON "landing_page_blocks_pricing_plans" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_pricing_order_idx" ON "landing_page_blocks_pricing" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_pricing_parent_id_idx" ON "landing_page_blocks_pricing" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_pricing_path_idx" ON "landing_page_blocks_pricing" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_faq_items_order_idx" ON "landing_page_blocks_faq_items" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_faq_items_parent_id_idx" ON "landing_page_blocks_faq_items" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_faq_order_idx" ON "landing_page_blocks_faq" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_faq_parent_id_idx" ON "landing_page_blocks_faq" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_faq_path_idx" ON "landing_page_blocks_faq" USING btree ("_path"); + CREATE INDEX "landing_page_blocks_cta_order_idx" ON "landing_page_blocks_cta" USING btree ("_order"); + CREATE INDEX "landing_page_blocks_cta_parent_id_idx" ON "landing_page_blocks_cta" USING btree ("_parent_id"); + CREATE INDEX "landing_page_blocks_cta_path_idx" ON "landing_page_blocks_cta" USING btree ("_path"); + CREATE INDEX "landing_page__status_idx" ON "landing_page" USING btree ("_status"); + CREATE INDEX "_landing_page_v_blocks_hero_highlights_order_idx" ON "_landing_page_v_blocks_hero_highlights" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_hero_highlights_parent_id_idx" ON "_landing_page_v_blocks_hero_highlights" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_hero_order_idx" ON "_landing_page_v_blocks_hero" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_hero_parent_id_idx" ON "_landing_page_v_blocks_hero" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_hero_path_idx" ON "_landing_page_v_blocks_hero" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_logo_cloud_items_order_idx" ON "_landing_page_v_blocks_logo_cloud_items" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_logo_cloud_items_parent_id_idx" ON "_landing_page_v_blocks_logo_cloud_items" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_logo_cloud_order_idx" ON "_landing_page_v_blocks_logo_cloud" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_logo_cloud_parent_id_idx" ON "_landing_page_v_blocks_logo_cloud" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_logo_cloud_path_idx" ON "_landing_page_v_blocks_logo_cloud" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_features_items_order_idx" ON "_landing_page_v_blocks_features_items" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_features_items_parent_id_idx" ON "_landing_page_v_blocks_features_items" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_features_order_idx" ON "_landing_page_v_blocks_features" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_features_parent_id_idx" ON "_landing_page_v_blocks_features" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_features_path_idx" ON "_landing_page_v_blocks_features" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_stats_items_order_idx" ON "_landing_page_v_blocks_stats_items" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_stats_items_parent_id_idx" ON "_landing_page_v_blocks_stats_items" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_stats_order_idx" ON "_landing_page_v_blocks_stats" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_stats_parent_id_idx" ON "_landing_page_v_blocks_stats" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_stats_path_idx" ON "_landing_page_v_blocks_stats" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_tech_stack_categories_technologies_order_idx" ON "_landing_page_v_blocks_tech_stack_categories_technologies" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_tech_stack_categories_technologies_parent_id_idx" ON "_landing_page_v_blocks_tech_stack_categories_technologies" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_tech_stack_categories_order_idx" ON "_landing_page_v_blocks_tech_stack_categories" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_tech_stack_categories_parent_id_idx" ON "_landing_page_v_blocks_tech_stack_categories" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_tech_stack_order_idx" ON "_landing_page_v_blocks_tech_stack" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_tech_stack_parent_id_idx" ON "_landing_page_v_blocks_tech_stack" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_tech_stack_path_idx" ON "_landing_page_v_blocks_tech_stack" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_testimonials_items_order_idx" ON "_landing_page_v_blocks_testimonials_items" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_testimonials_items_parent_id_idx" ON "_landing_page_v_blocks_testimonials_items" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_testimonials_order_idx" ON "_landing_page_v_blocks_testimonials" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_testimonials_parent_id_idx" ON "_landing_page_v_blocks_testimonials" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_testimonials_path_idx" ON "_landing_page_v_blocks_testimonials" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_pricing_plans_features_order_idx" ON "_landing_page_v_blocks_pricing_plans_features" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_pricing_plans_features_parent_id_idx" ON "_landing_page_v_blocks_pricing_plans_features" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_pricing_plans_order_idx" ON "_landing_page_v_blocks_pricing_plans" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_pricing_plans_parent_id_idx" ON "_landing_page_v_blocks_pricing_plans" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_pricing_order_idx" ON "_landing_page_v_blocks_pricing" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_pricing_parent_id_idx" ON "_landing_page_v_blocks_pricing" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_pricing_path_idx" ON "_landing_page_v_blocks_pricing" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_faq_items_order_idx" ON "_landing_page_v_blocks_faq_items" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_faq_items_parent_id_idx" ON "_landing_page_v_blocks_faq_items" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_faq_order_idx" ON "_landing_page_v_blocks_faq" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_faq_parent_id_idx" ON "_landing_page_v_blocks_faq" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_faq_path_idx" ON "_landing_page_v_blocks_faq" USING btree ("_path"); + CREATE INDEX "_landing_page_v_blocks_cta_order_idx" ON "_landing_page_v_blocks_cta" USING btree ("_order"); + CREATE INDEX "_landing_page_v_blocks_cta_parent_id_idx" ON "_landing_page_v_blocks_cta" USING btree ("_parent_id"); + CREATE INDEX "_landing_page_v_blocks_cta_path_idx" ON "_landing_page_v_blocks_cta" USING btree ("_path"); + CREATE INDEX "_landing_page_v_version_version__status_idx" ON "_landing_page_v" USING btree ("version__status"); + CREATE INDEX "_landing_page_v_created_at_idx" ON "_landing_page_v" USING btree ("created_at"); + CREATE INDEX "_landing_page_v_updated_at_idx" ON "_landing_page_v" USING btree ("updated_at"); + CREATE INDEX "_landing_page_v_latest_idx" ON "_landing_page_v" USING btree ("latest"); + CREATE INDEX "_landing_page_v_autosave_idx" ON "_landing_page_v" USING btree ("autosave");`); +}; + +export const down = async ({ + db, + payload, + req, +}: MigrateDownArgs): Promise => { + await db.execute(sql` + DROP TABLE "users_sessions" CASCADE; + DROP TABLE "users" CASCADE; + DROP TABLE "payload_kv" CASCADE; + DROP TABLE "payload_locked_documents" CASCADE; + DROP TABLE "payload_locked_documents_rels" CASCADE; + DROP TABLE "payload_preferences" CASCADE; + DROP TABLE "payload_preferences_rels" CASCADE; + DROP TABLE "payload_migrations" CASCADE; + DROP TABLE "landing_page_blocks_hero_highlights" CASCADE; + DROP TABLE "landing_page_blocks_hero" CASCADE; + DROP TABLE "landing_page_blocks_logo_cloud_items" CASCADE; + DROP TABLE "landing_page_blocks_logo_cloud" CASCADE; + DROP TABLE "landing_page_blocks_features_items" CASCADE; + DROP TABLE "landing_page_blocks_features" CASCADE; + DROP TABLE "landing_page_blocks_stats_items" CASCADE; + DROP TABLE "landing_page_blocks_stats" CASCADE; + DROP TABLE "landing_page_blocks_tech_stack_categories_technologies" CASCADE; + DROP TABLE "landing_page_blocks_tech_stack_categories" CASCADE; + DROP TABLE "landing_page_blocks_tech_stack" CASCADE; + DROP TABLE "landing_page_blocks_testimonials_items" CASCADE; + DROP TABLE "landing_page_blocks_testimonials" CASCADE; + DROP TABLE "landing_page_blocks_pricing_plans_features" CASCADE; + DROP TABLE "landing_page_blocks_pricing_plans" CASCADE; + DROP TABLE "landing_page_blocks_pricing" CASCADE; + DROP TABLE "landing_page_blocks_faq_items" CASCADE; + DROP TABLE "landing_page_blocks_faq" CASCADE; + DROP TABLE "landing_page_blocks_cta" CASCADE; + DROP TABLE "landing_page" CASCADE; + DROP TABLE "_landing_page_v_blocks_hero_highlights" CASCADE; + DROP TABLE "_landing_page_v_blocks_hero" CASCADE; + DROP TABLE "_landing_page_v_blocks_logo_cloud_items" CASCADE; + DROP TABLE "_landing_page_v_blocks_logo_cloud" CASCADE; + DROP TABLE "_landing_page_v_blocks_features_items" CASCADE; + DROP TABLE "_landing_page_v_blocks_features" CASCADE; + DROP TABLE "_landing_page_v_blocks_stats_items" CASCADE; + DROP TABLE "_landing_page_v_blocks_stats" CASCADE; + DROP TABLE "_landing_page_v_blocks_tech_stack_categories_technologies" CASCADE; + DROP TABLE "_landing_page_v_blocks_tech_stack_categories" CASCADE; + DROP TABLE "_landing_page_v_blocks_tech_stack" CASCADE; + DROP TABLE "_landing_page_v_blocks_testimonials_items" CASCADE; + DROP TABLE "_landing_page_v_blocks_testimonials" CASCADE; + DROP TABLE "_landing_page_v_blocks_pricing_plans_features" CASCADE; + DROP TABLE "_landing_page_v_blocks_pricing_plans" CASCADE; + DROP TABLE "_landing_page_v_blocks_pricing" CASCADE; + DROP TABLE "_landing_page_v_blocks_faq_items" CASCADE; + DROP TABLE "_landing_page_v_blocks_faq" CASCADE; + DROP TABLE "_landing_page_v_blocks_cta" CASCADE; + DROP TABLE "_landing_page_v" CASCADE; + DROP TYPE "public"."enum_landing_page_status"; + DROP TYPE "public"."enum__landing_page_v_version_status";`); +}; diff --git a/apps/next/src/migrations/index.ts b/apps/next/src/migrations/index.ts new file mode 100644 index 0000000..08cf3ae --- /dev/null +++ b/apps/next/src/migrations/index.ts @@ -0,0 +1,9 @@ +import * as migration_20261001_183341_baseline from './20261001_183341_baseline'; + +export const migrations = [ + { + up: migration_20261001_183341_baseline.up, + down: migration_20261001_183341_baseline.down, + name: '20261001_183341_baseline', + }, +]; diff --git a/apps/next/src/payload.config.ts b/apps/next/src/payload.config.ts index 1f0a8cd..c67daae 100644 --- a/apps/next/src/payload.config.ts +++ b/apps/next/src/payload.config.ts @@ -4,6 +4,7 @@ import { buildConfig } from 'payload'; import sharp from 'sharp'; import { env } from './env'; +import { migrations } from './migrations'; import { Users } from './payload/collections/users'; import { LandingPage } from './payload/globals/landing-page'; @@ -17,6 +18,15 @@ export default buildConfig({ pool: { connectionString: env.PAYLOAD_DB_URL, }, + // Schema changes ship as migrations (`bunx payload migrate:create ` + // from apps/next, through `bun with-env`). Production runs pending ones on + // startup. Dev push only ever touches a local database: a push against the + // production database would mark it as push-managed, and the next startup + // migration would stop on Payload's confirmation prompt. + push: ['localhost', '127.0.0.1'].includes( + new URL(env.PAYLOAD_DB_URL).hostname, + ), + prodMigrations: migrations, }), sharp, }); diff --git a/docker/Dockerfile b/docker/Dockerfile index 7ab2cd2..22356e5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,16 +1,18 @@ # syntax=docker/dockerfile:1 -FROM docker.io/oven/bun:1.3.10-alpine AS base +FROM docker.io/oven/bun:1.3.14-alpine AS base # Builder stage FROM base AS builder -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache curl libc6-compat WORKDIR /app ARG SENTRY_AUTH_TOKEN ARG SENTRY_DISABLE_AUTO_UPLOAD=false ARG NEXT_PUBLIC_SITE_URL ARG NEXT_PUBLIC_CONVEX_URL -ARG NEXT_PUBLIC_PLAUSIBLE_URL +ARG NEXT_PUBLIC_PLAUSIBLE_SRC +ARG NEXT_PUBLIC_POSTHOG_KEY +ARG NEXT_PUBLIC_POSTHOG_HOST ARG NEXT_PUBLIC_SENTRY_DSN ARG NEXT_PUBLIC_SENTRY_URL ARG NEXT_PUBLIC_SENTRY_ORG @@ -22,7 +24,9 @@ ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN ENV SENTRY_DISABLE_AUTO_UPLOAD=$SENTRY_DISABLE_AUTO_UPLOAD ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL ENV NEXT_PUBLIC_CONVEX_URL=$NEXT_PUBLIC_CONVEX_URL -ENV NEXT_PUBLIC_PLAUSIBLE_URL=$NEXT_PUBLIC_PLAUSIBLE_URL +ENV NEXT_PUBLIC_PLAUSIBLE_SRC=$NEXT_PUBLIC_PLAUSIBLE_SRC +ENV NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY +ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN ENV NEXT_PUBLIC_SENTRY_URL=$NEXT_PUBLIC_SENTRY_URL ENV NEXT_PUBLIC_SENTRY_ORG=$NEXT_PUBLIC_SENTRY_ORG @@ -40,7 +44,7 @@ RUN bun install --frozen-lockfile # Build with proper environment ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production -RUN cd apps/next && bun run build:docker +RUN sh scripts/build-next-bundle RUN bun scripts/copy-next-runtime-deps.mjs # Runner stage diff --git a/docker/compose.local.yml b/docker/compose.local.yml index fd92f32..eadfc01 100644 --- a/docker/compose.local.yml +++ b/docker/compose.local.yml @@ -4,7 +4,7 @@ services: postgres: image: postgres:17 container_name: convexmonorepo-local-postgres - ports: ['${POSTGRES_PORT:-5432}:5432'] + ports: ['${POSTGRES_PORT:-5436}:5432'] environment: - POSTGRES_USER=${POSTGRES_USER:-convexmonorepo} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-localdev} @@ -22,13 +22,13 @@ services: image: ghcr.io/get-convex/convex-backend:${BACKEND_TAG:-latest} container_name: convexmonorepo-local-convex ports: - - '${BACKEND_PORT:-3210}:3210' - - '${SITE_PROXY_PORT:-3211}:3211' + - '${BACKEND_PORT:-3240}:3210' + - '${SITE_PROXY_PORT:-3241}:3211' environment: - INSTANCE_NAME=${LOCAL_INSTANCE_NAME:-convexmonorepo_local} - INSTANCE_SECRET=${LOCAL_INSTANCE_SECRET:-0000000000000000000000000000000000000000000000000000000000000000} - - CONVEX_CLOUD_ORIGIN=http://localhost:${BACKEND_PORT:-3210} - - CONVEX_SITE_ORIGIN=http://localhost:${SITE_PROXY_PORT:-3211} + - CONVEX_CLOUD_ORIGIN=http://localhost:${BACKEND_PORT:-3240} + - CONVEX_SITE_ORIGIN=http://localhost:${SITE_PROXY_PORT:-3241} - DISABLE_BEACON=true - REDACT_LOGS_TO_CLIENT=false - DO_NOT_REQUIRE_SSL=true @@ -49,9 +49,9 @@ services: convex-dashboard: image: ghcr.io/get-convex/convex-dashboard:${DASHBOARD_TAG:-latest} container_name: convexmonorepo-local-convex-dashboard - ports: ['${DASHBOARD_PORT:-6791}:6791'] + ports: ['${DASHBOARD_PORT:-6794}:6791'] environment: - - NEXT_PUBLIC_DEPLOYMENT_URL=http://localhost:${BACKEND_PORT:-3210} + - NEXT_PUBLIC_DEPLOYMENT_URL=http://localhost:${BACKEND_PORT:-3240} depends_on: convex-backend: condition: service_healthy diff --git a/docker/compose.yml b/docker/compose.yml index 25e512a..a3251c6 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -12,7 +12,9 @@ services: SENTRY_DISABLE_AUTO_UPLOAD: ${SENTRY_DISABLE_AUTO_UPLOAD:-false} NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL} NEXT_PUBLIC_CONVEX_URL: ${NEXT_PUBLIC_CONVEX_URL} - NEXT_PUBLIC_PLAUSIBLE_URL: ${NEXT_PUBLIC_PLAUSIBLE_URL} + NEXT_PUBLIC_PLAUSIBLE_SRC: ${NEXT_PUBLIC_PLAUSIBLE_SRC:-} + NEXT_PUBLIC_POSTHOG_KEY: ${NEXT_PUBLIC_POSTHOG_KEY:-} + NEXT_PUBLIC_POSTHOG_HOST: ${NEXT_PUBLIC_POSTHOG_HOST:-} NEXT_PUBLIC_SENTRY_DSN: ${NEXT_PUBLIC_SENTRY_DSN} NEXT_PUBLIC_SENTRY_URL: ${NEXT_PUBLIC_SENTRY_URL} NEXT_PUBLIC_SENTRY_ORG: ${NEXT_PUBLIC_SENTRY_ORG} @@ -27,7 +29,9 @@ services: - SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN} - NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL:-http://localhost:${NEXT_PORT:-3000}} - NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-http://${BACKEND_CONTAINER_NAME:-convex-backend}:${BACKEND_PORT:-3210}} - - NEXT_PUBLIC_PLAUSIBLE_URL=${NEXT_PUBLIC_PLAUSIBLE_URL:-https://plausible.gbrown.org} + - NEXT_PUBLIC_PLAUSIBLE_SRC=${NEXT_PUBLIC_PLAUSIBLE_SRC:-} + - NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY:-} + - NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST:-} - NEXT_PUBLIC_SENTRY_DSN=${NEXT_PUBLIC_SENTRY_DSN} - NEXT_PUBLIC_SENTRY_URL=${NEXT_PUBLIC_SENTRY_URL} - NEXT_PUBLIC_SENTRY_ORG=${NEXT_PUBLIC_SENTRY_ORG:-sentry} diff --git a/docs/payload-cms.md b/docs/payload-cms.md index 27c5a0b..e863633 100644 --- a/docs/payload-cms.md +++ b/docs/payload-cms.md @@ -6,7 +6,7 @@ the application backend for auth, realtime data, files, and workflows. Configuration lives in `apps/next/src/payload.config.ts`. Payload uses `PAYLOAD_SECRET`, `PAYLOAD_DB_URL`, and `NEXT_PUBLIC_SITE_URL` from the validated environment contract. Local `dev` values come from Infisical and point Payload -to Postgres at `localhost:5432`, because Next runs on the host. +to Postgres at `localhost:5436`, because Next runs on the host. Start the isolated services with `bun db:up`, then run `bun dev:next`. The local database persists across `bun db:down`; `bun db:down:wipe` removes it. Local diff --git a/scripts/build-next-bundle b/scripts/build-next-bundle new file mode 100755 index 0000000..a5597f3 --- /dev/null +++ b/scripts/build-next-bundle @@ -0,0 +1,23 @@ +#!/usr/bin/env sh +set -eu + +ROOT_DIR="$(cd -- "$(dirname -- "$0")/.." && pwd)" + +if [ -n "${SENTRY_AUTH_TOKEN:-}" ] && + [ -n "${NEXT_PUBLIC_SENTRY_URL:-}" ] && + ! curl \ + --connect-timeout 3 \ + --max-time 10 \ + --silent \ + --show-error \ + --output /dev/null \ + "${NEXT_PUBLIC_SENTRY_URL%/}/api/0/"; then + echo "Sentry is unreachable; continuing without source-map upload." >&2 + SENTRY_AUTH_TOKEN="" + SKIP_ENV_VALIDATION=true + NEXT_PUBLIC_SENTRY_URL="http://127.0.0.1:9" + export NEXT_PUBLIC_SENTRY_URL SENTRY_AUTH_TOKEN SKIP_ENV_VALIDATION +fi + +cd "$ROOT_DIR/apps/next" +exec bun run build:docker diff --git a/scripts/ci-export-env.mjs b/scripts/ci-export-env.mjs new file mode 100755 index 0000000..4c1915f --- /dev/null +++ b/scripts/ci-export-env.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node +// ci-export-env.mjs [environment] +// +// CI's counterpart to scripts/export-env: prints an Infisical environment +// (default `staging`) as dotenv on stdout, in the same quoting export-env uses. +// It logs in as the `convexmonorepo-ci` machine identity through Universal Auth, using +// INFISICAL_CLIENT_ID and INFISICAL_CLIENT_SECRET from the job's Gitea secrets, +// and reads the project and host from .infisical.json. Plain Node, no CLI to +// install, so it runs in every job whether or not Bun is set up. +import { readFile } from 'node:fs/promises'; + +const environment = process.argv[2] ?? 'staging'; +const { INFISICAL_CLIENT_ID: clientId, INFISICAL_CLIENT_SECRET: clientSecret } = + process.env; + +const fail = (message) => { + console.error(`ci-export-env: ${message}`); + process.exit(1); +}; + +if (!clientId || !clientSecret) + fail('INFISICAL_CLIENT_ID and INFISICAL_CLIENT_SECRET must be set.'); + +const { workspaceId, domain } = JSON.parse( + await readFile(new URL('../.infisical.json', import.meta.url), 'utf8'), +); + +const request = async (path, init) => { + const response = await fetch(`${domain}${path}`, init); + if (!response.ok) + fail(`${init?.method ?? 'GET'} ${path} returned ${response.status}.`); + return response.json(); +}; + +const { accessToken } = await request('/v1/auth/universal-auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ clientId, clientSecret }), +}); + +const query = new URLSearchParams({ + workspaceId, + environment, + secretPath: '/', + expandSecretReferences: 'true', + include_imports: 'true', +}); +const { secrets, imports = [] } = await request(`/v3/secrets/raw?${query}`, { + headers: { Authorization: `Bearer ${accessToken}` }, +}); + +// Imported secrets first so the environment's own values win. +const values = new Map(); +for (const { secretKey, secretValue } of [ + ...imports.flatMap((folder) => folder.secrets), + ...secrets, +]) + values.set(secretKey, secretValue); + +const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`; +for (const [key, value] of [...values].sort(([a], [b]) => a.localeCompare(b))) + process.stdout.write(`${key}=${quote(value)}\n`); diff --git a/scripts/ci-install b/scripts/ci-install new file mode 100755 index 0000000..aec0ed2 --- /dev/null +++ b/scripts/ci-install @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Install on a runner, and survive a half-written cache entry. +# +# Runners have failed twice with "Fail extracting tarball" on a different +# package each time, on different machines, and passed on a re-run. The cache +# is shared between jobs on a host, so a job that dies mid-write leaves an +# entry the next job cannot read. Clearing it and installing again costs a +# couple of minutes; a human re-running the job costs however long it takes +# someone to notice. +set -euo pipefail +if bun install --frozen-lockfile; then exit 0; fi +echo "Install failed. Clearing the bun cache and trying once more." >&2 +rm -rf "${BUN_INSTALL_CACHE_DIR:-$HOME/.bun/install/cache}" +bun install --frozen-lockfile diff --git a/scripts/db/up b/scripts/db/up index b370f7e..12d4812 100755 --- a/scripts/db/up +++ b/scripts/db/up @@ -51,9 +51,9 @@ for i in $(seq 1 30); do sleep 1 done -info "Waiting for Convex at http://localhost:${BACKEND_PORT:-3210}" +info "Waiting for Convex at http://localhost:${BACKEND_PORT:-3240}" for i in $(seq 1 60); do - curl -fs "http://localhost:${BACKEND_PORT:-3210}/version" >/dev/null 2>&1 && break + curl -fs "http://localhost:${BACKEND_PORT:-3240}/version" >/dev/null 2>&1 && break [ "$i" -lt 60 ] || die "Convex did not become ready." sleep 2 done @@ -69,6 +69,11 @@ fi info "Deploying Convex schema and functions" (cd "$ROOT_DIR/packages/backend" && bun run setup) +# Env sync runs after the deploy: changing deployment env re-analyzes the +# deployed modules, which fails on a fresh volume before the first deploy. +info "Synchronizing the Convex function runtime environment" +sh "$ROOT_DIR/scripts/with-env" dev -- bash "$ROOT_DIR/scripts/sync-local-convex-env" + convex_env_names="$( sh "$ROOT_DIR/scripts/with-env" dev -- bash -c \ 'cd packages/backend && bunx convex env list' 2>/dev/null \ @@ -92,4 +97,4 @@ fi info "Seeding local Payload from snapshot if needed" bash "$ROOT_DIR/scripts/db/seed-payload" --yes -printf '\nLocal stack ready:\n App: http://localhost:3000\n Convex: http://localhost:%s\n Dashboard: http://localhost:%s\n Payload Postgres: localhost:%s\n' "${BACKEND_PORT:-3210}" "${DASHBOARD_PORT:-6791}" "${POSTGRES_PORT:-5432}" +printf '\nLocal stack ready:\n App: http://localhost:3000\n Convex: http://localhost:%s\n Dashboard: http://localhost:%s\n Payload Postgres: localhost:%s\n' "${BACKEND_PORT:-3240}" "${DASHBOARD_PORT:-6794}" "${POSTGRES_PORT:-5436}" diff --git a/scripts/dev-kill b/scripts/dev-kill new file mode 100755 index 0000000..220af79 --- /dev/null +++ b/scripts/dev-kill @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# Kill any lingering host-level dev servers (Next.js, Expo Metro, Convex CLI +# watchers, turbo orchestrators) that survived a Ctrl-C. Does not touch the +# dockerized Convex/Postgres stack โ€” use `bun db:down` for that. +# +# Usage: scripts/dev-kill (wired as `bun dev:kill`) +set -euo pipefail + +info() { printf '\033[36mโ–ถ %s\033[0m\n' "$*"; } +ok() { printf '\033[32mโœ“ %s\033[0m\n' "$*"; } + +PORTS=(3000 3001 8081 19000 19001 19002) +PATTERNS=('next-server' 'next dev' 'expo start' 'turbo run dev' 'convex dev') + +killed=false + +for port in "${PORTS[@]}"; do + pids="$(lsof -ti "tcp:$port" -sTCP:LISTEN 2>/dev/null || true)" + [ -z "$pids" ] && continue + for pid in $pids; do + cmd="$(ps -p "$pid" -o comm= 2>/dev/null || true)" + info "Killing $cmd (pid $pid) on port $port" + kill "$pid" 2>/dev/null || true + killed=true + done +done + +for pattern in "${PATTERNS[@]}"; do + pids="$(pgrep -f "$pattern" 2>/dev/null || true)" + [ -z "$pids" ] && continue + for pid in $pids; do + info "Killing '$pattern' (pid $pid)" + kill "$pid" 2>/dev/null || true + killed=true + done +done + +if [ "$killed" = true ]; then + sleep 1 + ok "Dev servers stopped." +else + ok "No dev servers were running." +fi diff --git a/scripts/export-env b/scripts/export-env index 14412fe..8c5ff7d 100755 --- a/scripts/export-env +++ b/scripts/export-env @@ -16,6 +16,12 @@ fi [ -f "$ROOT_DIR/.infisical.json" ] || { echo "export-env: run 'infisical init' in this repository." >&2; exit 1; } command -v infisical >/dev/null 2>&1 || { echo "export-env: Infisical CLI is required." >&2; exit 1; } +if [ -f "$ROOT_DIR/scripts/infisical-profile" ]; then + # shellcheck source=/dev/null + . "$ROOT_DIR/scripts/infisical-profile" + ensure_infisical_profile "$ROOT_DIR/scripts/flags/infisical.env" +fi + (cd "$ROOT_DIR" && infisical export --env="$ENVIRONMENT" --format=dotenv --silent) || { echo "export-env: failed to export '$ENVIRONMENT'; check login and project access." >&2 exit 1 diff --git a/scripts/infisical-profile b/scripts/infisical-profile new file mode 100755 index 0000000..e9941ad --- /dev/null +++ b/scripts/infisical-profile @@ -0,0 +1,117 @@ +#!/usr/bin/env sh +# +# ensure_infisical_profile +# +# Infisical's CLI keeps a single active login (~/.infisical/infisical-config.json) +# shared across every project on the machine. If you juggle multiple Infisical +# accounts (e.g. a client org vs. a personal org), running with-env in one repo +# can silently pull secrets while logged in as the wrong user. +# +# This switches the active `loggedInUserEmail` to the email configured in +# scripts/flags/infisical.env (INFISICAL_EMAIL=...) before infisical runs, +# provided that email is already logged in locally (`infisical login`). +# It is a no-op if the flag file is absent, only one user is logged in, or the +# expected user isn't logged in yet. +# +# POSIX sh, no bashisms: matches the rest of scripts/ which must stay +# busybox-compatible. + +warn_infisical_profile() { + echo "Warning: infisical-profile: $*" >&2 +} + +ensure_infisical_profile() { + profile_env_file="${1:-}" + + if [ -z "$profile_env_file" ] || [ ! -f "$profile_env_file" ]; then + return 0 + fi + + set -a + # shellcheck source=/dev/null + if ! . "$profile_env_file"; then + set +a + warn_infisical_profile "could not read $profile_env_file. Continuing with current Infisical profile." + return 0 + fi + set +a + + expected_email="${INFISICAL_EMAIL:-}" + if [ -z "$expected_email" ]; then + return 0 + fi + + config="${INFISICAL_CONFIG_PATH:-$HOME/.infisical/infisical-config.json}" + lock="${INFISICAL_LOCK_PATH:-$HOME/.infisical/.switch.lock}" + + if [ ! -f "$config" ]; then + warn_infisical_profile "Infisical config not found at $config. Continuing with current Infisical profile." + return 0 + fi + + if ! command -v jq >/dev/null 2>&1; then + warn_infisical_profile "jq is required to select Infisical profile. Continuing with current Infisical profile." + return 0 + fi + + if ! command -v flock >/dev/null 2>&1; then + warn_infisical_profile "flock is required to safely select Infisical profile. Continuing with current Infisical profile." + return 0 + fi + + if ! mkdir -p "$(dirname "$lock")"; then + warn_infisical_profile "could not create Infisical lock directory. Continuing with current Infisical profile." + return 0 + fi + + ( + if ! flock 9; then + warn_infisical_profile "could not lock Infisical profile config. Continuing with current Infisical profile." + return 0 + fi + + user_count="$(jq -r '(.loggedInUsers // []) | length' "$config" 2>/dev/null)" || { + warn_infisical_profile "could not parse Infisical config. Continuing with current Infisical profile." + return 0 + } + + if [ "$user_count" -lt 2 ]; then + return 0 + fi + + current_email="$(jq -r '.loggedInUserEmail // ""' "$config" 2>/dev/null)" || { + warn_infisical_profile "could not read current Infisical profile. Continuing with current Infisical profile." + return 0 + } + + if [ "$current_email" = "$expected_email" ]; then + return 0 + fi + + domain="$( + jq -er --arg email "$expected_email" \ + '.loggedInUsers[] | select(.email == $email) | .domain' \ + "$config" 2>/dev/null + )" || { + warn_infisical_profile "Infisical profile $expected_email is not logged in locally. Continuing with current Infisical profile." + return 0 + } + + tmp="$(mktemp)" || { + warn_infisical_profile "could not create temp file for Infisical profile switch. Continuing with current Infisical profile." + return 0 + } + + if jq --arg email "$expected_email" --arg domain "$domain" \ + '.loggedInUserEmail = $email | .LoggedInUserDomain = $domain' \ + "$config" > "$tmp"; then + chmod 600 "$tmp" + mv "$tmp" "$config" + else + rm -f "$tmp" + warn_infisical_profile "failed to update Infisical profile. Continuing with current Infisical profile." + fi + ) 9>"$lock" + + return 0 +} diff --git a/scripts/sync-local-convex-env b/scripts/sync-local-convex-env new file mode 100755 index 0000000..a31a74a --- /dev/null +++ b/scripts/sync-local-convex-env @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" +VALUE_FILE="" + +readonly RUNTIME_ENV_NAMES=( + AUTH_AUTHENTIK_ID + AUTH_AUTHENTIK_SECRET + AUTH_AUTHENTIK_ISSUER + USESEND_API_KEY + USESEND_URL + USESEND_FROM_EMAIL +) +readonly DEPLOYMENT_ENV_NAMES=( + CONVEX_SELF_HOSTED_URL + CONVEX_SELF_HOSTED_ADMIN_KEY +) + +die() { printf 'sync-local-convex-env: %s\n' "$*" >&2; exit 1; } +cleanup() { + if [ -n "$VALUE_FILE" ] && [ -f "$VALUE_FILE" ]; then + rm -f "$VALUE_FILE" + fi +} +trap cleanup EXIT INT TERM HUP + +for name in "${DEPLOYMENT_ENV_NAMES[@]}" "${RUNTIME_ENV_NAMES[@]}"; do + [ -n "${!name:-}" ] || die "Missing required environment variable: $name" +done + +VALUE_FILE="$(mktemp "${TMPDIR:-/tmp}/convex-monorepo-convex-env.value.XXXXXX")" +chmod 600 "$VALUE_FILE" + +for name in "${RUNTIME_ENV_NAMES[@]}"; do + printf '%s' "${!name}" > "$VALUE_FILE" + ( + cd "$ROOT_DIR/packages/backend" + env -u CONVEX_DEPLOY_KEY -u CONVEX_DEPLOYMENT \ + CONVEX_SELF_HOSTED_URL="$CONVEX_SELF_HOSTED_URL" \ + CONVEX_SELF_HOSTED_ADMIN_KEY="$CONVEX_SELF_HOSTED_ADMIN_KEY" \ + bunx convex env set "$name" --from-file "$VALUE_FILE" >/dev/null + ) +done + +printf 'Local Convex runtime environment synchronized.\n' diff --git a/turbo.json b/turbo.json index dcdd51f..a65e8eb 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://v2-9-18.turborepo.dev/schema.json", + "$schema": "https://v2-11-6.turborepo.dev/schema.json", "globalDependencies": [".infisical.json", ".local/*.env"], "globalEnv": [ "NODE_ENV", @@ -10,7 +10,9 @@ "PAYLOAD_DB_URL", "NEXT_PUBLIC_SITE_URL", "NEXT_PUBLIC_CONVEX_URL", - "NEXT_PUBLIC_PLAUSIBLE_URL", + "NEXT_PUBLIC_PLAUSIBLE_SRC", + "NEXT_PUBLIC_POSTHOG_KEY", + "NEXT_PUBLIC_POSTHOG_HOST", "NEXT_PUBLIC_SENTRY_DSN", "NEXT_PUBLIC_SENTRY_URL", "NEXT_PUBLIC_SENTRY_ORG", @@ -53,6 +55,7 @@ ], "globalPassThroughEnv": ["NODE_ENV"], "ui": "tui", + "agentGuidance": false, "tasks": { "topo": { "dependsOn": ["^topo"]