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

183 lines
8.1 KiB
Markdown

---
phase: 01-foundation
plan: "01"
subsystem: infra
tags: [next.js, typescript, tailwind, prisma, postgresql, redis, docker, vitest]
# Dependency graph
requires: []
provides:
- 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)
affects:
- 01-02
- 01-03
- 01-04
- 01-05
- All future phases (every plan builds on this scaffold)
# Tech tracking
tech-stack:
added:
- 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)
patterns:
- "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"
key-files:
created:
- 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
modified: []
key-decisions:
- "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"
patterns-established:
- "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"
# Metrics
duration: 11min
completed: 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*