Public Access
feat(mail): add broadcast mail for admins
Adds endpoints for PLATFORM_ADMIN to send mail to all active users or picked ones. Broadcasts and account mails share the same outbox; broadcasts take the rest of the daily limit after 10 mails are kept free for account mail. Users can opt out via settings. History is kept in mail_broadcast with recipient, sent and failed counts. The footer says where to opt out.
This commit is contained in:
1 parent
e72222a37f
commit
a60011ce9f
36 files changed
+1027
-20
No files matched your search
@@ -86,6 +86,13 @@ To Be Played is split into three repositories:
|
||||
to the admin page), and to the user "we got your registration", then "your account is active" or "your
|
||||
registration was denied", later "your account is blocked" and "your account will be deleted from <date>". The registration sends the browser language (`de` for a German browser, else `en`), which also becomes
|
||||
the user's app language. Plain text only. See "Mail flow".
|
||||
- **Broadcast mail**: a PLATFORM_ADMIN writes one mail with subject and text in English and German (plain text),
|
||||
and sends it to all active users or to picked ones, each in their app language. A user can turn it off
|
||||
(`user_settings.platform_mails`, default on); the footer of the mail says where. "Send test to me" mails the draft
|
||||
to the admin only, in the admin's language. One `mail_broadcast` row keeps the texts and the counts (recipients,
|
||||
sent, failed); `BROADCAST_SEND` goes to `admin_audit`. The sender takes account mails first and stays under
|
||||
`APP_MAIL_DAILY_LIMIT` per 24 h, 10 of them kept for account mails. A recipient who is blocked, deleted or opted
|
||||
out before the send counts as failed.
|
||||
- **Delete my account** with the password (5 wrong tries per 15 minutes, then `429`). It only sets
|
||||
`users.deleted_at` and revokes every refresh token: from then on no login, no refresh, no access token, no public
|
||||
profile, no Steam link, not in the landing stats. Email and display name stay taken; the Steam ID is freed, so an
|
||||
@@ -128,6 +135,11 @@ To Be Played is split into three repositories:
|
||||
| `PUT` | `/api/users/settings/profile-style` (`{accent, corners, bannerAppid, backgroundAppid}`; `204`) | access token |
|
||||
| `PUT` | `/api/users/settings/dashboard-tiles` (`{tiles: [{id, size}]}` in order, at most 20; `{tiles: null}` resets; `204`). Auth checks only the id shape `[A-Z_]{1,30}`: Core and the Frontend skip ids they do not know, and Core never shares the overview or the activity | access token |
|
||||
| `PUT` | `/api/users/settings/welcome-seen` (`204`; stamps `user_settings.welcome_seen_at`, sent as `settings.welcomeSeenAt` in `/me`, null until then) | access token |
|
||||
| `PUT` | `/api/users/settings/platform-mails` (`{enabled}`; `204`) | access token |
|
||||
| `GET` | `/api/admin/broadcasts` (`{results}` with the counts, newest first) | PLATFORM_ADMIN |
|
||||
| `GET` | `/api/admin/broadcasts/recipient-count?userIds=` (`{count}`; no `userIds` = all) | PLATFORM_ADMIN |
|
||||
| `POST` | `/api/admin/broadcasts` (`{subjectEn, subjectDe, bodyEn, bodyDe, userIds}`; `201 {recipientCount}`; `409 NO_RECIPIENTS`) | PLATFORM_ADMIN |
|
||||
| `POST` | `/api/admin/broadcasts/test` (same body; `204`) | PLATFORM_ADMIN |
|
||||
| `PUT` | `/api/users/settings/favorite-platform` (`{platform}`, free text up to 32, e.g. `PS5`; `null` clears it; `204`) | access token |
|
||||
| `PUT` | `/api/users/settings/profile-tiles` (`{tiles: [{id, size}]}`, e.g. `SUMMARY`, `STATUS`, `PLAYING`, `FINISHED`, `STATS_<GROUP>`; `204`) | access token |
|
||||
| `GET` | `/api/public/profiles/{displayName}` | public |
|
||||
@@ -179,6 +191,8 @@ use each other.
|
||||
`mail` writes each mail as a finished text into `mail_outbox` (`JdbcTemplate`, `repo`), in the transaction of the
|
||||
event, and `MailOutboxService` sends the rows every minute over SMTP. Other modules reach it through the port
|
||||
`shared/mail/AccountMailer`. Without `SPRING_MAIL_HOST` mail is off: the rows wait and go out once a host is set.
|
||||
Broadcasts (`BroadcastService`, `mail_broadcast`) queue one row per recipient; the same job sends them after the
|
||||
account mails and under the daily limit.
|
||||
|
||||
ArchUnit enforces these rules in `src/test/java/auth_service/architecture/ArchitectureTest.java`, so a layer
|
||||
violation fails the build. It also keeps the modules independent of each other and free of cycles. Test base
|
||||
@@ -277,10 +291,12 @@ sequenceDiagram
|
||||
participant B as Browser (Frontend)
|
||||
participant A as Auth
|
||||
participant M as SMTP (Hostinger)
|
||||
B->>A: admin sends a broadcast
|
||||
A->>A: mail_broadcast row + one outbox row per active, opted-in user
|
||||
B->>A: register / admin decides / admin blocks / delete account
|
||||
A->>A: event + mail_outbox row (PENDING), one transaction
|
||||
loop every minute (MailOutboxService)
|
||||
A->>M: up to 20 due rows
|
||||
A->>M: up to 20 due rows (account mails first, at most APP_MAIL_DAILY_LIMIT per 24 h, broadcast rows keep 10 free)
|
||||
M-->>A: ok: SENT / error: try again after 1, 2, 4 ... 60 min
|
||||
end
|
||||
Note over A: after 10 tries FAILED and a warning in the log; SENT rows are removed after 30 days
|
||||
@@ -381,6 +397,7 @@ Configuration comes from environment variables:
|
||||
| `SPRING_MAIL_HOST`, `SPRING_MAIL_USERNAME`, `SPRING_MAIL_PASSWORD` | SMTP server and its login (port 465, SSL). No host = mail off |
|
||||
| `APP_MAIL_FROM` | Sender address, the mailbox of the SMTP login. Needed with a host |
|
||||
| `APP_MAIL_ADMIN_TO` | Gets "a new registration waits". Needed with a host |
|
||||
| `APP_MAIL_DAILY_LIMIT` | Mails per rolling 24 h, all kinds together (default 100, Hostinger's free mailbox; 1000 Business Starter, 3000 Business Standard / Premium). Broadcast rows keep 10 free |
|
||||
|
||||
Generate a keypair:
|
||||
|
||||
|
||||
Reference in new issue
Block a user