diff --git a/.github/workflows/db-migration.yml b/.github/workflows/db-migration.yml new file mode 100644 index 0000000..713974f --- /dev/null +++ b/.github/workflows/db-migration.yml @@ -0,0 +1,59 @@ +name: Database Migration Check + +on: + pull_request: + paths: + - 'prisma/schema.prisma' + - 'prisma/migrations/**' + +permissions: + contents: read + checks: write + +jobs: + validate: + name: Validate Migrations + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: test_user + POSTGRES_PASSWORD: test_password + POSTGRES_DB: test_db + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Check migration status + run: npx prisma migrate status + env: + DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db + + - name: Deploy migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db + + - name: Generate Prisma client + run: npx prisma generate + env: + DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md new file mode 100644 index 0000000..e50a861 --- /dev/null +++ b/docs/MIGRATION.md @@ -0,0 +1,132 @@ +# Database Migration Workflow + +Sentinel uses [Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate) for schema management. This document describes the migration workflow for local development and production deployments. + +## Prerequisites + +- Node.js 20+ and dependencies installed (`npm install`) +- PostgreSQL instance running (local, Docker, or remote) +- `DATABASE_URL` environment variable set + +## Migration Scripts + +All migration commands are available via npm scripts or directly through shell scripts in `scripts/`. + +### Quick Reference + +| Command | Purpose | +|---|---| +| `npm run prisma:migrate:deploy` | Apply all pending migrations | +| `npm run prisma:migrate:status` | Show which migrations have been applied | +| `npm run db:migrate:new` | Create a migration file without applying it | +| `npm run db:migrate:dev [name]` | Full dev cycle: generate client + apply + create migration | +| `npm run db:status` | Check migration status | +| `npm run db:rollback:guide` | Print rollback instructions | +| `npm run prisma:studio` | Open Prisma Studio to inspect data | +| `npm run prisma:migrate:reset` | Reset database (drops all data) | + +## Development Workflow + +### 1. Make schema changes + +Edit `prisma/schema.prisma` to add/modify models, fields, or relations. + +### 2. Run the migration + +```bash +# Apply pending migrations and create a new migration for your changes +npm run db:migrate:dev add_user_roles +``` + +This runs: +1. `prisma generate` — regenerates the Prisma client +2. `prisma migrate deploy` — applies any pending migrations from other developers +3. `prisma migrate dev --name add_user_roles` — creates and applies the new migration + +### 3. Generate Prisma client (if needed) + +```bash +npm run prisma:generate +``` + +### 4. Verify migration status + +```bash +npm run prisma:migrate:status +``` + +## Production Deployment + +### Applying migrations + +```bash +# Set the production DATABASE_URL +export DATABASE_URL="postgresql://user:pass@prod-host:5432/sentinel" + +# Apply migrations +npm run prisma:migrate:deploy +``` + +### Via Docker + +Migrations run automatically during Docker startup via the entrypoint. To run manually: + +```bash +npm run docker:db:migrate +``` + +## Rollback Strategy + +Prisma Migrate does not support automatic rollback. Follow these steps: + +### Option 1 — Reverse migration (recommended) + +1. Revert the schema changes in `prisma/schema.prisma` +2. Create a reverse migration: + ```bash + npm run db:migrate:new + # Name it: rollback_ + ``` +3. Review and edit the generated migration file +4. Deploy: + ```bash + npm run prisma:migrate:deploy + ``` + +### Option 2 — Reset (development only) + +```bash +npm run prisma:migrate:reset +``` + +This drops all data and re-applies all migrations. + +### Option 3 — Mark a failed migration as rolled back + +If a migration was partially applied: + +```bash +npx prisma migrate resolve --rolled-back +``` + +Then fix the issue and create a new migration. + +## CI/CD Integration + +The CI pipeline (`ci.yml`) runs `prisma:generate` as part of the build step. For deployments, add the following step after build: + +```yaml +- name: Run database migrations + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} +``` + +## Best Practices + +1. **One change per migration** — Keep migrations focused on a single schema change +2. **Review migration SQL** — Always review the generated SQL before deploying +3. **Never edit applied migrations** — Create new migrations instead +4. **Commit migration files** — All migration files in `prisma/migrations/` must be committed +5. **Back up production DB** — Before deploying migrations to production +6. **Test locally first** — Always run migrations against a local copy of the database diff --git a/package.json b/package.json index 3196a77..ad6176a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,15 @@ "test:ci": "npm run test -- --ci --coverage", "prisma:generate": "prisma generate", "prisma:migrate": "prisma migrate dev", + "prisma:migrate:deploy": "prisma migrate deploy", + "prisma:migrate:status": "prisma migrate status", + "prisma:migrate:reset": "prisma migrate reset", + "prisma:studio": "prisma studio", + "db:migrate": "bash scripts/migrate.sh deploy", + "db:migrate:dev": "bash scripts/migrate-dev.sh", + "db:migrate:new": "bash scripts/migrate.sh create-only", + "db:status": "bash scripts/migrate.sh status", + "db:rollback:guide": "bash scripts/rollback.sh", "docker:up": "docker-compose up -d", "docker:down": "docker-compose down", "docker:logs": "docker-compose logs -f", diff --git a/scripts/migrate-dev.sh b/scripts/migrate-dev.sh new file mode 100755 index 0000000..6326bef --- /dev/null +++ b/scripts/migrate-dev.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ────────────────────────────────────────────────────────────────────────────── +# Sentinel – Development Migration Workflow +# +# Runs the full dev cycle: generate client → apply pending → create new +# migration if schema changed → seed (if applicable). +# +# Usage: +# ./scripts/migrate-dev.sh [name] +# +# name Optional. If provided, creates a named migration for schema changes. +# If omitted, only applies pending migrations and re-generates client. +# ────────────────────────────────────────────────────────────────────────────── + +BIN="npx prisma" +SCHEMA="prisma/schema.prisma" + +echo "✦ Step 1 — Generate Prisma client" +$BIN generate --schema="$SCHEMA" + +echo "" +echo "✦ Step 2 — Apply pending migrations" +$BIN migrate deploy --schema="$SCHEMA" + +NAME="${1:-}" +if [ -n "$NAME" ]; then + echo "" + echo "✦ Step 3 — Create migration: $NAME" + $BIN migrate dev --schema="$SCHEMA" --name "$NAME" + echo "✓ Migration '$NAME' created and applied" +fi + +echo "" +echo "✓ Development migration cycle complete" diff --git a/scripts/migrate.sh b/scripts/migrate.sh new file mode 100755 index 0000000..11f548b --- /dev/null +++ b/scripts/migrate.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ────────────────────────────────────────────────────────────────────────────── +# Sentinel – Database Migration Workflow +# +# Usage: +# ./scripts/migrate.sh [command] +# +# Commands: +# generate Generate a new migration from schema changes +# deploy Apply pending migrations to the database +# status Show migration status +# reset Reset the database (drops all data) +# studio Open Prisma Studio to inspect data +# create-only Create a migration file without applying it +# +# Environment: +# DATABASE_URL Required. PostgreSQL connection string. +# +# Examples: +# ./scripts/migrate.sh generate +# ./scripts/migrate.sh deploy +# DATABASE_URL="postgresql://user:pass@localhost:5432/sentinel" ./scripts/migrate.sh deploy +# ────────────────────────────────────────────────────────────────────────────── + +BIN="npx prisma" +SCHEMA="prisma/schema.prisma" + +show_help() { + sed -n '3,20p' "$0" + exit 0 +} + +if [ $# -eq 0 ]; then + show_help +fi + +CMD="${1:-}" + +case "$CMD" in + generate) + echo "✦ Generating Prisma client..." + $BIN generate --schema="$SCHEMA" + echo "✓ Prisma client generated" + ;; + + deploy) + echo "✦ Deploying pending migrations..." + $BIN migrate deploy --schema="$SCHEMA" + echo "✓ Migrations deployed" + ;; + + status) + echo "✦ Migration status:" + $BIN migrate status --schema="$SCHEMA" + ;; + + reset) + echo "⚠️ WARNING: This will drop all data in the database!" + read -rp "Are you sure? (yes/no): " CONFIRM + if [ "$CONFIRM" = "yes" ]; then + $BIN migrate reset --schema="$SCHEMA" --force + echo "✓ Database reset complete" + else + echo "Aborted." + fi + ;; + + studio) + echo "✦ Opening Prisma Studio..." + $BIN studio --schema="$SCHEMA" + ;; + + create-only) + echo "✦ Creating migration file (not applied)..." + read -rp "Migration name: " NAME + $BIN migrate dev --schema="$SCHEMA" --name "$NAME" --create-only + echo "✓ Migration file created — review and edit before deploying" + ;; + + *) + echo "Unknown command: $CMD" + show_help + ;; +esac diff --git a/scripts/rollback.sh b/scripts/rollback.sh new file mode 100755 index 0000000..fb3b674 --- /dev/null +++ b/scripts/rollback.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ────────────────────────────────────────────────────────────────────────────── +# Sentinel – Migration Rollback +# +# Rolls back the last N migrations. Prisma does not support named rollbacks +# natively; this script wraps `prisma migrate resolve` to handle the process. +# +# Usage: +# ./scripts/rollback.sh # Show rollback guide +# ./scripts/rollback.sh --last 1 # Roll back the last migration +# +# Note: Prisma uses "rollback" via `migrate diff` + `migrate resolve`. +# A safer approach is to create a "down" migration manually and apply it. +# ────────────────────────────────────────────────────────────────────────────── + +BIN="npx prisma" +SCHEMA="prisma/schema.prisma" + +echo "═══════════════════════════════════════════════════════════════" +echo " Sentinel – Migration Rollback Guide" +echo "═══════════════════════════════════════════════════════════════" +echo "" +echo "Prisma does not support automatic rollback. To roll back safely:" +echo "" +echo " Option 1 — Create a reverse migration (recommended):" +echo " 1. Revert the schema changes in schema.prisma" +echo " 2. Run: ./scripts/migrate.sh create-only" +echo " 3. Name it: rollback_" +echo " 4. Deploy: ./scripts/migrate.sh deploy" +echo "" +echo " Option 2 — Reset and re-migrate (dev only):" +echo " 1. Run: ./scripts/migrate.sh reset" +echo " 2. Run: ./scripts/migrate.sh deploy" +echo "" +echo " Option 3 — Resolve a failed migration:" +echo " 1. Mark migration as rolled back:" +echo " $BIN migrate resolve --rolled-back " +echo " 2. Apply the fix: ./scripts/migrate.sh deploy" +echo "" + +if [ "${1:-}" = "--last" ]; then + COUNT="${2:-1}" + echo "To roll back the last $COUNT migration(s):" + echo " 1. Revert schema.changes in prisma/schema.prisma" + echo " 2. Run: $BIN migrate dev --name rollback_step" + echo " 3. Verify: ./scripts/migrate.sh status" +fi