diff --git a/app/api/deps.py b/app/api/deps.py index 626fd5b..1dedd51 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -1,15 +1,22 @@ """Reusable FastAPI dependencies.""" from collections.abc import AsyncGenerator -from fastapi import Depends +import jwt +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer from sqlalchemy.ext.asyncio import AsyncSession +from app.core.config import settings from app.core.database import get_session from app.core.security import jwt_service, password_hasher +from app.models.user import User +from app.repositories.org_repo import OrganizationRepository from app.repositories.user_repo import UserRepository from app.services.auth_service import AuthService from app.services.user_service import UserService +oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.api_v1_prefix}/auth/token") + async def get_db_session() -> AsyncGenerator[AsyncSession, None]: """Provide a scoped database session.""" @@ -21,6 +28,10 @@ def get_user_repository(session: AsyncSession = Depends(get_db_session)) -> User return UserRepository(session=session) +def get_organization_repository(session: AsyncSession = Depends(get_db_session)) -> OrganizationRepository: + return OrganizationRepository(session=session) + + def get_user_service(repo: UserRepository = Depends(get_user_repository)) -> UserService: return UserService(user_repository=repo, password_hasher=password_hasher) @@ -33,3 +44,26 @@ def get_auth_service( password_hasher=password_hasher, jwt_service=jwt_service, ) + + +async def get_current_user( + token: str = Depends(oauth2_scheme), + repo: UserRepository = Depends(get_user_repository), +) -> User: + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + ) + try: + payload = jwt_service.decode(token) + sub = payload.get("sub") + if sub is None: + raise credentials_exception + user_id = int(sub) + except (jwt.PyJWTError, TypeError, ValueError): + raise credentials_exception from None + + user = await repo.get_by_id(user_id) + if user is None: + raise credentials_exception + return user diff --git a/app/api/v1/deals/views.py b/app/api/v1/deals/views.py index d7df5a6..c3d8ffc 100644 --- a/app/api/v1/deals/views.py +++ b/app/api/v1/deals/views.py @@ -24,7 +24,7 @@ async def list_deals( stage: str | None = None, owner_id: int | None = None, order_by: str | None = None, - order: str | None = Query(default=None, regex="^(asc|desc)$"), + order: str | None = Query(default=None, pattern="^(asc|desc)$"), ) -> dict[str, str]: """Placeholder for deal filtering endpoint.""" _ = (status_filter,) diff --git a/app/api/v1/organizations/views.py b/app/api/v1/organizations/views.py index 9e56a4d..52d2dbc 100644 --- a/app/api/v1/organizations/views.py +++ b/app/api/v1/organizations/views.py @@ -1,16 +1,22 @@ -"""Organization-related API stubs.""" +"""Organization-related API endpoints.""" from __future__ import annotations -from fastapi import APIRouter, status +from fastapi import APIRouter, Depends + +from app.api.deps import get_current_user, get_organization_repository +from app.models.organization import OrganizationRead +from app.models.user import User +from app.repositories.org_repo import OrganizationRepository router = APIRouter(prefix="/organizations", tags=["organizations"]) -def _stub(endpoint: str) -> dict[str, str]: - return {"detail": f"{endpoint} is not implemented yet"} +@router.get("/me", response_model=list[OrganizationRead]) +async def list_user_organizations( + current_user: User = Depends(get_current_user), + repo: OrganizationRepository = Depends(get_organization_repository), +) -> list[OrganizationRead]: + """Return organizations the authenticated user belongs to.""" - -@router.get("/me", status_code=status.HTTP_501_NOT_IMPLEMENTED) -async def list_user_organizations() -> dict[str, str]: - """Placeholder for returning organizations linked to the current user.""" - return _stub("GET /organizations/me") + organizations = await repo.list_for_user(current_user.id) + return [OrganizationRead.model_validate(org) for org in organizations]