Skip to main content

LOCAL_DEV_SETUP — staff-service

Sibling: SERVICE_OVERVIEW · DATA_MODEL · TESTING_STRATEGY

Strategic anchors: standards/SERVICE_TEMPLATE · 02 §17 Local Dev

How to bring staff-service up on a developer machine in under 5 minutes, run tests, and reproduce production-like behavior. Production runs on GCP (Cloud Run + Cloud SQL); locally we use Docker Compose with Postgres 16, Redis 7, and the Pub/Sub emulator.


1. Prerequisites

ToolVersionPurpose
Node.js20.x LTSRuntime
pnpm9.xMonorepo package manager
Docker DesktoplatestCompose stack
gcloud CLIlatestPub/Sub emulator install (gcloud components install pubsub-emulator)
openssl3.xLocal CA / KMS pepper material

The repo's .tool-versions is mise-compatible; mise install from the repo root handles Node + pnpm.


2. First-Time Setup

# from monorepo root
pnpm install
pnpm -F staff-service build

# create the local KMS pepper material
mkdir -p .secrets
openssl rand -hex 32 > .secrets/staff_pin_pepper.local
openssl rand -hex 32 > .secrets/staff_pii_envelope.local

# bring up dependencies (Postgres + Redis + Pub/Sub emulator)
pnpm -F staff-service dev:deps:up

# run migrations
pnpm -F staff-service db:migrate

# seed local fixtures
pnpm -F staff-service db:seed

dev:deps:up calls docker compose -f docker-compose.dev.yml up -d from services/staff-service/. The compose file is committed and pinned to specific image tags.


3. Running the Service

# all entrypoints, with hot-reload
pnpm -F staff-service dev

# or specific entrypoints
pnpm -F staff-service dev:api
pnpm -F staff-service dev:worker
pnpm -F staff-service dev:cron

dev runs all three entrypoints in parallel under concurrently. Logs are colored per process. Default ports: API on :3070, OpenAPI explorer on :3070/docs.


4. Environment Variables

A .env.local is generated by pnpm -F staff-service env:bootstrap based on .env.example. Key entries:

NODE_ENV=development
PORT=3070
LOG_LEVEL=debug

DATABASE_URL=postgres://staff:staff@localhost:55432/staff
REDIS_URL=redis://:devpass@localhost:56379/0

PUBSUB_PROJECT_ID=local-dev
PUBSUB_EMULATOR_HOST=localhost:58085

IAM_SERVICE_BASE_URL=http://localhost:3050
PROPERTY_SERVICE_BASE_URL=http://localhost:3030
AI_ORCHESTRATOR_BASE_URL=http://localhost:3090

KMS_PIN_PEPPER_KEY=file://./.secrets/staff_pin_pepper.local
KMS_PII_ENVELOPE_KEY=file://./.secrets/staff_pii_envelope.local

STAFF_AUTO_CLOSE_GRACE_MIN=60
STAFF_GAP_WARN_MIN=15
STAFF_PIN_LOCKOUT_MIN=15
STAFF_PIN_PEPPER_VERSION=v3

The file:// KMS scheme is a local-only adapter that reads from disk; CI rejects any file:// outside NODE_ENV=test.


5. Compose Services

ContainerImageHost portNotes
staff-postgrespostgres:16.4-alpine55432:5432Persistent volume staff_pg_data
staff-redisredis:7.4-alpine56379:6379AUTH devpass
pubsub-emulatorgcr.io/google.com/cloudsdktool/cloud-sdk:emulators58085:8085Auto-creates topics + subs from pubsub.dev.yaml
mailpitaxllent/mailpit:v158025:8025 (UI), 51025:1025 (SMTP)Captures invite emails (via iam-service)

Bring down with pnpm -F staff-service dev:deps:down. Reset state with dev:deps:nuke (drops volumes).


6. Seed Data

