Skip to content

feat(api): postgres schema migrations via Flyway - #40

Merged
markus-azer merged 8 commits into
mainfrom
feat/db-migrations
Jul 20, 2026
Merged

feat(api): postgres schema migrations via Flyway#40
markus-azer merged 8 commits into
mainfrom
feat/db-migrations

Conversation

@markus-azer

@markus-azer markus-azer commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

Introduces Postgres schema migrations with Flyway and a first profiles table.

Changes

  • Flyway runs versioned SQL from apps/api/sql/ as a docker-compose service. The api waits on it via service_completed_successfully.
  • V1__profiles.sqlprofiles table: id (uuid), name, tagline, avatar_url, socials (jsonb), body_md, updated_at.
  • Postgres.transaction() — runs a function in one transaction, commits on success, rolls back on throw.
  • Decision log — why Flyway over node-pg-migrate and Drizzle.

Notes

  • transaction() has no caller yet — it lands ahead of the content-sync work.
  • Apply locally with pnpm compose:up.

🤖 Generated with Claude Code

Greptile Summary

This PR wires Flyway into docker-compose to run versioned SQL migrations from apps/api/sql/, adds the first migration (V1__profiles.sql) creating a profiles table, and introduces a Postgres.transaction() helper for running work inside a single database transaction.

  • docker-compose.yml — Flyway runs after postgres is healthy and the API is gated on service_completed_successfully, so a failed migration blocks startup rather than letting the app start against a stale schema.
  • V1__profiles.sqlprofiles table with id uuid PRIMARY KEY, typed columns, and socials jsonb; updated_at is declared NOT NULL but has no DEFAULT and no auto-update trigger, so callers must supply and maintain it entirely by hand.
  • postgres.tstransaction() follows a standard begin/rollback/commit pattern but the ROLLBACK call sits outside a nested try/catch, meaning a connection failure during rollback discards the original error.

Confidence Score: 3/5

Safe to merge for local dev; the transaction helper has an error-masking bug that should be fixed before it gets a real caller.

The docker-compose wiring and migration file are straightforward and correct. The transaction() method has a real defect: if the connection drops while ROLLBACK is executing, the rollback exception replaces the original error, making failures harder to diagnose. The method has no callers yet, so nothing breaks today, but it will cause confusing error propagation the moment content-sync work lands.

apps/api/src/infrastructure/db/postgres.ts — the rollback error-swallowing issue should be addressed before transaction() gets its first caller

Important Files Changed

Filename Overview
apps/api/src/infrastructure/db/postgres.ts Adds transaction() method — correct structure but ROLLBACK errors can swallow the original exception when the connection drops mid-transaction
apps/api/sql/V1__profiles.sql New profiles table — well-typed columns; updated_at lacks DEFAULT and auto-update trigger, requiring callers to always supply and maintain it manually
docker-compose.yml Adds Flyway migration service that gates the API via service_completed_successfully — startup ordering is correct and dependency chain is sound
docs/decision-log/architecture/db-migrations.md Decision log for choosing Flyway — clear rationale, mentions integration-test reuse of V*.sql files against thoth_test (not implemented in this PR)

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant DC as docker-compose
    participant PG as postgres
    participant FW as flyway
    participant API as api

    DC->>PG: start
    PG-->>DC: healthy (pg_isready)
    DC->>FW: start (depends_on postgres healthy)
    FW->>PG: connect + run V1__profiles.sql
    PG-->>FW: migration applied
    FW-->>DC: exited 0 (service_completed_successfully)
    DC->>API: start (depends_on flyway completed)
    API->>PG: SELECT 1 (start/ping)
    PG-->>API: ok
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant DC as docker-compose
    participant PG as postgres
    participant FW as flyway
    participant API as api

    DC->>PG: start
    PG-->>DC: healthy (pg_isready)
    DC->>FW: start (depends_on postgres healthy)
    FW->>PG: connect + run V1__profiles.sql
    PG-->>FW: migration applied
    FW-->>DC: exited 0 (service_completed_successfully)
    DC->>API: start (depends_on flyway completed)
    API->>PG: SELECT 1 (start/ping)
    PG-->>API: ok
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(api): add postgres transaction help..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used:

  • Context used - CLAUDE.md (source)

