Add invite request system and Gitea Actions CI/CD pipeline
Some checks failed
Build & Deploy Staging / build (release) Waiting to run
Build & Deploy Staging / deploy (release) Has been cancelled

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:
adlee-was-taken
2026-04-07 19:38:52 -04:00
parent 0c0588f920
commit ef54ac201a
16 changed files with 1003 additions and 50 deletions

View File

@@ -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,