MeshiTrack/docs/phases/phase-0-foundation.md

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

  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/:

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