222 lines
7 KiB
Markdown
222 lines
7 KiB
Markdown
# Phase 0 — Foundation & Infrastructure
|
|
|
|
**Goal**: Repo scaffolding, Docker environment, and authentication. After this phase, a developer can clone the repo, run `docker compose up`, and see a working authenticated "Hello World" page.
|
|
|
|
---
|
|
|
|
## Deliverables
|
|
|
|
1. Monorepo initialized with Turborepo
|
|
2. NestJS API with health endpoint
|
|
3. Next.js web app with landing page
|
|
4. Shared types package building and importable
|
|
5. Docker Compose with all services running
|
|
6. Keycloak configured with realm, roles, and test users
|
|
7. Auth guard protecting API routes
|
|
8. User and Household schemas with basic CRUD
|
|
9. Dev seed script
|
|
10. Linting and formatting
|
|
|
|
---
|
|
|
|
## Tasks
|
|
|
|
### 0.1 — Monorepo Setup
|
|
|
|
- Initialize root `package.json` with workspaces: `packages/*`
|
|
- Add Turborepo config (`turbo.json`) with pipelines: `build`, `dev`, `lint`, `test`
|
|
- Create three packages:
|
|
- `packages/shared` — TypeScript library, compiled with `tsc`
|
|
- `packages/api` — NestJS app (`@nestjs/cli` scaffold)
|
|
- `packages/web` — Next.js app (`create-next-app` with App Router, TypeScript, Tailwind CSS)
|
|
- Root `tsconfig.base.json` with strict settings, extended by each package
|
|
- Configure package references so `api` and `web` depend on `shared`
|
|
|
|
### 0.2 — Shared Types (Initial)
|
|
|
|
Define in `packages/shared/src/`:
|
|
|
|
```typescript
|
|
// enums/roles.ts
|
|
export enum HouseholdRole {
|
|
OWNER = 'owner',
|
|
ADMIN = 'admin',
|
|
MEMBER = 'member',
|
|
}
|
|
|
|
// types/user.ts
|
|
export interface User {
|
|
id: string;
|
|
keycloakId: string;
|
|
displayName: string;
|
|
email: string;
|
|
householdIds: string[];
|
|
defaultHouseholdId: string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
// types/household.ts
|
|
export interface Household {
|
|
id: string;
|
|
name: string;
|
|
ownerUserId: string;
|
|
members: HouseholdMember[];
|
|
inviteCode: string;
|
|
settings: HouseholdSettings;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
export interface HouseholdMember {
|
|
userId: string;
|
|
role: HouseholdRole;
|
|
joinedAt: Date;
|
|
}
|
|
|
|
export interface HouseholdSettings {
|
|
timezone: string;
|
|
currency: string;
|
|
language: string;
|
|
}
|
|
```
|
|
|
|
- Add Zod validation schemas for create/update DTOs
|
|
- Export everything from `index.ts`
|
|
|
|
### 0.3 — Docker Compose
|
|
|
|
Create `docker/docker-compose.yml`:
|
|
|
|
```yaml
|
|
services:
|
|
mongodb:
|
|
image: mongo:7
|
|
ports: ['27017:27017']
|
|
volumes: [mongo-data:/data/db]
|
|
environment:
|
|
MONGO_INITDB_ROOT_USERNAME: meshitrack
|
|
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}
|
|
|
|
keycloak:
|
|
image: quay.io/keycloak/keycloak:24.0
|
|
ports: ['8080:8080']
|
|
environment:
|
|
KC_DB: dev-mem # Dev mode, in-memory DB
|
|
KEYCLOAK_ADMIN: admin
|
|
KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
|
|
command: start-dev --import-realm
|
|
volumes:
|
|
- ./keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json
|
|
|
|
api:
|
|
build:
|
|
context: ..
|
|
dockerfile: docker/Dockerfile.api
|
|
ports: ['3001:3001']
|
|
depends_on: [mongodb, keycloak]
|
|
environment:
|
|
MONGODB_URI: mongodb://meshitrack:${MONGO_PASSWORD}@mongodb:27017/meshitrack?authSource=admin
|
|
KEYCLOAK_URL: http://keycloak:8080
|
|
KEYCLOAK_REALM: meshitrack
|
|
KEYCLOAK_CLIENT_ID: meshitrack-api
|
|
|
|
web:
|
|
build:
|
|
context: ..
|
|
dockerfile: docker/Dockerfile.web
|
|
ports: ['3000:3000']
|
|
depends_on: [api]
|
|
environment:
|
|
NEXT_PUBLIC_API_URL: http://localhost:3001/api/v1
|
|
NEXT_PUBLIC_KEYCLOAK_URL: http://localhost:8080
|
|
NEXT_PUBLIC_KEYCLOAK_REALM: meshitrack
|
|
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID: meshitrack-web
|
|
|
|
mongo-express:
|
|
image: mongo-express
|
|
ports: ['8081:8081']
|
|
depends_on: [mongodb]
|
|
environment:
|
|
ME_CONFIG_MONGODB_ADMINUSERNAME: meshitrack
|
|
ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD}
|
|
ME_CONFIG_MONGODB_URL: mongodb://meshitrack:${MONGO_PASSWORD}@mongodb:27017/
|
|
profiles: [dev]
|
|
|
|
volumes:
|
|
mongo-data:
|
|
```
|
|
|
|
- Create `.env.example` with all required variables
|
|
- Create `Dockerfile.api` and `Dockerfile.web` (multi-stage builds)
|
|
|
|
### 0.4 — Keycloak Configuration
|
|
|
|
- Export a Keycloak realm JSON (`docker/keycloak/realm-export.json`) with:
|
|
- Realm: `meshitrack`
|
|
- Client: `meshitrack-web` (public, PKCE) for frontend
|
|
- Client: `meshitrack-api` (bearer-only) for backend validation
|
|
- Realm roles: `admin`, `member`
|
|
- Custom protocol mapper: `household-mapper` that maps user attribute `householdIds` to JWT claim
|
|
- Test users: `testuser1` / `testuser2` with passwords
|
|
|
|
### 0.5 — NestJS API Bootstrap
|
|
|
|
- `packages/api/src/main.ts`: bootstrap NestJS with:
|
|
- Global prefix `/api/v1`
|
|
- CORS configured for `http://localhost:3000`
|
|
- Swagger/OpenAPI docs at `/api/docs`
|
|
- Validation pipe (class-validator + class-transformer)
|
|
- Modules:
|
|
- `AuthModule`: Keycloak strategy (`passport-openidconnect` or `nest-keycloak-connect`), `@AuthGuard` decorator, extract user + householdId from JWT
|
|
- `UsersModule`: `User` Mongoose schema, sync user on first login (upsert from Keycloak token)
|
|
- `HouseholdsModule`: `Household` Mongoose schema, CRUD endpoints
|
|
- `POST /households` — create (creator becomes owner)
|
|
- `GET /households/:householdId` — get (members only)
|
|
- `POST /households/:householdId/invite` — generate invite code
|
|
- `POST /households/join` — join via invite code
|
|
- `PATCH /households/:householdId` — update settings (admin/owner)
|
|
- `HealthModule`: `GET /api/v1/health` — returns `{ status: 'ok', version, uptime }`
|
|
- Common:
|
|
- `HouseholdPlugin`: Fastify preHandler hook that reads `:householdId` from the URI, validates it against the user's `householdIds[]` JWT claim, and injects it into `request.householdId`
|
|
- `CurrentUser` param decorator
|
|
- `CurrentHousehold` param decorator
|
|
- Global exception filter with consistent error response shape
|
|
|
|
### 0.6 — Next.js Web Bootstrap
|
|
|
|
- Configure `next-auth` or `keycloak-js` for OIDC login flow
|
|
- Pages:
|
|
- `/login` — redirects to Keycloak
|
|
- `/` — dashboard (protected, shows "Welcome, {name}" + household selector)
|
|
- `/settings` — household management (create, invite, switch)
|
|
- Layout: navigation sidebar (placeholder links for future phases), top bar with user avatar + household switcher
|
|
- API client service (`packages/web/src/services/api-client.ts`): fetch wrapper that attaches the JWT `Authorization` header; household context is passed via URL (e.g. `/api/v1/households/:householdId/products`)
|
|
|
|
### 0.7 — Dev Seed Script
|
|
|
|
- `packages/api/src/scripts/seed.ts`:
|
|
- Create 2 test users (matching Keycloak test users)
|
|
- Create 1 household with both users as members
|
|
- Log credentials and household ID to console
|
|
|
|
---
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] `docker compose up` starts all services without errors
|
|
- [ ] Navigating to `http://localhost:3000` redirects to Keycloak login
|
|
- [ ] After login, dashboard shows user name and household
|
|
- [ ] `GET /api/v1/health` returns 200
|
|
- [ ] `GET /api/v1/households/:id` returns 401 without token, 200 with valid token
|
|
- [ ] `packages/shared` types are importable from both `api` and `web`
|
|
|
|
---
|
|
|
|
## Dependencies
|
|
|
|
None — this is the foundation phase.
|
|
|
|
## Estimated Effort
|
|
|
|
Medium-large. Mostly boilerplate and configuration, but Keycloak setup requires careful attention.
|