Skip to content

[#438] Add DB CI Migration to remove Dependency on Dev Lead#439

Merged
DVidal1205 merged 10 commits into
mainfrom
blade/db-migration-job
Apr 12, 2026
Merged

[#438] Add DB CI Migration to remove Dependency on Dev Lead#439
DVidal1205 merged 10 commits into
mainfrom
blade/db-migration-job

Conversation

@DVidal1205
Copy link
Copy Markdown
Contributor

@DVidal1205 DVidal1205 commented Apr 12, 2026

Why

PRs would be stuck in review hell until I was able to be in front of an editor to properly apply db:push commands before approving a merge. This PR fixes that dependency on me through a series of CI steps.

What

  • Added a committed Drizzle migration flow for @forge/db, including db:generate and db:migrate.
  • Added CI checks to verify migrations are committed, apply cleanly on a fresh database, and upgrade a prod-like sanitized dataset.
  • Updated the DB restore flow so the sanitized backup can be loaded into arbitrary ephemeral databases for smoke testing.
  • Updated the docs and PR template so local development and schema-change guidance now point to db:migrate instead of db:push.

Closes: #438

Test Plan

Going to need to merge this in with the migration set to false right now, so the job will pass even thought it is skipped. Then I will need to do some manual migrations to update the drizzle state, verify, and we should be good to have this run on every future PR :)

Checklist

  • Database: No schema changes, OR I have contacted the Development Lead to run db:push before merging
  • Environment Variables: No environment variables changed, OR I have contacted the Development Lead to modify them on Coolify BEFORE merging.

Summary by CodeRabbit

  • New Features

    • CI now validates generated migrations, runs fresh-DB and prod-like migration smoke tests, and can apply guarded production migrations on main.
    • Added db:generate and db:migrate workflows; contributors must generate and commit migration outputs when schemas change.
  • Documentation

    • Updated contributing, onboarding, and package docs to document the new migration workflow, local setup steps, and CI validation expectations.

@DVidal1205 DVidal1205 self-assigned this Apr 12, 2026
@DVidal1205 DVidal1205 requested a review from a team as a code owner April 12, 2026 21:01
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: a552dcc3-b86f-4990-9d4f-c0d58ace0ca7

📥 Commits

Reviewing files that changed from the base of the PR and between da0006b and 4656c8e.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • packages/db/drizzle/0001_careless_eternity.sql
  • packages/db/drizzle/meta/0000_snapshot.json
  • packages/db/drizzle/meta/0001_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/package.json
  • packages/db/scripts/get_prod_db.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/db/package.json
  • packages/db/drizzle/0001_careless_eternity.sql
  • packages/db/drizzle/meta/0001_snapshot.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/db/drizzle/meta/_journal.json
  • packages/db/scripts/get_prod_db.ts
  • .github/workflows/ci.yml
  • packages/db/drizzle/meta/0000_snapshot.json

📝 Walkthrough

Walkthrough

Replaces manual db:push workflow with committed SQL migrations and a Drizzle-based migration workflow: adds migration generation/migrate scripts, CI jobs to validate/apply/smoke-test migrations, documentation updates, initial migration files/snapshots/journal, and related DB tooling changes.

Changes

Cohort / File(s) Summary
PR Template & CI
\.github/PULL_REQUEST_TEMPLATE, \.github/workflows/ci.yml
PR checklist now requires pnpm db:generate and committed generated files; adds migration_check, migration_apply_fresh, migration_upgrade_smoke CI jobs; gates and adds migrate_prod; wires migration jobs into build dependencies.
Docs & CONTRIBUTING
CONTRIBUTING.md, docs/ARCHITECTURE.md, docs/GETTING-STARTED.md, packages/db/README.md
Replaces db:push guidance with db:migrate/db:generate workflow; documents committing generated migration artifacts and CI validation steps.
Root & Package Scripts
package.json, packages/db/package.json
Adds db:generate and db:migrate scripts at root; adds generate and migrate scripts to @forge/db package (drizzle-kit invocations + formatting).
Drizzle Config & Meta
packages/db/drizzle.config.ts, packages/db/drizzle/meta/*
Sets Drizzle out: "./drizzle" and adds migration snapshots and _journal.json entries for new migrations.
Migrations (SQL)
packages/db/drizzle/0000_petite_sunfire.sql, packages/db/drizzle/0001_careless_eternity.sql
Adds initial large schema migration (enums, auth tables, domain tables, constraints, indexes) and a follow-up migration adding created_at/updated_at columns to issues.
DB Tools & Scripts
packages/db/scripts/get_prod_db.ts
Rewrote truncate flow to write a temporary truncate_local.sql, added -v ON_ERROR_STOP=1, and wrapped execution in try/finally to remove the temp file.

Sequence Diagram

sequenceDiagram
    actor Dev as Developer
    participant PR as Pull Request
    participant CI_Check as migration_check
    participant CI_Fresh as migration_apply_fresh
    participant Build as build
    participant CI_Smoke as migration_upgrade_smoke
    participant Prod as migrate_prod

    Dev->>PR: Commit schema changes + run `pnpm db:generate` and commit generated files
    PR->>CI_Check: Start migration validation
    CI_Check->>CI_Check: Run `pnpm db:generate` and fail if diffs exist
    CI_Check-->>CI_Fresh: pass
    CI_Fresh->>CI_Fresh: Apply migrations to fresh DB twice (idempotency)
    CI_Fresh-->>Build: pass
    Build->>CI_Smoke: artifact available
    CI_Smoke->>CI_Smoke: materialize base schema, restore sanitized prod-like data, run `db:migrate` twice
    CI_Smoke-->>PR: pass
    alt Merged to main & ENABLE_PROD_DB_MIGRATIONS == 'true'
        PR->>Prod: trigger prod migration
        Prod->>Prod: run `pnpm db:migrate` against PROD_DATABASE_URL
        Prod-->>Dev: production schema updated
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • KnightHacks/forge PR 416 — Adds/changes issue-related schema and indices that overlap with the migration contents.
  • KnightHacks/forge PR 415 — Touches dues payment schema changes represented in the committed migrations.

Suggested labels

Database, Documentation, Major

Suggested reviewers

  • DGoel1602
  • alexanderpaolini
  • must108
🚥 Pre-merge checks | ✅ 6 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Validated Env Access ❓ Inconclusive Cannot verify if process.env is used outside allowed locations (.config. files and env.ts) due to limited repository access in the sandbox environment. Examine all TypeScript files modified in this PR, especially packages/db/scripts/get_prod_db.ts, to verify environment variable access uses import from env.ts rather than direct process.env calls.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title includes required issue bracket format [#438], is concise at 59 characters, and clearly describes the main change: automating DB migrations via CI.
Linked Issues check ✅ Passed PR implements all core objectives from #438: replaces manual dev lead db:push with automated migration CI job, adds migration validations (fresh DB apply, prod-like upgrade smoke test), and shifts responsibility to maintainers.
Out of Scope Changes check ✅ Passed All changes directly support the migration automation objective: CI workflows, documentation updates, scripts, and migration files are all in scope for eliminating manual dev lead dependency.
No Hardcoded Secrets ✅ Passed Comprehensive search of all modified files found no hardcoded secrets, API keys, passwords, or tokens. All sensitive values properly use environment variables.
No Typescript Escape Hatches ✅ Passed The any type in packages/db/scripts/seed_devdb.ts is pre-existing code with an explicit ESLint disable comment and was not modified by this PR. The modified files contain no TypeScript escape hatches.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch blade/db-migration-job

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/db/scripts/get_prod_db.ts (1)

78-100: ⚠️ Potential issue | 🟠 Major

Avoid shell-interpolating DB connection parameters in psql commands.

Lines 79 and 99 construct shell commands using template literals with database connection fields (${host}, ${port}, ${user}, ${database}, ${objectName}). This breaks with special characters and creates an injection surface—e.g., a hostname with backticks could execute arbitrary code.

Use execFile() with argv to pass arguments safely, avoiding shell interpretation entirely:

Suggested approach
-import { exec } from "child_process";
+import { execFile, spawn } from "child_process";
 ...
-const execAsync = promisify(exec);
+const execFileAsync = promisify(execFile);
 ...
     // For the truncate operation (line 78):
-    await execAsync(
-      `psql -v ON_ERROR_STOP=1 -h ${host} -p ${port} -U ${user} -d ${database} << 'EOF'
-      ...SQL...
-      EOF`,
+    await execFileAsync(
+      "psql",
+      ["-v", "ON_ERROR_STOP=1", "-h", host, "-p", port, "-U", user, "-d", database, "-c", 
+       "SET session_replication_role = replica; DO $$ ... END $$; SET session_replication_role = DEFAULT;"],
       { env: envN },
     );
 
     // For the restore operation (line 98):
-    await execAsync(
-      `psql -v ON_ERROR_STOP=1 -h ${host} -p ${port} -U ${user} ${database} < ${objectName}`,
+    const restore = spawn(
+      "psql",
+      ["-v", "ON_ERROR_STOP=1", "-h", host, "-p", port, "-U", user, "-d", database],
+      { env: envN, stdio: ["pipe", "inherit", "inherit"] },
     );
+    fs.createReadStream(objectName).pipe(restore.stdin);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/scripts/get_prod_db.ts` around lines 78 - 100, The code currently
shell-interpolates DB params into psql command strings inside execAsync (see
execAsync calls around the TRUNCATE heredoc and the file import using
${objectName}), which risks injection and breaking on special chars; replace
these with a non-shell exec approach (e.g., execFile or child_process.spawn)
passing psql as the executable and args as an array
(["-v","ON_ERROR_STOP=1","-h", host, "-p", String(port), "-U", user, "-d",
database, "-c", "<SQL statement>"] for the TRUNCATE block instead of a heredoc,
and ["-v","ON_ERROR_STOP=1","-h", host, "-p", String(port), "-U", user,
database, "-f", objectName] or stream the file to stdin for the import), ensure
you pass env: envN unchanged, and remove direct template interpolation of
host/port/user/database/objectName in shell commands.
🧹 Nitpick comments (2)
docs/GETTING-STARTED.md (1)

84-88: Add db:migrate after db:generate in the schema-change flow.

At Line 84, contributors are told to generate and commit migrations, but applying them locally is implied rather than explicit. Adding pnpm db:migrate here makes the test loop clearer and catches migration SQL issues earlier.

🛠️ Suggested update
 When you change files in `packages/db/src/schemas`, generate a new migration and commit it:
 
 ```bash
 pnpm db:generate
+pnpm db:migrate
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/GETTING-STARTED.md around lines 84 - 88, Update the schema-change
instructions to run the migration after generating it: after the pnpm db:generate step add an explicit pnpm db:migrate step so contributors both
create and apply the migration locally (ensure the docs show pnpm db:generate
followed by pnpm db:migrate and instruct committing the generated migration).


</details>

</blockquote></details>
<details>
<summary>docs/ARCHITECTURE.md (1)</summary><blockquote>

`81-85`: **Clarify package paths now that migrations are part of the source of truth.**

Line 81 adds migrations, but Line 82 still reads as if `@forge/db` is only under `packages/db/src/schemas/`. Consider listing both schema and migration locations to avoid confusion.

<details>
<summary>📝 Suggested doc tweak</summary>

```diff
-- Located in `packages/db/src/schemas/`
+- Schemas live in `packages/db/src/schemas/`, and committed migrations live in `packages/db/drizzle/`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/ARCHITECTURE.md` around lines 81 - 85, Update the ARCHITECTURE.md
wording to list both schema source locations so readers know migrations are now
part of the source of truth: mention that `@forge/db` includes committed SQL
migrations under packages/db/drizzle/ (or similar migrations directory) as well
as schema definitions under packages/db/src/schemas/, and clarify the developer
workflow (pnpm db:migrate then pnpm db:generate) so the generated files in
packages/db/drizzle/ are reviewed source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/db/scripts/get_prod_db.ts`:
- Around line 78-100: The code currently shell-interpolates DB params into psql
command strings inside execAsync (see execAsync calls around the TRUNCATE
heredoc and the file import using ${objectName}), which risks injection and
breaking on special chars; replace these with a non-shell exec approach (e.g.,
execFile or child_process.spawn) passing psql as the executable and args as an
array (["-v","ON_ERROR_STOP=1","-h", host, "-p", String(port), "-U", user, "-d",
database, "-c", "<SQL statement>"] for the TRUNCATE block instead of a heredoc,
and ["-v","ON_ERROR_STOP=1","-h", host, "-p", String(port), "-U", user,
database, "-f", objectName] or stream the file to stdin for the import), ensure
you pass env: envN unchanged, and remove direct template interpolation of
host/port/user/database/objectName in shell commands.

---

Nitpick comments:
In `@docs/ARCHITECTURE.md`:
- Around line 81-85: Update the ARCHITECTURE.md wording to list both schema
source locations so readers know migrations are now part of the source of truth:
mention that `@forge/db` includes committed SQL migrations under
packages/db/drizzle/ (or similar migrations directory) as well as schema
definitions under packages/db/src/schemas/, and clarify the developer workflow
(pnpm db:migrate then pnpm db:generate) so the generated files in
packages/db/drizzle/ are reviewed source of truth.

In `@docs/GETTING-STARTED.md`:
- Around line 84-88: Update the schema-change instructions to run the migration
after generating it: after the `pnpm db:generate` step add an explicit `pnpm
db:migrate` step so contributors both create and apply the migration locally
(ensure the docs show `pnpm db:generate` followed by `pnpm db:migrate` and
instruct committing the generated migration).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: c0055da0-cba0-4603-ad3d-265d8722459d

📥 Commits

Reviewing files that changed from the base of the PR and between 8984aef and da0006b.

📒 Files selected for processing (13)
  • .github/PULL_REQUEST_TEMPLATE
  • .github/workflows/ci.yml
  • CONTRIBUTING.md
  • docs/ARCHITECTURE.md
  • docs/GETTING-STARTED.md
  • package.json
  • packages/db/README.md
  • packages/db/drizzle.config.ts
  • packages/db/drizzle/0000_petite_sunfire.sql
  • packages/db/drizzle/meta/0000_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/package.json
  • packages/db/scripts/get_prod_db.ts

Copy link
Copy Markdown
Contributor

@DGoel1602 DGoel1602 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly lgtm I'm glad we have a real pipeline for this now

@@ -0,0 +1,13 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file probably shouldn't be commited

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is necessary for the migration logic

Comment thread packages/db/scripts/get_prod_db.ts Outdated
//Imma be real ts was GPT pls lmk if its cooked
await execAsync(
`psql -h localhost -p ${port} -U ${user} -d local << 'EOF'
`psql -v ON_ERROR_STOP=1 -h ${host} -p ${port} -U ${user} -d ${database} << 'EOF'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using localhost so that admins didnt accidently push it backups to prod, is there a reason to change this? I get changing local to database but why change host

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, this was from when i was thinking of using this job in the CI in ephemeral stuff but changed my mind on that

Copy link
Copy Markdown
Contributor

@DGoel1602 DGoel1602 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm type beat

@DVidal1205 DVidal1205 added this pull request to the merge queue Apr 12, 2026
Merged via the queue into main with commit 3d70821 Apr 12, 2026
11 checks passed
@DVidal1205 DVidal1205 deleted the blade/db-migration-job branch April 12, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Database migrations over manual dev lead work

2 participants