Files
NetForge/.planning/phases/01-foundation/01-01-SUMMARY.md
kevin-asprec 19a5520dd5 docs(01-01): complete project scaffold and dev environment plan
Tasks completed: 2/2
- Task 1: Next.js 15 project with Docker Compose dev environment
- Task 2: Prisma schema with Tenant/User models and Vitest setup

SUMMARY: .planning/phases/01-foundation/01-01-SUMMARY.md
2026-03-04 18:32:40 +08:00

8.1 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established duration completed
01-foundation 01 infra
next.js
typescript
tailwind
prisma
postgresql
redis
docker
vitest
Next.js 15 App Router project with TypeScript and Tailwind CSS v4
Docker Compose dev environment (PostgreSQL 16, Redis 7, Next.js app)
Prisma schema with Tenant and User models (RLS-ready with tenantId)
Singleton PrismaClient importable from @/lib/prisma
Vitest configured with node environment and @/* path alias
Smoke test suite passing (2 tests)
01-02
01-03
01-04
01-05
All future phases (every plan builds on this scaffold)
added patterns
next@16.1.6
react@19.2.3
prisma@6.19.2
@prisma/client@6.19.2
vitest@4.0.18
@vitejs/plugin-react@4.7.0
tailwindcss@4
@tailwindcss/postcss@4
typescript@5
postgres:16-alpine (Docker)
redis:7-alpine (Docker)
node:20-alpine (Docker base)
Singleton PrismaClient on globalThis to prevent hot-reload connection exhaustion
Docker Compose with healthcheck-based service dependency ordering
Separate DATABASE_URL (Docker internal) and DATABASE_URL_LOCAL (host access) in .env
tenantId on all tenant-scoped Prisma models — enforced by schema comment block
created modified
docker-compose.yml
Dockerfile
.dockerignore
.env
.env.example
prisma/schema.prisma
src/lib/prisma.ts
vitest.config.ts
src/lib/__tests__/setup.test.ts
src/app/page.tsx
src/app/layout.tsx
src/app/globals.css
package.json
tsconfig.json
next.config.ts
postcss.config.mjs
Used DATABASE_URL (docker service name) for app container and DATABASE_URL_LOCAL (localhost) for host-side Prisma CLI commands
tenantId is nullable on User to support super-admins with no tenant scope
User email uniqueness is per-tenant via @@unique([email, tenantId]), not globally unique
Grace period fields (suspendedAt, gracePeriodEndsAt) included on Tenant from day one — cannot be retrofitted
Vitest environment set to 'node' — tests do not use jsdom by default
Pattern: All tenant-scoped Prisma models must include tenantId String + @@index([tenantId])
Pattern: Singleton PrismaClient via globalThis.__prisma for hot-reload safety
Pattern: Docker healthchecks on db and redis with depends_on condition: service_healthy
11min 2026-03-04

Phase 1 Plan 01: Project Scaffold and Dev Environment Summary

Next.js 15 + Prisma + PostgreSQL 16 + Redis 7 dockerized dev environment with Tenant/User schema and Vitest smoke tests

Performance

  • Duration: 11 min
  • Started: 2026-03-04T10:20:08Z
  • Completed: 2026-03-04T10:31:15Z
  • Tasks: 2 completed
  • Files modified: 19

Accomplishments

  • Docker Compose starts PostgreSQL 16, Redis 7, and Next.js 15 dev server with one command (docker compose up -d)
  • Prisma schema with Tenant and User models synced to PostgreSQL, with RLS-ready tenantId pattern documented
  • Vitest configured with @/* alias — 2 smoke tests pass, validating the full import chain from test to PrismaClient
  • All environment variables documented in .env.example with notes distinguishing Docker-internal vs host access URLs

Task Commits

Each task was committed atomically:

  1. Task 1: Create Next.js project with Docker Compose dev environment - 90bc583 (feat)
  2. Task 2: Prisma schema with base models and Vitest setup - 1adeab2 (feat)

Plan metadata: (docs commit follows)

Files Created/Modified

  • docker-compose.yml - Three services: postgres:16-alpine, redis:7-alpine, Next.js app with healthcheck deps
  • Dockerfile - Node 20 alpine dev image
  • .dockerignore - Excludes node_modules, .next, .git
  • .env - Local dev env vars with both Docker-internal and host DATABASE_URL variants
  • .env.example - Documented env var template with usage notes
  • prisma/schema.prisma - Tenant + User models, Role + TenantStatus enums, RLS tenantId convention
  • src/lib/prisma.ts - Singleton PrismaClient (globalThis pattern for hot-reload safety)
  • vitest.config.ts - Node environment, globals: true, @/* path alias
  • src/lib/__tests__/setup.test.ts - Smoke test: prisma defined, $connect/$disconnect present
  • src/app/page.tsx - Simple "NetForge" heading for verification
  • package.json - Scripts: test, test:watch, db:push, db:generate, db:studio

Decisions Made

  • DATABASE_URL split: DATABASE_URL uses db hostname (Docker service name, for app container). DATABASE_URL_LOCAL uses localhost:5432 (for host-side CLI tools like npx prisma db push). This two-URL pattern is documented in .env.example.
  • Nullable tenantId on User: Super-admins are not scoped to a tenant — tenantId is nullable to support platform-level admin accounts without a separate SuperAdmin model.
  • Per-tenant email uniqueness: @@unique([email, tenantId]) allows the same email to be used across different ISP tenants (realistic for the domain), while enforcing uniqueness within one tenant.
  • Grace period fields on Tenant from day one: suspendedAt and gracePeriodEndsAt were included at schema creation — adding them later would require a migration during production use.
  • Vitest over Jest: Vitest aligns with Vite's ecosystem (used by Next.js 15 internally), has faster cold starts, and supports ESM natively.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Stopped conflicting isp-postgres container holding port 5432

  • Found during: Task 1 (Docker Compose startup)
  • Issue: An existing isp-postgres container from a prior project was bound to 0.0.0.0:5432, preventing netforge_db from binding the same port
  • Fix: Ran docker stop isp-postgres to free the port, then re-ran docker compose up -d
  • Verification: docker compose ps shows netforge_db healthy with 0.0.0.0:5432->5432/tcp
  • Committed in: 90bc583 (Task 1 commit)

2. [Rule 3 - Blocking] Force-recreated db container to establish host port binding

  • Found during: Task 2 (prisma db push)
  • Issue: The initial netforge_db container was created before isp-postgres was stopped — it didn't get the host port binding. 5432/tcp showed without host mapping.
  • Fix: Ran docker compose up -d --force-recreate db to recreate with proper port binding
  • Verification: docker port netforge_db shows 0.0.0.0:5432->5432/tcp; prisma db push succeeded
  • Committed in: 1adeab2 (Task 2 commit)

3. [Rule 3 - Blocking] Worked around npm project name restriction

  • Found during: Task 1 (Next.js initialization)
  • Issue: npx create-next-app@latest . fails because npm forbids capital letters in the project name ("NetForge")
  • Fix: Initialized in netforge-temp/ subdirectory, then moved all files to root, updated package.json name to netforge
  • Verification: Project runs correctly; name is netforge in package.json
  • Committed in: 90bc583 (Task 1 commit)

Total deviations: 3 auto-fixed (all Rule 3 - Blocking) Impact on plan: All fixes were environment-level blockers unrelated to plan scope. No architectural changes. No scope creep.

Issues Encountered

  • docker-compose.yml included an obsolete version: "3.9" key that triggered a Docker Compose warning. Removed proactively to keep output clean.

User Setup Required

None — no external service configuration required. Everything runs locally via Docker Compose.

Next Phase Readiness

  • Docker Compose dev environment is fully operational: docker compose up -d starts all three services
  • Prisma schema is synced to PostgreSQL — next plans can add models and run migrations immediately
  • @/lib/prisma is importable — all future server-side code can use the singleton client
  • Vitest is configured — TDD tasks in subsequent plans can use npm test immediately
  • No blockers for Phase 1 Plan 02 (authentication scaffold)

Phase: 01-foundation Completed: 2026-03-04