mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-18 14:27:20 +00:00
9.4 KiB
9.4 KiB
Multi-Tenancy Implementation Summary
🎯 Overview
This implementation adds full multi-tenancy support to AeThex-OS, enabling organizations, team collaboration, and project-based ownership.
✅ Deliverables Completed
1. Database Schema Changes (shared/schema.ts)
New Tables Added:
- ✅ organizations - Workspace/team containers
id,name,slug,owner_user_id,plan, timestamps
- ✅ organization_members - Team membership
id,organization_id,user_id,role(owner/admin/member/viewer)- Unique constraint on (organization_id, user_id)
- ✅ project_collaborators - Project-level permissions
id,project_id,user_id,role,permissions(jsonb)- Unique constraint on (project_id, user_id)
- CASCADE on project deletion
Existing Tables Updated:
Added nullable organization_id column to:
- ✅
projects(also addedowner_user_idfor standardization) - ✅
aethex_projects - ✅
marketplace_listings - ✅
marketplace_transactions - ✅
files - ✅
custom_apps - ✅
aethex_sites - ✅
aethex_opportunities - ✅
aethex_events
All with foreign key constraints (ON DELETE RESTRICT) and indexes.
2. SQL Migrations
Migration 0004: Organizations & Collaborators
File: /migrations/0004_multi_tenancy_organizations.sql
- Creates
organizations,organization_members,project_collaboratorstables - Adds foreign key constraints
- Creates indexes for common queries
Migration 0005: Organization FKs
File: /migrations/0005_add_organization_fks.sql
- Adds
organization_idcolumns to all entity tables - Creates foreign keys with ON DELETE RESTRICT
- Adds indexes for org-scoped queries
- Backfills
projects.owner_user_idfrom existing data
Backfill Script
File: /script/backfill-organizations.ts
- Creates default organization for each existing user
- Format:
"<display_name>'s Workspace" - Generates unique slugs
- Adds user as organization owner
- Backfills
organization_idfor user's existing entities
3. Server Middleware (server/org-middleware.ts)
Middleware Functions:
- ✅ attachOrgContext - Non-blocking middleware that:
- Reads org ID from
x-org-idheader or session - Falls back to user's first/default org
- Verifies membership and attaches
req.orgId,req.orgRole
- Reads org ID from
- ✅ requireOrgMember - Blocks requests without org membership
- ✅ requireOrgRole(minRole) - Enforces role hierarchy (viewer < member < admin < owner)
Helper Functions:
- ✅ assertProjectAccess(projectId, userId, minRole) - Checks:
- Project ownership
- Collaborator role
- Organization membership (if project is in an org)
4. Server API Routes (server/routes.ts)
Organization Routes:
- ✅
GET /api/orgs- List user's organizations - ✅
POST /api/orgs- Create new organization (auto-adds creator as owner) - ✅
GET /api/orgs/:slug- Get organization details (requires membership) - ✅
GET /api/orgs/:slug/members- List organization members (requires membership)
Project Routes (Updated):
- ✅
GET /api/projects- Org-scoped list (admin sees all, users see org projects) - ✅
GET /api/projects/:id- Access-controlled project fetch - ✅
GET /api/projects/:id/collaborators- List collaborators (requires contributor role) - ✅
POST /api/projects/:id/collaborators- Add collaborator (requires admin role) - ✅
PATCH /api/projects/:id/collaborators/:collabId- Update role/permissions (requires admin) - ✅
DELETE /api/projects/:id/collaborators/:collabId- Remove collaborator (requires admin)
Middleware Application:
app.use("/api/orgs", requireAuth, attachOrgContext);
app.use("/api/projects", attachOrgContext);
app.use("/api/files", attachOrgContext);
app.use("/api/marketplace", attachOrgContext);
5. Client Components
OrgSwitcher Component (client/src/components/OrgSwitcher.tsx)
- Dropdown menu in top nav
- Lists user's organizations
- Shows current org with checkmark
- Stores selection in localStorage
- Provides hooks:
useCurrentOrgId()- Get active org IDuseOrgHeaders()- Get headers for API calls
Organizations List Page (client/src/pages/orgs.tsx)
- View all user's organizations
- Create new organization with name + slug
- Auto-generates slug from name
- Shows user's role per org
- Navigation to settings
Organization Settings Page (client/src/pages/orgs/settings.tsx)
- Tabbed interface: General | Members
- General Tab:
- Display org name, slug, plan
- (Edit capabilities noted as "coming soon")
- Members Tab:
- List all members with avatars
- Show roles with colored badges + icons
- Owner (purple/crown), Admin (cyan/shield), Member (slate/user), Viewer (slate/eye)
Routes Added to App.tsx:
<Route path="/orgs">{() => <ProtectedRoute><Orgs /></ProtectedRoute>}</Route>
<Route path="/orgs/:slug/settings">{() => <ProtectedRoute><OrgSettings /></ProtectedRoute>}</Route>
6. Documentation
README_EXPANSION.md Updated
- Added section: "Multi-Tenancy & Project Ownership"
- Documented difference between
projectsandaethex_projects:- projects: Canonical internal project graph, org-scoped, full collaboration
- aethex_projects: Public showcase/portfolio, creator-focused
- Outlined future migration plan to link the two
🔧 Usage Guide
For Developers
Running Migrations:
# Apply migrations
npx drizzle-kit push
# Run backfill script
npx tsx script/backfill-organizations.ts
Making Org-Scoped API Calls (Client):
import { useOrgHeaders } from "@/components/OrgSwitcher";
function MyComponent() {
const orgHeaders = useOrgHeaders();
const { data } = useQuery({
queryKey: ["/api/projects"],
queryFn: async () => {
const res = await fetch("/api/projects", {
credentials: "include",
headers: orgHeaders, // Adds x-org-id header
});
return res.json();
},
});
}
Checking Project Access (Server):
import { assertProjectAccess } from "./org-middleware.js";
app.get("/api/projects/:id/some-action", requireAuth, async (req, res) => {
const accessCheck = await assertProjectAccess(
req.params.id,
req.session.userId!,
'contributor' // minimum required role
);
if (!accessCheck.hasAccess) {
return res.status(403).json({ error: accessCheck.reason });
}
// Proceed with action
});
🚀 What's Next (Future Work)
Phase 2: Full Org Scoping
- Scope
aethex_sites,aethex_opportunities,aethex_eventslist endpoints - Add org filtering to admin dashboards
- Implement org-wide analytics
Phase 3: Advanced Permissions
- Granular permissions matrix (read/write/delete per resource)
- Project templates and cloning
- Org-level roles (billing admin, content moderator, etc.)
Phase 4: Billing & Plans
- Integrate Stripe for org subscriptions
- Enforce feature limits per plan (free/pro/enterprise)
- Usage metering and billing dashboard
Phase 5: Invitations & Discovery
- Email-based invitations to join organizations
- Invite links with tokens
- Public org directory (for discoverability)
- Transfer ownership flows
📊 Architecture Decisions
Why Nullable organization_id First?
- Safety: Existing data remains intact
- Gradual Migration: Users can operate without orgs initially
- Backfill-Ready: Script can populate later without breaking changes
Why RESTRICT on Delete?
- Data Safety: Accidental org deletion won't cascade delete all projects
- Audit Trail: Forces manual cleanup or archive before deletion
- Future Proof: Can implement "soft delete" or "archive" later
Why Separate project_collaborators?
- Flexibility: Collaborators can differ from org members
- Granular Control: Project-level permissions independent of org roles
- Cross-Org Collaboration: Future support for external collaborators
Why Keep Legacy Columns (user_id, owner_id)?
- Backwards Compatibility: Existing code paths still work
- Migration Safety: Can validate new columns before removing old ones
- Rollback Path: Easy to revert if issues found
✅ Testing Checklist
- Run migrations on clean database
- Run backfill script with existing user data
- Create organization via UI
- Invite member to organization (manual DB insert for now)
- Switch between orgs using OrgSwitcher
- Verify projects are scoped to selected org
- Add collaborator to project
- Verify access control (viewer vs admin)
- Check that legacy
projectsqueries still work (admin routes)
📝 Migration Checklist for Production
-
Pre-Migration:
- Backup database
- Review all foreign key constraints
- Test migrations on staging
-
Migration:
- Run 0004_multi_tenancy_organizations.sql
- Run 0005_add_organization_fks.sql
- Run backfill-organizations.ts script
- Verify all users have default org
-
Post-Migration:
- Verify existing projects still accessible
- Check org member counts
- Test org switching in UI
- Monitor for access control issues
-
Cleanup (Later):
- Once validated, make
organization_idNOT NULL - Drop legacy columns (
user_id,owner_idfrom projects) - Update all queries to use new columns
- Once validated, make
Status: ✅ Multi-tenancy foundation complete and ready for use.