Appearance
Admin Service API
Phase: 9 Status: P9-003 foundation plus P9-030 maintenance guard adapter
Scope
admin-service owns the native foundation for feature maintenance state and admin audit history.
This slice does not cut over public /api/* routes. The gateway continues to proxy legacy admin routes until auth/RBAC adapters and response compatibility are explicitly enabled.
Legacy Evidence
- Feature registry and exact labels:
node-platform/packages/shared/src/feature-maintenance.ts. - Public/admin feature routes:
node-platform/apps/api/src/modules/admin/feature-maintenance.controller.ts. - Feature state merge/cache/update behavior:
node-platform/apps/api/src/modules/admin/feature-maintenance.service.ts. - Global maintenance guard and AI classification GET bypass:
node-platform/apps/api/src/common/feature-maintenance.guard.ts. - Guard registration:
node-platform/apps/api/src/app.module.ts. - Legacy models:
node-platform/apps/api/prisma/schema.prismamodelsFeatureMaintenanceandAuditLog. - Admin operations audit listing:
node-platform/apps/api/src/modules/admin/operations.controller.ts. - Admin maintenance UI callers:
node-platform/apps/web/app/admin/maintenance/page.tsx. - Admin operations audit UI shape:
node-platform/apps/web/app/admin/operations/page.tsx.
Native Routes
| Method | Path | Purpose |
|---|---|---|
GET | /v1 | Service marker. |
GET | /v1/feature-maintenance/public | Public active feature payload for guard/frontend adapters. |
GET | /v1/feature-maintenance/guard/{key} | Guard decision for gateway/BFF adapters, including allowed/blocked state and read-only AI classification job bypass. |
GET | /v1/admin/feature-maintenance | Admin list of all feature maintenance states. |
PATCH | /v1/admin/feature-maintenance/{key} | Admin update for enabled and message. |
GET | /v1/admin/operations/source-map | Read-only owner map for future admin operations/dashboard metrics and commands. |
GET | /v1/admin/operations/source-snapshots | Service-local probe of configured owner APIs from the operations source map. |
POST | /v1/admin/audit-logs | Internal/admin audit event writer. |
GET | /v1/admin/audit-logs | Admin audit list with filters. |
POST | /v1/admin/notification-broadcasts | Internal explicit-recipient notification broadcast producer. |
POST | /v1/support/notification-events | Internal support ticket notification producer with already-resolved recipients. |
Compatibility Rules
- Feature definitions are native data in Go, copied from the legacy registry.
- Preserve exact Vietnamese labels such as
Trợ Lý AI,AI Phân Loại Ngân Hàng Câu Hỏi,Import Đề Thi Nhanh (VIP),MathType -> Word Equation,OCR Tài Liệu, andMẫu Word. - Feature update accepts only
enabledand optional nullablemessage. - Message values are trimmed; empty messages are stored as
null. - Public status returns
{ items, activeFeatureKeys, generatedAt }. - Guard decision returns the same registry metadata plus
enabled,allowed,blocked, optionalreason=feature_maintenance, optionalbypassReason=readonly_ai_classification_job, and optional inspectedmethod/path. - Admin list returns every registry item, defaulting unconfigured rows to
enabled=false. - Read-only AI classification history/progress GETs must remain inspectable during maintenance:
/api/questions/ai-classify/jobsand/api/questions/ai-classify/jobs/:id/.... - Audit logs preserve
organizationId,actorId,action,entity,entityId,metadata, andcreatedAt. - Audit listing preserves
action,entity,limit, andskipfilters; limit defaults to100and clamps to1..500, skip defaults to0. - When
AUDIT_SERVICE_URLorAUDIT_SERVICE_BASE_URLis set, new admin audit logs are also projected to audit-service/v1/audit-eventsbest-effort withsourceService=admin-service,sourceEventId=<admin audit log id>,category=ADMIN,severity=INFO,retentionPolicy=admin-operations, andcreatedAt -> occurredAt. Projection failures are ignored so the current admin compatibility write remains available. GET /v1/admin/operations/source-mapis a planning/read-model contract only. It maps health, queue failures/depths, import jobs, outbox events and commands, AI provider status at ai-classifier-service/v1/ai/providers/status, wallet review summaries, support queues, identity/profile counts, organization roster/member counts, classroom/exam counts, attempt metrics, document totals/pending reviews, and audit history to owner services and APIs. It does not aggregate live metrics and does not move public admin operations routes.queue.failuresnow points to docx-import-serviceGET /v1/admin/import/docx/queue-summary;import.jobspoints to docx-import-serviceGET /v1/import/docx/jobs;queue.depthsuses docx-import-serviceGET /v1/admin/import/docx/queue-summaryand ai-classifier-serviceGET /v1/admin/ai-classifier/queue-summary.operations.queue-retrypoints to docx-import-servicePOST /v1/import/docx/docx-fast-jobs/{id}/reprocessand ai-classifier-servicePOST /v1/ai-classifier/jobs/{id}/retry.organization.rosternow points to the school-service owner summaryGET /v1/admin/organizations/roster-summary; IAM still owns permission-bearing organization membership.learning.classroom-countspoints to classroom-serviceGET /v1/admin/classrooms/summary.learning.exam-countspoints to exam-serviceGET /v1/admin/exams/summary.learning.attempt-metricspoints to attempt-serviceGET /v1/admin/attempts/summaryfor local attempt rows; derived result/weak-topic/dashboard rollups still belong to analytics-service.identity.countspoints to IAMGET /v1/admin/identity/summaryand profile-serviceGET /v1/admin/profiles/summary; IAM owns access-control counts while profile-service owns display/read-model counts.support.queuespoints to notification-serviceGET /v1/admin/support/notification-summary; it summarizes support notification/event/delivery state only and does not move support ticket storage into admin-service or notification-service.GET /v1/admin/operations/source-snapshotsuses the source map as the required key list and only probes owner endpoints configured inADMIN_OPERATIONS_SOURCE_TARGETS(key=url,key=url). Unconfigured owners returnstatus=unconfigured; failing owner endpoints returnstatus=degradedorstatus=unavailable. This route is still a service-local read-model step, not dashboard parity and not public/api/admin/overviewor/api/admin/operations*cutover. Configured owner probes run concurrently with a maximum of eight in flight, preserve source-map order, and report per-owner latency/status independently.- SVC-023 owner-source and route/browser evidence is recorded in Admin Owner Source And Route Proof. The proof keeps the default gateway table legacy-proxied and treats configured owner snapshots as health/status evidence only; admin-service does not synthesize owner totals.
POST /v1/admin/notification-broadcastsaccepts only already-resolvedrecipientIdsplusclientRequestId,title, andcontent. It writes a local admin audit log and, whenNOTIFICATION_SERVICE_URLorNOTIFICATION_SERVICE_BASE_URLis configured, best-effort emits notification-servicePOST /v1/events/notificationwithsourceService=admin-service,sourceEventId=admin:broadcast:{clientRequestId},type=admin.notification.broadcast.v1, andpreferenceType=ADMIN_BROADCASTunless a custom notificationtypeis supplied. It does not implement legacyALLorROLESrecipient discovery; IAM/profile/gateway resolvers must supply concrete recipient ids first.POST /v1/support/notification-eventsaccepts only already-resolvedrecipientIdsplusticketId,clientRequestId,title, andcontent. It writes a local support audit log usingeventTypesuch assupport.ticket.createdorsupport.ticket.replyand, whenNOTIFICATION_SERVICE_URLorNOTIFICATION_SERVICE_BASE_URLis configured, best-effort emits notification-servicePOST /v1/events/notificationwithsourceService=admin-service,sourceEventId=support:ticket:{ticketId}:notification:{clientRequestId},type=support.ticket.notification.v1,entity=SupportTicket, andpreferenceType=SUPPORT_TICKET. It preserves legacy support notification inbox types such asSUPPORT_TICKET_CREATED,SUPPORT_TICKET_REPLY, andSUPPORT_TICKET_STATUSthrough the inputtypefield. It does not store support tickets, discover admins/requesters, or write notification rows directly.
Deferred Routes
/api/admin/operations/health, queues, metrics, import jobs, outbox, and retry/clear commands./api/admin/users*public compatibility wrapping. Teacher KYC review storage and owner-side native list/detail/approve/reject now live in profile-service at/v1/admin/users/kyc/teacher*; admin-service must not copy KYC storage or document hashes./api/admin/ai-settings*./api/admin/inbox*target search plusALL/ROLESrecipient discovery./api/admin/wallet*,/api/wallet*, and/api/hooks*./api/support*public route cutover and support ticket persistence.- Admin dashboard aggregation.
Gateway Route Rehearsal
The default gateway route table keeps /api/feature-maintenance*, /api/admin/feature-maintenance*, /api/admin/operations*, and broad /api/admin* traffic on legacy.
The native guard route is service-local for gateway/BFF adapter development: GET /v1/feature-maintenance/guard/{key}. Passing method=GET&path=/api/questions/ai-classify/jobs/... returns allowed=true with bypassReason=readonly_ai_classification_job for assessment.questionBankAiClassification, preserving the legacy read-only AI classification job bypass while default /api/* routing remains legacy.
Use deploy/gateway/routes.feature-maintenance-native-example.json for Compose or deploy/gateway/routes.feature-maintenance-native-localhost-example.json for local go run rehearsal. The non-default table rewrites only:
GET /api/feature-maintenance/publicto/v1/feature-maintenance/publicwithout gateway auth.GET /api/admin/feature-maintenanceto/v1/admin/feature-maintenancewith gateway auth andADMINrole.PATCH /api/admin/feature-maintenance/{key}to/v1/admin/feature-maintenance/{key}with gateway auth andADMINrole.
Broad feature-maintenance siblings, nested admin feature-maintenance paths, AI classification job reads, operations/audit, users, AI settings, wallet, support, dashboard, broad admin, and fallback routes remain legacy-proxied until maintenance guard, RBAC, audit, and response compatibility adapters are explicit.
The admin audit route-table rehearsal uses deploy/gateway/routes.admin-audit-native-example.json for Compose or deploy/gateway/routes.admin-audit-native-localhost-example.json for local go run. The non-default table rewrites only:
GET /api/admin/operations/auditto/v1/admin/audit-logswith gateway auth andADMINrole.
Audit-service has a separate compatibility-preview rehearsal through deploy/gateway/routes.audit-admin-compat-native-example.json, deploy/gateway/routes.audit-admin-compat-native-localhost-example.json, and make test-audit-admin-compat-routes. That rehearsal targets audit-service /v1/admin/operations/audit-compat and does not promote the default public route.
This audit rehearsal intentionally does not require organization at the gateway because the legacy AdminOperationsController is guarded by global admin role only. Admin operations health, queues, retry/clear commands, metrics, import jobs, outbox, AI settings, feature-maintenance routes not in the table, broad admin, and fallback routes remain legacy-proxied until the remaining operational controls and tenant behavior are explicit.
The owner-source status rehearsal uses deploy/gateway/routes.admin-owner-source-native-example.json for Compose or deploy/gateway/routes.admin-owner-source-native-localhost-example.json for local development. It rewrites only these ADMIN-protected exact reads:
GET /api/admin/operations/source-mapto/v1/admin/operations/source-map.GET /api/admin/operations/source-snapshotsto/v1/admin/operations/source-snapshots.
The admin-web operations panel may display owner status, HTTP code, and latency from the snapshot route through Gateway. It keeps local workflow fixtures when the rehearsal is unavailable and does not aggregate or synthesize dashboard metrics. The default route table remains legacy-proxied.
Rollback
- Keep
/api/feature-maintenance*,/api/admin/feature-maintenance*, and/api/admin/operations*aslegacy_proxygateway routes. - Disable native adapters by removing route-table entries or switching them back to
legacy_proxy. - Drop service-owned
feature_maintenanceandaudit_logsonly for local test reset; do not delete legacyFeatureMaintenanceorAuditLog.
Validation Queries
sql
SELECT key, enabled, updated_at
FROM feature_maintenance
ORDER BY key;
SELECT action, entity, entity_id, actor_id, created_at
FROM audit_logs
ORDER BY created_at DESC
LIMIT 20;