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:
@@ -75,6 +75,13 @@ class UpdatePreferencesRequest(BaseModel):
|
||||
preferences: dict
|
||||
|
||||
|
||||
class InviteRequestBody(BaseModel):
|
||||
"""Invite request body."""
|
||||
name: str
|
||||
email: str
|
||||
message: Optional[str] = None
|
||||
|
||||
|
||||
class ConvertGuestRequest(BaseModel):
|
||||
"""Convert guest to user request."""
|
||||
guest_id: str
|
||||
@@ -332,6 +339,7 @@ async def signup_info():
|
||||
|
||||
return {
|
||||
"invite_required": invite_required,
|
||||
"invite_request_enabled": config.INVITE_REQUEST_ENABLED,
|
||||
"open_signups_enabled": open_signups_enabled,
|
||||
"daily_limit": config.DAILY_OPEN_SIGNUPS if not unlimited else None,
|
||||
"remaining_today": remaining,
|
||||
@@ -339,6 +347,55 @@ async def signup_info():
|
||||
}
|
||||
|
||||
|
||||
@router.post("/request-invite")
|
||||
async def request_invite(
|
||||
request_body: InviteRequestBody,
|
||||
request: Request,
|
||||
):
|
||||
"""
|
||||
Public endpoint: submit a request for an invite code.
|
||||
|
||||
Stores the request in the database and notifies admins via email.
|
||||
"""
|
||||
if not config.INVITE_REQUEST_ENABLED:
|
||||
raise HTTPException(status_code=404, detail="Invite requests are not enabled")
|
||||
|
||||
if not _admin_service:
|
||||
raise HTTPException(status_code=503, detail="Service not initialized")
|
||||
|
||||
name = request_body.name.strip()
|
||||
email = request_body.email.strip().lower()
|
||||
message = request_body.message.strip() if request_body.message else None
|
||||
|
||||
if not name or len(name) > 100:
|
||||
raise HTTPException(status_code=400, detail="Name is required (max 100 characters)")
|
||||
if not email or "@" not in email:
|
||||
raise HTTPException(status_code=400, detail="Valid email is required")
|
||||
|
||||
client_ip = get_client_ip(request)
|
||||
|
||||
request_id = await _admin_service.create_invite_request(
|
||||
name=name,
|
||||
email=email,
|
||||
message=message,
|
||||
ip_address=client_ip,
|
||||
)
|
||||
|
||||
# Notify admin emails
|
||||
if config.ADMIN_EMAILS:
|
||||
from services.email_service import get_email_service
|
||||
email_service = get_email_service()
|
||||
for admin_email in config.ADMIN_EMAILS:
|
||||
await email_service.send_invite_request_admin_notification(
|
||||
to=admin_email,
|
||||
requester_name=name,
|
||||
requester_email=email,
|
||||
message=message or "",
|
||||
)
|
||||
|
||||
return {"status": "ok", "message": "Your request has been submitted. We'll be in touch!"}
|
||||
|
||||
|
||||
@router.post("/verify-email")
|
||||
async def verify_email(
|
||||
request_body: VerifyEmailRequest,
|
||||
|
||||
Reference in New Issue
Block a user