Add invite request system and Gitea Actions CI/CD pipeline
Invite request feature: - Public form to request an invite when INVITE_REQUEST_ENABLED=true - Stores requests in new invite_requests DB table - Emails admins on new request, emails requester on approve/deny - Admin panel tab to review, approve, and deny requests - Approval auto-creates invite code and sends signup link CI/CD pipeline: - Build & push Docker image to Gitea registry on release - Auto-deploy to staging with health check - Manual workflow_dispatch for production deploys Also includes client layout/sizing improvements for card grid and opponent spacing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -418,3 +418,76 @@ async def revoke_invite_code(
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="Invite code not found")
|
||||
return {"message": "Invite code revoked successfully"}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Invite Request Endpoints
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@router.get("/invite-requests")
|
||||
async def list_invite_requests(
|
||||
status: Optional[str] = None,
|
||||
admin: User = Depends(require_admin_v2),
|
||||
service: AdminService = Depends(get_admin_service_dep),
|
||||
):
|
||||
"""List invite requests, optionally filtered by status (pending, approved, denied)."""
|
||||
requests = await service.get_invite_requests(status=status)
|
||||
return {"requests": [r.to_dict() for r in requests]}
|
||||
|
||||
|
||||
@router.post("/invite-requests/{request_id}/approve")
|
||||
async def approve_invite_request(
|
||||
request_id: int,
|
||||
request: Request,
|
||||
admin: User = Depends(require_admin_v2),
|
||||
service: AdminService = Depends(get_admin_service_dep),
|
||||
):
|
||||
"""Approve an invite request — creates a code and emails the requester."""
|
||||
code = await service.approve_invite_request(
|
||||
request_id=request_id,
|
||||
admin_id=admin.id,
|
||||
ip_address=get_client_ip(request),
|
||||
)
|
||||
if not code:
|
||||
raise HTTPException(status_code=404, detail="Request not found or already handled")
|
||||
|
||||
# Get the request details to send the approval email
|
||||
requests = await service.get_invite_requests()
|
||||
req = next((r for r in requests if r.id == request_id), None)
|
||||
if req:
|
||||
from services.email_service import get_email_service
|
||||
email_service = get_email_service()
|
||||
await email_service.send_invite_approved_email(
|
||||
to=req.email,
|
||||
name=req.name,
|
||||
invite_code=code,
|
||||
)
|
||||
|
||||
return {"code": code, "message": "Request approved and invite sent"}
|
||||
|
||||
|
||||
@router.post("/invite-requests/{request_id}/deny")
|
||||
async def deny_invite_request(
|
||||
request_id: int,
|
||||
request: Request,
|
||||
admin: User = Depends(require_admin_v2),
|
||||
service: AdminService = Depends(get_admin_service_dep),
|
||||
):
|
||||
"""Deny an invite request — optionally emails the requester."""
|
||||
result = await service.deny_invite_request(
|
||||
request_id=request_id,
|
||||
admin_id=admin.id,
|
||||
ip_address=get_client_ip(request),
|
||||
)
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Request not found or already handled")
|
||||
|
||||
from services.email_service import get_email_service
|
||||
email_service = get_email_service()
|
||||
await email_service.send_invite_denied_email(
|
||||
to=result["email"],
|
||||
name=result["name"],
|
||||
)
|
||||
|
||||
return {"message": "Request denied"}
|
||||
|
||||
Reference in New Issue
Block a user