From a4037c3bf5d66f1303ffa629f77ab7cdfd5f0eb6 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 21 Mar 2026 13:33:12 +0900 Subject: feat(admin): add bulk restart page for all games MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 終了したゲームを一括で multiplayer に変更し、main player を 全削除して再スタートするための管理画面を /admin/restart に追加。 観戦者参加時のカンニング防止が目的。 Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/admin/handler.go | 54 ++++++++++++++++++++++++++++++++++++ backend/admin/templates/restart.html | 19 +++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 backend/admin/templates/restart.html 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") +} diff --git a/backend/admin/templates/restart.html b/backend/admin/templates/restart.html new file mode 100644 index 0000000..2bb77e9 --- /dev/null +++ b/backend/admin/templates/restart.html @@ -0,0 +1,19 @@ +{{ template "base.html" . }} + +{{ define "breadcrumb" }} +Dashboard +{{ end }} + +{{ define "content" }} +

Restart All Games

+

全ゲームの main player を削除し、multiplayer に変更して一括リスタートします。

+
+
+ + +
+
+ +
+
+{{ end }} -- cgit v1.3.1