Appearance
Current System Audit
Phase: 0 Legacy Audit Status: initial evidence-backed pass Rule: legacy source is read-only reference; no legacy file was edited for this audit.
Scope
This audit covers the current HOCTAPAZ repo under /Users/velikho/Desktop/WORKING/HOCTAPAZ as the legacy source of truth before any Go source implementation. It focuses on backend framework, route topology, database schema, auth/tenant behavior, frontend API calls, storage, worker queues, DOCX import, question bank, exam, and attempt flows.
Repo Inventory
Evidence:
- Root workspace uses
pnpm@10.27.0, Turbo scripts, and package-level scripts inpackage.json. - Backend package is
apps/api/package.json; frontend package isapps/web/package.json. - Backend entrypoints are
apps/api/src/main.tsandapps/api/src/worker.ts. - Prisma schema is
apps/api/prisma/schema.prisma. - Existing local dependency compose file is
docker-compose.yml.
Observed structure:
apps/api: NestJS/Express backend, Prisma, BullMQ workers, S3-compatible storage, Swagger.apps/web: Next 15 frontend, React 19, direct calls to/api/....packages/shared: shared TS contracts, Zod schemas, feature maintenance routing and Vietnamese product labels.packages/eslint-config,packages/tsconfig: shared tool config.apps/mcp-teacher-server: separate MCP teacher server package.docs,output,artifacts,work,tmp: existing legacy docs/artifacts/output. They are not source-of-truth for behavior unless cross-checked.
Backend Framework
Evidence:
apps/api/package.jsondepends on@nestjs/common,@nestjs/core,@nestjs/platform-express,@nestjs/swagger,express,@prisma/client,bullmq,ioredis,@aws-sdk/client-s3,openai, and document/media libraries.apps/api/src/main.tscreatesNestFactory.create(AppModule, { bodyParser: false }), installs Express JSON/urlencoded parsers, sets global prefixapi, adds globalValidationPipe, and registers Swagger at/docs.apps/api/src/app.module.tsregisters all controllers and providers in one Nest module.
Current behavior:
- REST API base path is
/api. - Compatibility alias rewrites
/api/v1/...to/api/.... - SePay webhook aliases can be rewritten to
/api/hooks/sepay-payment. - Swagger docs are exposed at
/docs. - Body size defaults to
25mbviaAPI_BODY_LIMITorAPI_JSON_BODY_LIMIT. - CORS allows configured origins plus local/dev HOCTAPAZ hosts.
Migration note:
- Go API Gateway must preserve
/apipath compatibility for the current frontend and may expose/v1internally behind adapters. - Swagger/OpenAPI contracts must capture the envelope shape and current response semantics before frontend cutover.
Runtime Roles And Workers
Evidence:
apps/api/src/app.module.ts:150setsenableWorkers = process.env.HOCTAPAZ_PROCESS_ROLE !== "api".apps/api/src/app.module.ts:286-294conditionally registersAlgorithmExamImportWorker,QuestionClassificationWorker,QuestionDuplicateResolutionWorker,QuestionPermanentDeleteWorker, andAzCreditBillingCycleWorker.apps/api/src/worker.ts:6-11starts a Nest application context with default roleworker.apps/api/src/modules/exam-paper-templates/algorithm-exam-import.queue.ts:4-65defines BullMQ queueexam-import-algorithmusingREDIS_URLorredis://localhost:6388.
Current behavior:
- API and workers are same Nest module with different process roles.
- Background jobs use BullMQ/Redis.
- Some realtime import updates use Redis pub/sub, not only DB polling.
Migration note:
- Go services need explicit separation between HTTP deployment and worker deployment.
- API Gateway should not run domain workers.
Auth And Tenant Flow
Evidence:
- Auth routes are in
apps/api/src/modules/auth/auth.controller.ts:48-245. AuthServicelogin/register/refresh/logout are inapps/api/src/modules/auth/auth.service.ts:58-142.- Access/refresh token payload generation is in
apps/api/src/modules/auth/auth.service.ts:686-737. - JWT guard reads Bearer token or cookie
hoctapaz.accessTokeninapps/api/src/common/auth.guard.ts:46-100. - Tenant guard reads
X-Organization-Idor queryorganizationIdinapps/api/src/common/tenant.guard.ts:20-45. - User profile and refresh token tables are in Prisma models
User,RefreshToken,TeacherProfile,StudentProfile,ParentProfile,TeacherKyc.
Current behavior:
- Login normalizes email and delegates credential checks to
IdentityService.verifyUser. - Access token includes
sub,type=access,role,accountCode,fullName,email,isGuest,guestSourceLinkId,defaultOrganizationId, and optional impersonation fields. - Refresh token includes
sub,type=refresh,jti, expires after 7 days, and is persisted viaRefreshToken. - Guard enforces roles via
@Roles(...). - Tenant guard resolves or validates organization membership;
ADMINcan force requested organization. - Frontend stores
hoctapaz.accessTokenandhoctapaz.organizationIdin localStorage/cookies.
Migration note:
auth-servicemust preserve token payload compatibility at gateway boundary or gateway must adapt legacy/new payloads.user-serviceandschool-serviceneed a clear boundary for profile data vs organization membership.- Current JWT secret default
dev-secret-change-memust not be carried into production defaults.
Request Context And Health
Evidence:
RequestContextMiddlewaresetsX-Request-IdandX-Correlation-Id, logging JSON request fields inapps/api/src/common/request-context.middleware.ts:14-37.HealthControllerexposes/api/health/live,/api/health/ready, and/api/metricsinapps/api/src/modules/health/health.controller.ts:15-48.checkSystemDependenciesprobes Prisma/Postgres, Redis, and storage inapps/api/src/modules/health/system-health.util.ts:15-44.
Migration note:
- Go services must expose
/healthzand/readyz; gateway should also retain/api/health/liveand/api/health/readycompatibility until frontend/ops cutover. - Correlation ID propagation is a platform requirement.
Frontend API Coupling
Evidence:
apps/web/lib/api-config.ts:19-51resolves API base URL fromNEXT_PUBLIC_API_URL,NEXT_PUBLIC_WEB_URL, server-side API vars, orhttp://localhost:4001.apps/web/lib/api.ts:13-58calls${API_URL}/api${path}, sendsX-Request-Id,X-Organization-Id, and Bearer token.apps/web/lib/client-api.ts:50-81mirrors client-side headers and direct/apifetches.- Many feature components call
/api/...directly, including import workspace, wallet, question bank, exams, admin, storage, and auth.
Current behavior:
- Frontend is tightly coupled to legacy
/apiroutes. - The gateway must be a compatibility layer first, not a breaking frontend rewrite.
Database And Persistence
Evidence:
apps/api/prisma/schema.prisma:1-7configures Prisma Client against PostgreSQL usingDATABASE_URL.apps/api/src/prisma/prisma.service.ts:5-21extends PrismaClient and connects on module init.- Prisma schema contains 79 models and 45 enums.
- Migrations are under
apps/api/prisma/migrations/.
Current behavior:
- One legacy database contains auth, users, organizations, classrooms, courses, documents, media, question bank, imports, exams, attempts, notifications, audit, wallet, support, AI settings, and outbox data.
- Query boundaries are service-code boundaries, not database ownership boundaries.
Migration note:
- New Go platform must split by service database. A migrator must read legacy DB but never mutate it by default.
Storage And Media
Evidence:
apps/api/src/modules/storage/storage.service.ts:65-88configures S3-compatible storage with default buckethoctapaz-local, endpointhttp://localhost:9000, and env-driven credentials.apps/api/src/modules/storage/storage.service.ts:90-122validates MIME/size and creates presigned upload URLs.apps/api/src/modules/storage/storage.controller.ts:44-99exposes/api/storage/presigned-upload,/api/storage/media-assets, and media content endpoints.docker-compose.yml:8-34defines MinIO and bucket initialization for legacy local infrastructure.
Current behavior:
- Files live in S3-compatible storage; metadata lives in Prisma
MediaAssetand related document/import tables. - Media content can be served with image variants for formula/trim previews.
Migration note:
document-serviceshould ownMediaAsset-like metadata and object keys.- DOCX import must emit media references instead of embedding storage assumptions directly into question-bank tables.
DOCX Import Flow
Evidence:
- Main import controller is
apps/api/src/modules/exam-paper-templates/exam-paper-templates.controller.ts:150-1056. - DOCX Fast job creation and enqueue flow is
apps/api/src/modules/exam-paper-templates/exam-paper-templates.controller.ts:210-307. - MathType -> Word Equation conversion endpoint is
apps/api/src/modules/exam-paper-templates/exam-paper-templates.controller.ts:347-453. - OCR document conversion endpoints are
apps/api/src/modules/exam-paper-templates/exam-paper-templates.controller.ts:485-607. - Teacher library/history/SSE stream is
apps/api/src/modules/exam-paper-templates/exam-paper-templates.controller.ts:702-865. - Review/update/approve/reprocess/reject endpoints are
apps/api/src/modules/exam-paper-templates/exam-paper-templates.controller.ts:867-1055. - BullMQ actions include
pipeline,fast-lane,heavy-lane,docx-fast,convert-formulas,approve, andocr-documentinapps/api/src/modules/exam-paper-templates/algorithm-exam-import.queue.ts:16-33. - Worker dispatch is in
apps/api/src/modules/exam-paper-templates/algorithm-exam-import.worker.ts:129-227. - DOCX Fast calls external Go Formula DOCX
/v1/import/docx/simpleinapps/api/src/modules/exam-paper-templates/go-formula-docx-import.service.ts:618-683. - DOCX Fast returns timing and warnings in
apps/api/src/modules/exam-paper-templates/go-formula-docx-import.service.ts:759-817.
Current behavior:
- Legacy already has a Go Formula DOCX dependency for fast import, configured by
GO_FORMULA_DOCX_URLorGO_FORMULA_DOCX_BASE_URL. - Parse result is stored on
ExamImportJob.parseResultJsonand draft/review metadata inpackagingJson. - SSE streams initial snapshots plus heartbeat and job updates.
- Exact workflow names in source include
docx-fast,mathtype-equation,ocr-document,MathType -> Word Equation,OCR Tài Liệu, andImport Đề Thi Nhanh (VIP).
Migration note:
docx-import-serviceshould be the first deep domain service. It must preserve warnings, media, formulas, table data, source locations, progress, and latency.- The new service should absorb or wrap the existing Go Formula DOCX behavior rather than silently changing parser semantics.
Question Bank Flow
Evidence:
QuestionsControllerlist/create/update/delete and AI routes are inapps/api/src/modules/questions/questions.controller.ts:81-147,649-835, and1000-1060.QuestionTypesController, folders, and groups are inapps/api/src/modules/questions/question-admin.controllers.ts:23-240.createQuestionvalidates question type contract, scoring rule, rich content, options, and versions inapps/api/src/modules/app-data/app-data.questions-write.ts:345-535.- Soft delete vs hard delete behavior is in
apps/api/src/modules/app-data/app-data.questions-write.ts:880-1045. - List/filter pagination is in
apps/api/src/modules/app-data/app-data.questions-read.ts:68-287. - Shared question types are
SINGLE_CHOICE,MULTIPLE_CHOICE,TRUE_FALSE,TRUE_FALSE_GROUP,SHORT_ANSWER,SHORT_NUMERIC_ANSWER,ESSAYinpackages/shared/src/index.ts:54-62.
Current behavior:
- Question CRUD is scoped by organization and owner unless admin/all-org mode applies.
- Questions have
QuestionVersion, options, tags, custom type definitions, rich content JSON, source metadata, and scoring rules. - Used questions are archived by normal delete; hard delete explicitly removes exam/attempt/course references and can delete historical attempt rows.
- AI classification jobs expose list/detail/errors/events/cancel/apply endpoints.
Migration note:
question-bank-servicemust preserve current question type definitions and the manual override behavior from import/editor workflows.- Hard-delete semantics need explicit product approval before reproducing, because it can delete attempt-related rows.
Exam And Attempt Flow
Evidence:
- Exam routes are in
apps/api/src/modules/exams/exams.controller.ts:27-225and export/event routes in315-404. - Attempt routes are in
apps/api/src/modules/attempts/attempts.controller.ts:14-70. createExampersists scheduling, shuffle, result mode, delivery mode, access password hash, and metadata inapps/api/src/modules/app-data/app-data.exams-authoring.ts:143-206.- Draft-only change rule is enforced for adding/removing questions in
apps/api/src/modules/app-data/app-data.exams-authoring.ts:516-641and746-770. - Publishing refreshes
ExamQuestion.questionSnapshotJsoninapps/api/src/modules/app-data/app-data.exams-authoring.ts:772-818. - Starting an attempt creates
ExamAttemptQuestion.questionSnapshotJsonand option order snapshots inapps/api/src/modules/app-data/app-data.exams-attempts.ts:81-314. - Saving answers uses optimistic versioning and logs
SAVE_ANSWERevents inapps/api/src/modules/app-data/app-data.exams-attempts.ts:395-487. - Submitting grades against attempt snapshots in
apps/api/src/modules/app-data/app-data.exams-attempts.ts:490-628.
Current behavior:
- Only students can start attempts.
- Exams must be
PUBLISHED, online, open, not closed, and either assigned/shared by valid link. - Attempts snapshot question content and option order to avoid later question-bank edits changing active/historical attempts.
- Result visibility depends on exam result mode.
Migration note:
attempt-servicemust not directly depend onquestion-bank-serviceinternal schema; snapshot/API/event contract is mandatory.exam-serviceowns exam authoring and publish snapshot;attempt-serviceowns attempt snapshots, answer saves, grading, and events.
Classroom, Course, Document, Notification, Admin
Evidence:
- Classroom route group has 45 routes in controller decorators.
- Course route group has 33 routes in controller decorators.
- Documents route group has teacher/admin document routes in
apps/api/src/modules/documents/documents.controller.ts. - Notifications are in
apps/api/src/modules/notifications/notifications.controller.ts. - Admin operations, users, dashboard, AI settings, wallet, and content admin are in
apps/api/src/modules/adminand wallet controllers. - Feature maintenance definitions map product areas to route/API prefixes in
packages/shared/src/feature-maintenance.ts.
Current behavior:
- These domains are implemented inside the same Nest module and same Prisma database.
- Admin flows write audit logs in multiple places.
Migration note:
- Service boundaries should follow ownership first, not route prefix only. Some route prefixes combine multiple domain concerns.
Open Questions
- Exact production legacy database name is not encoded in source;
DATABASE_URLcontrols it. - Complete field-level migration transforms require a second pass per model and should be executed before writing migration scripts.
- Current test coverage quality per domain was not fully audited in this pass.
- Some admin/wallet/support flows are out of the first strangler path but still need mapping before final cutover.