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
321 lines
18 KiB
Markdown
321 lines
18 KiB
Markdown
# 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
|