diplomail (Stage B): admin/owner sends + lifecycle hooks
Tests · Go / test (push) Successful in 1m52s
Tests · Go / test (pull_request) Successful in 1m53s
Tests · Integration / integration (pull_request) Successful in 1m36s

Item 7 of the spec wants game-state and membership-state changes to
land as durable inbox entries the affected players can re-read after
the fact — push alone times out of the 5-minute ring buffer. Stage B
adds the admin-kind send matrix (owner-driven via /user, site-admin
driven via /admin) plus the lobby lifecycle hooks: paused / cancelled
emit a broadcast system mail to active members, kick / ban emit a
single-recipient system mail to the affected user (which they keep
read access to even after the membership row is revoked, per item 8).

Migration relaxes diplomail_messages_kind_sender_chk so an owner
sending kind=admin keeps sender_kind=player; the new
LifecyclePublisher dep on lobby.Service is wired through a thin
adapter in cmd/backend/main, mirroring how lobby's notification
publisher is plumbed today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-15 18:47:54 +02:00
parent 535e27008f
commit b3f24cc440
17 changed files with 1398 additions and 23 deletions
+7 -1
View File
@@ -76,6 +76,7 @@ type RouterDependencies struct {
AdminRuntimes *AdminRuntimesHandlers
AdminEngineVersions *AdminEngineVersionsHandlers
AdminMail *AdminMailHandlers
AdminDiplomail *AdminDiplomailHandlers
AdminNotifications *AdminNotificationsHandlers
AdminGeo *AdminGeoHandlers
InternalSessions *InternalSessionsHandlers
@@ -165,7 +166,7 @@ func withDefaultHandlers(deps RouterDependencies) RouterDependencies {
deps.UserGames = NewUserGamesHandlers(nil, nil, deps.Logger)
}
if deps.UserMail == nil {
deps.UserMail = NewUserMailHandlers(nil, deps.Logger)
deps.UserMail = NewUserMailHandlers(nil, nil, nil, deps.Logger)
}
if deps.UserSessions == nil {
deps.UserSessions = NewUserSessionsHandlers(nil, deps.Logger)
@@ -188,6 +189,9 @@ func withDefaultHandlers(deps RouterDependencies) RouterDependencies {
if deps.AdminMail == nil {
deps.AdminMail = NewAdminMailHandlers(nil, deps.Logger)
}
if deps.AdminDiplomail == nil {
deps.AdminDiplomail = NewAdminDiplomailHandlers(nil, deps.Logger)
}
if deps.AdminNotifications == nil {
deps.AdminNotifications = NewAdminNotificationsHandlers(nil, deps.Logger)
}
@@ -274,6 +278,7 @@ func registerUserRoutes(router *gin.Engine, instruments *metrics.Instruments, de
userMail := userGames.Group("/:game_id/mail")
userMail.POST("/messages", deps.UserMail.SendPersonal())
userMail.POST("/admin", deps.UserMail.SendAdmin())
userMail.GET("/messages/:message_id", deps.UserMail.Get())
userMail.POST("/messages/:message_id/read", deps.UserMail.MarkRead())
userMail.DELETE("/messages/:message_id", deps.UserMail.Delete())
@@ -314,6 +319,7 @@ func registerAdminRoutes(router *gin.Engine, instruments *metrics.Instruments, d
games.POST("/:game_id/force-start", deps.AdminGames.ForceStart())
games.POST("/:game_id/force-stop", deps.AdminGames.ForceStop())
games.POST("/:game_id/ban-member", deps.AdminGames.BanMember())
games.POST("/:game_id/mail", deps.AdminDiplomail.Send())
runtimes := group.Group("/runtimes")
runtimes.GET("/:game_id", deps.AdminRuntimes.Get())