video-accessibility/backend/app/models/organization.py
Vadym Samoilenko 6f1be645ce feat(saas): Phase 0+1 — Organization/Membership entities and dev branch
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>
2026-04-27 16:46:24 +01:00

51 lines
1.3 KiB
Python

from datetime import datetime
from enum import Enum
from typing import Optional
from pydantic import BaseModel
class OrgRole(str, Enum):
OWNER = "owner"
ADMIN = "admin"
MANAGER = "manager"
MEMBER = "member"
VIEWER = "viewer"
@classmethod
def _ordering(cls) -> list:
return [cls.VIEWER, cls.MEMBER, cls.MANAGER, cls.ADMIN, cls.OWNER]
def __ge__(self, other: "OrgRole") -> bool:
return self._ordering().index(self) >= self._ordering().index(other)
def __gt__(self, other: "OrgRole") -> bool:
return self._ordering().index(self) > self._ordering().index(other)
def __le__(self, other: "OrgRole") -> bool:
return self._ordering().index(self) <= self._ordering().index(other)
def __lt__(self, other: "OrgRole") -> bool:
return self._ordering().index(self) < self._ordering().index(other)
class Organization(BaseModel):
id: Optional[str] = None
name: str
slug: str
is_active: bool = True
plan: str = "standard"
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class OrganizationCreate(BaseModel):
name: str
slug: str
class OrganizationUpdate(BaseModel):
name: Optional[str] = None
slug: Optional[str] = None
is_active: Optional[bool] = None
plan: Optional[str] = None