feat: add organization retrieval endpoint and JWT authentication support
This commit is contained in:
parent
845737abca
commit
ea8f0eda65
|
|
@ -1,15 +1,22 @@
|
||||||
"""Reusable FastAPI dependencies."""
|
"""Reusable FastAPI dependencies."""
|
||||||
from collections.abc import AsyncGenerator
|
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 sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
from app.core.database import get_session
|
from app.core.database import get_session
|
||||||
from app.core.security import jwt_service, password_hasher
|
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.repositories.user_repo import UserRepository
|
||||||
from app.services.auth_service import AuthService
|
from app.services.auth_service import AuthService
|
||||||
from app.services.user_service import UserService
|
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]:
|
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
|
||||||
"""Provide a scoped database session."""
|
"""Provide a scoped database session."""
|
||||||
|
|
@ -21,6 +28,10 @@ def get_user_repository(session: AsyncSession = Depends(get_db_session)) -> User
|
||||||
return UserRepository(session=session)
|
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:
|
def get_user_service(repo: UserRepository = Depends(get_user_repository)) -> UserService:
|
||||||
return UserService(user_repository=repo, password_hasher=password_hasher)
|
return UserService(user_repository=repo, password_hasher=password_hasher)
|
||||||
|
|
||||||
|
|
@ -33,3 +44,26 @@ def get_auth_service(
|
||||||
password_hasher=password_hasher,
|
password_hasher=password_hasher,
|
||||||
jwt_service=jwt_service,
|
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
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ async def list_deals(
|
||||||
stage: str | None = None,
|
stage: str | None = None,
|
||||||
owner_id: int | None = None,
|
owner_id: int | None = None,
|
||||||
order_by: str | 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]:
|
) -> dict[str, str]:
|
||||||
"""Placeholder for deal filtering endpoint."""
|
"""Placeholder for deal filtering endpoint."""
|
||||||
_ = (status_filter,)
|
_ = (status_filter,)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
"""Organization-related API stubs."""
|
"""Organization-related API endpoints."""
|
||||||
from __future__ import annotations
|
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"])
|
router = APIRouter(prefix="/organizations", tags=["organizations"])
|
||||||
|
|
||||||
|
|
||||||
def _stub(endpoint: str) -> dict[str, str]:
|
@router.get("/me", response_model=list[OrganizationRead])
|
||||||
return {"detail": f"{endpoint} is not implemented yet"}
|
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."""
|
||||||
|
|
||||||
|
organizations = await repo.list_for_user(current_user.id)
|
||||||
@router.get("/me", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
return [OrganizationRead.model_validate(org) for org in organizations]
|
||||||
async def list_user_organizations() -> dict[str, str]:
|
|
||||||
"""Placeholder for returning organizations linked to the current user."""
|
|
||||||
return _stub("GET /organizations/me")
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue