diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-03-21 13:33:12 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-03-21 13:33:12 +0900 |
| commit | a4037c3bf5d66f1303ffa629f77ab7cdfd5f0eb6 (patch) | |
| tree | 4f9ff7219fe9315f9858f73a030afce3ee46addf /backend/admin/handler.go | |
| parent | fc191e09c8b1acd8cfefc43a6e678a6d38e4ce12 (diff) | |
| download | phperkaigi-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.go | 54 |
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") +} |