Summary by CodeRabbit

  • New Features
    • Added persistent profiles storage with required name, social links (JSON), and body content, plus optional tagline and avatar URL with update timestamps.
    • Introduced automated, versioned SQL migrations via a dedicated migration service to ensure schema is applied before the API starts.
  • Bug Fixes
    • Improved reliability of multi-step database operations by using transactional execution with commit/rollback handling.
  • Documentation
    • Added an architecture decision log entry explaining the database migration approach and tool selection.

Copilot AI review requested due to automatic review settings July 16, 2026 22:02
@github-actions github-actions Bot added the size/s < 100 LOC label Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds a versioned profiles schema migration, configures Flyway to run before API startup, documents the migration decision, pins the PostgreSQL image, and adds pooled-client transaction handling to PostgreSQL infrastructure.

Changes

Database foundation

Layer / File(s) Summary
Versioned schema migration and startup wiring
apps/api/sql/V1__profiles.sql, docker-compose.yml, docs/decision-log/architecture/db-migrations.md
Adds the profiles table, runs Flyway after PostgreSQL is healthy, starts the API after migration success, pins the PostgreSQL image, and documents the migration decision.
PostgreSQL transaction lifecycle
apps/api/src/infrastructure/db/postgres.ts
Adds a transaction method that begins, commits or rolls back, rethrows failures, and releases the pooled client.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant postgres
  participant flyway
  participant api
  postgres->>flyway: report healthy
  flyway->>postgres: execute V1__profiles.sql
  flyway-->>api: signal successful completion
  api->>postgres: start after migration completion
Loading

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding PostgreSQL schema migrations via Flyway.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/db-migrations

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

@markus-azer markus-azer changed the title feat(api): Postgres schema migrations via Flyway feat(api): postgres schema migrations via Flyway Jul 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class Postgres schema migrations to the API stack by introducing Flyway as the migration runner and landing an initial profiles table, alongside a small DB helper to support upcoming transactional work.

Changes:

  • Adds an architecture decision log entry documenting the choice of Flyway and the intended migration workflow.
  • Updates docker-compose.yml to run Flyway migrations from apps/api/sql/ before starting the API container.
  • Introduces a Postgres.transaction() helper and a first migration V1__profiles.sql to create the profiles table.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
docs/decision-log/architecture/db-migrations.md Documents the migration approach (Flyway + versioned SQL under apps/api/sql/).
docker-compose.yml Adds a Flyway service and gates API startup on successful migrations.
apps/api/src/infrastructure/db/postgres.ts Adds transaction() helper for running work on a single client in a DB transaction.
apps/api/sql/V1__profiles.sql Adds initial schema migration to create the profiles table.

Comment thread docker-compose.yml
Comment thread apps/api/src/infrastructure/db/postgres.ts
Comment thread apps/api/src/infrastructure/db/postgres.ts
Comment thread apps/api/sql/V1__profiles.sql
Copilot AI review requested due to automatic review settings July 20, 2026 19:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

