feat(campaigns): modification-request router endpoints
This commit is contained in:
@@ -100,6 +100,21 @@ from app.services.campaign_crud_service import (
|
||||
from app.services.campaign_crud_service import (
|
||||
reject_campaign as crud_reject,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
create_modification_request as crud_create_mod_request,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
approve_modification_request as crud_approve_mod_request,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
reject_modification_request as crud_reject_mod_request,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
list_modification_requests as crud_list_mod_requests,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
serialize_modification_request as crud_serialize_mod_request,
|
||||
)
|
||||
|
||||
# Import log_action from app.services.audit_service
|
||||
from app.services.audit_service import log_action
|
||||
@@ -194,6 +209,22 @@ class RejectPayload(BaseModel):
|
||||
reason: str
|
||||
|
||||
|
||||
class ModificationRequestPayload(BaseModel):
|
||||
"""Payload for a lead requesting to add/remove a test on an active campaign."""
|
||||
|
||||
action: str # add_test | remove_test
|
||||
test_id: str
|
||||
justification: str
|
||||
order_index: Optional[int] = None
|
||||
phase: Optional[str] = None
|
||||
|
||||
|
||||
class RejectModificationPayload(BaseModel):
|
||||
"""Payload for a manager rejecting a modification request."""
|
||||
|
||||
review_notes: str
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /campaigns — List campaigns with filters
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -501,6 +532,133 @@ def reject_campaign_endpoint(
|
||||
return serialize_campaign(db, campaign)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /campaigns/{id}/modification-requests — Request a test add/remove
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.post("/{campaign_id}/modification-requests", status_code=201)
|
||||
def create_modification_request_endpoint(
|
||||
campaign_id: str,
|
||||
payload: ModificationRequestPayload,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
) -> dict:
|
||||
"""File a request to add/remove a test on an active campaign — needs manager approval."""
|
||||
with UnitOfWork(db) as uow:
|
||||
request = crud_create_mod_request(
|
||||
db, campaign_id,
|
||||
requester_id=current_user.id,
|
||||
action=payload.action,
|
||||
test_id=payload.test_id,
|
||||
justification=payload.justification,
|
||||
order_index=payload.order_index,
|
||||
phase=payload.phase,
|
||||
)
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="request_campaign_modification",
|
||||
entity_type="campaign",
|
||||
entity_id=campaign_id,
|
||||
details={
|
||||
"action": payload.action,
|
||||
"test_id": payload.test_id,
|
||||
"justification": payload.justification,
|
||||
},
|
||||
)
|
||||
uow.commit()
|
||||
return crud_serialize_mod_request(db, request)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /campaigns/{id}/modification-requests — List requests for one campaign
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.get("/{campaign_id}/modification-requests")
|
||||
def list_campaign_modification_requests_endpoint(
|
||||
campaign_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
) -> list:
|
||||
"""List modification requests filed against a specific campaign."""
|
||||
return crud_list_mod_requests(db, campaign_id=campaign_id)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /campaigns/modification-requests/pending — Manager's global queue
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.get("/modification-requests/pending")
|
||||
def list_pending_modification_requests_endpoint(
|
||||
db: Session = Depends(get_db),
|
||||
# admin passes automatically via require_any_role's built-in bypass — do not add "admin" here
|
||||
current_user: User = Depends(require_any_role("manager")),
|
||||
) -> list:
|
||||
"""List all modification requests awaiting manager review, across all campaigns."""
|
||||
return crud_list_mod_requests(db, status="pending")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /campaigns/modification-requests/{id}/approve — Apply the requested change
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.post("/modification-requests/{request_id}/approve")
|
||||
def approve_modification_request_endpoint(
|
||||
request_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
# admin passes automatically via require_any_role's built-in bypass — do not add "admin" here
|
||||
current_user: User = Depends(require_any_role("manager")),
|
||||
) -> dict:
|
||||
"""Manager approves a modification request — the test change is applied now."""
|
||||
with UnitOfWork(db) as uow:
|
||||
request = crud_approve_mod_request(db, request_id, reviewer_id=current_user.id)
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="approve_campaign_modification",
|
||||
entity_type="campaign",
|
||||
entity_id=str(request.campaign_id),
|
||||
details={
|
||||
"request_id": request_id,
|
||||
"action": request.action,
|
||||
"test_id": str(request.test_id) if request.test_id else None,
|
||||
},
|
||||
)
|
||||
uow.commit()
|
||||
return crud_serialize_mod_request(db, request)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /campaigns/modification-requests/{id}/reject — Deny the requested change
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.post("/modification-requests/{request_id}/reject")
|
||||
def reject_modification_request_endpoint(
|
||||
request_id: str,
|
||||
payload: RejectModificationPayload,
|
||||
db: Session = Depends(get_db),
|
||||
# admin passes automatically via require_any_role's built-in bypass — do not add "admin" here
|
||||
current_user: User = Depends(require_any_role("manager")),
|
||||
) -> dict:
|
||||
"""Manager rejects a modification request. No change is applied."""
|
||||
with UnitOfWork(db) as uow:
|
||||
request = crud_reject_mod_request(
|
||||
db, request_id,
|
||||
reviewer_id=current_user.id,
|
||||
review_notes=payload.review_notes,
|
||||
)
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="reject_campaign_modification",
|
||||
entity_type="campaign",
|
||||
entity_id=str(request.campaign_id),
|
||||
details={"request_id": request_id, "review_notes": payload.review_notes},
|
||||
)
|
||||
uow.commit()
|
||||
return crud_serialize_mod_request(db, request)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DELETE /campaigns/{id} — Delete campaign
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user