Spaces:
Running
Running
| from typing import List, Any | |
| from uuid import UUID | |
| from fastapi import APIRouter, Depends, Request | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from sqlmodel import select, delete | |
| from app.api import deps | |
| from app.core.db import get_db | |
| from app.models.models import User, Workspace, WorkspaceMember, WorkspaceRole, Invite | |
| from app.schemas.workspace import ( | |
| WorkspaceCreate, WorkspaceRead, MemberRead, MemberInvite, MemberUpdateRole | |
| ) | |
| from app.schemas.envelope import ResponseEnvelope, wrap_data, wrap_error | |
| from app.services.entitlements import get_workspace_entitlements | |
| from app.services.audit_service import audit_event | |
| router = APIRouter() | |
| async def create_workspace( | |
| *, | |
| db: AsyncSession = Depends(get_db), | |
| workspace_in: WorkspaceCreate, | |
| current_user: User = Depends(deps.get_current_user), | |
| request: Request, | |
| ) -> Any: | |
| """Create a new workspace and add current user as Owner.""" | |
| workspace = Workspace(name=workspace_in.name) | |
| db.add(workspace) | |
| await db.flush() | |
| membership = WorkspaceMember( | |
| user_id=current_user.id, | |
| workspace_id=workspace.id, | |
| role=WorkspaceRole.OWNER | |
| ) | |
| db.add(membership) | |
| await audit_event( | |
| db, action="workspace_create", entity_type="workspace", | |
| entity_id=str(workspace.id), actor_user_id=current_user.id, | |
| outcome="success", workspace_id=workspace.id, request=request, | |
| ) | |
| await db.commit() | |
| await db.refresh(workspace) | |
| return wrap_data(workspace) | |
| async def list_workspaces( | |
| db: AsyncSession = Depends(get_db), | |
| current_user: User = Depends(deps.get_current_user) | |
| ) -> Any: | |
| """List all workspaces the current user belongs to.""" | |
| result = await db.execute( | |
| select(Workspace) | |
| .join(WorkspaceMember) | |
| .where(WorkspaceMember.user_id == current_user.id) | |
| ) | |
| workspaces = result.scalars().all() | |
| return wrap_data(workspaces) | |
| # --- Member Management (Scoped via X-Workspace-ID) --- | |
| async def list_members( | |
| db: AsyncSession = Depends(get_db), | |
| workspace: Workspace = Depends(deps.get_active_workspace), | |
| ) -> Any: | |
| """List all members in the active workspace.""" | |
| # In a real app, join with User to get details | |
| query = ( | |
| select(User.id.label("user_id"), User.email, User.full_name, WorkspaceMember.role) | |
| .join(WorkspaceMember, User.id == WorkspaceMember.user_id) | |
| .where(WorkspaceMember.workspace_id == workspace.id) | |
| ) | |
| result = await db.execute(query) | |
| members = [ | |
| {"user_id": row.user_id, "email": row.email, "full_name": row.full_name, "role": row.role} | |
| for row in result | |
| ] | |
| return wrap_data(members) | |
| async def invite_member( | |
| *, | |
| db: AsyncSession = Depends(get_db), | |
| invite_in: MemberInvite, | |
| workspace: Workspace = Depends(deps.get_active_workspace), | |
| current_user: User = Depends(deps.get_current_user), | |
| _ = Depends(deps.require_role([WorkspaceRole.OWNER, WorkspaceRole.MEMBER])), | |
| request: Request, | |
| ) -> Any: | |
| """Invite a new member to the workspace.""" | |
| import secrets | |
| from datetime import datetime, timedelta | |
| from app.services.email_service import EmailService | |
| token = secrets.token_urlsafe(32) | |
| invite = Invite( | |
| workspace_id=workspace.id, | |
| email=invite_in.email, | |
| token=token, | |
| expires_at=datetime.utcnow() + timedelta(days=7) | |
| ) | |
| db.add(invite) | |
| await db.commit() | |
| # Send Email | |
| # We should get user name if possible, for now using "A member" if not available easily without query | |
| # But we access current_user via dependency in many places. Here we don't have it explicitly in args | |
| # but we can assume the caller is a member. | |
| inviter_name = current_user.full_name or current_user.email | |
| await EmailService.send_invite_email(invite.email, invite.token, workspace.name, inviter_name) | |
| await audit_event( | |
| db, action="workspace_invite", entity_type="invite", | |
| entity_id=invite_in.email, actor_user_id=current_user.id, | |
| outcome="success", workspace_id=workspace.id, request=request, | |
| metadata={"invited_email": invite_in.email}, | |
| ) | |
| await db.commit() | |
| return wrap_data({"message": f"Invite sent to {invite_in.email}"}) | |
| async def remove_member( | |
| member_id: UUID, | |
| request: Request, | |
| db: AsyncSession = Depends(get_db), | |
| workspace: Workspace = Depends(deps.get_active_workspace), | |
| current_user: User = Depends(deps.get_current_user), | |
| _ = Depends(deps.require_role([WorkspaceRole.OWNER])), | |
| ) -> Any: | |
| """Remove a member from the workspace.""" | |
| if member_id == workspace.id: # Should be member_id == user.id of owner? | |
| # Prevent removing oneself if last owner? (Business logic) | |
| pass | |
| await db.execute( | |
| delete(WorkspaceMember) | |
| .where( | |
| WorkspaceMember.workspace_id == workspace.id, | |
| WorkspaceMember.user_id == member_id | |
| ) | |
| ) | |
| await audit_event( | |
| db, action="workspace_member_remove", entity_type="workspace_member", | |
| entity_id=str(member_id), actor_user_id=current_user.id, | |
| outcome="success", workspace_id=workspace.id, request=request, | |
| ) | |
| await db.commit() | |
| return wrap_data({"message": "Member removed"}) | |
| async def update_member_role( | |
| member_id: UUID, | |
| role_in: MemberUpdateRole, | |
| request: Request, | |
| db: AsyncSession = Depends(get_db), | |
| workspace: Workspace = Depends(deps.get_active_workspace), | |
| current_user: User = Depends(deps.get_current_user), | |
| _ = Depends(deps.require_role([WorkspaceRole.OWNER])), | |
| ) -> Any: | |
| """Update a member's role.""" | |
| result = await db.execute( | |
| select(WorkspaceMember) | |
| .where( | |
| WorkspaceMember.workspace_id == workspace.id, | |
| WorkspaceMember.user_id == member_id | |
| ) | |
| ) | |
| membership = result.scalars().first() | |
| if not membership: | |
| return wrap_error("Member not found") | |
| membership.role = role_in.role | |
| db.add(membership) | |
| await audit_event( | |
| db, action="workspace_role_change", entity_type="workspace_member", | |
| entity_id=str(member_id), actor_user_id=current_user.id, | |
| outcome="success", workspace_id=workspace.id, request=request, | |
| metadata={"new_role": role_in.role}, | |
| ) | |
| await db.commit() | |
| return wrap_data({"message": "Role updated"}) | |
| # --- Entitlements (Mission 14) --- | |
| async def get_entitlements( | |
| db: AsyncSession = Depends(get_db), | |
| workspace: Workspace = Depends(deps.get_active_workspace), | |
| ) -> Any: | |
| """Return merged entitlements + usage for the current workspace.""" | |
| entitlements = await get_workspace_entitlements(workspace.id, db) | |
| return wrap_data(entitlements) | |