Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches:
- main
- develop
- "*"
- '*'
pull_request:

jobs:
Expand Down Expand Up @@ -36,6 +36,29 @@ jobs:
validate:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: teachlink
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 10

env:
DATABASE_HOST: localhost
DATABASE_PORT: 5432
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
DATABASE_NAME: teachlink
NODE_ENV: ci

steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -62,6 +85,15 @@ jobs:
- name: Build application
run: pnpm run build

- name: Run migrations
run: pnpm run migration:run

- name: Check for schema drift
run: pnpm run migration:generate:check

- name: Revert migrations
run: pnpm run migration:revert

# Vulnerability and license compliance scanning. Runs in addition to the
# validation job so deploy-blocking checks do not couple with lint/typecheck.
security-scan:
Expand Down
60 changes: 58 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,14 @@ Defined in `.github/workflows/ci.yml`. Full documentation in [README.md](README.

| Job | Description | Blocks merge |
| ------------ | ---------------------------- | ------------ |
| `install` | `npm ci` with cache | Yes |
| `install` | `pnpm install` with cache | Yes |
| `lint` | ESLint zero-warning check | Yes |
| `format` | Prettier check (no rewrite) | Yes |
| `typecheck` | `tsc --noEmit` | Yes |
| `build` | `nest build` | Yes |
| `build` | `pnpm build` | Yes |
| `migrations` | `pnpm run migration:run` | Yes |
| `drift-check` | `pnpm run migration:generate:check` | Yes |
| `revert` | `pnpm run migration:revert` | Yes |
| `unit-tests` | Jest + coverage ≥ 70% | Yes |
| `e2e-tests` | Supertest + Postgres + Redis | Yes |
| `ci-success` | Aggregate gate | Yes |
Expand All @@ -374,13 +377,66 @@ npm run format:check
# Type error
npm run typecheck

# Build failure
npm run build

# Unit test / coverage failure
npm run test:ci

# Migration failure (requires Postgres + Redis running locally)
npm run migration:run
npm run migration:revert

# Schema drift check (fails if model changes exist without a migration)
npm run migration:generate:check

# E2E failure (requires local Postgres + Redis)
npm run test:e2e
```

### Migration testing

When model definitions change, a corresponding migration file must be created and committed alongside the model changes. Failing to do so will cause the `drift-check` CI job to fail.

#### Creating a new migration

```bash
# Generate a migration from model changes
npm run migration:generate -- <migration-name>
```

#### Verifying migrations

```bash
# Apply all pending migrations
npm run migration:run

# Revert the last migration (verifies reversibility)
npm run migration:revert

# Re-apply to restore the database state
npm run migration:run
```

#### Checking for schema drift

Schema drift occurs when entity definitions have changed but no migration file has been generated to reflect those changes. The CI pipeline runs a drift check on every PR:

```bash
# Fails if model changes exist without a corresponding migration
npm run migration:generate:check
```

If this check fails locally, run the migration generator and commit the new migration file:

```bash
npm run migration:generate -- add-example-field
git add src/migrations/
git commit -m "feat(module): add example field migration (#<N>)"
```

---

If CI fails on your PR, fix the issue locally, confirm it passes, then push. Do not ask reviewers to approve while CI is red.

---
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Every pull request and every push to `main` / `develop` runs an automated pipeli
| **Format** | Prettier | Any file that would be reformatted |
| **Type Check** | `tsc --noEmit` | Any TypeScript error |
| **Build** | NestJS CLI | Compilation failure |
| **Migrations** | TypeORM CLI | Migration error or schema drift |
| **Unit Tests** | Jest + ts-jest | Test failure or coverage below 70 % |
| **E2E Tests** | Jest + Supertest | Test failure (uses real Postgres + Redis) |

Expand All @@ -138,6 +139,15 @@ pnpm test:ci

# E2E tests (requires Postgres + Redis running locally)
pnpm test:e2e

# Run database migrations
pnpm run migration:run

# Revert last migration (verifies reversibility)
pnpm run migration:revert

# Check for schema drift (fails if model changes exist without a migration)
pnpm run migration:generate:check
```

### Coverage thresholds
Expand Down Expand Up @@ -168,6 +178,7 @@ Every pull request and every push to `main` / `develop` runs an automated pipeli
| **Format** | Prettier | Any file that would be reformatted |
| **Type Check** | `tsc --noEmit` | Any TypeScript error |
| **Build** | NestJS CLI | Compilation failure |
| **Migrations** | TypeORM CLI | Migration error or schema drift |
| **Unit Tests** | Jest + ts-jest | Test failure or coverage below 70 % |
| **E2E Tests** | Jest + Supertest | Test failure (uses real Postgres + Redis) |

Expand All @@ -191,6 +202,15 @@ npm run test:ci

# E2E tests (requires Postgres + Redis running locally)
npm run test:e2e

# Run database migrations
npm run migration:run

# Revert last migration (verifies reversibility)
npm run migration:revert

# Check for schema drift (fails if model changes exist without a migration)
npm run migration:generate:check
```

### Coverage thresholds
Expand Down
2 changes: 1 addition & 1 deletion docs/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ pnpm migrate:status # Check status
pnpm build

# Run migrations using TypeORM CLI
npx typeorm-ts-node-commonjs migration:run -d src/config/datasource.ts
npx typeorm migration:run -d src/config/datasource.ts
```

---
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
"migrate:rollback:count": "curl -s -X POST http://localhost:3000/migrations/rollback/${COUNT:-1} | npx json",
"migrate:rollback:to": "curl -s -X POST http://localhost:3000/migrations/rollback/to/${MIGRATION_NAME} | npx json",
"migrate:reset": "curl -s -X DELETE http://localhost:3000/migrations/reset | npx json",
"migration:run": "npx typeorm migration:run -d src/config/datasource.ts",
"migration:revert": "npx typeorm migration:revert -d src/config/datasource.ts",
"migration:generate": "npx typeorm migration:generate -d src/config/datasource.ts",
"migration:generate:check": "BEFORE=$(find src/migrations -maxdepth 1 -name '[0-9]*.ts' | wc -l) && npx typeorm migration:generate -d src/config/datasource.ts src/migrations/drift-check-$(date +%s) && AFTER=$(find src/migrations -maxdepth 1 -name '[0-9]*.ts' | wc -l) && find src/migrations -maxdepth 1 -name '*drift-check*' -delete && if [ \"$AFTER\" -gt \"$BEFORE\" ]; then echo \"ERROR: Schema drift detected - model changes exist without a corresponding migration. Run npm run migration:generate to create the missing migration file.\" && exit 1; fi",
"docs:generate": "node scripts/generate-api-docs.js",
"docs:validate": "node scripts/validate-openapi.js",
"docs:check": "npm run docs:generate && npm run docs:generate:examples && git diff --exit-code -- openapi-spec.json docs/api/openapi-spec.json docs/api/examples.md docs/site docs/examples",
Expand Down
9 changes: 9 additions & 0 deletions src/config/datasource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { DataSource, DataSourceOptions } from 'typeorm';
import { getDatabaseConfig } from './database.config';

export const AppDataSource = new DataSource({
...(getDatabaseConfig() as DataSourceOptions),
synchronize: false,
migrations: ['src/migrations/**/*.{ts,js}'],
migrationsTableName: 'migrations',
});