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

38
packages/types/src/ai.ts Normal file
View File

@@ -0,0 +1,38 @@
export interface AICoachMessage {
id: string;
sessionId: string;
role: "user" | "assistant" | "system";
content: string;
metadata: {
tokensUsed: number;
processingTimeMs: number;
model: string;
} | null;
createdAt: string;
}
export interface AICoachContext {
userId: string;
lessonId: string | null;
simulationType: string | null;
portfolio: Record<string, unknown> | null;
recentDecisions: string[];
userLevel: number;
preferredLocale: string;
}
export interface AIModeConfig {
mode: "MANAGED" | "BYOK" | "DISABLED";
apiEndpoint: string | null;
modelName: string | null;
maxTokensPerMessage: number;
temperature: number;
}
export interface AITokenBudget {
tenantId: string;
monthlyLimit: number;
usedThisMonth: number;
remaining: number;
resetDate: string;
}

View File

@@ -0,0 +1,22 @@
export enum AnalyticsEventType {
LESSON_STARTED = "LESSON_STARTED",
LESSON_COMPLETED = "LESSON_COMPLETED",
QUIZ_ATTEMPTED = "QUIZ_ATTEMPTED",
SIMULATION_JOINED = "SIMULATION_JOINED",
TRADE_EXECUTED = "TRADE_EXECUTED",
RISK_WARNING_SHOWN = "RISK_WARNING_SHOWN",
RISK_WARNING_IGNORED = "RISK_WARNING_IGNORED",
PANIC_SELL_DETECTED = "PANIC_SELL_DETECTED",
HYPE_TRAP_TRIGGERED = "HYPE_TRAP_TRIGGERED",
AI_COACH_CONSULTED = "AI_COACH_CONSULTED",
}
export interface AnalyticsEvent {
id: string;
eventType: AnalyticsEventType;
userId: string;
tenantId: string;
sessionId: string | null;
payload: Record<string, unknown>;
timestamp: string;
}

View File

@@ -0,0 +1,76 @@
export interface Classroom {
id: string;
name: string;
description: string | null;
tenantId: string;
teacherId: string;
inviteCode: string;
students: ClassroomStudent[];
assignments: Assignment[];
createdAt: string;
updatedAt: string;
}
export interface ClassroomStudent {
id: string;
classroomId: string;
userId: string;
joinedAt: string;
studentName: string;
studentEmail: string;
}
export interface Assignment {
id: string;
classroomId: string;
lessonId: string;
title: string;
description: string | null;
dueDate: string | null;
requiredScore: number;
maxAttempts: number;
submissions: AssignmentSubmission[];
createdAt: string;
updatedAt: string;
}
export interface AssignmentSubmission {
id: string;
assignmentId: string;
userId: string;
score: number | null;
completed: boolean;
attempts: number;
submittedAt: string | null;
updatedAt: string;
}
export interface TeacherReport {
classroomId: string;
classroomName: string;
totalStudents: number;
averageScore: number;
completionRate: number;
studentReports: StudentReport[];
generatedAt: string;
}
export interface StudentReport {
userId: string;
studentName: string;
compositeScore: CompositeScore;
lessonsCompleted: number;
averageQuizScore: number;
simulationsPlayed: number;
totalXp: number;
lastActivity: string | null;
}
export interface CompositeScore {
overall: number;
roi: number;
riskManagement: number;
esgAlignment: number;
quizScore: number;
lessonCompletion: number;
}

View File

@@ -0,0 +1,70 @@
export enum BlockType {
HERO = "HERO",
TEXT = "TEXT",
MEDIA = "MEDIA",
QUIZ = "QUIZ",
CALCULATOR = "CALCULATOR",
BUDGET_GAME = "BUDGET_GAME",
BLIND_SIMULATION = "BLIND_SIMULATION",
DRAG_DROP = "DRAG_DROP",
REFLECTION = "REFLECTION",
TEACHER_NOTE = "TEACHER_NOTE",
ASSIGNMENT = "ASSIGNMENT",
SUMMARY = "SUMMARY",
}
export enum CurriculumTrack {
SURVIVAL_BASICS = "SURVIVAL_BASICS",
CREDIT_DEBT = "CREDIT_DEBT",
WEALTH_BUILDER = "WEALTH_BUILDER",
ANTI_HYPE = "ANTI_HYPE",
MACROECONOMICS = "MACROECONOMICS",
}
export interface LessonBlock {
id: string;
type: BlockType;
order: number;
content: Record<string, unknown>;
config: Record<string, unknown> | null;
}
export interface Lesson {
id: string;
slug: string;
title: string;
description: string;
track: CurriculumTrack;
moduleId: string;
order: number;
estimatedMinutes: number;
xpReward: number;
blocks: LessonBlock[];
tags: string[];
prerequisites: string[];
createdAt: string;
updatedAt: string;
}
export interface Module {
id: string;
slug: string;
title: string;
description: string;
track: CurriculumTrack;
order: number;
lessons: Lesson[];
createdAt: string;
updatedAt: string;
}
export interface UserProgress {
userId: string;
lessonId: string;
completed: boolean;
score: number | null;
timeSpentSeconds: number;
attempts: number;
startedAt: string;
completedAt: string | null;
}

View File

