feat: complete Phase 1 foundation scaffold
Some checks failed
CI / Lint (push) Has been cancelled
CI / TypeCheck (push) Has been cancelled
CI / Test (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/api/Dockerfile name:api]) (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/cms/Dockerfile name:cms]) (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/web/Dockerfile name:web]) (push) Has been cancelled
CI / Build (push) Has been cancelled
Deploy / Deploy (push) Has been cancelled

- Monorepo: Turborepo + pnpm workspaces with 7 apps/packages
- Backend: NestJS scaffold with 9 modules (auth, tenant, curriculum, simulation, portfolio, ai-coach, analytics, classroom, gamification)
- Frontend: React 18 + Vite + TailwindCSS + shadcn/ui with 10 pages, 14 UI primitives, 3 Zustand stores
- Database: Prisma schema with 19 models, 13 enums, seed script, multi-tenant ready
- Docker: Dev, production, and Portainer Compose files with Traefik reverse proxy
- Configuration: .env.example (47 vars), Zod validation, frontend-safe env exposure
- i18n: English + Greek locale files (10 files), ICU MessageFormat, react-i18next
- Shared packages: @investplay/types, @investplay/ui, @investplay/i18n, @investplay/utils
- CI/CD: GitHub Actions (lint, typecheck, test, build, deploy, PR checks)
- Documentation: CONTEXT.md, ARCHITECTURE.md (10 Mermaid diagrams), API.md, LOCALIZATION.md, DEVELOPMENT-ROADMAP.md
- Infrastructure: Dockerfiles (dev + prod), nginx configs, backup/healthcheck scripts
- Security: JWT auth guards, role-based access, rate limiting, Helmet, CORS, PII sanitization
This commit is contained in:
Lefteris Notas
2026-06-12 19:32:57 +03:00
parent fa71c60cfc
commit e75f913f1c
226 changed files with 17547 additions and 0 deletions

320
docs/CONTEXT.md Normal file
View File

