Appearance
Profile Service API
profile-service owns profile data and role-specific education profile views. It does not handle password, token, session, role, or permission logic.
Native endpoints:
GET /v1/admin/profiles/summaryPOST /v1/profilesGET /v1/profiles/{accountId}PATCH /v1/profiles/{accountId}GET /v1/profiles/{accountId}/publicGET /v1/profiles/{accountId}/contactsPOST /v1/profiles/{accountId}/contactsDELETE /v1/profiles/{accountId}/contacts/{contactId}GET /v1/profiles/{accountId}/childrenPOST /v1/internal/profiles/parent-recipients/resolvePOST /v1/profiles/{accountId}/studentGET /v1/profiles/{accountId}/studentPATCH /v1/profiles/{accountId}/studentPOST /v1/profiles/{accountId}/teacherGET /v1/profiles/{accountId}/teacherPATCH /v1/profiles/{accountId}/teacherPOST /v1/profiles/{accountId}/teacher-kycGET /v1/profiles/{accountId}/teacher-kycGET /v1/admin/users/kyc/teacherGET /v1/admin/users/kyc/teacher/{userId}POST /v1/admin/users/kyc/teacher/{userId}/approvePOST /v1/admin/users/kyc/teacher/{userId}/rejectPOST /v1/profiles/{accountId}/parentGET /v1/profiles/{accountId}/parentPATCH /v1/profiles/{accountId}/parentPOST /v1/profiles/{accountId}/staffGET /v1/profiles/{accountId}/staffPATCH /v1/profiles/{accountId}/staff
Native Profile Foundation
Phase 8 profile-service starts with profile read/update ownership. This is an internal /v1 foundation, not a public /api/auth/me* default cutover.
Legacy evidence:
node-platform/apps/api/src/modules/auth/auth.controller.ts:189-245mapsGET /api/auth/me,PATCH /api/auth/me/profile, and password update.node-platform/apps/api/src/modules/auth/auth.controller.ts:89-147maps role-specific teacher/student/parent profile update routes.node-platform/apps/api/src/modules/auth/auth.controller.ts:163-181maps teacher self-service KYC submit/read.node-platform/apps/api/src/modules/admin/users.controller.ts:67-115maps admin teacher KYC list/detail/approve/reject.node-platform/apps/api/src/modules/auth/auth.service.ts:176-246delegates current-profile hydration and profile updates to identity data APIs.node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:209-258returns full profile withteacherProfile,studentProfile,parentProfile, safeteacherKyc, and membership summaries.node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:260-351upserts teacher, student, and parent profile rows and records audit logs.node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:453-604lists, details, approves, and rejects teacher KYC while omittingcccdNumberHash.node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:607-695validates and updates base profile fields, with email/phone conflict checks.node-platform/packages/shared/src/index.ts:1027-1102definesprofileUpdateSchema,teacherProfileSchema,studentProfileSchema, andparentProfileSchema.node-platform/apps/api/prisma/schema.prisma:247-325definesTeacherProfile,TeacherKyc,StudentProfile, andParentProfile.node-platform/apps/web/components/account/profile-settings-client.tsx:255-556shows current frontend loading/auth/me, then PATCHing base and role-specific profile routes.
Compatibility endpoints retained during migration:
PUT /v1/users/{userId}/profile-snapshotGET /v1/users/{userId}/profilePATCH /v1/users/{userId}/profilePATCH /v1/users/{userId}/teacher-profilePOST /v1/users/{userId}/teacher-kycGET /v1/users/{userId}/teacher-kycPATCH /v1/users/{userId}/student-profilePATCH /v1/users/{userId}/parent-profile
Internal read-model sync endpoints:
PUT /v1/internal/profiles/{accountId}/children/{studentAccountId}DELETE /v1/internal/profiles/{accountId}/children/{studentAccountId}POST /v1/internal/profiles/parent-recipients/resolve
Native contract:
GET /v1/admin/profiles/summaryreturns profile-service-owned profile, role/status projection, role-specific profile, KYC status, contact, and parent-child read-model counts for admin-serviceidentity.countssource snapshots. It requiresX-Internal-Serviceand the sharedX-Internal-Token. It does not expose credentials, sessions, JWT claims, permissions, or access-control decisions; those stay in IAM.POST /v1/profilescreates the base profile row for an IAMaccount_idand returns409if it already exists. IAM-owned fields such as role/status may appear as synchronized read-model projections for compatibility; they are not authoritative permission data in Profile.PUT /v1/users/{userId}/profile-snapshotupserts the profile-service-owned public user row from IAM/backfill data. It stores no password hash or refresh token.GET /v1/users/{userId}/profilereturns a full profile view with base user fields and nullableteacherProfile,studentProfile,parentProfile,staffProfile, andteacherKyc.POST /v1/users/{userId}/teacher-kycandGET /v1/users/{userId}/teacher-kycprovide the native compatibility target for the legacy teacher self-service KYC path. Profile-service ownsteacher_kycstorage, validates 9-12 digit CCCD input, stores a deterministic HMAC/SHA-256 hash plus last4 instead of plaintext CCCD, and returns onlyTeacherKycSafe. The safe response never exposes document hashes, plaintext CCCD, image URLs, permanent address, issue/expiry metadata, verifier, or notes. Non-default gateway route tables can rehearse exact teacher-onlyGET/POST /api/auth/me/kyc/teachercalls to these endpoints with{userId}from the verified gateway JWT principal; default public promotion and browser proof remain separate cutover work.GET /v1/admin/users/kyc/teacher,GET /v1/admin/users/kyc/teacher/{userId},POST /v1/admin/users/kyc/teacher/{userId}/approve, andPOST /v1/admin/users/kyc/teacher/{userId}/rejectare owner-side native targets for the legacy admin teacher KYC review workflow. They requireX-Internal-Serviceand the sharedX-Internal-Tokenafter gateway/admin authorization. They preserve list/detail pagination and search semantics, return admin-only review fields such as document image URLs, address, issuing metadata, verifier, notes, and user summary, and still never return plaintext CCCD orcccdNumberHash. Review mutations require gateway actor id headers, setVERIFIEDorREJECTED. Non-default gateway route tables can rehearse/api/admin/users/kyc/teacher*directly to these endpoints with a verifiedADMINprincipal; default promotion, browser proof, and admin-service compatibility wrapping remain separate cutover work.GET /v1/profiles/{accountId}/publicreturns only public display data and role-profile metadata such as school name or teacher subjects. It does not expose email, phone, profile contacts, role, status, permission scopes, credentials, or tokens.GET/POST/DELETE /v1/profiles/{accountId}/contactsmanages internal profile contact metadata. Contacts are private profile data and do not update IAM credentials, roles, permissions, JWT claims, or route authorization.GET /v1/profiles/{accountId}/childrenlists profile-service read-model child links with redacted child public profile views. It is not an authorization check and does not replace IAM parent-student permission links.PUT/DELETE /v1/internal/profiles/{accountId}/children/{studentAccountId}requireX-Internal-Serviceand the sharedX-Internal-Token, then sync the parent-child profile projection from backfill/event workflows. They do not grant or revoke IAM access.POST /v1/internal/profiles/parent-recipients/resolveacceptsstudentIds, optionalorganizationId, and optionalpurpose, then returns{ items, recipientIds, generatedAt }from the parent-child profile projection. It requiresX-Internal-Serviceand the sharedX-Internal-Token. This is for BFF/notification producer handoff such asWEAK_TOPIC_ALERT; it does not validate or grant IAM parent-student access.PATCH /v1/users/{userId}/profileupdates base profile fields with legacy-compatible validation:fullNameis required, trimmed, length 2-120.emailis optional, lowercased, max 160, unique when present.- empty
phonebecomes null; non-emptyphonemax 32 and unique. - empty
avatarUrlbecomes null; non-empty URL must start withhttp://orhttps://.
PATCH /v1/users/{userId}/teacher-profileupserts teacher-specific profile fields.POST /v1/profiles/{accountId}/teacher-kycandGET /v1/profiles/{accountId}/teacher-kycexpose the same profile-owned KYC self submit/read contract using account-id terminology.PATCH /v1/users/{userId}/student-profileupserts student-specific profile fields.PATCH /v1/users/{userId}/parent-profileupserts parent-specific profile fields.- New
/v1/profiles/{accountId}routes expose the same profile boundary using the account-id terminology from the target service matrix. services/profile-service/migrations/000004_profile_query_indexes.sqladds explicit lookup indexes for the currentaccountId/display-name/phone query contract. It does not complete the later UUID primary-key/backfill cutover.- Local profile mutation events include a generated event id, action, entity, account id, outcome, request id, correlation id, and optional gateway actor context. When
AUDIT_SERVICE_URLorAUDIT_SERVICE_BASE_URLis configured, profile-service projects those events to audit-service/v1/audit-eventswithsourceService=profile-service,sourceEventId=<profile event id>,category=DATA,severity=INFO,metadataSchemaVersion=1,retentionPolicy=profile-events, andoccurredAtfrom the local event. Teacher KYC submit emitsprofile.teacher_kyc.submittedand admin review emitsprofile.teacher_kyc.reviewedwith only redacted metadata (section=teacher-kyc, status/action); sensitive document fields are not added to the audit metadata.
Envelope:
json
{
"success": true,
"data": {
"id": "0f7c9a62-3b6d-49a8-8d2e-5f8d4b0f91a2",
"accountCode": "HTA12345678",
"email": "[email protected]",
"fullName": "Teacher One",
"role": "TEACHER",
"status": "ACTIVE",
"teacherProfile": {
"schoolName": "THPT A",
"subjects": ["Toán"]
},
"studentProfile": null,
"parentProfile": null,
"teacherKyc": null,
"memberships": []
},
"message": "OK"
}Gateway Rehearsal
- Non-default route tables
deploy/gateway/routes.profile-native-example.jsonanddeploy/gateway/routes.profile-native-localhost-example.jsoncan route current-userPATCH /api/auth/me/profile*calls to these compatibility endpoints by resolving{userId}from the verified gateway JWT principal. - Non-default route tables
deploy/gateway/routes.profile-kyc-native-example.jsonanddeploy/gateway/routes.profile-kyc-native-localhost-example.jsoncan route exact teacher self-serviceGET/POST /api/auth/me/kyc/teachercalls to/v1/users/{userId}/teacher-kyc. They require aTEACHERgateway principal, do not require organization context, and leave admin KYC routes to the separate admin KYC route table. - Non-default route tables
deploy/gateway/routes.profile-admin-kyc-native-example.jsonanddeploy/gateway/routes.profile-admin-kyc-native-localhost-example.jsoncan route admin reviewGET /api/admin/users/kyc/teacher,GET /api/admin/users/kyc/teacher/{userId},POST /api/admin/users/kyc/teacher/{userId}/approve, andPOST /api/admin/users/kyc/teacher/{userId}/rejectcalls to/v1/admin/users/kyc/teacher*. They require anADMINgateway principal, do not require organization context, preserve query/body payloads, and leave broader admin user routes legacy.staff-webcan exercise this exact candidate only with?staffVerificationGateway=nativeand anADMINGateway session; its adapter limits rendering to the returned status, name, account code, and document last4. Unit coverage proves request shape and failure handling only; default promotion and live browser proof remain separate work. - The default route table never legacy-proxies
/api/auth. Exact IAM endpoints are native; profile/KYC Auth paths that are not explicitly promoted return410 ROUTE_REMOVEDuntil their owner-service route is enabled.
Database
- Database:
hoctapaz_profile_db. - Service-owned profile tables include base profiles plus student, teacher, parent, and staff profile rows. Credential and session fields remain
iam-servicedata. profile.created,profile.updated, andprofile.snapshot.upsertedare emitted through a noop event publisher by default so a broker-backed or audit publisher can be attached later.