# DevOps Features Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Implement Swagger docs, Knex migrations, quality gates, and secret scanning. **Architecture:** Swagger UI served at /api/docs with JSDoc-annotated routes. Knex.js handles database migrations with auto-migration on startup. Quality gates integrated into CI workflow. Gitleaks pre-commit hook for secret scanning. **Tech Stack:** swagger-jsdoc, swagger-ui-express, knex, vitest coverage, bundlesize, gitleaks --- ## Task 1: Install Backend Dependencies **Files:** - Modify: `backend/package.json` **Step 1: Install Knex for migrations** ```bash cd backend && npm install knex swagger-jsdoc swagger-ui-express ``` **Step 2: Verify installation** Run: `cd backend && npm list knex swagger-jsdoc swagger-ui-express --depth=0` Expected: All packages listed with versions **Step 3: Commit** ```bash git add backend/package.json backend/package-lock.json git commit -m "build(deps): add knex, swagger-jsdoc, swagger-ui-express" ``` --- ## Task 2: Install Frontend Quality Gate Dependencies **Files:** - Modify: `package.json` **Step 1: Install coverage and bundlesize packages** ```bash npm install --save-dev @vitest/coverage-v8 bundlesize ``` **Step 2: Verify installation** Run: `npm list @vitest/coverage-v8 bundlesize --depth=0` Expected: All packages listed with versions **Step 3: Commit** ```bash git add package.json package-lock.json git commit -m "build(deps): add coverage and bundlesize for quality gates" ``` --- ## Task 3: Create Knex Configuration **Files:** - Create: `backend/knexfile.js` **Step 1: Create knexfile.js** Create `backend/knexfile.js`: ```javascript 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 }; ``` **Step 2: Verify syntax** Run: `cd backend && node -e "import('./knexfile.js').then(m => console.log(m.default))"` Expected: Knex config object printed **Step 3: Commit** ```bash git add backend/knexfile.js git commit -m "feat(db): add knex configuration" ``` --- ## Task 4: Create Initial Migration **Files:** - Create: `backend/migrations/20260220000001_initial_schema.js` **Step 1: Create migrations directory** ```bash mkdir -p backend/migrations ``` **Step 2: Create initial migration** Create `backend/migrations/20260220000001_initial_schema.js`: ```javascript export async function up(knex) { await knex.schema.createTable('cv_data', (table) => { table.integer('id').primary(); table.text('data').notNullable(); table.datetime('updated_at').defaultTo(knex.fn.now()); }); } export async function down(knex) { await knex.schema.dropTableIfExists('cv_data'); } ``` **Step 3: Commit** ```bash git add backend/migrations/ git commit -m "feat(db): add initial schema migration" ``` --- ## Task 5: Create Seed File **Files:** - Create: `backend/seeds/initial_cv_data.js` **Step 1: Create seeds directory** ```bash mkdir -p backend/seeds ``` **Step 2: Create seed file** Create `backend/seeds/initial_cv_data.js`: ```javascript export async function seed(knex) { const existing = await knex('cv_data').where({ id: 1 }).first(); if (!existing) { await knex('cv_data').insert({ id: 1, data: JSON.stringify({ personal: { name: "Tuan-Dat Tran", title: "Junior DevOps Engineer", intro: "Passionierter DevOps Engineer mit Fokus auf Cloud-Infrastruktur, Container-Orchestrierung und automatisierte Deployment-Pipelines.", email: "tuan-dat.tran@example.com", github: "https://github.com/tuan-dat-tran", linkedin: "https://linkedin.com/in/tuan-dat-tran", location: "Deutschland" }, experience: [], skills: {}, education: [], projects: [] }) }); } } ``` **Step 3: Commit** ```bash git add backend/seeds/ git commit -m "feat(db): add initial cv data seed" ``` --- ## Task 6: Update Database Init to Use Knex **Files:** - Modify: `backend/db/init.js` **Step 1: Replace init.js with Knex-based version** Replace `backend/db/init.js` with: ```javascript import knex from 'knex'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import config from '../knexfile.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); let db = null; export async function getDB() { if (!db) { db = knex(config); await db.migrate.latest(); } return db; } export async function initDB() { const db = await getDB(); const existing = await db('cv_data').where({ id: 1 }).first(); if (!existing) { await db('cv_data').insert({ id: 1, data: JSON.stringify({ personal: { name: "Tuan-Dat Tran", title: "Junior DevOps Engineer", intro: "Passionierter DevOps Engineer mit Fokus auf Cloud-Infrastruktur.", email: "tuan-dat.tran@example.com", github: "https://github.com/tuan-dat-tran", linkedin: "https://linkedin.com/in/tuan-dat-tran", location: "Deutschland" }, experience: [], skills: {}, education: [], projects: [] }) }); console.log('Initialized database with default CV data'); } console.log(`Database initialized`); } export async function closeDB() { if (db) { await db.destroy(); db = null; } } ``` **Step 2: Verify syntax** Run: `cd backend && node -c db/init.js` Expected: No syntax errors **Step 3: Commit** ```bash git add backend/db/init.js git commit -m "refactor(db): use knex for database operations" ``` --- ## Task 7: Update CV Routes for Knex **Files:** - Modify: `backend/routes/cv.js` **Step 1: Update routes to use Knex** Replace `backend/routes/cv.js` with: ```javascript import { Router } from 'express'; import { getDB } from '../db/init.js'; import { authMiddleware } from '../middleware/auth.js'; const router = Router(); /** * @openapi * /cv: * get: * summary: Get CV data * tags: [CV] * responses: * 200: * description: CV data * content: * application/json: * schema: * type: object * 404: * description: CV data not found */ router.get('/', async (req, res) => { try { const db = await getDB(); const row = await db('cv_data').where({ id: 1 }).first(); if (!row) { return res.status(404).json({ error: 'CV data not found' }); } res.json(JSON.parse(row.data)); } catch (error) { console.error('Error fetching CV:', error); res.status(500).json({ error: 'Failed to fetch CV data' }); } }); /** * @openapi * /cv: * put: * summary: Update CV data * tags: [CV] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * responses: * 200: * description: CV data updated * 401: * description: Unauthorized */ router.put('/', authMiddleware, async (req, res) => { try { const data = req.body; if (!data || typeof data !== 'object') { return res.status(400).json({ error: 'Invalid CV data' }); } if (!data.personal?.name) { return res.status(400).json({ error: 'personal.name is required' }); } const db = await getDB(); await db('cv_data').where({ id: 1 }).update({ data: JSON.stringify(data), updated_at: db.fn.now() }); res.json({ success: true, message: 'CV data updated' }); } catch (error) { console.error('Error updating CV:', error); res.status(500).json({ error: 'Failed to update CV data' }); } }); /** * @openapi * /cv/export: * get: * summary: Export CV data as JSON file * tags: [CV] * responses: * 200: * description: CV JSON file * content: * application/json: * schema: * type: object */ router.get('/export', async (req, res) => { try { const db = await getDB(); const row = await db('cv_data').where({ id: 1 }).first(); if (!row) { return res.status(404).json({ error: 'CV data not found' }); } res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', 'attachment; filename="cv.json"'); res.send(row.data); } catch (error) { console.error('Error exporting CV:', error); res.status(500).json({ error: 'Failed to export CV data' }); } }); /** * @openapi * /cv/import: * post: * summary: Import CV data from JSON * tags: [CV] * security: * - bearerAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * responses: * 200: * description: CV data imported * 401: * description: Unauthorized */ router.post('/import', authMiddleware, async (req, res) => { try { const data = req.body; if (!data || typeof data !== 'object') { return res.status(400).json({ error: 'Invalid CV data' }); } if (!data.personal?.name) { return res.status(400).json({ error: 'personal.name is required' }); } const db = await getDB(); await db('cv_data').where({ id: 1 }).update({ data: JSON.stringify(data), updated_at: db.fn.now() }); res.json({ success: true, message: 'CV data imported' }); } catch (error) { console.error('Error importing CV:', error); res.status(500).json({ error: 'Failed to import CV data' }); } }); export default router; ``` **Step 2: Commit** ```bash git add backend/routes/cv.js git commit -m "refactor(api): update cv routes for knex with openapi docs" ``` --- ## Task 8: Create Swagger Docs Route **Files:** - Create: `backend/routes/docs.js` **Step 1: Create docs route** Create `backend/routes/docs.js`: ```javascript import { Router } from 'express'; import swaggerJsdoc from 'swagger-jsdoc'; import swaggerUi from 'swagger-ui-express'; const router = Router(); 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', }, }, }, }, apis: ['./routes/*.js'], }; const specs = swaggerJsdoc(options); router.use('/docs', swaggerUi.serve, swaggerUi.setup(specs, { customCss: '.swagger-ui .topbar { display: none }', customSiteTitle: 'CV API Documentation' })); router.get('/docs.json', (req, res) => { res.setHeader('Content-Type', 'application/json'); res.send(specs); }); export default router; ``` **Step 2: Commit** ```bash git add backend/routes/docs.js git commit -m "feat(api): add swagger docs route" ``` --- ## Task 9: Add OpenAPI Docs to Auth Routes **Files:** - Modify: `backend/routes/auth.js` **Step 1: Add JSDoc comments to auth routes** Read `backend/routes/auth.js` and add OpenAPI JSDoc comments to each endpoint: - Add `@openapi` comments for `/auth/config`, `/auth/login` - Document request bodies and responses **Step 2: Commit** ```bash git add backend/routes/auth.js git commit -m "docs(api): add openapi docs to auth routes" ``` --- ## Task 10: Register Swagger Route in Server **Files:** - Modify: `backend/server.js` **Step 1: Import and use docs route** Add to `backend/server.js`: ```javascript import docsRoutes from './routes/docs.js'; // After other route imports app.use('/api', docsRoutes); ``` **Step 2: Commit** ```bash git add backend/server.js git commit -m "feat(api): register swagger docs route" ``` --- ## Task 11: Configure Vitest Coverage **Files:** - Modify: `vitest.config.js` **Step 1: Add coverage configuration** Update `vitest.config.js` to include coverage settings: ```javascript coverage: { provider: 'v8', reporter: ['text', 'lcov', 'html'], exclude: [ 'node_modules/', 'tests/', '**/*.test.js', '**/*.config.js' ], lines: 80, functions: 80, branches: 80, statements: 80 } ``` **Step 2: Add coverage script to package.json** Add script: `"test:coverage": "vitest run --coverage"` **Step 3: Commit** ```bash git add vitest.config.js package.json git commit -m "feat(ci): add coverage configuration" ``` --- ## Task 12: Configure Bundle Size Checking **Files:** - Modify: `package.json` **Step 1: Add bundlesize configuration** Add to `package.json`: ```json "bundlesize": [ { "path": "./dist/assets/*.js", "maxSize": "500kb" }, { "path": "./dist/assets/*.css", "maxSize": "100kb" } ] ``` **Step 2: Add bundle check script** Add script: `"bundle:check": "npm run build && bundlesize"` **Step 3: Commit** ```bash git add package.json git commit -m "feat(ci): add bundlesize configuration" ``` --- ## Task 13: Create Lighthouse CI Configuration **Files:** - Create: `.lighthouserc.json` **Step 1: Create lighthouse config** Create `.lighthouserc.json`: ```json { "ci": { "collect": { "url": ["http://localhost:4173/cv/"], "numberOfRuns": 3 }, "assert": { "assertions": { "categories:performance": ["error", { "minScore": 0.8 }], "categories:accessibility": ["warn", { "minScore": 0.9 }], "categories:best-practices": ["warn", { "minScore": 0.9 }] } }, "upload": { "target": "temporary-public-storage" } } } ``` **Step 2: Commit** ```bash git add .lighthouserc.json git commit -m "feat(ci): add lighthouse ci configuration" ``` --- ## Task 14: Add Quality Gates to CI Workflow **Files:** - Modify: `.github/workflows/ci.yml` **Step 1: Add quality job** Add a new `quality` job to `.github/workflows/ci.yml`: ```yaml quality: name: Quality Gates 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: Run tests with coverage run: npm run test:coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false - name: Build run: npm run build - name: Check bundle size run: npx bundlesize - name: Security audit run: npm audit --audit-level=moderate continue-on-error: true ``` **Step 2: Update lighthouse job** Update the existing `lighthouse` job to use the new config and enforce thresholds. **Step 3: Commit** ```bash git add .github/workflows/ci.yml git commit -m "feat(ci): add quality gates to ci workflow" ``` --- ## Task 15: Create Gitleaks Configuration **Files:** - Create: `.gitleaks.toml` **Step 1: Create gitleaks config** Create `.gitleaks.toml`: ```toml title = "Gitleaks Configuration" [extend] useDefault = true [[allowlists]] description = "Allowlisted files" paths = [ 'package-lock.json', 'backend/package-lock.json' ] [[allowlists]] description = "Test secrets" regexes = [ 'test.*password', 'test.*secret', 'test.*token' ] ``` **Step 2: Commit** ```bash git add .gitleaks.toml git commit -m "feat(security): add gitleaks configuration" ``` --- ## Task 16: Add Gitleaks to Pre-commit Hook **Files:** - Modify: `.husky/pre-commit` **Step 1: Update pre-commit hook** Update `.husky/pre-commit` to: ```bash 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 ``` **Step 2: Commit** ```bash git add .husky/pre-commit git commit -m "feat(security): add gitleaks to pre-commit hook" ``` --- ## Task 17: Update Backend Tests for Knex **Files:** - Modify: `backend/__tests__/api.test.js` **Step 1: Update tests to use async/await with Knex** Update the test file to work with the new async Knex-based API. **Step 2: Run tests** Run: `cd backend && npm test` Expected: All tests pass **Step 3: Commit** ```bash git add backend/__tests__/ git commit -m "test(api): update tests for knex" ``` --- ## Task 18: Update Documentation **Files:** - Modify: `README.md` **Step 1: Add gitleaks setup instructions** Add to README.md under a new "Development Setup" section: ```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/latest/download/gitleaks_linux_x64.tar.gz | tar -xz sudo mv gitleaks /usr/local/bin/ # Windows scoop install gitleaks \`\`\` ### API Documentation API documentation is available at `/api/docs` when running the server. ``` **Step 2: Commit** ```bash git add README.md git commit -m "docs(readme): add gitleaks setup and api docs info" ``` --- ## Task 19: Verify All Configurations **Step 1: Run lint** ```bash npm run lint ``` Expected: No errors **Step 2: Run tests** ```bash npm run test:run ``` Expected: All tests pass **Step 3: Run backend tests** ```bash cd backend && npm test ``` Expected: All tests pass **Step 4: Test coverage** ```bash npm run test:coverage ``` Expected: Coverage report generated **Step 5: Build and check bundle** ```bash npm run build && npx bundlesize ``` Expected: Bundle sizes within limits --- ## Task 20: Final Commit and Summary **Step 1: Verify all files are committed** ```bash git status ``` Expected: Nothing to commit **Step 2: View commit history** ```bash git log --oneline -20 ``` **Step 3: Summary** DevOps features implemented: - **Swagger UI** at `/api/docs` with OpenAPI 3.0 spec - **Knex migrations** with auto-migration on startup - **Quality gates**: coverage, bundle size, npm audit, Lighthouse CI - **Secret scanning**: Gitleaks pre-commit hook --- ## Post-Implementation Checklist - [ ] Add `CODECOV_TOKEN` secret to GitHub - [ ] Test Swagger UI at `/api/docs` - [ ] Verify migrations run on startup - [ ] Install gitleaks on developer machine - [ ] Run full CI pipeline to verify quality gates