# 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.