pnpm -F staff-service db:seed populates:

  • 1 tenant: ten_localdev01000000000000000
  • 2 properties: ppt_localdevdohaXXXXXXXXXX, ppt_localdevkblXXXXXXXXXXX
  • 5 departments per property (front_office, housekeeping, maintenance, fnb, security)
  • 10 positions (front_desk, housekeeper, maintenance_tech, gm, dept_manager, …)
  • 25 staff (mix of email + PIN-only; languages ps/fa/en/ar)
  • 1 active ShiftPattern per position per property
  • Generated shifts for the next 14 days

The seed is deterministic (uses a fixed PRNG seed for ULIDs). It also writes a JWT + actor card to .secrets/dev-actors/ for use with the API explorer.


7. Running Tests

# unit + application
pnpm -F staff-service test

# unit only (faster)
pnpm -F staff-service test:unit

# integration (Testcontainers — requires Docker)
pnpm -F staff-service test:integration

# sync / offline
pnpm -F staff-service test:sync

# pact contracts (publishes to local broker)
pnpm -F staff-service test:contract

# coverage gate (≥ 85 % overall, ≥ 95 % domain)
pnpm -F staff-service test:coverage

test:integration and test:sync use @testcontainers/postgresql, @testcontainers/redis, and the Pub/Sub emulator container; nothing of the dev compose stack is required.


8. API Exploration

  • Swagger UI at http://localhost:3070/docs.
  • Postman collection at services/staff-service/postman/staff-service.postman_collection.json; environment variables included.
  • A repl is available: pnpm -F staff-service repl boots a Node REPL with all use cases wired against the local stack — handy for quickly exercising PunchClock or AssignStaff.

9. Tracing & Logging Locally

pnpm -F staff-service dev:trace adds an OTLP collector container (otel/opentelemetry-collector:latest) and a Jaeger UI at http://localhost:56686. Set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:54317 in .env.local to enable.

Logs are JSON in production; locally, a pretty-printer (pino-pretty) is piped automatically.


10. Common Tasks

10.1 Add a new use case

pnpm -F staff-service gen:use-case --name RequestEmergencyOffShift

The generator scaffolds: a domain test, an application use case file, a controller route, an integration test stub, and an OpenAPI fragment. PR-ready in 5 minutes.

10.2 Add a new event subject

pnpm -F staff-service gen:event --topic melmastoon.staff.<aggregate>.<verb_past>.v1

Generates: schema JSON, TypeScript type, outbox helper, and a contract test stub. Updates EVENT_SCHEMAS.md topic table (the doc file has a <!-- generated --> block the generator targets).

10.3 Reset the local DB

pnpm -F staff-service db:reset # drops + recreates + migrates + seeds

10.4 Replay a Pub/Sub event from emulator

pnpm -F staff-service pubsub:replay --subject melmastoon.iam.user.registered.v1 --file fixtures/iam-user-registered.json

11. Hooking Up iam-service and property-service

For full-flow local dev (e.g., termination cascade), bring up iam-service and property-service with their own compose stacks. The monorepo-level pnpm dev:cluster boots all three plus the BFFs. Networking uses host-local ports; cross-service URLs are pre-wired in .env.example.


12. Troubleshooting

SymptomLikely causeFix
dev:deps:up fails on port 55432Existing Postgres on hostEdit docker-compose.dev.yml to remap; update .env.local
Migrations fail on first runPostgres not yet readyRe-run pnpm -F staff-service db:migrate after 5 s
Pub/Sub publish hangsEmulator container not startedpnpm -F staff-service dev:deps:up
KMS adapter throws ENOENTMissing .secrets/staff_pin_pepper.localRe-run setup step 2 (openssl rand)
Punch returns KMS_UNAVAILABLEKMS env var pointed to wrong pathVerify KMS_PIN_PEPPER_KEY in .env.local
Idempotency conflicts on test rerunsRedis state from previous runpnpm -F staff-service dev:deps:reset or flush DB 0
outbox.spec.ts flakySlow Docker on first Testcontainers pulldocker pull postgres:16.4-alpine ahead of time

If stuck for > 15 min, ping #staff-svc-dev on Slack with the exact command + log tail. Onboarding pair-programming sessions are scheduled weekly (Wednesdays, 14:00 GST).