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