# DevOps Features Design **Date:** 2026-02-20 **Status:** Approved **Author:** Claude (AI Assistant) ## Overview Implement four DevOps features for the CV application: 1. **Swagger/OpenAPI Documentation** - Interactive API docs at `/api/docs` 2. **Database Migrations** - Knex.js-based migration system with auto-migration 3. **Quality Gates** - Code coverage, bundle size, npm audit, Lighthouse CI 4. **Secret Scanning** - Gitleaks pre-commit hook ## 1. Swagger/OpenAPI Documentation ### Architecture ``` Route Files (JSDoc comments) │ ▼ swagger-jsdoc (generates spec at runtime) │ ▼ swagger-ui-express │ ▼ GET /api/docs → Interactive Swagger UI GET /api/docs.json → Raw OpenAPI spec ``` ### Endpoints | Method | Path | Description | |--------|------|-------------| | GET | `/api/docs` | Swagger UI interface | | GET | `/api/docs.json` | Raw OpenAPI JSON spec | ### Implementation **Dependencies:** - `swagger-jsdoc` - Generate OpenAPI spec from JSDoc - `swagger-ui-express` - Serve Swagger UI **JSDoc format in route files:** ```javascript /** * @openapi * /api/cv: * get: * summary: Get CV data * tags: [CV] * responses: * 200: * description: CV data * content: * application/json: * schema: * $ref: '#/components/schemas/CVData' * 404: * description: CV data not found */ router.get('/', (req, res) => { ... }); /** * @openapi * /api/cv: * put: * summary: Update CV data * tags: [CV] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/CVData' * responses: * 200: * description: CV data updated * 401: * description: Unauthorized */ router.put('/', authMiddleware, (req, res) => { ... }); ``` **Swagger setup:** ```javascript // backend/routes/docs.js import swaggerJsdoc from 'swagger-jsdoc'; import swaggerUi from 'swagger-ui-express'; const options = { definition: { openapi: '3.0.0', info: { title: 'CV API', version: '1.0.0', description: 'API for CV/Resume management', }, servers: [ { url: '/api', description: 'API server' } ], components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', }, }, schemas: { CVData: { /* schema definition */ }, }, }, }, apis: ['./routes/*.js'], }; const specs = swaggerJsdoc(options); router.use('/docs', swaggerUi.serve, swaggerUi.setup(specs)); router.get('/docs.json', (req, res) => res.json(specs)); ``` --- ## 2. Database Migrations (Knex.js) ### Architecture ``` backend/ ├── knexfile.js # Knex configuration ├── migrations/ │ ├── 20260220000001_initial_schema.js │ └── 20260220000002_add_auth_tables.js ├── seeds/ │ └── initial_cv_data.js └── db/ ├── connection.js # Knex instance └── init.js # Auto-migration on startup ``` ### Knex Configuration ```javascript // knexfile.js export default { client: 'better-sqlite3', connection: { filename: process.env.DB_PATH || './data/cv.db' }, migrations: { directory: './migrations', tableName: 'knex_migrations' }, seeds: { directory: './seeds' }, useNullAsDefault: true }; ``` ### Migration File Format ```javascript // migrations/20260220000001_initial_schema.js export async function up(knex) { await knex.schema.createTable('cv_data', (table) => { table.integer('id').primary().checkTo('=', 1); table.text('data').notNullable(); table.datetime('updated_at').defaultTo(knex.fn.now()); }); } export async function down(knex) { await knex.schema.dropTableIfExists('cv_data'); } ``` ### Auto-Migration on Startup ```javascript // db/init.js import knex from 'knex'; import config from '../knexfile.js'; let db = null; export async function getDB() { if (!db) { db = knex(config); await db.migrate.latest(); } return db; } export async function closeDB() { if (db) { await db.destroy(); db = null; } } ``` ### CLI Commands ```bash # Create new migration npx knex migrate:make migration_name --knexfile knexfile.js # Run migrations manually npx knex migrate:latest --knexfile knexfile.js # Rollback last migration npx knex migrate:rollback --knexfile knexfile.js # Run seeds npx knex seed:run --knexfile knexfile.js ``` ### Dependencies - `knex` - SQL query builder and migrations --- ## 3. Quality Gates ### Overview | Gate | Tool | Threshold | Action | |------|------|-----------|--------| | Code Coverage | vitest + Codecov | 80% minimum | Fail CI | | Bundle Size | bundlesize | 500KB max | Fail CI | | npm audit | npm audit | Moderate+ | Fail CI | | Lighthouse | @lhci/cli | Performance 90+ | Fail CI | ### CI Workflow Integration ```yaml # .github/workflows/ci.yml additions jobs: quality: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci # Code Coverage - name: Run tests with coverage run: npm run test:coverage - name: Upload to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true # Bundle Size - name: Build run: npm run build - name: Check bundle size run: npx bundlesize # npm audit - name: Security audit run: npm audit --audit-level=moderate lighthouse: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Run Lighthouse CI run: npm run test:lighthouse ``` ### Configuration Files **package.json:** ```json { "scripts": { "test:coverage": "vitest run --coverage" }, "bundlesize": [ { "path": "./dist/assets/*.js", "maxSize": "500kb" }, { "path": "./dist/assets/*.css", "maxSize": "100kb" } ] } ``` **vitest.config.js:** ```javascript export default defineConfig({ test: { coverage: { provider: 'v8', reporter: ['text', 'lcov', 'html'], exclude: ['node_modules/', 'tests/'], lines: 80, functions: 80, branches: 80, statements: 80 } } }); ``` **.lighthouserc.json:** ```json { "ci": { "collect": { "url": ["http://localhost:4173/"], "numberOfRuns": 3 }, "assert": { "assertions": { "categories:performance": ["error", { "minScore": 0.9 }], "categories:accessibility": ["warn", { "minScore": 0.9 }], "categories:best-practices": ["warn", { "minScore": 0.9 }] } }, "upload": { "target": "temporary-public-storage" } } } ``` ### Dependencies - `@vitest/coverage-v8` - Coverage reporter - `bundlesize` - Bundle size checking ### Secrets Required | Secret | Description | |--------|-------------| | `CODECOV_TOKEN` | Codecov upload token | --- ## 4. Secret Scanning (Gitleaks) ### Architecture ``` Developer stages files │ ▼ git commit │ ▼ .husky/pre-commit │ ├─► npm run lint │ └─► gitleaks protect --staged │ ▼ Secrets found? │ │ Yes No │ │ ▼ ▼ Block commit Allow commit ``` ### Pre-commit Hook ```bash # .husky/pre-commit npm run lint # Gitleaks secret scanning if command -v gitleaks &> /dev/null; then gitleaks protect --verbose --staged if [ $? -eq 1 ]; then echo "" echo "❌ Secrets detected in staged files!" echo "Please remove sensitive data before committing." echo "" exit 1 fi fi ``` ### Gitleaks Configuration ```toml # .gitleaks.toml title = "Gitleaks Configuration" [extend] useDefault = true [[allowlists]] description = "Allowlisted files" paths = [ 'package-lock.json', 'backend/package-lock.json', '.*\\.md$' ] [[allowlists]] description = "Allowlisted patterns" regexes = [ 'VITE_API_URL.*localhost', ] ``` ### Developer Setup Gitleaks must be installed on developer machines: ```bash # macOS brew install gitleaks # Linux # Download from https://github.com/gitleaks/gitleaks/releases # Or use go install go install github.com/gitleaks/gitleaks/v8@latest # Windows scoop install gitleaks # Or choco install gitleaks ``` ### Documentation Add to README or CONTRIBUTING.md: ```markdown ### Pre-commit Setup This project uses Gitleaks for secret scanning. Install it before committing: ```bash # macOS brew install gitleaks # Linux curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.18.1/gitleaks_8.18.1_linux_x64.tar.gz | tar -xz sudo mv gitleaks /usr/local/bin/ ``` The pre-commit hook will automatically scan for secrets before each commit. ``` --- ## Implementation Summary ### New Dependencies **Backend:** - `knex` - Database migrations and query builder - `swagger-jsdoc` - OpenAPI spec generation - `swagger-ui-express` - Swagger UI middleware **Frontend:** - `@vitest/coverage-v8` - Coverage reporting - `bundlesize` - Bundle size checking **Dev:** - `gitleaks` (binary, not npm package) ### New Files ``` backend/ ├── knexfile.js ├── migrations/ │ └── 20260220000001_initial_schema.js ├── seeds/ │ └── initial_cv_data.js ├── routes/ │ └── docs.js └── db/ └── connection.js .gitleaks.toml .lighthouserc.json ``` ### Modified Files ``` package.json # Add scripts, bundlesize config vitest.config.js # Add coverage config .github/workflows/ci.yml # Add quality gates .husky/pre-commit # Add gitleaks backend/db/init.js # Use Knex instead of better-sqlite3 directly backend/routes/cv.js # Add JSDoc comments backend/routes/auth.js # Add JSDoc comments README.md # Add gitleaks setup instructions ``` --- ## Post-Implementation Checklist - [ ] Install dependencies - [ ] Configure Knex and create initial migration - [ ] Add Swagger UI route - [ ] Add JSDoc comments to all API routes - [ ] Configure Codecov (add CODECOV_TOKEN secret) - [ ] Add bundlesize configuration - [ ] Update CI workflow with quality gates - [ ] Add gitleaks to pre-commit hook - [ ] Update documentation