Organization & Multi-Tenancy Implementation
Date: December 26, 2025 Status: In Progress
Overview
This document outlines the Organization and Multi-Tenancy implementation for StemBlock AI, enabling schools and districts to manage their own users, classes, and invitations within isolated organizational boundaries.
Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Approach | Hybrid - New Organization entity integrated with Workspaces | Clean separation while leveraging existing workspace infrastructure |
| Role Hierarchy | ORG_ADMIN as new UserRole | Clear role separation; scoped to single organization |
| Membership | Single organization per user | Simpler boundaries, cleaner data isolation |
| Migration | Default organization for existing data | Zero-downtime migration, backward compatible |
Data Model
Organization Entity
model Organization {
id String @id @default(uuid())
name String
slug String @unique
description String?
logoUrl String?
settings Json? // Features, branding, limits
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
users User[]
classes Class[]
workspaces Workspace[]
invitations Invitation[]
}
Updated Entities
User: Added organizationId foreign key
Class: Added organizationId foreign key
Workspace: Added organizationId (nullable for personal workspaces)
Invitation: Added organizationId (nullable for self-registration)
Role Enum
enum UserRole {
STUDENT
PARENT
COACH
ORG_ADMIN // Organization administrator
ADMIN // Platform administrator
}
Permission Matrix
| Action | STUDENT | PARENT | COACH | ORG_ADMIN | ADMIN |
|---|---|---|---|---|---|
| View own organization | ✓ | ✓ | ✓ | ✓ | All |
| Update org settings | - | - | - | Own | All |
| Create organization | - | - | - | - | ✓ |
| Delete organization | - | - | - | - | ✓ |
| Manage org users | - | - | - | Own | All |
| Manage org classes | - | - | Own | Own | All |
| Create invitations | - | - | Class | Org | All |
| View org statistics | - | - | - | Own | All |
API Endpoints
Admin Endpoints (ADMIN only)
| Method | Endpoint | Description |
|---|---|---|
| POST | /admin/organizations | Create organization |
| GET | /admin/organizations | List all organizations |
| GET | /admin/organizations/:id | Get organization details |
| PUT | /admin/organizations/:id | Update organization |
| DELETE | /admin/organizations/:id | Delete organization |
| POST | /admin/organizations/:id/users | Add user to organization |
| DELETE | /admin/organizations/:id/users/:userId | Remove user |
| POST | /admin/organizations/transfer-user | Transfer user between orgs |
Organization Endpoints (ORG_ADMIN + ADMIN)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/organizations/me | Get current user's org |
| GET | /api/v1/organizations/:id | Get org by ID |
| PUT | /api/v1/organizations/:id | Update org settings |
| GET | /api/v1/organizations/:id/users | Get org users |
| GET | /api/v1/organizations/:id/classes | Get org classes |
| GET | /api/v1/organizations/:id/invitations | Get org invitations |
| GET | /api/v1/organizations/:id/stats | Get org statistics |
Backend Implementation
Module Structure
src/organizations/
├── organizations.module.ts
├── organizations.service.ts
├── organizations.controller.ts # ORG_ADMIN + ADMIN access
├── admin-organizations.controller.ts # ADMIN-only endpoints
├── index.ts
├── dto/
│ ├── create-organization.dto.ts
│ ├── update-organization.dto.ts
│ └── index.ts
└── guards/
└── organization.guard.ts
OrganizationGuard
Ensures users can only access resources within their organization:
@Injectable()
export class OrganizationGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const user = request.user;
// ADMIN bypasses all org checks
if (user.role === UserRole.ADMIN) return true;
// Verify user belongs to target organization
if (user.organizationId !== targetOrgId) {
throw new ForbiddenException('Access denied');
}
return true;
}
}
Frontend Implementation
Type Updates
// types/index.ts
export type UserRole = 'STUDENT' | 'COACH' | 'PARENT' | 'ADMIN' | 'ORG_ADMIN'
export interface Organization {
id: string
name: string
slug: string
settings?: OrganizationSettings
createdAt: string
updatedAt: string
}
export interface User {
// ... existing fields ...
organizationId?: string
organization?: Organization
}
New Pages
app/org-admin/
├── dashboard/page.tsx # Org stats and quick actions
├── users/page.tsx # Manage users within org
├── classes/page.tsx # Manage classes within org
├── invitations/page.tsx # Manage invitations within org
└── settings/page.tsx # Org settings (name, logo)
Navigation
ORG_ADMIN users see organization management menu:
- Dashboard
- Users
- Classes
- Invitations
- Settings
Migration Strategy
Phase 1: Schema Changes (Non-Breaking)
- Add Organization table
- Add nullable
organizationIdto User, Class, Workspace, Invitation
Phase 2: Data Migration
- Create default organization ("StemBlock AI")
- Backfill all existing users to default org
- Backfill all existing classes to default org
Phase 3: Constraint Updates
- Make User.organizationId required
- Make Class.organizationId required
- Add composite indexes
Rollback Strategy
-- Emergency rollback
ALTER TABLE "users" DROP COLUMN IF EXISTS "organization_id";
ALTER TABLE "classes" DROP COLUMN IF EXISTS "organization_id";
DROP TABLE IF EXISTS "organizations";
Test Users
| Role | Password | Tier | |
|---|---|---|---|
| ADMIN | admin@test.com | Admin123! | ENTERPRISE |
| ORG_ADMIN | orgadmin@test.com | OrgAdmin123! | ENTERPRISE |
| COACH | coach@test.com | Coach123! | COMMUNITY |
| STUDENT | alex@test.com | Student123! | COMMUNITY |
| PARENT | parent@test.com | Parent123! | PRO |
Files Modified
Backend
| File | Change |
|---|---|
prisma/schema.prisma | Added Organization model, ORG_ADMIN role, relations |
prisma/seed.ts | Added default org, admin users |
src/app.module.ts | Import OrganizationsModule |
src/organizations/* | New module (service, controllers, guards, DTOs) |
Frontend (Planned)
| File | Change |
|---|---|
types/index.ts | Add Organization interface, ORG_ADMIN role |
lib/auth-context.tsx | Add organization to auth state |
lib/api.ts | Add organizationsAPI, orgAdminAPI |
components/Navigation.tsx | Add ORG_ADMIN navigation |
app/org-admin/* | New pages for org management |
Future Enhancements
- Organization Hierarchy: Support for districts with multiple schools
- Cross-Organization Sharing: Allow sharing resources between organizations
- SSO Integration: SAML/OAuth for enterprise organizations
- White-Label Branding: Custom branding per organization
- Usage Analytics: Per-organization usage metrics and billing
Related Documentation
- CODEBASE_OVERVIEW.md - Overall codebase structure
- API_ENDPOINTS_SUMMARY.md - Complete API reference
- ROUTING_AND_NAVIGATION.md - URL structure