Skip to content

School Service API

Current Endpoints

  • GET /healthz
  • GET /readyz
  • GET /v1
  • PUT /v1/organizations/{organizationId}/snapshot
  • GET /v1/organizations
  • POST /v1/organizations
  • GET /v1/organizations/{organizationId}
  • PATCH /v1/organizations/{organizationId}
  • DELETE /v1/organizations/{organizationId}
  • DELETE /v1/organizations/{organizationId}/purge
  • GET /v1/organizations/{organizationId}/units
  • POST /v1/organizations/{organizationId}/units
  • PATCH /v1/organizations/{organizationId}/units/{unitId}
  • DELETE /v1/organizations/{organizationId}/units/{unitId}
  • GET /v1/organizations/{organizationId}/members
  • POST /v1/organizations/{organizationId}/members
  • PUT /v1/organizations/{organizationId}/members/{userId}
  • PATCH /v1/organizations/{organizationId}/members/{userId}
  • DELETE /v1/organizations/{organizationId}/members/{userId}
  • GET /v1/users/{userId}/memberships
  • POST /v1/tenant/resolve
  • POST /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-204 maps legacy organization, unit, member, archive, purge, and student-import routes.
  • apps/api/src/modules/app-data/app-data.organizations.ts:12-461 implements 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-101 wraps organization resolve, membership, manager, and CRUD calls for guards/controllers.
  • apps/api/src/common/tenant.guard.ts:20-45 reads X-Organization-Id or query organizationId; admins can select any org, non-admin users must be active members.
  • apps/api/src/common/current-organization.decorator.ts:8-14 exposes the resolved organization id to route handlers.
  • apps/api/prisma/schema.prisma:124-232 defines Organization, OrganizationUnit, and OrganizationMember with unique slug, unique unit code by organization, and unique member by organization/user.
  • packages/shared/src/index.ts:39-52 defines organization type/status/member role/member status enums.
  • packages/shared/src/index.ts:1132-1152 defines organization, unit, and member validation schemas.
  • apps/web/app/admin/organizations/page.tsx:268-495 shows the admin page consuming /organizations, /organizations/:id, /units, and /members routes.
  • apps/web/lib/client-api.ts:52-62 and apps/web/components/layout/organization-switcher.tsx:20-64 show frontend persistence of hoctapaz.organizationId and membership-driven switching.

Native contract:

  • PUT /v1/organizations/{organizationId}/snapshot upserts a service-owned row from legacy backfill or bootstrap data, including status and timestamps when supplied.
  • GET /v1/organizations supports q, status, type, page, limit, userId, and userRole. Admin-style reads return paged data; non-admin user reads are membership-scoped.
  • POST /v1/organizations creates an organization and optionally assigns X-User-Id as an active OWNER, matching legacy create behavior.
  • GET/PATCH/DELETE /v1/organizations/{organizationId} preserve detail, partial update, and archive semantics.
  • DELETE /v1/organizations/{organizationId}/purge hard 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, default type=BRANCH, and block delete while members are assigned to the unit.
  • Member routes preserve upsert by (organizationId,userId), role/status updates, and last active OWNER protection.
  • GET /v1/users/{userId}/memberships returns the membership summary needed by auth/user session hydration.
  • POST /v1/tenant/resolve mirrors legacy default resolution: requested id wins, otherwise first active user membership, otherwise org_local_center.
  • POST /v1/tenant/validate mirrors TenantGuard: admins pass, non-admins require active membership, and manager-only checks require OWNER or ORG_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.sql creates organizations, organization_units, and organization_members.
  • organization_members.user_id stores the public user id from auth/user-service; school-service does not query the user-service database.
  • organization_units.code is 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 0 until 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/organizations
  • GET /api/organizations/{organizationId}
  • GET /api/organizations/{organizationId}/units
  • GET /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.

Go-platform documentation is generated from repository Markdown.