@@ -0,0 +1,60 @@
export enum XPReason {
LESSON_COMPLETED = "LESSON_COMPLETED",
QUIZ_PASSED = "QUIZ_PASSED",
STREAK_MAINTAINED = "STREAK_MAINTAINED",
CHALLENGE_COMPLETED = "CHALLENGE_COMPLETED",
SIMULATION_COMPLETED = "SIMULATION_COMPLETED",
DIVERSIFICATION = "DIVERSIFICATION",
RISK_MANAGEMENT = "RISK_MANAGEMENT",
HYPE_AVOIDED = "HYPE_AVOIDED",
DEBT_PAID_OFF = "DEBT_PAID_OFF",
}
export interface Badge {
id: string;
slug: string;
name: string;
description: string;
iconUrl: string;
category: "milestone" | "skill" | "behavior" | "special";
requiredXp: number | null;
createdAt: string;
}
export interface Achievement {
id: string;
badgeId: string;
userId: string;
unlockedAt: string;
badge: Badge;
}
export interface UserBadge {
id: string;
userId: string;
badgeId: string;
badge: Badge;
earnedAt: string;
visible: boolean;
}
export interface LeaderboardEntry {
userId: string;
rank: number;
firstName: string;
lastName: string;
avatarUrl: string | null;
xp: number;
level: number;
badges: number;
tenantId: string;
}
export interface XPEvent {
userId: string;
amount: number;
reason: XPReason;
referenceId: string;
referenceType: "lesson" | "quiz" | "simulation" | "challenge" | "streak";
createdAt: string;
}

View File

@@ -0,0 +1,40 @@
export type { User, UserRole } from "./user.js";
export type { Tenant, TenantFeatureFlags, AIMode } from "./tenant.js";
export type {
BlockType,
CurriculumTrack,
LessonBlock,
Lesson,
Module,
UserProgress,
} from "./curriculum.js";
export type {
SimulationType,
SimulationTick,
Portfolio,
Trade,
SimulationSession,
} from "./simulation.js";
export type {
Badge,
Achievement,
UserBadge,
LeaderboardEntry,
XPEvent,
XPReason,
} from "./gamification.js";
export type {
Classroom,
ClassroomStudent,
Assignment,
AssignmentSubmission,
TeacherReport,
CompositeScore,
} from "./classroom.js";
export type { AnalyticsEvent, AnalyticsEventType } from "./analytics.js";
export type {
AICoachMessage,
AICoachContext,
AIModeConfig,
AITokenBudget,
} from "./ai.js";

View File

@@ -0,0 +1,91 @@
export enum SimulationType {
BUDGET_CHALLENGE = "BUDGET_CHALLENGE",
EMERGENCY_FUND = "EMERGENCY_FUND",
COST_OF_LIVING = "COST_OF_LIVING",
BLIND_ASSET = "BLIND_ASSET",
MARKET_CRASH = "MARKET_CRASH",
RENT_VS_TRANSPORT = "RENT_VS_TRANSPORT",
SAVINGS_HABIT = "SAVINGS_HABIT",
ESG_PORTFOLIO = "ESG_PORTFOLIO",
INTEREST_APY = "INTEREST_APY",
DEBT_REPAYMENT = "DEBT_REPAYMENT",
}
export interface SimulationTick {
tick: number;
label: string;
values: Record<string, number>;
events: SimulationEvent[];
}
export interface SimulationEvent {
type: string;
description: string;
impact: Record<string, number>;
choice?: {
options: SimulationChoice[];
chosenOptionId: string | null;
};
}
export interface SimulationChoice {
id: string;
label: string;
description: string;
impacts: Record<string, number>;
isTrap?: boolean;
trapReveal?: string;
}
export interface Portfolio {
cash: number;
investments: Investment[];
debt: Debt[];
monthlyIncome: number;
monthlyExpenses: number;
}
export interface Investment {
id: string;
name: string;
type: "stock" | "bond" | "etf" | "crypto" | "real_estate" | "commodity" | "cash";
amount: number;
value: number;
risk: "low" | "medium" | "high";
esgScore: number | null;
}
export interface Debt {
id: string;
name: string;
type: "credit_card" | "student_loan" | "mortgage" | "personal_loan";
principal: number;
interestRate: number;
minimumPayment: number;
}
export interface Trade {
id: string;
sessionId: string;
assetName: string;
type: "buy" | "sell";
quantity: number;
price: number;
totalValue: number;
timestamp: string;
reason: string;
}
export interface SimulationSession {
id: string;
userId: string;
simulationType: SimulationType;
ticks: SimulationTick[];
portfolio: Portfolio;
trades: Trade[];
score: number;
decisions: number;
completed: boolean;
startedAt: string;
completedAt: string | null;
}

View File

@@ -0,0 +1,32 @@
export enum AIMode {
MANAGED = "MANAGED",
BYOK = "BYOK",
DISABLED = "DISABLED",
}
export interface TenantFeatureFlags {
aiCoach: boolean;
classroom: boolean;
simulations: boolean;
gamification: boolean;
tenantDashboard: boolean;
researchProgram: boolean;
}
export interface Tenant {
id: string;
name: string;
domain: string | null;
branding: {
logoUrl: string | null;
primaryColor: string | null;
accentColor: string | null;
faviconUrl: string | null;
} | null;
features: TenantFeatureFlags;
aiMode: AIMode;
monthlyTokenBudget: number;
defaultLocale: string;
excludeFromResearch: boolean;
createdAt: string;
}

View File

@@ -0,0 +1,28 @@
export enum UserRole {
STUDENT = "STUDENT",
TEACHER = "TEACHER",
TENANT_ADMIN = "TENANT_ADMIN",
PLATFORM_ADMIN = "PLATFORM_ADMIN",
CONTENT_MANAGER = "CONTENT_MANAGER",
RESEARCHER = "RESEARCHER",
}
export interface User {
id: string;
email: string;
firstName: string;
lastName: string;
role: UserRole;
locale: string;
tenantId: string;
xp: number;
level: number;
streaks: {
current: number;
longest: number;
lastActivityDate: string | null;
};
avatarUrl: string | null;
createdAt: string;
updatedAt: string;
}