kamau1's picture
feat: Add project team management endpoints - Fix team stats to count actual team members - Add filtering to GET /projects/{id}/team (role, region, active status, search) - Add PATCH /projects/{id}/team/{member_id} for updating team members - Add DELETE /projects/{id}/team/{member_id} for removing team members
9dd2bfc
"""
API Route Dependencies - Supabase Auth Integration
"""
from typing import Generator
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from app.core.database import SessionLocal
from app.core.supabase_auth import supabase_auth
from app.models.user import User
import logging
logger = logging.getLogger(__name__)
security = HTTPBearer()
def get_db() -> Generator[Session, None, None]:
"""
Database session dependency with automatic transaction management
CRITICAL: Sessions MUST be committed or rolled back explicitly.
If you don't commit, all changes are lost when the session closes.
"""
db = SessionLocal()
try:
yield db
# AUTO-COMMIT: If endpoint doesn't raise exception, commit the transaction
db.commit()
except Exception:
# AUTO-ROLLBACK: If endpoint raises exception, rollback the transaction
db.rollback()
raise
finally:
db.close()
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
) -> User:
"""
Get current authenticated user from Supabase Auth token
Args:
credentials: HTTP Bearer token (Supabase JWT)
db: Database session
Returns:
Current user
Raises:
HTTPException: If token is invalid or user not found
"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
token = credentials.credentials
# Verify token with Supabase and get user
auth_user = await supabase_auth.get_user(token)
if not auth_user:
logger.warning("Invalid or expired token")
raise credentials_exception
user_id = auth_user.id
# Get user from our database
user = db.query(User).filter(
User.id == user_id,
User.deleted_at == None
).first()
if user is None:
logger.warning(f"User not found in database: {user_id}")
raise credentials_exception
return user
except HTTPException:
raise
except Exception as e:
logger.error(f"Authentication error: {str(e)}")
raise credentials_exception
async def get_current_active_user(
current_user: User = Depends(get_current_user)
) -> User:
"""
Get current active user
Args:
current_user: Current authenticated user
Returns:
Current active user
Raises:
HTTPException: If user is inactive
"""
logger.info(f"Checking active user: {current_user.id}, is_active: {current_user.is_active}, type: {type(current_user.is_active)}")
# Handle None case explicitly
if current_user.is_active is None:
logger.warning(f"User {current_user.id} has NULL is_active field - defaulting to False")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User account status is undefined"
)
if not current_user.is_active:
logger.warning(f"User {current_user.id} is inactive")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Inactive user"
)
logger.info(f"User {current_user.id} is active - proceeding")
return current_user
def get_org_scope(user: User) -> dict:
"""
Get organization scope for org admin users.
Returns dict with org filtering info:
- is_org_scoped: bool - Whether user is org admin (needs scoping)
- client_id: UUID or None - Client org to filter by
- contractor_id: UUID or None - Contractor org to filter by
Platform admins and non-org-admins return is_org_scoped=False
"""
if user.role in ['client_admin', 'contractor_admin']:
return {
'is_org_scoped': True,
'client_id': user.client_id,
'contractor_id': user.contractor_id,
'org_type': 'client' if user.client_id else 'contractor'
}
return {
'is_org_scoped': False,
'client_id': None,
'contractor_id': None,
'org_type': None
}