This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Faculytics API is a NestJS backend for an analytics platform that integrates with Moodle LMS. It uses MikroORM with PostgreSQL, JWT authentication via Passport, and Zod for configuration validation.
# Development
npm run start:dev # Start with watch mode
npm run build # Build the project
npm run lint # Lint and auto-fix
docker compose up # Start Redis + mock worker for local dev
# Testing
npm run test # Run unit tests
npm run test -- --testPathPattern=<pattern> # Run specific test file
npm run test:e2e # Run E2E tests
npm run test:cov # Run tests with coverage
# Database (MikroORM)
npx mikro-orm migration:create # Create new migration
npx mikro-orm migration:up # Apply pending migrations
npx mikro-orm migration:list # Check migration statusThe app uses a split between Infrastructure and Application modules (src/modules/index.module.ts):
- InfrastructureModules: ConfigModule, PassportModule, MikroOrmModule, JwtModule, ScheduleModule, BullModule, TerminusModule
- ApplicationModules: HealthModule, MoodleModule, AuthModule, ChatKitModule, EnrollmentsModule, QuestionnaireModule, AnalysisModule
Entity Base Class (src/entities/base.entity.ts):
- All entities extend
CustomBaseEntitywith UUID primary key, timestamps, and soft delete - Soft delete is enforced globally via MikroORM filter in
mikro-orm.config.ts
Custom Repository Pattern:
- Entities specify their repository:
@Entity({ repository: () => UserRepository }) - Repositories are in
src/repositories/
Environment Configuration:
- Zod schemas in
src/configurations/env/validate all env vars at startup - Access validated env via
import { env } from 'src/configurations/index.config'
JWT Authentication:
- Use
@UseJwtGuard()decorator fromsrc/security/decorators/to protect endpoints - Two Passport strategies:
jwt(access token) andrefresh-jwt(refresh token)
Login Strategy Pattern (src/modules/auth/strategies/):
- Authentication uses the Strategy pattern via the
LoginStrategyinterface - Each strategy defines a
priority(lower = higher precedence),CanHandle(), andExecute() - Priority ranges:
0-99core auth (local passwords),100-199external providers (Moodle),200+fallbacks - LocalLoginStrategy (priority 10): bcrypt password comparison for users with local credentials
- MoodleLoginStrategy (priority 100): Moodle token-based auth with user hydration
- New strategies are registered via the
LOGIN_STRATEGIESinjection token
Cron Jobs (src/crons/):
- Extend
BaseJobclass which provides startup execution and graceful shutdown - Jobs register results in
StartupJobRegistryfor boot summary - Active jobs:
RefreshTokenCleanupJob: Purges expired refresh tokens every 12 hours (7-day retention)
Moodle Sync Scheduling (src/modules/moodle/schedulers/):
- Dynamic cron via
SchedulerRegistry(no static@Cron()decorator) - Interval resolves: DB (
SystemConfig) > env var (MOODLE_SYNC_INTERVAL_MINUTES) > per-env default - Admin-configurable at runtime via
PUT /moodle/sync/schedule(min 30 minutes) SyncLogentity tracks every sync with per-phase metrics (fetched, inserted, updated, deactivated)SyncLogdoes NOT extendCustomBaseEntity— queries must usefilters: { softDelete: false }
BullMQ on Redis provides async job processing for AI analysis tasks:
- Queue-per-type pattern: Each analysis type (sentiment, topic model, embeddings) gets its own BullMQ queue and processor
- Entry points:
AnalysisService.EnqueueJob()andEnqueueBatch()— other modules use these to dispatch analysis jobs - BaseAnalysisProcessor: Abstract base class handling HTTP dispatch to external workers, Zod validation of responses, retry/error handling, and observability events
- Mock worker:
mock-worker/directory contains a Hono HTTP server mimicking worker responses for local dev - Local dev:
docker compose upstarts Redis and mock worker
The questionnaire module includes a data ingestion system (src/modules/questionnaires/ingestion/):
- SourceAdapter interface: Defines
extract()async generator for streaming records - Adapters: CSV and Excel adapters in
adapters/ - IngestionEngine: Processes streams with concurrency control (p-limit), dry-run support, and timeout handling
- IngestionMapperService: Maps raw data to domain entities
MoodleModulehandles communication with Moodle LMS- Users are synced from Moodle site info
- Enrollments, categories, and courses are synced via cron jobs
MoodleClientenforces a 10-second request timeout on all Moodle API calls- Network/timeout errors are wrapped in
MoodleConnectivityErrorand surfaced as 401 responses
Tests use NestJS TestingModule with Jest mocks:
const module: TestingModule = await Test.createTestingModule({
providers: [
ServiceUnderTest,
{ provide: Dependency, useValue: { method: jest.fn() } },
],
}).compile();The project is tracked on a GitHub Projects board.
- Backlog — Issues are created freely (rough ideas, bugs, feature requests). No ticket number required yet.
- Ready — During refinement, the issue is cleaned up with details and scope, then assigned a
FAC-XXticket number in the title. The ticket number signals the issue is well-defined and ready to be picked up. - In Progress — A developer picks up the issue and begins work.
- In Review — A PR is opened and linked to the issue.
- In Develop — PR is merged into the
developbranch. - In Staging — The merge commit is cherry-picked onto the
stagingbranch for pre-production validation. - Done — Deployed to production.
- Issue titles follow the format:
FAC-XX type: description(e.g.,FAC-33 feat: add Moodle connectivity error handling) - PR titles match their issue title
- Issue bodies include the PR link and merge commit hash for cherry-pick reference when moving to staging
- The
FAC-XXticket number prefix is the quality gate — only assigned issues are ready for development
Required environment variables (see .env.sample):
PORT: Server port (default:5200)NODE_ENV:development|production|test(default:development)DATABASE_URL: PostgreSQL connection string (supports Neon.tech SSL)MOODLE_BASE_URL: Moodle instance URLMOODLE_MASTER_KEY: Moodle web services master keyJWT_SECRET,REFRESH_SECRET: Token signing secretsCORS_ORIGINS: JSON array of allowed originsOPENAI_API_KEY: OpenAI API key (for ChatKit module)REDIS_URL: Redis connection URL (used for caching and job queues)
Optional:
JWT_ACCESS_TOKEN_EXPIRY: Access token lifetime (default:300s)JWT_REFRESH_TOKEN_EXPIRY: Refresh token lifetime (default:30d)JWT_BCRYPT_ROUNDS: Bcrypt cost factor for refresh-token hashing (default:10; values below10warn outside production)OPENAPI_MODE: Set to"true"to enable Swagger docs (default: disabled)SYNC_ON_STARTUP: Set to"true"to run Course and Enrollment sync on startup (default: disabled)SUPER_ADMIN_USERNAME: Default super admin username (default:superadmin)SUPER_ADMIN_PASSWORD: Default super admin password (default:password123)BULLMQ_DEFAULT_ATTEMPTS: Job retry attempts (default:3)BULLMQ_DEFAULT_BACKOFF_MS: Initial backoff delay in ms (default:5000)BULLMQ_DEFAULT_TIMEOUT_MS: Job-level timeout in ms (default:120000)BULLMQ_HTTP_TIMEOUT_MS: HTTP request timeout in ms (default:90000)BULLMQ_SENTIMENT_CONCURRENCY: Sentiment processor concurrency (default:3)SENTIMENT_WORKER_URL: RunPod/mock worker URL for sentiment analysisMOODLE_SYNC_INTERVAL_MINUTES: Sync schedule interval in minutes (min30; defaults per env: dev=60, staging=360, prod=180)