Introduces the multi-tenant SaaS foundation alongside the existing client/team/project model (zero-downtime shim period): Backend: - app/models/organization.py — Organization + OrgRole enum (OWNER/ADMIN/MANAGER/MEMBER/VIEWER) - app/models/membership.py — Membership model with MemberDetail for enriched responses - app/services/membership_service.py — upsert/remove/list/has_org_role helpers - app/api/v1/routes_organizations.py — /organizations CRUD + /members sub-resource + /me/memberships - main.py — registers organizations router - migrations: create memberships collection (unique index) + backfill from pm_client_ids/team members Frontend: - types/api.ts — Organization, OrgRole, Membership, OrganizationCreateRequest types; Client marked @deprecated - hooks/useClients.ts — useOrganizations, useOrganization, useOrgMembers, useAddOrgMember, useUpdateOrgMember, useRemoveOrgMember, useMyMemberships - lib/api.ts — listOrganizations, getOrganization, createOrganization, updateOrganization, listOrgMembers, addOrgMember, updateOrgMember, removeOrgMember, getMyMemberships Reads fall back to the clients collection during transition; all writes go to organizations. Existing /clients endpoints and hooks are untouched. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
34 lines
728 B
Python
34 lines
728 B
Python
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from .organization import OrgRole
|
|
|
|
|
|
class Membership(BaseModel):
|
|
id: Optional[str] = None
|
|
user_id: str
|
|
organization_id: str
|
|
role_in_org: OrgRole
|
|
created_at: Optional[datetime] = None
|
|
created_by: Optional[str] = None
|
|
|
|
|
|
class MembershipCreate(BaseModel):
|
|
user_id: str
|
|
role_in_org: OrgRole = OrgRole.MEMBER
|
|
|
|
|
|
class MembershipUpdate(BaseModel):
|
|
role_in_org: OrgRole
|
|
|
|
|
|
class MemberDetail(BaseModel):
|
|
"""Membership enriched with user info for list endpoints."""
|
|
membership_id: str
|
|
user_id: str
|
|
email: str
|
|
full_name: str
|
|
role_in_org: OrgRole
|
|
created_at: Optional[datetime] = None
|