aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/admin/handler.go
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-03-21 13:33:12 +0900
committernsfisis <nsfisis@gmail.com>2026-03-21 13:33:12 +0900
commita4037c3bf5d66f1303ffa629f77ab7cdfd5f0eb6 (patch)
tree4f9ff7219fe9315f9858f73a030afce3ee46addf /backend/admin/handler.go
parentfc191e09c8b1acd8cfefc43a6e678a6d38e4ce12 (diff)
downloadphperkaigi-2026-albatross-a4037c3bf5d66f1303ffa629f77ab7cdfd5f0eb6.tar.gz
phperkaigi-2026-albatross-a4037c3bf5d66f1303ffa629f77ab7cdfd5f0eb6.tar.zst
phperkaigi-2026-albatross-a4037c3bf5d66f1303ffa629f77ab7cdfd5f0eb6.zip
feat(admin): add bulk restart page for all games
終了したゲームを一括で multiplayer に変更し、main player を 全削除して再スタートするための管理画面を /admin/restart に追加。 観戦者参加時のカンニング防止が目的。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'backend/admin/handler.go')
-rw-r--r--backend/admin/handler.go54
1 files changed, 54 insertions, 0 deletions
diff --git a/backend/admin/handler.go b/backend/admin/handler.go
index fcf85a3..61c58a3 100644
--- a/backend/admin/handler.go
+++ b/backend/admin/handler.go
@@ -87,6 +87,9 @@ func (h *Handler) RegisterHandlers(g *echo.Group) {
g.POST("/problems/:problemID/testcases/:testcaseID", h.postTestcaseEdit)
g.POST("/problems/:problemID/testcases/:testcaseID/delete", h.postTestcaseDelete)
+ g.GET("/restart", h.getRestart)
+ g.POST("/restart", h.postRestart)
+
g.GET("/tournaments", h.getTournaments)
g.GET("/tournaments/new", h.getTournamentNew)
g.POST("/tournaments/new", h.postTournamentNew)
@@ -1182,3 +1185,54 @@ func (h *Handler) postTournamentEdit(c echo.Context) error {
return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/tournaments")
}
+
+func (h *Handler) getRestart(c echo.Context) error {
+ return c.Render(http.StatusOK, "restart", echo.Map{
+ "BasePath": h.conf.BasePath,
+ "Title": "Restart All Games",
+ })
+}
+
+func (h *Handler) postRestart(c echo.Context) error {
+ endAtRaw := c.FormValue("end_at")
+ endAt, err := time.ParseInLocation("2006-01-02T15:04", endAtRaw, jst)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, "Invalid end_at")
+ }
+ endAtUTC := endAt.UTC()
+
+ startedAt := time.Now().Add(10 * time.Second)
+ durationSeconds := int(endAtUTC.Sub(startedAt).Seconds())
+ if durationSeconds <= 0 {
+ return echo.NewHTTPError(http.StatusBadRequest, "end_at must be in the future")
+ }
+
+ ctx := c.Request().Context()
+
+ games, err := h.q.ListAllGames(ctx)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
+ }
+
+ for _, g := range games {
+ err := h.gameSvc.UpdateGameWithPlayers(ctx, game.UpdateGameParams{
+ GameID: int(g.GameID),
+ GameType: "multiplayer",
+ IsPublic: g.IsPublic,
+ DisplayName: g.DisplayName,
+ DurationSeconds: durationSeconds,
+ StartedAt: pgtype.Timestamp{
+ Time: startedAt,
+ Valid: true,
+ },
+ ProblemID: int(g.ProblemID),
+ MainPlayerIDs: []int{},
+ })
+ if err != nil {
+ slog.ErrorContext(ctx, "failed to restart game", "game_id", g.GameID, "error", err)
+ return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to restart game %d: %s", g.GameID, err.Error()))
+ }
+ }
+
+ return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/games")
+}