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