Skip to content

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/summary
  • POST /v1/profiles
  • GET /v1/profiles/{accountId}
  • PATCH /v1/profiles/{accountId}
  • GET /v1/profiles/{accountId}/public
  • GET /v1/profiles/{accountId}/contacts
  • POST /v1/profiles/{accountId}/contacts
  • DELETE /v1/profiles/{accountId}/contacts/{contactId}
  • GET /v1/profiles/{accountId}/children
  • POST /v1/internal/profiles/parent-recipients/resolve
  • POST /v1/profiles/{accountId}/student
  • GET /v1/profiles/{accountId}/student
  • PATCH /v1/profiles/{accountId}/student
  • POST /v1/profiles/{accountId}/teacher
  • GET /v1/profiles/{accountId}/teacher
  • PATCH /v1/profiles/{accountId}/teacher
  • POST /v1/profiles/{accountId}/teacher-kyc
  • GET /v1/profiles/{accountId}/teacher-kyc
  • GET /v1/admin/users/kyc/teacher
  • GET /v1/admin/users/kyc/teacher/{userId}
  • POST /v1/admin/users/kyc/teacher/{userId}/approve
  • POST /v1/admin/users/kyc/teacher/{userId}/reject
  • POST /v1/profiles/{accountId}/parent
  • GET /v1/profiles/{accountId}/parent
  • PATCH /v1/profiles/{accountId}/parent
  • POST /v1/profiles/{accountId}/staff
  • GET /v1/profiles/{accountId}/staff
  • PATCH /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-245 maps GET /api/auth/me, PATCH /api/auth/me/profile, and password update.
  • node-platform/apps/api/src/modules/auth/auth.controller.ts:89-147 maps role-specific teacher/student/parent profile update routes.
  • node-platform/apps/api/src/modules/auth/auth.controller.ts:163-181 maps teacher self-service KYC submit/read.
  • node-platform/apps/api/src/modules/admin/users.controller.ts:67-115 maps admin teacher KYC list/detail/approve/reject.
  • node-platform/apps/api/src/modules/auth/auth.service.ts:176-246 delegates current-profile hydration and profile updates to identity data APIs.
  • node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:209-258 returns full profile with teacherProfile, studentProfile, parentProfile, safe teacherKyc, and membership summaries.
  • node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:260-351 upserts teacher, student, and parent profile rows and records audit logs.
  • node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:453-604 lists, details, approves, and rejects teacher KYC while omitting cccdNumberHash.
  • node-platform/apps/api/src/modules/app-data/app-data.identity-core.ts:607-695 validates and updates base profile fields, with email/phone conflict checks.
  • node-platform/packages/shared/src/index.ts:1027-1102 defines profileUpdateSchema, teacherProfileSchema, studentProfileSchema, and parentProfileSchema.
  • node-platform/apps/api/prisma/schema.prisma:247-325 defines TeacherProfile, TeacherKyc, StudentProfile, and ParentProfile.
  • node-platform/apps/web/components/account/profile-settings-client.tsx:255-556 shows current frontend loading /auth/me, then PATCHing base and role-specific profile routes.

