Files
kilo-cv/docs/plans/2026-02-20-devops-features.md
2026-02-20 16:52:06 +01:00

18 KiB

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

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

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

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

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:

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

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

mkdir -p backend/migrations

Step 2: Create initial migration

Create backend/migrations/20260220000001_initial_schema.js:

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

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

mkdir -p backend/seeds

Step 2: Create seed file

Create backend/seeds/initial_cv_data.js:

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

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:

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

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:

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

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:

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

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

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:

import docsRoutes from './routes/docs.js';

// After other route imports
app.use('/api', docsRoutes);

Step 2: Commit

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:

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

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:

"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

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:

{
  "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

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:

  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

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:

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

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:

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

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

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:

### 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

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

npm run lint

Expected: No errors

Step 2: Run tests

npm run test:run

Expected: All tests pass

Step 3: Run backend tests

cd backend && npm test

Expected: All tests pass

Step 4: Test coverage

npm run test:coverage

Expected: Coverage report generated

Step 5: Build and check bundle

npm run build && npx bundlesize

Expected: Bundle sizes within limits


Task 20: Final Commit and Summary

Step 1: Verify all files are committed

git status

Expected: Nothing to commit

Step 2: View commit history

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