mirror of
https://github.com/logos-co/logos-web.git
synced 2026-08-27 12:11:14 +00:00
feat(migrations): add initial migration for database schema setup
- Created initial migration file to set up the database schema with necessary tables and types. - Added migration index file to register the new migration. - Introduced a production-ready Docker Compose configuration for deploying the CMS with a self-hosted PostgreSQL database.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Keep the Docker build context small and reproducible.
|
||||
# pnpm-lock.yaml + sources are needed; everything below is not.
|
||||
|
||||
# Dependencies (reinstalled inside the image)
|
||||
**/node_modules
|
||||
.pnpm-store
|
||||
**/.pnpm-store
|
||||
|
||||
# Build outputs / caches
|
||||
**/.next
|
||||
**/out
|
||||
**/dist
|
||||
**/build
|
||||
**/.turbo
|
||||
**/coverage
|
||||
**/*.tsbuildinfo
|
||||
|
||||
# VCS / CI
|
||||
.git
|
||||
.github
|
||||
|
||||
# Local env files — secrets are injected at runtime via docker-compose, never baked
|
||||
**/.env
|
||||
**/.env.*
|
||||
!**/.env.example
|
||||
|
||||
# Vercel
|
||||
**/.vercel
|
||||
|
||||
# Editor / OS noise
|
||||
**/.DS_Store
|
||||
.claude
|
||||
|
||||
# Docker files themselves
|
||||
**/Dockerfile
|
||||
**/.dockerignore
|
||||
docker-compose*.yml
|
||||
@@ -13,6 +13,7 @@ node_modules
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.docker
|
||||
.claude
|
||||
|
||||
# Testing
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# =============================================================================
|
||||
# Environment for docker-compose.prod.yml — copy to .env.docker at the repo
|
||||
# root and fill in real values. NEVER commit the filled-in file.
|
||||
#
|
||||
# cp apps/cms/.env.docker.example .env.docker
|
||||
# docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build
|
||||
# =============================================================================
|
||||
|
||||
# --- Self-hosted Postgres ----------------------------------------------------
|
||||
POSTGRES_USER=logos
|
||||
POSTGRES_PASSWORD=change-me-strong-password
|
||||
POSTGRES_DB=logos_cms
|
||||
|
||||
# --- Payload core ------------------------------------------------------------
|
||||
# Generate with: openssl rand -hex 32
|
||||
PAYLOAD_SECRET=change-me-openssl-rand-hex-32
|
||||
|
||||
# --- Public URLs (BAKED INTO THE CLIENT BUNDLE AT BUILD TIME) -----------------
|
||||
# Must be the real public origins. Changing these requires a rebuild.
|
||||
NEXT_PUBLIC_SERVER_URL=https://cms.logos.co
|
||||
NEXT_PUBLIC_WEB_URL=https://logos.co
|
||||
|
||||
# --- Host port mapping -------------------------------------------------------
|
||||
# Host port that maps to the container's :3000. Put a reverse proxy (TLS) in
|
||||
# front of this in production.
|
||||
CMS_PORT=3001
|
||||
|
||||
# --- Postgres tuning (optional — defaults shown) -----------------------------
|
||||
# PAYLOAD_DB_SCHEMA=payload
|
||||
# PAYLOAD_DB_POOL_MAX=10
|
||||
# Schema auto-sync on boot. Keep "true" for the first deploy; flip to "false"
|
||||
# once SQL migrations are wired up so prod no longer mutates schema on boot.
|
||||
# PAYLOAD_DB_PUSH=true
|
||||
|
||||
# --- GitHub "Create PR" workflow (optional admin feature) --------------------
|
||||
GITHUB_OWNER=logos-co
|
||||
GITHUB_REPO=logos-web
|
||||
GITHUB_APP_ID=123456
|
||||
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
|
||||
GITHUB_INSTALLATION_ID=12345678
|
||||
GITHUB_PR_BASE_BRANCH=develop
|
||||
@@ -0,0 +1,95 @@
|
||||
# =============================================================================
|
||||
# Production image for apps/cms (Payload CMS 3.83 + Next.js 16)
|
||||
#
|
||||
# Build context MUST be the monorepo root so pnpm can resolve workspace
|
||||
# packages (@repo/content). From the repo root:
|
||||
#
|
||||
# docker build -f apps/cms/Dockerfile \
|
||||
# --build-arg NEXT_PUBLIC_SERVER_URL=https://cms.example.com \
|
||||
# --build-arg NEXT_PUBLIC_WEB_URL=https://www.example.com \
|
||||
# -t logos-cms .
|
||||
#
|
||||
# NEXT_PUBLIC_* values are inlined into the client bundle at build time, so
|
||||
# they must be the real public origins here — not at runtime.
|
||||
# =============================================================================
|
||||
|
||||
FROM node:24-bookworm-slim AS base
|
||||
ENV PNPM_HOME=/pnpm
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# deps — install only what cms (and its workspace deps) need, with a frozen
|
||||
# lockfile for reproducible builds.
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM base AS deps
|
||||
# Copy the workspace manifest layer first for better layer caching.
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .npmrc ./
|
||||
COPY apps/cms/package.json apps/cms/package.json
|
||||
COPY packages/config/package.json packages/config/package.json
|
||||
COPY packages/content/package.json packages/content/package.json
|
||||
COPY packages/tokens/package.json packages/tokens/package.json
|
||||
COPY packages/types/package.json packages/types/package.json
|
||||
COPY packages/ui/package.json packages/ui/package.json
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile --filter cms...
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# builder — copy sources and build the Next.js production output.
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM base AS builder
|
||||
ARG NEXT_PUBLIC_SERVER_URL
|
||||
ARG NEXT_PUBLIC_WEB_URL
|
||||
# Build-time placeholders: payload.config.ts validates these env vars at module
|
||||
# load (during `next build`). DATABASE_URL/PAYLOAD_SECRET are NOT inlined and
|
||||
# are overridden with real values at runtime; the build never connects to the DB.
|
||||
ENV NODE_ENV=production \
|
||||
NEXT_TELEMETRY_DISABLED=1 \
|
||||
NEXT_PUBLIC_SERVER_URL=${NEXT_PUBLIC_SERVER_URL} \
|
||||
NEXT_PUBLIC_WEB_URL=${NEXT_PUBLIC_WEB_URL} \
|
||||
DATABASE_URL=postgresql://build:build@localhost:5432/build \
|
||||
PAYLOAD_SECRET=build-time-placeholder-secret
|
||||
|
||||
# Bring in the resolved node_modules from the deps stage, then layer the full
|
||||
# monorepo sources on top.
|
||||
COPY --from=deps /app/ ./
|
||||
COPY . .
|
||||
RUN pnpm --filter cms build
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# runner — production runtime. Runs `next start` via pnpm.
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM base AS runner
|
||||
ENV NODE_ENV=production \
|
||||
NEXT_TELEMETRY_DISABLED=1 \
|
||||
PORT=3000 \
|
||||
HOSTNAME=0.0.0.0
|
||||
|
||||
# Run as an unprivileged user (with a home dir so any tool that needs a cache
|
||||
# has a writable location).
|
||||
RUN groupadd --system --gid 1001 nodejs \
|
||||
&& useradd --system --create-home --uid 1001 --gid nodejs nextjs
|
||||
|
||||
# Copy the fully built monorepo (sources + node_modules + .next output).
|
||||
COPY --from=builder --chown=nextjs:nodejs /app ./
|
||||
|
||||
# Media uploads are written here at runtime; mount a volume on this path so
|
||||
# uploaded files survive container restarts (see docker-compose).
|
||||
RUN mkdir -p /app/apps/web/public/cms/uploads \
|
||||
&& chown -R nextjs:nodejs /app/apps/web/public/cms/uploads \
|
||||
&& chmod +x /app/apps/cms/docker-entrypoint.sh
|
||||
|
||||
USER nextjs
|
||||
WORKDIR /app/apps/cms
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD node -e "fetch('http://localhost:'+(process.env.PORT||3000)+'/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
|
||||
# The entrypoint applies DB migrations, then execs the CMD below. `next start`
|
||||
# runs via the workspace-local binary to avoid invoking pnpm/corepack at runtime
|
||||
# (corepack would try to download a package manager into a non-writable cache
|
||||
# and crash-loop the container).
|
||||
ENTRYPOINT ["/app/apps/cms/docker-entrypoint.sh"]
|
||||
CMD ["node_modules/.bin/next", "start"]
|
||||
@@ -0,0 +1,174 @@
|
||||
# Logos CMS
|
||||
|
||||
Payload CMS 3.83 admin app on Next.js 16. Admin UI lives at `/admin`, the app runs on port **3001**.
|
||||
|
||||
Postgres is **not** the production source of truth for content — it stores users, sessions, drafts, and PR cache. Published content changes flow through the GitHub "Create PR" workflow targeting `develop`. See [`AGENTS.md`](./AGENTS.md) for the content-workflow rules.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js `>=24 <25`
|
||||
- pnpm `11.1.0` (via Corepack: `corepack enable`)
|
||||
- A reachable Postgres instance (local, Supabase, or the bundled Docker Postgres)
|
||||
- For Docker deploys: Docker + Docker Compose
|
||||
|
||||
All commands run **from the monorepo root** so pnpm can resolve workspace packages (`@repo/content`, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
Local development runs `next dev` directly against your own Postgres, with Payload schema auto-sync enabled.
|
||||
|
||||
### 1. Configure environment
|
||||
|
||||
```bash
|
||||
cp apps/cms/.env.example apps/cms/.env
|
||||
```
|
||||
|
||||
Fill in at minimum:
|
||||
|
||||
| Variable | Purpose |
|
||||
| --- | --- |
|
||||
| `PAYLOAD_SECRET` | Required to boot. Generate with `openssl rand -hex 32`. |
|
||||
| `DATABASE_URL` | Postgres connection string. |
|
||||
| `NEXT_PUBLIC_SERVER_URL` | CMS origin — defaults to `http://localhost:3001`. |
|
||||
| `NEXT_PUBLIC_WEB_URL` | Web frontend origin — defaults to `http://localhost:3000`. |
|
||||
|
||||
The `GITHUB_*` variables are only required to exercise the Admin "Create PR" action. See [`.env.example`](./.env.example) for every option and its default.
|
||||
|
||||
> Isolate dev data from other environments by setting `PAYLOAD_DB_SCHEMA=payload_dev` when sharing one Postgres database.
|
||||
|
||||
### 2. Run
|
||||
|
||||
```bash
|
||||
pnpm --filter cms dev # from the repo root
|
||||
# or
|
||||
pnpm dev:cms # turbo wrapper (also builds workspace deps)
|
||||
```
|
||||
|
||||
The dev wrapper (`scripts/dev.ts`) starts Next on port 3001 and auto-accepts Payload's interactive schema-push prompt. It **refuses to run in deployment environments** — production/staging must use the build/start path with reviewed migrations, never local dev schema sync.
|
||||
|
||||
### Common tasks
|
||||
|
||||
```bash
|
||||
pnpm --filter cms lint # eslint, zero warnings
|
||||
pnpm --filter cms check-types # payload typegen + tsc --noEmit
|
||||
pnpm --filter cms test # node:test suite
|
||||
pnpm --filter cms generate-types # regenerate Payload types after schema changes
|
||||
pnpm --filter cms generate-import-map
|
||||
```
|
||||
|
||||
Run `generate-types` after any Payload collection or schema change that affects generated types.
|
||||
|
||||
---
|
||||
|
||||
## Production deployment (Docker)
|
||||
|
||||
The production image bundles the built monorepo and runs `next start`. The build context **must be the monorepo root** so pnpm can resolve workspace packages.
|
||||
|
||||
### Option A — Docker Compose (recommended)
|
||||
|
||||
Brings up the CMS plus a self-hosted Postgres on an internal network. From the repo root:
|
||||
|
||||
```bash
|
||||
# 1. Create the env file and fill in real values
|
||||
cp apps/cms/.env.docker.example .env.docker
|
||||
|
||||
# 2. Build and start
|
||||
docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build
|
||||
```
|
||||
|
||||
Required values in `.env.docker`:
|
||||
|
||||
| Variable | Notes |
|
||||
| --- | --- |
|
||||
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | Credentials for the bundled Postgres. |
|
||||
| `PAYLOAD_SECRET` | `openssl rand -hex 32`. |
|
||||
| `NEXT_PUBLIC_SERVER_URL` | **Baked into the client bundle at build time** — must be the real public CMS origin. Changing it requires a rebuild. |
|
||||
| `NEXT_PUBLIC_WEB_URL` | **Build-time** public web origin (same caveat). |
|
||||
| `CMS_PORT` | Host port mapped to the container's `:3000` (default `3001`). |
|
||||
|
||||
The compose file constructs `DATABASE_URL` automatically from the Postgres vars and connects over the internal network — Postgres is **not** published to the host by default.
|
||||
|
||||
What's persisted:
|
||||
|
||||
- **`pgdata`** volume — Postgres data
|
||||
- **`cms_uploads`** volume — uploaded media (`/app/apps/web/public/cms/uploads`)
|
||||
|
||||
The container entrypoint applies database migrations before the server starts, so the schema is ready on first boot — no manual step needed. See [Database migrations](#database-migrations) below for how this works and how to add a migration.
|
||||
|
||||
Put a reverse proxy (TLS termination) in front of `CMS_PORT` in production.
|
||||
|
||||
### Option B — standalone image
|
||||
|
||||
Build and run the image against an external Postgres (e.g. Supabase). Build from the repo root:
|
||||
|
||||
```bash
|
||||
docker build -f apps/cms/Dockerfile \
|
||||
--build-arg NEXT_PUBLIC_SERVER_URL=https://cms.logos.co \
|
||||
--build-arg NEXT_PUBLIC_WEB_URL=https://logos.co \
|
||||
-t logos-cms .
|
||||
```
|
||||
|
||||
Run, injecting runtime secrets:
|
||||
|
||||
```bash
|
||||
docker run -d --name logos-cms -p 3001:3000 \
|
||||
-e DATABASE_URL='postgresql://user:password@host:5432/db' \
|
||||
-e PAYLOAD_SECRET='<openssl rand -hex 32>' \
|
||||
-e NEXT_PUBLIC_SERVER_URL='https://cms.logos.co' \
|
||||
-e NEXT_PUBLIC_WEB_URL='https://logos.co' \
|
||||
-v cms_uploads:/app/apps/web/public/cms/uploads \
|
||||
logos-cms
|
||||
```
|
||||
|
||||
> `NEXT_PUBLIC_*` are inlined into the client bundle at **build time**, so they must be the real public origins passed as `--build-arg`. `DATABASE_URL` and `PAYLOAD_SECRET` are **not** baked in — they're supplied at runtime, and the build never connects to the database.
|
||||
|
||||
### Build vs. runtime variables
|
||||
|
||||
| Variable | When it's read | Notes |
|
||||
| --- | --- | --- |
|
||||
| `NEXT_PUBLIC_SERVER_URL`, `NEXT_PUBLIC_WEB_URL` | **Build time** (inlined into client bundle) | Must be real public origins; changing requires a rebuild. |
|
||||
| `DATABASE_URL`, `PAYLOAD_SECRET` | **Runtime** | Never baked into the image. |
|
||||
| `PAYLOAD_DB_SCHEMA`, `PAYLOAD_DB_POOL_MAX` | Runtime | Defaults: `payload`, `10`. |
|
||||
| `PAYLOAD_DB_PUSH` | Runtime | Only affects **local dev** (`NODE_ENV !== production`). Ignored in production — see [Database migrations](#database-migrations). |
|
||||
| `GITHUB_*` | Runtime | Optional — only for the Admin "Create PR" action. |
|
||||
|
||||
### Database migrations
|
||||
|
||||
Payload **never** auto-syncs the schema (`push`) when `NODE_ENV=production` — that includes both self-hosted Docker and Vercel. Production schema is owned entirely by the reviewed SQL migrations in [`src/migrations/`](./src/migrations/).
|
||||
|
||||
**How it runs in Docker:** the container's [entrypoint](./docker-entrypoint.sh) runs `payload migrate` before `next start`. Migrations are idempotent — already-applied ones (tracked in the `payload-migrations` table) are skipped — so a fresh database is set up on first boot and restarts are no-ops.
|
||||
|
||||
**Local dev** uses schema auto-push instead (`PAYLOAD_DB_PUSH` defaults on; set `PAYLOAD_DB_PUSH=false` to disable), so you normally don't run migrations during development.
|
||||
|
||||
**When you change a collection or schema**, generate a migration and commit it alongside the code:
|
||||
|
||||
```bash
|
||||
pnpm --filter cms migrate:create # generate a new migration from current schema
|
||||
pnpm --filter cms migrate:status # list applied / pending migrations
|
||||
pnpm --filter cms migrate # apply pending migrations (run against the target DB)
|
||||
```
|
||||
|
||||
The generated file under `src/migrations/` and the updated `src/migrations/index.ts` **must be committed** — production applies exactly what's in that directory.
|
||||
|
||||
### Health check
|
||||
|
||||
The image exposes a health endpoint used by Docker's `HEALTHCHECK`:
|
||||
|
||||
```
|
||||
GET /api/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment file reference
|
||||
|
||||
| File | Used by | Committed? |
|
||||
| --- | --- | --- |
|
||||
| [`.env.example`](./.env.example) | Local dev template → copy to `apps/cms/.env` | template only |
|
||||
| [`.env.docker.example`](./.env.docker.example) | Docker Compose template → copy to repo-root `.env.docker` | template only |
|
||||
|
||||
Never commit the filled-in `.env` / `.env.docker` files — secrets are injected at runtime, never baked into the image.
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Production container entrypoint.
|
||||
#
|
||||
# Payload does NOT push the schema in production (see @payloadcms/db-postgres
|
||||
# connect.js: pushDevSchema only runs when NODE_ENV !== 'production'). The
|
||||
# repo policy is migrations for any deployed environment, so we apply pending
|
||||
# SQL migrations before the server starts accepting traffic.
|
||||
#
|
||||
# `payload migrate` is idempotent — already-applied migrations are skipped, so
|
||||
# this is safe to run on every (re)start.
|
||||
set -e
|
||||
|
||||
echo "[entrypoint] Applying Payload migrations..."
|
||||
PAYLOAD_CONFIG_PATH=payload.config.ts node_modules/.bin/payload migrate
|
||||
|
||||
echo "[entrypoint] Migrations done. Starting server..."
|
||||
exec "$@"
|
||||
@@ -12,6 +12,9 @@
|
||||
"check-types": "next typegen && tsc --noEmit",
|
||||
"generate-types": "PAYLOAD_CONFIG_PATH=payload.config.ts payload generate:types",
|
||||
"generate-import-map": "PAYLOAD_CONFIG_PATH=payload.config.ts payload generate:importmap",
|
||||
"migrate": "PAYLOAD_CONFIG_PATH=payload.config.ts payload migrate",
|
||||
"migrate:create": "PAYLOAD_CONFIG_PATH=payload.config.ts payload migrate:create",
|
||||
"migrate:status": "PAYLOAD_CONFIG_PATH=payload.config.ts payload migrate:status",
|
||||
"save-as-pr-smoke": "tsx scripts/save-as-pr-smoke.ts",
|
||||
"sync-from-content": "tsx scripts/sync-from-content.ts"
|
||||
},
|
||||
|
||||
@@ -41,8 +41,8 @@ import {
|
||||
export const Rfps: CollectionConfig = {
|
||||
slug: 'rfps',
|
||||
labels: {
|
||||
plural: 'RFSs',
|
||||
singular: 'RFS',
|
||||
plural: 'RFPs',
|
||||
singular: 'RFP',
|
||||
},
|
||||
admin: {
|
||||
components: recentPrAdminComponents,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,492 @@
|
||||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
// Payload's `migrate` CLI does not auto-create a custom schema (the adapter
|
||||
// uses schemaName: 'payload'). Without this, a fresh/self-hosted Postgres
|
||||
// fails the first migration with: schema "payload" does not exist.
|
||||
// Keep this line if the initial migration is ever regenerated.
|
||||
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS "payload";`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TYPE "payload"."enum_pages_status" AS ENUM('draft', 'published');
|
||||
CREATE TYPE "payload"."enum__pages_v_version_status" AS ENUM('draft', 'published');
|
||||
CREATE TYPE "payload"."enum_builder_listing_settings_page" AS ENUM('ideas', 'rfps');
|
||||
CREATE TYPE "payload"."enum_builder_listing_settings_default_view" AS ENUM('grid', 'list');
|
||||
CREATE TYPE "payload"."enum_rfps_status" AS ENUM('draft', 'review', 'published', 'archived');
|
||||
CREATE TYPE "payload"."enum_rfps_reward_currency" AS ENUM('USDC');
|
||||
CREATE TYPE "payload"."enum_ideas_status" AS ENUM('draft', 'review', 'published', 'archived');
|
||||
CREATE TYPE "payload"."enum_ideas_reward_currency" AS ENUM('USDC');
|
||||
CREATE TYPE "payload"."enum_builder_resources_status" AS ENUM('draft', 'review', 'published', 'archived');
|
||||
CREATE TYPE "payload"."enum_circles_status" AS ENUM('draft', 'review', 'published', 'archived');
|
||||
CREATE TYPE "payload"."enum_circle_events_status" AS ENUM('draft', 'review', 'published', 'archived');
|
||||
CREATE TYPE "payload"."enum_circle_initiatives_status" AS ENUM('draft', 'review', 'published', 'archived');
|
||||
CREATE TYPE "payload"."enum_circle_resources_status" AS ENUM('draft', 'review', 'published', 'archived');
|
||||
CREATE TYPE "payload"."enum_content_change_requests_status" AS ENUM('draft', 'open', 'merged', 'closed');
|
||||
CREATE TABLE "payload"."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 "payload"."users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar,
|
||||
"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,
|
||||
"login_attempts" numeric DEFAULT 0,
|
||||
"lock_until" timestamp(3) with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."media" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"alt" varchar NOT NULL,
|
||||
"public_path" varchar,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"url" varchar,
|
||||
"thumbnail_u_r_l" varchar,
|
||||
"filename" varchar,
|
||||
"mime_type" varchar,
|
||||
"filesize" numeric,
|
||||
"width" numeric,
|
||||
"height" numeric,
|
||||
"focal_x" numeric,
|
||||
"focal_y" numeric
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."pages" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar,
|
||||
"title" varchar,
|
||||
"route" varchar,
|
||||
"page" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"_status" "payload"."enum_pages_status" DEFAULT 'draft'
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."_pages_v" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"parent_id" integer,
|
||||
"version_slug" varchar,
|
||||
"version_title" varchar,
|
||||
"version_route" varchar,
|
||||
"version_page" jsonb,
|
||||
"version_updated_at" timestamp(3) with time zone,
|
||||
"version_created_at" timestamp(3) with time zone,
|
||||
"version__status" "payload"."enum__pages_v_version_status" DEFAULT 'draft',
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"latest" boolean
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."builder_hub_settings" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"settings" jsonb NOT NULL,
|
||||
"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"."builder_listing_settings" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"page" "payload"."enum_builder_listing_settings_page" NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"breadcrumb_label" varchar NOT NULL,
|
||||
"submit_cta_label" varchar NOT NULL,
|
||||
"submit_cta_href" varchar NOT NULL,
|
||||
"submit_cta_external" boolean DEFAULT false,
|
||||
"default_view" "payload"."enum_builder_listing_settings_default_view" DEFAULT 'grid' NOT NULL,
|
||||
"page_size" numeric NOT NULL,
|
||||
"previous_label" varchar NOT NULL,
|
||||
"next_label" varchar NOT NULL,
|
||||
"bottom_cta_title" varchar NOT NULL,
|
||||
"bottom_cta_label" varchar NOT NULL,
|
||||
"bottom_cta_href" varchar NOT NULL,
|
||||
"bottom_cta_external" boolean DEFAULT false,
|
||||
"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"."site_settings_content" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"settings" jsonb NOT NULL,
|
||||
"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"."site_navigation_content" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"navigation" jsonb NOT NULL,
|
||||
"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"."site_footer_content" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"footer" jsonb NOT NULL,
|
||||
"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"."rfps" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"status" "payload"."enum_rfps_status" DEFAULT 'draft' NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"tagline" varchar,
|
||||
"summary" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"cta_label" varchar,
|
||||
"reward_amount" numeric NOT NULL,
|
||||
"reward_currency" "payload"."enum_rfps_reward_currency" DEFAULT 'USDC' NOT NULL,
|
||||
"reward_xp" numeric,
|
||||
"apply_url" varchar NOT NULL,
|
||||
"featured" boolean DEFAULT false,
|
||||
"order" numeric,
|
||||
"published_at" timestamp(3) with time zone,
|
||||
"closes_at" timestamp(3) with time zone,
|
||||
"owner_name" varchar,
|
||||
"owner_handle" 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"."rfps_texts" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer NOT NULL,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"text" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."ideas" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"status" "payload"."enum_ideas_status" DEFAULT 'draft' NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"tagline" varchar,
|
||||
"summary" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"cta_label" varchar,
|
||||
"submitter_name" varchar,
|
||||
"submitter_handle" varchar NOT NULL,
|
||||
"reward_amount" numeric,
|
||||
"reward_currency" "payload"."enum_ideas_reward_currency" DEFAULT 'USDC',
|
||||
"reward_xp" numeric,
|
||||
"discussion_url" varchar,
|
||||
"featured" boolean DEFAULT false,
|
||||
"order" numeric,
|
||||
"submitted_at" timestamp(3) with time zone,
|
||||
"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"."ideas_texts" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer NOT NULL,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"text" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."builder_resources" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"status" "payload"."enum_builder_resources_status" DEFAULT 'draft' NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"cta_label" varchar NOT NULL,
|
||||
"href" varchar NOT NULL,
|
||||
"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"."circles_organizers" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"handle" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."circles" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"status" "payload"."enum_circles_status" DEFAULT 'draft' NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"city" varchar NOT NULL,
|
||||
"country" varchar NOT NULL,
|
||||
"region" varchar,
|
||||
"lat" numeric NOT NULL,
|
||||
"lng" numeric NOT NULL,
|
||||
"timezone" varchar NOT NULL,
|
||||
"member_count" numeric,
|
||||
"discord_channel" varchar,
|
||||
"discord_url" varchar,
|
||||
"forum_url" varchar,
|
||||
"join_url" varchar NOT NULL,
|
||||
"image_src" varchar,
|
||||
"image_alt" varchar,
|
||||
"image_width" numeric,
|
||||
"image_height" numeric,
|
||||
"order" 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 "payload"."circle_events_hosted_by" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"name" varchar NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."circle_events" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"status" "payload"."enum_circle_events_status" DEFAULT 'draft' NOT NULL,
|
||||
"circle_slug" varchar NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"location_label" varchar NOT NULL,
|
||||
"starts_at" timestamp(3) with time zone NOT NULL,
|
||||
"ends_at" timestamp(3) with time zone,
|
||||
"timezone" varchar NOT NULL,
|
||||
"venue_name" varchar,
|
||||
"address" varchar,
|
||||
"event_url" varchar,
|
||||
"featured" boolean DEFAULT false,
|
||||
"sequence_number" numeric,
|
||||
"image_src" varchar,
|
||||
"image_alt" varchar,
|
||||
"image_width" numeric,
|
||||
"image_height" 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 "payload"."circle_initiatives" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"status" "payload"."enum_circle_initiatives_status" DEFAULT 'draft' NOT NULL,
|
||||
"circle_slug" varchar NOT NULL,
|
||||
"href" varchar NOT NULL,
|
||||
"location_label" varchar NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"cta_label" varchar NOT NULL,
|
||||
"image_src" varchar,
|
||||
"image_alt" varchar,
|
||||
"image_width" numeric,
|
||||
"image_height" numeric,
|
||||
"featured" boolean DEFAULT false,
|
||||
"order" 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 "payload"."circle_resources" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"status" "payload"."enum_circle_resources_status" DEFAULT 'draft' NOT NULL,
|
||||
"title" varchar NOT NULL,
|
||||
"description" varchar NOT NULL,
|
||||
"cta_label" varchar NOT NULL,
|
||||
"href" varchar NOT NULL,
|
||||
"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"."content_change_requests" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"content_type" varchar NOT NULL,
|
||||
"target_path" varchar NOT NULL,
|
||||
"branch_name" varchar NOT NULL,
|
||||
"pull_request_number" numeric,
|
||||
"pull_request_url" varchar,
|
||||
"status" "payload"."enum_content_change_requests_status" DEFAULT 'draft' NOT NULL,
|
||||
"commit_sha" varchar,
|
||||
"created_by_id" integer,
|
||||
"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"."payload_kv" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"key" varchar NOT NULL,
|
||||
"data" jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "payload"."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"."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"."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
|
||||
);
|
||||
|
||||
ALTER TABLE "payload"."users_sessions" ADD CONSTRAINT "users_sessions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "payload"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload"."_pages_v" ADD CONSTRAINT "_pages_v_parent_id_pages_id_fk" FOREIGN KEY ("parent_id") REFERENCES "payload"."pages"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "payload"."rfps_texts" ADD CONSTRAINT "rfps_texts_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload"."rfps"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload"."ideas_texts" ADD CONSTRAINT "ideas_texts_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload"."ideas"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload"."circles_organizers" ADD CONSTRAINT "circles_organizers_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "payload"."circles"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload"."circle_events_hosted_by" ADD CONSTRAINT "circle_events_hosted_by_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "payload"."circle_events"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload"."content_change_requests" ADD CONSTRAINT "content_change_requests_created_by_id_users_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "payload"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "payload"."payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "payload"."payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload"."payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "payload"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX "users_sessions_order_idx" ON "payload"."users_sessions" USING btree ("_order");
|
||||
CREATE INDEX "users_sessions_parent_id_idx" ON "payload"."users_sessions" USING btree ("_parent_id");
|
||||
CREATE INDEX "users_updated_at_idx" ON "payload"."users" USING btree ("updated_at");
|
||||
CREATE INDEX "users_created_at_idx" ON "payload"."users" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "users_email_idx" ON "payload"."users" USING btree ("email");
|
||||
CREATE INDEX "media_updated_at_idx" ON "payload"."media" USING btree ("updated_at");
|
||||
CREATE INDEX "media_created_at_idx" ON "payload"."media" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "media_filename_idx" ON "payload"."media" USING btree ("filename");
|
||||
CREATE UNIQUE INDEX "pages_slug_idx" ON "payload"."pages" USING btree ("slug");
|
||||
CREATE UNIQUE INDEX "pages_route_idx" ON "payload"."pages" USING btree ("route");
|
||||
CREATE INDEX "pages_updated_at_idx" ON "payload"."pages" USING btree ("updated_at");
|
||||
CREATE INDEX "pages_created_at_idx" ON "payload"."pages" USING btree ("created_at");
|
||||
CREATE INDEX "pages__status_idx" ON "payload"."pages" USING btree ("_status");
|
||||
CREATE INDEX "_pages_v_parent_idx" ON "payload"."_pages_v" USING btree ("parent_id");
|
||||
CREATE INDEX "_pages_v_version_version_slug_idx" ON "payload"."_pages_v" USING btree ("version_slug");
|
||||
CREATE INDEX "_pages_v_version_version_route_idx" ON "payload"."_pages_v" USING btree ("version_route");
|
||||
CREATE INDEX "_pages_v_version_version_updated_at_idx" ON "payload"."_pages_v" USING btree ("version_updated_at");
|
||||
CREATE INDEX "_pages_v_version_version_created_at_idx" ON "payload"."_pages_v" USING btree ("version_created_at");
|
||||
CREATE INDEX "_pages_v_version_version__status_idx" ON "payload"."_pages_v" USING btree ("version__status");
|
||||
CREATE INDEX "_pages_v_created_at_idx" ON "payload"."_pages_v" USING btree ("created_at");
|
||||
CREATE INDEX "_pages_v_updated_at_idx" ON "payload"."_pages_v" USING btree ("updated_at");
|
||||
CREATE INDEX "_pages_v_latest_idx" ON "payload"."_pages_v" USING btree ("latest");
|
||||
CREATE UNIQUE INDEX "builder_hub_settings_slug_idx" ON "payload"."builder_hub_settings" USING btree ("slug");
|
||||
CREATE INDEX "builder_hub_settings_updated_at_idx" ON "payload"."builder_hub_settings" USING btree ("updated_at");
|
||||
CREATE INDEX "builder_hub_settings_created_at_idx" ON "payload"."builder_hub_settings" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "builder_listing_settings_page_idx" ON "payload"."builder_listing_settings" USING btree ("page");
|
||||
CREATE INDEX "builder_listing_settings_updated_at_idx" ON "payload"."builder_listing_settings" USING btree ("updated_at");
|
||||
CREATE INDEX "builder_listing_settings_created_at_idx" ON "payload"."builder_listing_settings" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "site_settings_content_slug_idx" ON "payload"."site_settings_content" USING btree ("slug");
|
||||
CREATE INDEX "site_settings_content_updated_at_idx" ON "payload"."site_settings_content" USING btree ("updated_at");
|
||||
CREATE INDEX "site_settings_content_created_at_idx" ON "payload"."site_settings_content" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "site_navigation_content_slug_idx" ON "payload"."site_navigation_content" USING btree ("slug");
|
||||
CREATE INDEX "site_navigation_content_updated_at_idx" ON "payload"."site_navigation_content" USING btree ("updated_at");
|
||||
CREATE INDEX "site_navigation_content_created_at_idx" ON "payload"."site_navigation_content" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "site_footer_content_slug_idx" ON "payload"."site_footer_content" USING btree ("slug");
|
||||
CREATE INDEX "site_footer_content_updated_at_idx" ON "payload"."site_footer_content" USING btree ("updated_at");
|
||||
CREATE INDEX "site_footer_content_created_at_idx" ON "payload"."site_footer_content" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "rfps_slug_idx" ON "payload"."rfps" USING btree ("slug");
|
||||
CREATE INDEX "rfps_updated_at_idx" ON "payload"."rfps" USING btree ("updated_at");
|
||||
CREATE INDEX "rfps_created_at_idx" ON "payload"."rfps" USING btree ("created_at");
|
||||
CREATE INDEX "rfps_texts_order_parent" ON "payload"."rfps_texts" USING btree ("order","parent_id");
|
||||
CREATE UNIQUE INDEX "ideas_slug_idx" ON "payload"."ideas" USING btree ("slug");
|
||||
CREATE INDEX "ideas_updated_at_idx" ON "payload"."ideas" USING btree ("updated_at");
|
||||
CREATE INDEX "ideas_created_at_idx" ON "payload"."ideas" USING btree ("created_at");
|
||||
CREATE INDEX "ideas_texts_order_parent" ON "payload"."ideas_texts" USING btree ("order","parent_id");
|
||||
CREATE UNIQUE INDEX "builder_resources_slug_idx" ON "payload"."builder_resources" USING btree ("slug");
|
||||
CREATE INDEX "builder_resources_updated_at_idx" ON "payload"."builder_resources" USING btree ("updated_at");
|
||||
CREATE INDEX "builder_resources_created_at_idx" ON "payload"."builder_resources" USING btree ("created_at");
|
||||
CREATE INDEX "circles_organizers_order_idx" ON "payload"."circles_organizers" USING btree ("_order");
|
||||
CREATE INDEX "circles_organizers_parent_id_idx" ON "payload"."circles_organizers" USING btree ("_parent_id");
|
||||
CREATE UNIQUE INDEX "circles_slug_idx" ON "payload"."circles" USING btree ("slug");
|
||||
CREATE INDEX "circles_updated_at_idx" ON "payload"."circles" USING btree ("updated_at");
|
||||
CREATE INDEX "circles_created_at_idx" ON "payload"."circles" USING btree ("created_at");
|
||||
CREATE INDEX "circle_events_hosted_by_order_idx" ON "payload"."circle_events_hosted_by" USING btree ("_order");
|
||||
CREATE INDEX "circle_events_hosted_by_parent_id_idx" ON "payload"."circle_events_hosted_by" USING btree ("_parent_id");
|
||||
CREATE UNIQUE INDEX "circle_events_slug_idx" ON "payload"."circle_events" USING btree ("slug");
|
||||
CREATE INDEX "circle_events_circle_slug_idx" ON "payload"."circle_events" USING btree ("circle_slug");
|
||||
CREATE INDEX "circle_events_updated_at_idx" ON "payload"."circle_events" USING btree ("updated_at");
|
||||
CREATE INDEX "circle_events_created_at_idx" ON "payload"."circle_events" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "circle_initiatives_slug_idx" ON "payload"."circle_initiatives" USING btree ("slug");
|
||||
CREATE INDEX "circle_initiatives_circle_slug_idx" ON "payload"."circle_initiatives" USING btree ("circle_slug");
|
||||
CREATE INDEX "circle_initiatives_updated_at_idx" ON "payload"."circle_initiatives" USING btree ("updated_at");
|
||||
CREATE INDEX "circle_initiatives_created_at_idx" ON "payload"."circle_initiatives" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "circle_resources_slug_idx" ON "payload"."circle_resources" USING btree ("slug");
|
||||
CREATE INDEX "circle_resources_updated_at_idx" ON "payload"."circle_resources" USING btree ("updated_at");
|
||||
CREATE INDEX "circle_resources_created_at_idx" ON "payload"."circle_resources" USING btree ("created_at");
|
||||
CREATE INDEX "content_change_requests_content_type_idx" ON "payload"."content_change_requests" USING btree ("content_type");
|
||||
CREATE INDEX "content_change_requests_target_path_idx" ON "payload"."content_change_requests" USING btree ("target_path");
|
||||
CREATE UNIQUE INDEX "content_change_requests_branch_name_idx" ON "payload"."content_change_requests" USING btree ("branch_name");
|
||||
CREATE INDEX "content_change_requests_status_idx" ON "payload"."content_change_requests" USING btree ("status");
|
||||
CREATE INDEX "content_change_requests_created_by_idx" ON "payload"."content_change_requests" USING btree ("created_by_id");
|
||||
CREATE INDEX "content_change_requests_updated_at_idx" ON "payload"."content_change_requests" USING btree ("updated_at");
|
||||
CREATE INDEX "content_change_requests_created_at_idx" ON "payload"."content_change_requests" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "payload_kv_key_idx" ON "payload"."payload_kv" USING btree ("key");
|
||||
CREATE INDEX "payload_preferences_key_idx" ON "payload"."payload_preferences" USING btree ("key");
|
||||
CREATE INDEX "payload_preferences_updated_at_idx" ON "payload"."payload_preferences" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_preferences_created_at_idx" ON "payload"."payload_preferences" USING btree ("created_at");
|
||||
CREATE INDEX "payload_preferences_rels_order_idx" ON "payload"."payload_preferences_rels" USING btree ("order");
|
||||
CREATE INDEX "payload_preferences_rels_parent_idx" ON "payload"."payload_preferences_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "payload_preferences_rels_path_idx" ON "payload"."payload_preferences_rels" USING btree ("path");
|
||||
CREATE INDEX "payload_preferences_rels_users_id_idx" ON "payload"."payload_preferences_rels" USING btree ("users_id");
|
||||
CREATE INDEX "payload_migrations_updated_at_idx" ON "payload"."payload_migrations" USING btree ("updated_at");
|
||||
CREATE INDEX "payload_migrations_created_at_idx" ON "payload"."payload_migrations" USING btree ("created_at");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP TABLE "payload"."users_sessions" CASCADE;
|
||||
DROP TABLE "payload"."users" CASCADE;
|
||||
DROP TABLE "payload"."media" CASCADE;
|
||||
DROP TABLE "payload"."pages" CASCADE;
|
||||
DROP TABLE "payload"."_pages_v" CASCADE;
|
||||
DROP TABLE "payload"."builder_hub_settings" CASCADE;
|
||||
DROP TABLE "payload"."builder_listing_settings" CASCADE;
|
||||
DROP TABLE "payload"."site_settings_content" CASCADE;
|
||||
DROP TABLE "payload"."site_navigation_content" CASCADE;
|
||||
DROP TABLE "payload"."site_footer_content" CASCADE;
|
||||
DROP TABLE "payload"."rfps" CASCADE;
|
||||
DROP TABLE "payload"."rfps_texts" CASCADE;
|
||||
DROP TABLE "payload"."ideas" CASCADE;
|
||||
DROP TABLE "payload"."ideas_texts" CASCADE;
|
||||
DROP TABLE "payload"."builder_resources" CASCADE;
|
||||
DROP TABLE "payload"."circles_organizers" CASCADE;
|
||||
DROP TABLE "payload"."circles" CASCADE;
|
||||
DROP TABLE "payload"."circle_events_hosted_by" CASCADE;
|
||||
DROP TABLE "payload"."circle_events" CASCADE;
|
||||
DROP TABLE "payload"."circle_initiatives" CASCADE;
|
||||
DROP TABLE "payload"."circle_resources" CASCADE;
|
||||
DROP TABLE "payload"."content_change_requests" CASCADE;
|
||||
DROP TABLE "payload"."payload_kv" CASCADE;
|
||||
DROP TABLE "payload"."payload_preferences" CASCADE;
|
||||
DROP TABLE "payload"."payload_preferences_rels" CASCADE;
|
||||
DROP TABLE "payload"."payload_migrations" CASCADE;
|
||||
DROP TYPE "payload"."enum_pages_status";
|
||||
DROP TYPE "payload"."enum__pages_v_version_status";
|
||||
DROP TYPE "payload"."enum_builder_listing_settings_page";
|
||||
DROP TYPE "payload"."enum_builder_listing_settings_default_view";
|
||||
DROP TYPE "payload"."enum_rfps_status";
|
||||
DROP TYPE "payload"."enum_rfps_reward_currency";
|
||||
DROP TYPE "payload"."enum_ideas_status";
|
||||
DROP TYPE "payload"."enum_ideas_reward_currency";
|
||||
DROP TYPE "payload"."enum_builder_resources_status";
|
||||
DROP TYPE "payload"."enum_circles_status";
|
||||
DROP TYPE "payload"."enum_circle_events_status";
|
||||
DROP TYPE "payload"."enum_circle_initiatives_status";
|
||||
DROP TYPE "payload"."enum_circle_resources_status";
|
||||
DROP TYPE "payload"."enum_content_change_requests_status";`)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as migration_20260528_233905_initial from './20260528_233905_initial';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20260528_233905_initial.up,
|
||||
down: migration_20260528_233905_initial.down,
|
||||
name: '20260528_233905_initial'
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,72 @@
|
||||
# =============================================================================
|
||||
# Production deployment for apps/cms with a self-hosted Postgres.
|
||||
#
|
||||
# Usage:
|
||||
# 1. cp apps/cms/.env.docker.example .env.docker # then fill in real values
|
||||
# 2. docker compose -f docker-compose.prod.yml --env-file .env.docker up -d --build
|
||||
#
|
||||
# The container entrypoint applies Payload SQL migrations before starting, so
|
||||
# the schema is set up on first boot — no manual migration step is needed.
|
||||
# (In production Payload never schema-pushes; PAYLOAD_DB_PUSH only affects dev.)
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-bookworm
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
# Not published to the host — only the cms service reaches it over the
|
||||
# internal network. Uncomment to expose for external admin access.
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
|
||||
cms:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/cms/Dockerfile
|
||||
args:
|
||||
# Baked into the client bundle at build time — must be the real origins.
|
||||
NEXT_PUBLIC_SERVER_URL: ${NEXT_PUBLIC_SERVER_URL}
|
||||
NEXT_PUBLIC_WEB_URL: ${NEXT_PUBLIC_WEB_URL}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
# Connects to the self-hosted Postgres over the compose network.
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
||||
PAYLOAD_SECRET: ${PAYLOAD_SECRET}
|
||||
NEXT_PUBLIC_SERVER_URL: ${NEXT_PUBLIC_SERVER_URL}
|
||||
NEXT_PUBLIC_WEB_URL: ${NEXT_PUBLIC_WEB_URL}
|
||||
PAYLOAD_DB_SCHEMA: ${PAYLOAD_DB_SCHEMA:-payload}
|
||||
PAYLOAD_DB_PUSH: ${PAYLOAD_DB_PUSH:-true}
|
||||
PAYLOAD_DB_POOL_MAX: ${PAYLOAD_DB_POOL_MAX:-10}
|
||||
# GitHub "Create PR" workflow (optional — admin feature).
|
||||
GITHUB_OWNER: ${GITHUB_OWNER:-}
|
||||
GITHUB_REPO: ${GITHUB_REPO:-}
|
||||
GITHUB_APP_ID: ${GITHUB_APP_ID:-}
|
||||
GITHUB_APP_PRIVATE_KEY: ${GITHUB_APP_PRIVATE_KEY:-}
|
||||
GITHUB_INSTALLATION_ID: ${GITHUB_INSTALLATION_ID:-}
|
||||
GITHUB_PR_BASE_BRANCH: ${GITHUB_PR_BASE_BRANCH:-develop}
|
||||
ports:
|
||||
- "${CMS_PORT:-3001}:3000"
|
||||
volumes:
|
||||
# Persist uploaded media (Payload writes to apps/web/public/cms/uploads).
|
||||
- cms_uploads:/app/apps/web/public/cms/uploads
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
cms_uploads:
|
||||
Reference in New Issue
Block a user