Appearance
School Service API
Current Endpoints
GET /healthzGET /readyzGET /v1PUT /v1/organizations/{organizationId}/snapshotGET /v1/organizationsPOST /v1/organizationsGET /v1/organizations/{organizationId}PATCH /v1/organizations/{organizationId}DELETE /v1/organizations/{organizationId}DELETE /v1/organizations/{organizationId}/purgeGET /v1/organizations/{organizationId}/unitsPOST /v1/organizations/{organizationId}/unitsPATCH /v1/organizations/{organizationId}/units/{unitId}DELETE /v1/organizations/{organizationId}/units/{unitId}GET /v1/organizations/{organizationId}/membersPOST /v1/organizations/{organizationId}/membersPUT /v1/organizations/{organizationId}/members/{userId}PATCH /v1/organizations/{organizationId}/members/{userId}DELETE /v1/organizations/{organizationId}/members/{userId}GET /v1/users/{userId}/membershipsPOST /v1/tenant/resolvePOST /v1/tenant/validate
Native Organization Foundation
Phase 8 school-service starts with organization, unit, membership, and tenant validation ownership. This is an internal /v1 foundation, not a public /api/organizations* cutover.
Legacy evidence:
apps/api/src/modules/organizations/organizations.controller.ts:14-204maps legacy organization, unit, member, archive, purge, and student-import routes.apps/api/src/modules/app-data/app-data.organizations.ts:12-461implements default organization creation, list pagination, manager checks, organization CRUD, unit dependency checks, member upsert/update/remove, and last active OWNER protection.apps/api/src/modules/tenant/tenant.service.ts:1-101wraps organization resolve, membership, manager, and CRUD calls for guards/controllers.apps/api/src/common/tenant.guard.ts:20-45readsX-Organization-Idor queryorganizationId; admins can select any org, non-admin users must be active members.apps/api/src/common/current-organization.decorator.ts:8-14exposes the resolved organization id to route handlers.apps/api/prisma/schema.prisma:124-232definesOrganization,OrganizationUnit, andOrganizationMemberwith unique slug, unique unit code by organization, and unique member by organization/user.packages/shared/src/index.ts:39-52defines organization type/status/member role/member status enums.packages/shared/src/index.ts:1132-1152defines organization, unit, and member validation schemas.apps/web/app/admin/organizations/page.tsx:268-495shows the admin page consuming/organizations,/organizations/:id,/units, and/membersroutes.apps/web/lib/client-api.ts:52-62andapps/web/components/layout/organization-switcher.tsx:20-64show frontend persistence ofhoctapaz.organizationIdand membership-driven switching.
Native contract:
PUT /v1/organizations/{organizationId}/snapshotupserts a service-owned row from legacy backfill or bootstrap data, including status and timestamps when supplied.GET /v1/organizationssupportsq,status,type,page,limit,userId, anduserRole. Admin-style reads return paged data; non-admin user reads are membership-scoped.POST /v1/organizationscreates an organization and optionally assignsX-User-Idas an activeOWNER, matching legacy create behavior.GET/PATCH/DELETE /v1/organizations/{organizationId}preserve detail, partial update, and archive semantics.DELETE /v1/organizations/{organizationId}/purgehard deletes service-owned org data for admin-only adapters; public gateway cutover must keep role enforcement outside this endpoint.- Unit routes preserve organization-scoped uniqueness for
code, defaulttype=BRANCH, and block delete while members are assigned to the unit. - Member routes preserve upsert by
(organizationId,userId), role/status updates, and last activeOWNERprotection. GET /v1/users/{userId}/membershipsreturns the membership summary needed by auth/user session hydration.POST /v1/tenant/resolvemirrors legacy default resolution: requested id wins, otherwise first active user membership, otherwiseorg_local_center.POST /v1/tenant/validatemirrors TenantGuard: admins pass, non-admins require active membership, and manager-only checks requireOWNERorORG_ADMIN.
Envelope:
json
{
"success": true,
"data": {
"id": "org_local_center",
"name": "HOCTAPAZ Local Center",
"slug": "hoctapaz-local",
"type": "CENTER",
"status": "ACTIVE",
"_count": {
"members": 1,
"units": 0,
"classrooms": 0
}
},
"message": "OK"
}Database:
services/school-service/migrations/000002_organizations.sqlcreatesorganizations,organization_units, andorganization_members.organization_members.user_idstores the public user id from auth/user-service; school-service does not query the user-service database.organization_units.codeis nullable; native PostgreSQL uniqueness uses a partial unique index so multiple null codes are allowed per organization, matching expected optional-code behavior.- Classroom counts are returned as
0until classroom-service owns a native read model or school-service receives classroom membership events.
Validation queries:
sql
SELECT id, legacy_id, name, slug, type, status
FROM organizations
ORDER BY created_at DESC
LIMIT 20;
SELECT organization_id, id, name, code, type
FROM organization_units
ORDER BY created_at DESC
LIMIT 20;
SELECT organization_id, user_id, role, status, unit_id, joined_at
FROM organization_members
ORDER BY joined_at DESC
LIMIT 20;Rollback for this native slice:
- Keep
/api/organizations*routed to legacy. - Keep auth/user session membership hydration on legacy until gateway/auth adapters consume
/v1/users/{userId}/memberships. - Disable gateway callers for
/v1/tenant/*and/v1/organizations*. - Drop school-service local organization tables with the migration down step if local test data must be reset.
Gateway read rehearsal:
deploy/gateway/routes.organizations-read-native-example.json and deploy/gateway/routes.organizations-read-native-localhost-example.json provide non-default route-table rehearsals for read-only organization paths. They route only:
GET /api/organizationsGET /api/organizations/{organizationId}GET /api/organizations/{organizationId}/unitsGET /api/organizations/{organizationId}/members
to school-service with gateway identity and organization header injection. Organization writes, purge, unit/member mutations, and /api/organizations/{organizationId}/student-imports stay legacy-proxied because public RBAC/tenant enforcement and user-profile hydration need separate adapters.
Run make test-organization-routes before any live or browser rehearsal.
Non-goals for P8-003:
- Public gateway adapter/cutover for
/api/organizations*. - Student import job ownership.
- Classroom/course membership enforcement beyond organization membership.
- School academic years, grade taxonomy, curriculum, or branch-specific classroom counts.
- User profile hydration inside member rows; this remains user-service or gateway adapter work.