Compatibility endpoints retained during migration:

  • PUT /v1/users/{userId}/profile-snapshot
  • GET /v1/users/{userId}/profile
  • PATCH /v1/users/{userId}/profile
  • PATCH /v1/users/{userId}/teacher-profile
  • POST /v1/users/{userId}/teacher-kyc
  • GET /v1/users/{userId}/teacher-kyc
  • PATCH /v1/users/{userId}/student-profile
  • PATCH /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/summary returns profile-service-owned profile, role/status projection, role-specific profile, KYC status, contact, and parent-child read-model counts for admin-service identity.counts source snapshots. It requires X-Internal-Service and the shared X-Internal-Token. It does not expose credentials, sessions, JWT claims, permissions, or access-control decisions; those stay in IAM.
  • POST /v1/profiles creates the base profile row for an IAM account_id and returns 409 if 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-snapshot upserts the profile-service-owned public user row from IAM/backfill data. It stores no password hash or refresh token.
  • GET /v1/users/{userId}/profile returns a full profile view with base user fields and nullable teacherProfile, studentProfile, parentProfile, staffProfile, and teacherKyc.
  • POST /v1/users/{userId}/teacher-kyc and GET /v1/users/{userId}/teacher-kyc provide the native compatibility target for the legacy teacher self-service KYC path. Profile-service owns teacher_kyc storage, validates 9-12 digit CCCD input, stores a deterministic HMAC/SHA-256 hash plus last4 instead of plaintext CCCD, and returns only TeacherKycSafe. 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-only GET/POST /api/auth/me/kyc/teacher calls 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, and POST /v1/admin/users/kyc/teacher/{userId}/reject are owner-side native targets for the legacy admin teacher KYC review workflow. They require X-Internal-Service and the shared X-Internal-Token after 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 or cccdNumberHash. Review mutations require gateway actor id headers, set VERIFIED or REJECTED. Non-default gateway route tables can rehearse /api/admin/users/kyc/teacher* directly to these endpoints with a verified ADMIN principal; default promotion, browser proof, and admin-service compatibility wrapping remain separate cutover work.
  • GET /v1/profiles/{accountId}/public returns 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}/contacts manages 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}/children lists 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} require X-Internal-Service and the shared X-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/resolve accepts studentIds, optional organizationId, and optional purpose, then returns { items, recipientIds, generatedAt } from the parent-child profile projection. It requires X-Internal-Service and the shared X-Internal-Token. This is for BFF/notification producer handoff such as WEAK_TOPIC_ALERT; it does not validate or grant IAM parent-student access.
  • PATCH /v1/users/{userId}/profile updates base profile fields with legacy-compatible validation:
    • fullName is required, trimmed, length 2-120.
    • email is optional, lowercased, max 160, unique when present.
    • empty phone becomes null; non-empty phone max 32 and unique.
    • empty avatarUrl becomes null; non-empty URL must start with http:// or https://.
  • PATCH /v1/users/{userId}/teacher-profile upserts teacher-specific profile fields.
  • POST /v1/profiles/{accountId}/teacher-kyc and GET /v1/profiles/{accountId}/teacher-kyc expose the same profile-owned KYC self submit/read contract using account-id terminology.
  • PATCH /v1/users/{userId}/student-profile upserts student-specific profile fields.
  • PATCH /v1/users/{userId}/parent-profile upserts 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.sql adds explicit lookup indexes for the current accountId/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_URL or AUDIT_SERVICE_BASE_URL is configured, profile-service projects those events to audit-service /v1/audit-events with sourceService=profile-service, sourceEventId=<profile event id>, category=DATA, severity=INFO, metadataSchemaVersion=1, retentionPolicy=profile-events, and occurredAt from the local event. Teacher KYC submit emits profile.teacher_kyc.submitted and admin review emits profile.teacher_kyc.reviewed with 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.json and deploy/gateway/routes.profile-native-localhost-example.json can route current-user PATCH /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.json and deploy/gateway/routes.profile-kyc-native-localhost-example.json can route exact teacher self-service GET/POST /api/auth/me/kyc/teacher calls to /v1/users/{userId}/teacher-kyc. They require a TEACHER gateway 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.json and deploy/gateway/routes.profile-admin-kyc-native-localhost-example.json can route admin review GET /api/admin/users/kyc/teacher, GET /api/admin/users/kyc/teacher/{userId}, POST /api/admin/users/kyc/teacher/{userId}/approve, and POST /api/admin/users/kyc/teacher/{userId}/reject calls to /v1/admin/users/kyc/teacher*. They require an ADMIN gateway principal, do not require organization context, preserve query/body payloads, and leave broader admin user routes legacy. staff-web can exercise this exact candidate only with ?staffVerificationGateway=native and an ADMIN Gateway 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 return 410 ROUTE_REMOVED until 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-service data.
  • profile.created, profile.updated, and profile.snapshot.upserted are emitted through a noop event publisher by default so a broker-backed or audit publisher can be attached later.

Go-platform documentation is generated from repository Markdown.