fix template, remove okr, use casan.*
This commit is contained in:
@@ -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 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user