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:
@@ -165,6 +165,76 @@ class EmailService:
|
||||
|
||||
return await self._send_email(to, subject, html)
|
||||
|
||||
async def send_invite_request_admin_notification(
|
||||
self,
|
||||
to: str,
|
||||
requester_name: str,
|
||||
requester_email: str,
|
||||
message: str,
|
||||
) -> Optional[str]:
|
||||
"""Notify admin of a new invite request."""
|
||||
if not self.is_configured():
|
||||
logger.info(f"Email not configured. Would send invite request notification to {to}")
|
||||
return None
|
||||
|
||||
admin_url = f"{self.base_url}/admin.html"
|
||||
message_html = f"<p><strong>Message:</strong> {message}</p>" if message else ""
|
||||
|
||||
subject = f"Golf Game invite request from {requester_name}"
|
||||
html = f"""
|
||||
<h2>New Invite Request</h2>
|
||||
<p><strong>Name:</strong> {requester_name}</p>
|
||||
<p><strong>Email:</strong> {requester_email}</p>
|
||||
{message_html}
|
||||
<p><a href="{admin_url}">Review in Admin Panel</a></p>
|
||||
"""
|
||||
|
||||
return await self._send_email(to, subject, html)
|
||||
|
||||
async def send_invite_approved_email(
|
||||
self,
|
||||
to: str,
|
||||
name: str,
|
||||
invite_code: str,
|
||||
) -> Optional[str]:
|
||||
"""Notify requester that their invite was approved."""
|
||||
if not self.is_configured():
|
||||
logger.info(f"Email not configured. Would send invite approval to {to}")
|
||||
return None
|
||||
|
||||
signup_url = f"{self.base_url}/?invite={invite_code}"
|
||||
|
||||
subject = "Your Golf Game invite is ready!"
|
||||
html = f"""
|
||||
<h2>You're In, {name}!</h2>
|
||||
<p>Your request to join Golf Game has been approved.</p>
|
||||
<p>Use this link to create your account:</p>
|
||||
<p><a href="{signup_url}">{signup_url}</a></p>
|
||||
<p>Or sign up manually with invite code: <strong>{invite_code}</strong></p>
|
||||
<p>This invite is single-use and expires in 7 days.</p>
|
||||
"""
|
||||
|
||||
return await self._send_email(to, subject, html)
|
||||
|
||||
async def send_invite_denied_email(
|
||||
self,
|
||||
to: str,
|
||||
name: str,
|
||||
) -> Optional[str]:
|
||||
"""Notify requester that their invite was denied."""
|
||||
if not self.is_configured():
|
||||
logger.info(f"Email not configured. Would send invite denial to {to}")
|
||||
return None
|
||||
|
||||
subject = "Golf Game invite request update"
|
||||
html = f"""
|
||||
<h2>Hi {name},</h2>
|
||||
<p>Thanks for your interest in Golf Game. Unfortunately, we're not able to approve your invite request at this time.</p>
|
||||
<p>We may open up registrations in the future — stay tuned!</p>
|
||||
"""
|
||||
|
||||
return await self._send_email(to, subject, html)
|
||||
|
||||
async def _send_email(
|
||||
self,
|
||||
to: str,
|
||||
|
||||
Reference in New Issue
Block a user