@@ -0,0 +1,320 @@
# InvestPlay — Project Context
## Overview
InvestPlay is a cross-platform financial literacy platform targeting users aged 16-25. It combines interactive educational content, realistic market simulations, gamified progression, and AI-powered coaching to teach investing fundamentals. The platform serves both individual learners (B2C) and educational institutions (B2B) through a multi-tenant architecture with schema-level data isolation.
### Vision
Make financial literacy universally accessible and engaging for the next generation of investors, starting with EU markets (Greece initial launch) and expanding globally.
### Target Users
| Persona | Age Range | Platform | Key Need |
|---------|-----------|----------|----------|
| Self-directed learner | 16-25 | Web, Mobile | Learn investing at own pace, earn XP, compete on leaderboards |
| Student (classroom) | 14-20 | Web | Complete assigned lessons, participate in live simulations |
| Teacher | 25-55 | Web | Create classrooms, assign curriculum, monitor progress |
| Institution admin | 30-60 | Web, Desktop | White-label platform, manage billing, view analytics |
| Parent (future) | 35-55 | Web, Mobile | Monitor child's progress, manage subscription |
## Tech Stack
| Layer | Technology | Purpose |
|-------|-----------|---------|
| **Monorepo** | Turborepo 2.5 + pnpm 9.15 | Orchestrate builds, share code, manage dependencies |
| **Backend** | NestJS 11 + Node.js 20 | Modular API server with DI, guards, interceptors |
| **API Protocol** | GraphQL (Apollo Server 4) | Primary CRUD — type-safe, no over-fetching |
| **Real-time** | Socket.io (NestJS WebSocket gateway) | Live simulations, leaderboards, AI streaming |
| **REST** | Express (NestJS platform) | Webhooks (Stripe, Clerk, Resend), health checks |
| **Frontend** | React 18 + TypeScript + Vite 6 | SPA with fast HMR, tree-shaking, code splitting |
| **CSS** | TailwindCSS 4 + shadcn/ui (Radix primitives) | Design tokens, accessible components, dark mode |
| **CMS** | Payload CMS 3 (self-hosted, headless) | Dynamic block-based content, i18n, media library |
| **Database** | PostgreSQL 16 + Prisma ORM 6 | ACID compliance, JSON support, multi-schema tenancy |
| **Cache/Queue** | Redis 7 + BullMQ (NestJS BullMQ module) | Rate limiting, job queues, session store, leaderboards |
| **Storage** | S3-compatible (MinIO dev / Cloudflare R2 prod) | User avatars, PDF reports, CSV market data |
| **Auth** | Clerk (B2C) / JWT with SAML SSO (B2B) | Social login, passwordless, institutional SSO |
| **Email** | Resend + React Email templates | Welcome emails, progress reports, teacher invites |
| **Billing** | Stripe Connect | B2B seat-based subscriptions, usage metering |
| **AI** | OpenAI / Gemini (via backend proxy) | Socratic AI coach with token budget enforcement |
| **Container** | Docker Compose (dev) + Portainer/Traefik (prod) | Consistent environments, auto-SSL, monitoring |
| **CI/CD** | GitHub Actions -> GHCR -> Portainer webhook | Automated testing, building, and deployment |
| **Testing** | Vitest 3 + Testing Library + Playwright | Unit, integration, and E2E coverage |
| **Desktop** | Tauri v2 (Rust-based, future) | Native Windows/macOS/Linux wrapper |
| **Mobile** | Capacitor (future) | Native iOS/Android wrapper |
## Directory Structure
```
investplay/
├── apps/
│ ├── api/ # NestJS backend
│ │ ├── src/
│ │ │ ├── modules/
│ │ │ │ ├── auth/ # JWT, SSO, role guards
│ │ │ │ ├── tenant/ # Multi-tenancy, feature flags
│ │ │ │ ├── curriculum/ # CMS-backed lesson delivery
│ │ │ │ ├── simulation/ # Historical tick streaming
│ │ │ │ ├── portfolio/ # Virtual trades, P&L calc
│ │ │ │ ├── ai-coach/ # LLM proxy with cost control
│ │ │ │ ├── analytics/ # Behavioral telemetry
│ │ │ │ ├── classroom/ # Teacher dashboard, assignments
│ │ │ │ ├── gamification/ # XP, badges, streaks
│ │ │ │ └── billing/ # Stripe Connect integration
│ │ │ ├── common/
│ │ │ │ ├── guards/ # JWT guard, tenant guard, roles guard
│ │ │ │ ├── decorators/ # @CurrentUser, @TenantId
│ │ │ │ ├── filters/ # Global exception filter
│ │ │ │ └── interceptors/ # Logging, timing, tenant injection
│ │ │ ├── config/ # Env-based config modules
│ │ │ └── main.ts # NestJS bootstrap
│ │ ├── prisma/
│ │ │ ├── schema.prisma # Full data model
│ │ │ └── migrations/ # Prisma migration history
│ │ ├── test/ # E2E test suites
│ │ ├── Dockerfile
│ │ └── package.json
│ ├── web/ # React + Vite frontend
│ │ ├── src/
│ │ │ ├── components/ # UI components (per feature)
│ │ │ ├── pages/ # Route pages
│ │ │ ├── hooks/ # Custom React hooks
│ │ │ ├── stores/ # Zustand stores
│ │ │ ├── lib/ # Utilities, API client
│ │ │ ├── styles/ # TailwindCSS setup
│ │ │ └── i18n/ # i18next configuration
│ │ ├── public/ # Static assets
│ │ ├── index.html
│ │ ├── vite.config.ts
│ │ ├── Dockerfile
│ │ └── package.json
│ ├── cms/ # Payload CMS
│ │ ├── src/
│ │ │ ├── collections/ # Content models
│ │ │ ├── globals/ # Site-wide settings
│ │ │ └── payload.config.ts
│ │ ├── Dockerfile
│ │ └── package.json
│ ├── mobile/ # Capacitor (future)
│ └── desktop/ # Tauri (future)
├── packages/
│ ├── types/ # @investplay/types
│ │ └── src/
│ │ ├── user.ts
│ │ ├── tenant.ts
│ │ ├── curriculum.ts
│ │ ├── simulation.ts
│ │ ├── portfolio.ts
│ │ └── analytics.ts
│ ├── ui/ # @investplay/ui
│ │ └── src/
│ │ ├── components/ # shadcn/ui primitives
│ │ └── lib/ # cn(), formatCurrency(), etc.
│ ├── i18n/ # @investplay/i18n
│ │ └── src/
│ │ ├── locales/
│ │ │ ├── en/ # English translations
│ │ │ │ ├── common.json
│ │ │ │ ├── finance.json
│ │ │ │ ├── gamification.json
│ │ │ │ ├── classroom.json
│ │ │ │ └── ai-coach.json
│ │ │ └── el/ # Greek translations
│ │ │ ├── common.json
│ │ │ └── ...
│ │ └── index.ts # i18next config
│ └── utils/ # @investplay/utils
│ └── src/
│ ├── validation.ts # Zod schemas
│ ├── formatting.ts # Currency, date, number
│ ├── constants.ts # App constants
│ └── env.ts # Env validation
├── docker/
│ ├── traefik/
│ │ ├── traefik.yml # Static config
│ │ └── dynamic.yml # Dynamic config (middleware, TLS)
│ └── nginx/
│ └── nginx.conf # Web SPA nginx config
├── scripts/
│ ├── setup.sh # Initial dev setup
│ ├── healthcheck.sh # Service health verification
│ └── backup.sh # Database backup script
├── docs/ # Project documentation
├── specs/ # Detailed specifications
├── .github/
│ └── workflows/
│ ├── ci.yml # Continuous integration
│ ├── deploy.yml # Deployment pipeline
│ └── pr-checks.yml # PR quality gates
├── .env.example # Environment template
├── turbo.json # Turborepo pipeline config
├── pnpm-workspace.yaml # Workspace definition
├── tsconfig.base.json # Shared TS config
├── .eslintrc.js # ESLint root config
├── .prettierrc # Prettier formatting
└── package.json # Root package.json
```
## Architecture Patterns
### Multi-tenancy
**Approach:** Schema-level isolation via PostgreSQL schemas.
- Each tenant (institution) gets its own PostgreSQL schema
- `TenantContext` stored in `AsyncLocalStorage` — resolved from JWT `tenantId` claim
- Prisma multi-schema extension for dynamic schema switching
- Global/shared tables (lookups, platform config) in `public` schema
- Connection pooling per schema via Prisma's `connection_limit`
- Tenant resolution pipeline: HTTP request -> JWT middleware -> extract tenantId -> set AsyncLocalStorage -> Prisma middleware applies schema filter
### API Design
- **GraphQL** is the primary API for all CRUD operations — single endpoint `/graphql`
- **REST** used only for webhooks (Stripe, Clerk, Resend) and `/health`
- **WebSocket** namespaces for real-time features: `/simulations` (tick streaming), `/ai-coach` (chat streaming), `/classroom` (live teacher broadcasts)
- Input validation via Zod schemas at every API boundary
- All GraphQL queries have depth limiting (max 7) and cost analysis
### Content Management
- Payload CMS serves as the single source of truth for all educational content
- Content model: Track -> Module -> Lesson -> Block (dynamic array)
- Block types: Hero, Text, Image, Video, Quiz, Calculator, BudgetGame, TradeSimulation
- Frontend renders content via a `BlockRenderer` component that maps CMS block types to React components
- CMS localization: each content collection supports locale variants
### AI Proxy Architecture
- All LLM requests route through the NestJS `AICoachModule` — never direct from client
- Context injection: backend appends portfolio state, lesson context, and user history to the system prompt
- Rate limiting via Redis token bucket (per-user daily, per-tenant monthly)
- BYOK (Bring Your Own Key): institutions can provide their own API key for higher quotas
- PII sanitization: names, emails, phone numbers stripped before sending to LLM
- Audit logging: every AI interaction logged with token count, latency, and model used
### Security Architecture
- **Authentication:** JWT RS256 (asymmetric) — access token (15m) + refresh token (7d)
- **Authorization:** Role-based guards (`@Roles(ROLE.TEACHER)`) + tenant scope enforcement
- **GraphQL safety:** Depth limiting (max 7), query cost analysis, persisted operations (future)
- **HTTP hardening:** Helmet.js headers, CORS whitelist per tenant, rate limiting (100 req/min default)
- **WebSocket:** JWT handshake authentication, room-based isolation per tenant
- **Secrets:** Zero secrets in code — all via environment variables, Docker secrets, or secret manager
- **GDPR:** Soft-deletes, PII anonymization pipeline, data export API, retention policies
## Key Design Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Monorepo tool | Turborepo | Better DX than Nx for this scale; native ESM support; simpler cache config |
| Package manager | pnpm | Disk-efficient (hard links), strict dependency resolution, workspace protocol |
| Backend framework | NestJS | Modular architecture with DI, decorator-based guards, native GraphQL + WebSocket support |
| Frontend build | Vite (not Next.js) | SPA is sufficient (no SSR SEO needed); faster builds; simpler deployment (nginx static) |
| CMS | Payload CMS (self-hosted) | TypeScript-native, code-first config, block-based content, built-in i18n, no vendor lock-in |
| Database ORM | Prisma | Type-safe queries, migration-first workflow, multi-schema preview feature for tenancy |
| Auth (initial) | Clerk | Fast to integrate for B2C; supports social login, passwordless, webhooks |
| Auth (B2B future) | Auth0 / SAML SSO | Required for institutional customers with Active Directory / Google Workspace |
| API protocol | GraphQL primary | Type-safe contracts, no over-fetching, single endpoint, introspection for DX tools |
| Real-time | Socket.io | Mature, fallback transport, room support, NestJS integration |
| Background jobs | BullMQ + Redis | Persistence, delayed jobs, rate limiting, job events for monitoring |
| Styling | TailwindCSS + shadcn/ui | Utility-first, design tokens, accessible Radix primitives, small bundle |
| i18n | i18next + ICU MessageFormat | Industry standard, pluralization, context, rich text in translations |
| AI cost control | Server-side token bucket | Hard enforcement not reliant on OpenAI budget alerts; Redis is fast and atomic |
| Container registry | GHCR | Free with GitHub, tight integration with Actions, no extra credentials |
| Deployment | Docker Compose + Traefik | Simpler than k8s for current scale; Traefik auto-SSL with Let's Encrypt |
| Desktop (future) | Tauri v2 | Rust-based, 10x smaller binary than Electron, memory-efficient |
| Mobile (future) | Capacitor | Minimal wrapper, access to native APIs, shares web codebase |
## State Management
| State Type | Technology | Usage |
|-----------|-----------|-------|
| Server state | @tanstack/react-query | Data fetching, caching, optimistic updates, pagination |
| GraphQL state | Apollo Client (future) | When GraphQL adoption increases; for now REST-like via react-query |
| Client/auth state | Zustand | Auth session, UI preferences (theme, sidebar), gamification (XP, level) |
| Form state | React Hook Form + Zod | All forms with schema validation |
| URL state | React Router v7 | Search params, path params for routing |
| Real-time state | Socket.io client | Live simulation ticks, leaderboard updates, AI chat streaming |
## Naming Conventions
| Category | Convention | Example |
|----------|-----------|---------|
| Files (components) | kebab-case | `user-profile.tsx`, `simulation-chart.tsx` |
| Files (modules/classes) | PascalCase | `AuthModule.ts`, `UserService.ts` |
| Variables | camelCase | `isLoading`, `userData` |
| Functions | camelCase | `getUserById()`, `formatCurrency()` |
| React components | PascalCase | `UserProfile`, `SimulationChart` |
| Types | PascalCase | `User`, `TenantConfig` |
| Interfaces | PascalCase (no I prefix) | `UserRepository`, `AuthPayload` |
| Enums | PascalCase (singular) | `Role`, `LessonStatus`, `TenantTier` |
| Database tables | snake_case | `users`, `user_progress`, `classroom_students` |
| Database columns | snake_case | `tenant_id`, `first_name`, `created_at` |
| GraphQL types | PascalCase | `User`, `LessonProgress`, `TradeInput` |
| GraphQL fields | camelCase | `userId`, `portfolioValue`, `totalXp` |
| Environment variables | UPPER_SNAKE_CASE | `DATABASE_URL`, `JWT_SECRET` |
## Environment Variables
See [.env.example](../.env.example) for the complete template with all variables and descriptions.
Key groups:
- **APP** — `NODE_ENV`, `PORT`, `APP_URL`, `API_URL`, `CORS_ORIGINS`
- **DATABASE** — `DATABASE_URL`, `DATABASE_POOL_MIN`, `DATABASE_POOL_MAX`
- **REDIS** — `REDIS_URL`, `REDIS_PASSWORD`
- **JWT** — `JWT_SECRET`, `JWT_EXPIRES_IN`, `JWT_REFRESH_EXPIRES_IN`
- **AUTH** — `CLERK_SECRET_KEY`, `CLERK_WEBHOOK_SECRET`, `AUTH_SSO_ENABLED`
- **AI** — `OPENAI_API_KEY`, `GEMINI_API_KEY`, `AI_DEFAULT_DAILY_LIMIT`, `AI_DEFAULT_MONTHLY_TOKEN_BUDGET`
- **STORAGE** — `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_BUCKET`
- **EMAIL** — `RESEND_API_KEY`, `EMAIL_FROM`
- **BILLING** — `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`
- **TENANT** — `TENANT_DEFAULT_AI_MODE`, `TENANT_MAX_TOKENS_MONTHLY`
- **i18n** — `DEFAULT_LOCALE`, `SUPPORTED_LOCALES`
- **CMS** — `CMS_API_URL`, `CMS_API_KEY`
- **SECURITY** — `RATE_LIMIT_TTL`, `RATE_LIMIT_MAX`, `GRAPHQL_DEPTH_LIMIT`
- **LOGGING** — `LOG_LEVEL`, `LOG_FORMAT`
## Testing Strategy
| Layer | Tool | Scope | Frequency |
|-------|------|-------|-----------|
| Unit | Vitest | Backend services, utils, hooks, formatting | Every commit |
| Integration | Supertest + Vitest | API endpoints, GraphQL resolvers | Every commit |
| Component | Vitest + Testing Library | React component rendering, user interactions | Every commit |
| E2E | Playwright | Critical user flows (login, lesson, simulation) | CI / pre-release |
| Contract | GraphQL schema tests | Schema validation, resolver shape | On schema change |
| Coverage target | 80%+ | Core business logic (services, resolvers, hooks) | CI gate |
## Development Workflow
1. Create a feature branch from `main`:
- `git checkout -b feat/my-feature` (new feature)
- `git checkout -b fix/my-bug` (bug fix)
2. Start the development environment: `docker compose -f docker-compose.dev.yml up`
3. Develop iteratively with hot-reload enabled
4. Write tests following TDD cycle: Red -> Green -> Refactor
5. Run quality gates locally:
```bash
pnpm lint
pnpm typecheck
pnpm test
```
6. Commit using conventional commits:
```
feat(auth): add SAML SSO login flow
fix(simulation): correct tick timestamp offset
docs(api): update websocket event reference
```
7. Push branch and open a Pull Request
8. CI runs automatically: lint -> typecheck -> test -> build
9. PR must pass all checks and receive code review approval
10. Squash merge to `main` (keeps history clean)
11. CI runs again on main, then auto-deploys to production
## Related Documents
- [ARCHITECTURE.md](./ARCHITECTURE.md) — System architecture, request flow, multi-tenancy deep dive
- [API.md](./API.md) — API reference, authentication, endpoints, error codes
- [LOCALIZATION.md](./LOCALIZATION.md) — i18n setup, adding locales, ICU formatting
- [DEVELOPMENT-ROADMAP.md](./DEVELOPMENT-ROADMAP.md) — Phase plan and progress tracking