7 KiB
7 KiB
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
- Monorepo initialized with Turborepo
- NestJS API with health endpoint
- Next.js web app with landing page
- Shared types package building and importable
- Docker Compose with all services running
- Keycloak configured with realm, roles, and test users
- Auth guard protecting API routes
- User and Household schemas with basic CRUD
- Dev seed script
- Linting and formatting
Tasks
0.1 — Monorepo Setup
- Initialize root
package.jsonwith workspaces:packages/* - Add Turborepo config (
turbo.json) with pipelines:build,dev,lint,test - Create three packages:
packages/shared— TypeScript library, compiled withtscpackages/api— NestJS app (@nestjs/cliscaffold)packages/web— Next.js app (create-next-appwith App Router, TypeScript, Tailwind CSS)
- Root
tsconfig.base.jsonwith strict settings, extended by each package - Configure package references so
apiandwebdepend onshared
0.2 — Shared Types (Initial)
Define in packages/shared/src/:
// 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:
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.examplewith all required variables - Create
Dockerfile.apiandDockerfile.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-mapperthat maps user attributehouseholdIdsto JWT claim - Test users:
testuser1/testuser2with passwords
- Realm:
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)
- Global prefix
- Modules:
AuthModule: Keycloak strategy (passport-openidconnectornest-keycloak-connect),@AuthGuarddecorator, extract user + householdId from JWTUsersModule:UserMongoose schema, sync user on first login (upsert from Keycloak token)HouseholdsModule:HouseholdMongoose schema, CRUD endpointsPOST /households— create (creator becomes owner)GET /households/:householdId— get (members only)POST /households/:householdId/invite— generate invite codePOST /households/join— join via invite codePATCH /households/:householdId— update settings (admin/owner)
HealthModule:GET /api/v1/health— returns{ status: 'ok', version, uptime }
- Common:
HouseholdPlugin: Fastify preHandler hook that reads:householdIdfrom the URI, validates it against the user'shouseholdIds[]JWT claim, and injects it intorequest.householdIdCurrentUserparam decoratorCurrentHouseholdparam decorator- Global exception filter with consistent error response shape
0.6 — Next.js Web Bootstrap
- Configure
next-authorkeycloak-jsfor 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 JWTAuthorizationheader; 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 upstarts all services without errors- Navigating to
http://localhost:3000redirects to Keycloak login - After login, dashboard shows user name and household
GET /api/v1/healthreturns 200GET /api/v1/households/:idreturns 401 without token, 200 with valid tokenpackages/sharedtypes are importable from bothapiandweb
Dependencies
None — this is the foundation phase.
Estimated Effort
Medium-large. Mostly boilerplate and configuration, but Keycloak setup requires careful attention.