From 74e1dcfccfb7f5b3300b0c3e59a8acdc41c88b75 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Thu, 5 Mar 2026 01:54:13 -0500 Subject: [PATCH] feat: add admin survey invite CRUD endpoints Co-Authored-By: Claude Opus 4.6 --- backend/app/api/endpoints/admin_survey.py | 82 +++++++++++++++++++++++ backend/app/api/router.py | 4 ++ 2 files changed, 86 insertions(+) create mode 100644 backend/app/api/endpoints/admin_survey.py diff --git a/backend/app/api/endpoints/admin_survey.py b/backend/app/api/endpoints/admin_survey.py new file mode 100644 index 00000000..edc91a54 --- /dev/null +++ b/backend/app/api/endpoints/admin_survey.py @@ -0,0 +1,82 @@ +"""Admin endpoints for managing survey invites.""" +import logging +from typing import Annotated + +from fastapi import APIRouter, Depends +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.api.deps import require_admin +from app.core.config import settings +from app.core.database import get_db +from app.core.email import EmailService +from app.models.survey_invite import SurveyInvite +from app.models.user import User +from app.schemas.survey import SurveyInviteCreate, SurveyInviteResponse + +logger = logging.getLogger(__name__) + +router = APIRouter(prefix="/admin", tags=["admin-survey"]) + +FRONTEND_URL = "https://resolutionflow.com" + + +def _build_invite_response(invite: SurveyInvite) -> SurveyInviteResponse: + base_url = FRONTEND_URL if not settings.DEBUG else "http://localhost:5173" + return SurveyInviteResponse( + id=str(invite.id), + token=invite.token, + recipient_name=invite.recipient_name, + recipient_email=invite.recipient_email, + status=invite.status, + email_sent=invite.email_sent, + created_at=invite.created_at, + completed_at=invite.completed_at, + survey_url=f"{base_url}/survey?t={invite.token}", + ) + + +@router.post("/survey-invites", response_model=SurveyInviteResponse) +async def create_survey_invite( + data: SurveyInviteCreate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(require_admin)], +): + """Create a survey invite. Optionally sends email.""" + invite = SurveyInvite( + recipient_name=data.recipient_name, + recipient_email=data.recipient_email, + ) + db.add(invite) + await db.flush() + + if data.send_email and data.recipient_email: + try: + base_url = FRONTEND_URL if not settings.DEBUG else "http://localhost:5173" + survey_url = f"{base_url}/survey?t={invite.token}" + sent = await EmailService.send_survey_invite_email( + to_email=data.recipient_email, + recipient_name=data.recipient_name, + survey_url=survey_url, + ) + if sent: + invite.email_sent = True + except Exception: + logger.exception("Failed to send survey invite email") + + await db.commit() + await db.refresh(invite) + return _build_invite_response(invite) + + +@router.get("/survey-invites", response_model=list[SurveyInviteResponse]) +async def list_survey_invites( + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(require_admin)], +): + """List all survey invites.""" + result = await db.execute( + select(SurveyInvite).order_by(SurveyInvite.created_at.desc()) + ) + invites = result.scalars().all() + return [_build_invite_response(i) for i in invites] diff --git a/backend/app/api/router.py b/backend/app/api/router.py index d266af2a..256d0de9 100644 --- a/backend/app/api/router.py +++ b/backend/app/api/router.py @@ -10,6 +10,8 @@ from app.api.endpoints import ai_fix from app.api.endpoints import ai_chat from app.api.endpoints import copilot from app.api.endpoints import assistant_chat +from app.api.endpoints import survey +from app.api.endpoints import admin_survey api_router = APIRouter() @@ -44,3 +46,5 @@ api_router.include_router(ai_fix.router) api_router.include_router(ai_chat.router) api_router.include_router(copilot.router) api_router.include_router(assistant_chat.router) +api_router.include_router(survey.router) +api_router.include_router(admin_survey.router)