| """ |
| Team management API endpoints (TEAM accounts only). |
| """ |
| from uuid import uuid4 |
| from datetime import datetime |
| from typing import Optional |
| from fastapi import APIRouter, Depends, HTTPException, Query, status |
|
|
| from app.dependencies import require_team_account, get_supabase |
| from app.models.team import ( |
| OrganizationCreate, |
| OrganizationUpdate, |
| Organization, |
| OrganizationWithStats, |
| OrganizationListResponse, |
| ) |
| from app.services.supabase_client import SupabaseService |
|
|
|
|
| router = APIRouter() |
|
|
|
|
| @router.post("", response_model=Organization, status_code=status.HTTP_201_CREATED) |
| async def create_organization( |
| org_data: OrganizationCreate, |
| current_user: dict = Depends(require_team_account), |
| supabase: SupabaseService = Depends(get_supabase), |
| ): |
| """ |
| Create a new organization (team). |
| |
| **Requires TEAM account.** |
| """ |
| org_id = str(uuid4()) |
| |
| org_record = { |
| "id": org_id, |
| "name": org_data.name, |
| "description": org_data.description, |
| "logo_url": org_data.logo_url, |
| "owner_id": current_user["id"], |
| } |
| |
| await supabase.insert("organizations", org_record) |
| |
| return Organization(**org_record, created_at=datetime.utcnow()) |
|
|
|
|
| @router.get("", response_model=OrganizationListResponse) |
| async def list_organizations( |
| current_user: dict = Depends(require_team_account), |
| supabase: SupabaseService = Depends(get_supabase), |
| ): |
| """ |
| List organizations owned by the current user. |
| |
| **Requires TEAM account.** |
| """ |
| orgs = await supabase.select( |
| "organizations", |
| filters={"owner_id": current_user["id"]}, |
| order_by="created_at", |
| ascending=False, |
| ) |
| |
| return OrganizationListResponse( |
| organizations=[Organization(**o) for o in orgs], |
| total=len(orgs), |
| ) |
|
|
|
|
| @router.get("/{org_id}", response_model=OrganizationWithStats) |
| async def get_organization( |
| org_id: str, |
| current_user: dict = Depends(require_team_account), |
| supabase: SupabaseService = Depends(get_supabase), |
| ): |
| """ |
| Get organization details with statistics. |
| |
| **Requires TEAM account.** |
| """ |
| org = await supabase.select_one("organizations", org_id) |
| |
| if not org: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="Organization not found" |
| ) |
| |
| if org["owner_id"] != current_user["id"]: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="You don't have access to this organization" |
| ) |
| |
| |
| players = await supabase.select("players", filters={"organization_id": org_id}) |
| videos = await supabase.select("videos", filters={"organization_id": org_id}) |
| |
| return OrganizationWithStats( |
| **org, |
| player_count=len(players), |
| video_count=len(videos), |
| total_analysis_count=len([v for v in videos if v.get("status") == "completed"]), |
| ) |
|
|
|
|
| @router.put("/{org_id}", response_model=Organization) |
| async def update_organization( |
| org_id: str, |
| update_data: OrganizationUpdate, |
| current_user: dict = Depends(require_team_account), |
| supabase: SupabaseService = Depends(get_supabase), |
| ): |
| """ |
| Update organization details. |
| |
| **Requires TEAM account.** |
| """ |
| org = await supabase.select_one("organizations", org_id) |
| |
| if not org: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="Organization not found" |
| ) |
| |
| if org["owner_id"] != current_user["id"]: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="You don't have permission to update this organization" |
| ) |
| |
| update_dict = update_data.model_dump(exclude_unset=True) |
| |
| if not update_dict: |
| raise HTTPException( |
| status_code=status.HTTP_400_BAD_REQUEST, |
| detail="No fields to update" |
| ) |
| |
| updated = await supabase.update("organizations", org_id, update_dict) |
| |
| return Organization(**updated) |
|
|
|
|
| @router.delete("/{org_id}", status_code=status.HTTP_204_NO_CONTENT) |
| async def delete_organization( |
| org_id: str, |
| current_user: dict = Depends(require_team_account), |
| supabase: SupabaseService = Depends(get_supabase), |
| ): |
| """ |
| Delete an organization and all associated data. |
| |
| **Requires TEAM account.** |
| """ |
| org = await supabase.select_one("organizations", org_id) |
| |
| if not org: |
| raise HTTPException( |
| status_code=status.HTTP_404_NOT_FOUND, |
| detail="Organization not found" |
| ) |
| |
| if org["owner_id"] != current_user["id"]: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="You don't have permission to delete this organization" |
| ) |
| |
| |
| videos = await supabase.select("videos", filters={"organization_id": org_id}) |
| video_ids = [v.get("id") for v in videos if v.get("id")] |
|
|
| |
| for vid in video_ids: |
| try: |
| await supabase.delete_where("analysis_results", {"video_id": vid}) |
| except Exception: |
| pass |
| try: |
| await supabase.delete_where("detections", {"video_id": vid}) |
| except Exception: |
| pass |
| try: |
| await supabase.delete_where("analytics", {"video_id": vid}) |
| except Exception: |
| pass |
| try: |
| await supabase.delete("videos", vid) |
| except Exception: |
| pass |
|
|
| |
| players = await supabase.select("players", filters={"organization_id": org_id}) |
| player_ids = [p.get("id") for p in players if p.get("id")] |
| for pid in player_ids: |
| try: |
| await supabase.delete_where("analytics", {"player_id": pid}) |
| except Exception: |
| pass |
| try: |
| await supabase.delete("players", pid) |
| except Exception: |
| pass |
|
|
| await supabase.delete("organizations", org_id) |
| |
| return None |
|
|