docs(release): add devops features implementation plan
This commit is contained in:
948
docs/plans/2026-02-20-devops-features.md
Normal file
948
docs/plans/2026-02-20-devops-features.md
Normal file
@@ -0,0 +1,948 @@
|
|||||||
|
# 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
|
||||||
Reference in New Issue
Block a user