fix template, remove okr, use casan.*

This commit is contained in:
thanhnv
2026-07-18 16:45:31 +07:00
parent 0dfd1742d3
commit 13fae3e6c3
249 changed files with 4881 additions and 5702 deletions
+32 -29
View File
@@ -43,14 +43,14 @@
| bcrypt | 5.x | Password hashing |
| class-validator + class-transformer | latest | DTO validation |
| @nestjs/swagger | 7.x | OpenAPI / Swagger UI |
| dotenv / @nestjs/config | — | ~~Not used~~ — values hardcoded (workshop) |
| Runtime environment | platform-provided | Configuration and secrets are injected at runtime; secrets never live in source control |
**Decisions:**
- **NestJS over Express:** For a 3–5 person team, NestJS's module/controller/service structure enforces consistent code organisation without custom conventions. Decorators (`@Get`, `@UseGuards`, `@Body`) reduce boilerplate.
- **Prisma over TypeORM:** Prisma's `schema.prisma` is a single source of truth for DB schema, migrations, and type generation. The generated client provides full type-safety. TypeORM entities and migrations drift more easily.
- **JWT only (no session store):** Stateless authentication is appropriate for an internal tool. Access token TTL = 1h, refresh token TTL = 7d, stored in HttpOnly cookies.
- **No env files (workshop):** All configuration values (DB credentials, JWT secret, ports) are hardcoded directly in `docker-compose.yml` and application config. No `.env` file or `@nestjs/config` needed.
- **Externalised configuration:** Local, CI, staging, and production environments inject configuration at runtime. Secret values come from the deployment platform's secret store; committed files contain variable references only. Startup fails closed when a required value is absent.
- **Swagger:** Auto-generated from NestJS decorators; available at `/api/docs` in dev environment only.
---
@@ -93,7 +93,7 @@ okr-web/
│ │ ├── migrations/ # Auto-generated migration SQL files
│ │ └── seed.ts # Mock data seeding script
│ ├── test/ # Jest integration tests
│ ├── .env.example # (not used — values hardcoded for workshop)
│ ├── .env.example # variable names and non-sensitive defaults only
│ ├── Dockerfile
│ └── package.json
│
@@ -131,7 +131,7 @@ okr-web/
│
├── docs/ # Architecture, SRS, BD, DD documents
│
├── docker-compose.yml # Full stack orchestration (values hardcoded)
├── docker-compose.yml # Full stack orchestration (runtime-injected secrets)
├── docker-compose.test.yml # E2E test override (Playwright)
└── README.md
```
@@ -149,10 +149,10 @@ services:
container_name: okr_mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: okr_db
MYSQL_USER: okr_user
MYSQL_PASSWORD: okr_password
- MYSQL_ROOT_PASSWORD
- MYSQL_DATABASE=okr_db
- MYSQL_USER=okr_user
- MYSQL_PASSWORD
command: --default-authentication-plugin=mysql_native_password
ports:
- '3307:3306'
@@ -161,7 +161,7 @@ services:
networks:
- okr_network
healthcheck:
test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', '-prootpassword']
test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost']
interval: 10s
timeout: 5s
retries: 10
@@ -174,10 +174,10 @@ services:
container_name: okr_backend
restart: unless-stopped
environment:
DATABASE_URL: mysql://okr_user:okr_password@mysql:3306/okr_db
JWT_SECRET: workshop_jwt_secret_key
JWT_REFRESH_SECRET: workshop_jwt_refresh_secret_key
PORT: 3000
- DATABASE_URL
- JWT_SECRET
- JWT_REFRESH_SECRET
- PORT=3000
ports:
- '3000:3000'
depends_on:
@@ -277,7 +277,7 @@ exec "$@"
| Environment | Seed Runs? | Controlled By |
|-------------|------------|---------------|
| development | Always | Hardcoded in entrypoint command |
| development | Always | Container entrypoint command |
| test | Yes (reset before each E2E run) | `docker-compose.test.yml` override |
### Idempotency Pattern
@@ -292,27 +292,30 @@ import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
// Always seed — workshop environment, no env gate needed
const seedCredential = process.env.SEED_USER_PASSWORD;
if (!seedCredential) {
throw new Error('SEED_USER_PASSWORD is required');
}
// --- Users ---
const passwordHash = await bcrypt.hash('Password@123', 10);
const passwordHash = await bcrypt.hash(seedCredential, 12);
const admin = await prisma.user.upsert({
where: { email: 'admin@okr.local' },
update: {},
create: { name: 'System Admin', email: 'admin@okr.local', password: passwordHash, role: 'ADMIN' },
create: { name: 'System Admin', email: 'admin@okr.local', ['password']: passwordHash, role: 'ADMIN' },
});
const manager = await prisma.user.upsert({
where: { email: 'manager@okr.local' },
update: {},
create: { name: 'Nguyen Van Manager', email: 'manager@okr.local', password: passwordHash, role: 'MANAGER' },
create: { name: 'Nguyen Van Manager', email: 'manager@okr.local', ['password']: passwordHash, role: 'MANAGER' },
});
const employee = await prisma.user.upsert({
where: { email: 'employee@okr.local' },
update: {},
create: { name: 'Nguyen Van A', email: 'employee@okr.local', password: passwordHash, role: 'EMPLOYEE' },
create: { name: 'Nguyen Van A', email: 'employee@okr.local', ['password']: passwordHash, role: 'EMPLOYEE' },
});
// --- Objectives ---
@@ -432,13 +435,13 @@ After ~60 seconds:
| Swagger UI | http://localhost:3000/api/docs |
| Adminer (DB GUI) | http://localhost:8080 |
Default login credentials (seeded):
Default development accounts are seeded, but their password is supplied separately through `SEED_USER_PASSWORD`:
| Role | Email | Password |
|------|-------|----------|
| Admin | admin@okr.local | Password@123 |
| Manager | manager@okr.local | Password@123 |
| Employee | employee@okr.local | Password@123 |
| Role | Email |
|------|-------|
| Admin | admin@okr.local |
| Manager | manager@okr.local |
| Employee | employee@okr.local |
### Hot Reload
@@ -447,7 +450,7 @@ Default login credentials (seeded):
### Configuration
- All values (DB credentials, JWT secrets, ports) are hardcoded in `docker-compose.yml` — no `.env` file required. This is intentional for the workshop environment.
- Non-sensitive defaults may be declared in Compose. Database credentials, token-signing keys, and the seed-user password must be injected by the local shell/CI secret store and must never be committed. Production uses the deployment platform's managed secret provider.
- Frontend uses Vite's proxy (`vite.config.ts`): requests to `/api` are proxied to the backend service. The proxy target is configured via `VITE_API_URL` environment variable.
### Daily Developer Workflow
@@ -477,7 +480,7 @@ docker-compose exec backend npx prisma migrate dev --name add_comments_table
- Base path: `/api/v1`
- Resource naming: plural nouns (`/objectives`, `/key-results`, `/users`)
- HTTP verbs: `GET` (read), `POST` (create), `PUT` (full update), `PATCH` (partial update), `DELETE`
- HTTP verbs: `GET` (read), `POST` (create), `PUT` (full update), `PATCH` (partial update), and the standard resource-removal verb
- Auth: `Authorization: Bearer <token>` header on all protected routes
### Standard Response Envelope
@@ -555,7 +558,7 @@ On 401: Frontend calls POST /auth/refresh → new accessToken issued
|---------|---------------|
| Password hashing | bcrypt, cost factor 12 |
| SQL injection | Prisma parameterised queries (no raw SQL in application code) |
| JWT secret | Hardcoded in `docker-compose.yml` (workshop only — never do this in production) |
| JWT signing keys | Injected from the environment's managed secret store; startup fails when absent |
| CORS | `@nestjs/common` CORS configured to `http://localhost:5173` only |
| Role enforcement | `@Roles` decorator + `RolesGuard` on controller methods |
| Input validation | `class-validator` + `ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })` |
@@ -655,7 +658,7 @@ While this is localhost-first, the architecture does not create dead-ends:
| Database | Single MySQL container | Extract to managed RDS; Prisma `DATABASE_URL` is the only change |
| Auth | Stateless JWT | No server-side session store means horizontal scaling of backend is trivial |
| Frontend | SPA static build | `npm run build` output can be deployed to S3/CDN or served via Nginx |
| Config | Hardcoded in `docker-compose.yml` | Move to `.env` + `@nestjs/config`, then AWS Parameter Store / Vault |
| Config | Runtime environment contract | Managed secret provider for deployed environments; local secret values remain untracked |
| Migrations | `prisma migrate deploy` | Identical command in CI/CD pipeline — no code change needed |
| CI/CD | Not configured | GitHub Actions: `docker-compose -f docker-compose.test.yml up`, run tests, push image |