Add initial polling backend logic
This commit is contained in:
191
src/govoplan_poll/backend/router.py
Normal file
191
src/govoplan_poll/backend/router.py
Normal file
@@ -0,0 +1,191 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal, get_api_principal, has_scope
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_poll.backend.manifest import READ_SCOPE, RESPOND_SCOPE, WRITE_SCOPE
|
||||
from govoplan_poll.backend.schemas import (
|
||||
PollCreateRequest,
|
||||
PollDecisionRequest,
|
||||
PollListResponse,
|
||||
PollResponse,
|
||||
PollResponseItem,
|
||||
PollResponseListResponse,
|
||||
PollResultSummaryResponse,
|
||||
PollStatusResponse,
|
||||
PollSubmitResponseRequest,
|
||||
PollUpdateRequest,
|
||||
)
|
||||
from govoplan_poll.backend.service import (
|
||||
PollError,
|
||||
close_poll,
|
||||
create_poll,
|
||||
decide_poll,
|
||||
get_poll,
|
||||
list_poll_responses,
|
||||
list_polls,
|
||||
open_poll,
|
||||
poll_response,
|
||||
poll_response_item,
|
||||
poll_result_summary_by_id,
|
||||
submit_poll_response,
|
||||
update_poll,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/poll", tags=["poll"])
|
||||
|
||||
|
||||
def _require_scope(principal: ApiPrincipal, scope: str) -> None:
|
||||
if not has_scope(principal, scope):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"Missing scope: {scope}")
|
||||
|
||||
|
||||
def _poll_http_error(exc: PollError) -> HTTPException:
|
||||
if str(exc) == "Poll not found":
|
||||
return HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))
|
||||
return HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
|
||||
|
||||
|
||||
def _poll_response(poll) -> PollResponse:
|
||||
return PollResponse.model_validate(poll_response(poll))
|
||||
|
||||
|
||||
@router.get("/polls", response_model=PollListResponse)
|
||||
def api_list_polls(
|
||||
status_filter: str | None = Query(default=None, alias="status"),
|
||||
kind: str | None = None,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollListResponse:
|
||||
_require_scope(principal, READ_SCOPE)
|
||||
polls = list_polls(session, tenant_id=principal.tenant_id, status=status_filter, kind=kind)
|
||||
return PollListResponse(polls=[_poll_response(poll) for poll in polls])
|
||||
|
||||
|
||||
@router.post("/polls", response_model=PollResponse, status_code=status.HTTP_201_CREATED)
|
||||
def api_create_poll(
|
||||
payload: PollCreateRequest,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollResponse:
|
||||
_require_scope(principal, WRITE_SCOPE)
|
||||
try:
|
||||
poll = create_poll(session, tenant_id=principal.tenant_id, user_id=principal.account_id, payload=payload)
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
return _poll_response(poll)
|
||||
|
||||
|
||||
@router.get("/polls/{poll_id}", response_model=PollResponse)
|
||||
def api_get_poll(
|
||||
poll_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollResponse:
|
||||
_require_scope(principal, READ_SCOPE)
|
||||
try:
|
||||
return _poll_response(get_poll(session, tenant_id=principal.tenant_id, poll_id=poll_id))
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
|
||||
|
||||
@router.patch("/polls/{poll_id}", response_model=PollResponse)
|
||||
def api_update_poll(
|
||||
poll_id: str,
|
||||
payload: PollUpdateRequest,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollResponse:
|
||||
_require_scope(principal, WRITE_SCOPE)
|
||||
try:
|
||||
return _poll_response(update_poll(session, tenant_id=principal.tenant_id, poll_id=poll_id, payload=payload))
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
|
||||
|
||||
@router.post("/polls/{poll_id}/open", response_model=PollStatusResponse)
|
||||
def api_open_poll(
|
||||
poll_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollStatusResponse:
|
||||
_require_scope(principal, WRITE_SCOPE)
|
||||
try:
|
||||
poll = open_poll(session, tenant_id=principal.tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
return PollStatusResponse(poll=_poll_response(poll))
|
||||
|
||||
|
||||
@router.post("/polls/{poll_id}/close", response_model=PollStatusResponse)
|
||||
def api_close_poll(
|
||||
poll_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollStatusResponse:
|
||||
_require_scope(principal, WRITE_SCOPE)
|
||||
try:
|
||||
poll = close_poll(session, tenant_id=principal.tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
return PollStatusResponse(poll=_poll_response(poll))
|
||||
|
||||
|
||||
@router.post("/polls/{poll_id}/decide", response_model=PollStatusResponse)
|
||||
def api_decide_poll(
|
||||
poll_id: str,
|
||||
payload: PollDecisionRequest,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollStatusResponse:
|
||||
_require_scope(principal, WRITE_SCOPE)
|
||||
try:
|
||||
poll = decide_poll(session, tenant_id=principal.tenant_id, poll_id=poll_id, payload=payload)
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
return PollStatusResponse(poll=_poll_response(poll))
|
||||
|
||||
|
||||
@router.post("/polls/{poll_id}/responses", response_model=PollResponseItem, status_code=status.HTTP_201_CREATED)
|
||||
def api_submit_poll_response(
|
||||
poll_id: str,
|
||||
payload: PollSubmitResponseRequest,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollResponseItem:
|
||||
_require_scope(principal, RESPOND_SCOPE)
|
||||
try:
|
||||
response = submit_poll_response(session, tenant_id=principal.tenant_id, poll_id=poll_id, payload=payload)
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
return PollResponseItem.model_validate(poll_response_item(response))
|
||||
|
||||
|
||||
@router.get("/polls/{poll_id}/responses", response_model=PollResponseListResponse)
|
||||
def api_list_poll_responses(
|
||||
poll_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollResponseListResponse:
|
||||
_require_scope(principal, READ_SCOPE)
|
||||
try:
|
||||
responses = list_poll_responses(session, tenant_id=principal.tenant_id, poll_id=poll_id)
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
return PollResponseListResponse(responses=[PollResponseItem.model_validate(poll_response_item(response)) for response in responses])
|
||||
|
||||
|
||||
@router.get("/polls/{poll_id}/summary", response_model=PollResultSummaryResponse)
|
||||
def api_poll_summary(
|
||||
poll_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> PollResultSummaryResponse:
|
||||
_require_scope(principal, READ_SCOPE)
|
||||
try:
|
||||
return PollResultSummaryResponse.model_validate(poll_result_summary_by_id(session, tenant_id=principal.tenant_id, poll_id=poll_id))
|
||||
except PollError as exc:
|
||||
raise _poll_http_error(exc) from exc
|
||||
Reference in New Issue
Block a user