apps/api/src/infrastructure/db/postgres.ts:45

  • ROLLBACK errors are currently swallowed silently (catch(() => {})). This makes connection/transaction failures harder to diagnose in production. Elsewhere, swallowed errors are still logged (e.g. apps/api/src/lifecycle/manager.ts:64-69). Consider logging rollback failures while still rethrowing the original error.
		} catch (err) {
			await client.query("ROLLBACK").catch(() => {});
			throw err;

Comment thread docs/decision-log/architecture/db-migrations.md
Comment thread apps/api/src/infrastructure/db/postgres.ts
Comment thread apps/api/sql/V1__profiles.sql

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/api/src/infrastructure/db/postgres.ts (1)

36-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add transaction lifecycle tests before the first caller.

Cover successful commit, callback failure with original-error preservation, rollback failure causing client destruction, and client release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/infrastructure/db/postgres.ts` around lines 36 - 49, Add
transaction lifecycle tests targeting the transaction method in the Postgres
database class before introducing its first caller. Verify successful callbacks
commit and release the client, callback failures preserve the original error
while rolling back, and rollback failures destroy the client rather than
silently reusing it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/infrastructure/db/postgres.ts`:
- Around line 43-47: Update the transaction error-handling block around the
catch/finally logic to track whether ROLLBACK fails and release the client with
destruction enabled in that case. Preserve normal reuse for successful rollback,
while ensuring the finally block invokes client.release(true) when the
connection’s transaction state is unknown.

---

Nitpick comments:
In `@apps/api/src/infrastructure/db/postgres.ts`:
- Around line 36-49: Add transaction lifecycle tests targeting the transaction
method in the Postgres database class before introducing its first caller.
Verify successful callbacks commit and release the client, callback failures
preserve the original error while rolling back, and rollback failures destroy
the client rather than silently reusing it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a9fca19-e64a-46dc-b55f-955645df3cdc

📥 Commits

Reviewing files that changed from the base of the PR and between 9cf0b5f and 2be53d1.

📒 Files selected for processing (4)
  • apps/api/sql/V1__profiles.sql
  • apps/api/src/infrastructure/db/postgres.ts
  • docker-compose.yml
  • docs/decision-log/architecture/db-migrations.md

Comment thread apps/api/src/infrastructure/db/postgres.ts Outdated
Copilot AI review requested due to automatic review settings July 20, 2026 19:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

apps/api/sql/V1__profiles.sql:8

  • updated_at is NOT NULL but has no DEFAULT, so every INSERT must supply it explicitly. Also, without a BEFORE UPDATE trigger, updated_at will become stale unless every UPDATE remembers to set it.

Consider giving it DEFAULT now() and adding a trigger to automatically refresh it on updates.

    socials    jsonb NOT NULL,
    body_md    text NOT NULL,
    updated_at timestamptz NOT NULL

Copilot AI review requested due to automatic review settings July 20, 2026 19:47
@markus-azer
markus-azer enabled auto-merge (squash) July 20, 2026 19:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

docs/decision-log/architecture/db-migrations.md:14

  • Decision-log rule requires the final Why: ... line to name each rejected alternative and why it was rejected (docs/decision-log/README.md:13). This entry mentions node-pg-migrate and Drizzle, but only explains the rejection reason for Drizzle. Update the Why: line to include why node-pg-migrate was not selected, and keep the language formal (e.g., “Docker”, “runs on the JVM”).
Trade-off: Flyway is JVM, run via docker not node. Integration tests reuse the same `V*.sql` against `thoth_test` to skip the JVM.

Why: raw `pg`, no ORM, so plain SQL keeps schema infra-owned with checksums for free. node-pg-migrate was the node-native runner-up. Drizzle was rejected — it adds an ORM the codebase avoids.

Comment thread apps/api/src/infrastructure/db/postgres.ts
Comment thread apps/api/src/infrastructure/db/postgres.ts
Copilot AI review requested due to automatic review settings July 20, 2026 19:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 20, 2026 20:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

docs/decision-log/architecture/db-migrations.md:14

  • Decision-log rule requires naming each rejected alternative and why (docs/decision-log/README.md:13). This entry names node-pg-migrate as the runner-up, but doesn’t state why it was rejected, so the rationale is incomplete.
Trade-off: Flyway is JVM, run via docker not node. Integration tests reuse the same `V*.sql` against `thoth_test` to skip the JVM.

Why: raw `pg`, no ORM, so plain SQL keeps schema infra-owned with checksums for free. node-pg-migrate was the node-native runner-up. Drizzle was rejected — it adds an ORM the codebase avoids.

@markus-azer
markus-azer merged commit d647790 into main Jul 20, 2026
8 checks passed
@markus-azer
markus-azer deleted the feat/db-migrations branch July 20, 2026 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s < 100 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants