7cac910de4
Add the mail, notifications, and broadcast pages over the mail, notification,
and diplomail services (no new business logic), completing the operator console.
- GET /_gm/mail deliveries (paginated) + dead-letters
- GET /_gm/mail/deliveries/{id} delivery detail + attempts
- POST /_gm/mail/deliveries/{id}/resend re-enqueue a non-sent delivery
- GET /_gm/notifications notifications + dead-letters + malformed
- GET/POST /_gm/broadcast multi-game admin diplomatic broadcast
Console depends on MailAdmin / NotificationAdmin / DiplomailAdmin interfaces
(satisfied by the concrete services); pages render in tests without a database.
Delivery detail and dead-letters live under /_gm/mail/deliveries/* and
/_gm/mail/... static segments to avoid a param/static route conflict. Resend
and broadcast flow through the CSRF guard.
Tests: mail page, delivery detail (+ not-found), resend (+ bad-CSRF),
notifications overview, broadcast form + send (input assertions) + bad game
ids, and unavailable. Plus an integration test that drives /_gm end to end
through the real gateway → backend (401 challenge + authenticated dashboard).
Docs: backend/docs/admin-console.md page inventory completed.
22 lines
1.0 KiB
Go Template
22 lines
1.0 KiB
Go Template
{{define "content" -}}
|
|
{{$csrf := .CSRFToken}}
|
|
<h1>Broadcast</h1>
|
|
<nav class="subnav"><a href="/_gm/mail">Deliveries</a> · <a href="/_gm/notifications">Notifications</a> · <a href="/_gm/broadcast">Broadcast</a></nav>
|
|
<section class="panel">
|
|
<h2>Admin broadcast</h2>
|
|
<form method="post" action="/_gm/broadcast" class="form">
|
|
<input type="hidden" name="_csrf" value="{{$csrf}}">
|
|
<label>Scope
|
|
<select name="scope"><option value="all_running">all running games</option><option value="selected">selected games</option></select>
|
|
</label>
|
|
<label>Game IDs (comma-separated, for "selected") <input type="text" name="game_ids" placeholder="uuid,uuid"></label>
|
|
<label>Recipients
|
|
<select name="recipients"><option value="active">active members</option><option value="active_and_removed">active and removed</option><option value="all_members">all members</option></select>
|
|
</label>
|
|
<label>Subject <input type="text" name="subject"></label>
|
|
<label>Body <input type="text" name="body" required></label>
|
|
<button type="submit">Send broadcast</button>
|
|
</form>
|
|
</section>
|
|
{{- end}}
|