diff options
Diffstat (limited to 'backend/admin/handler.go')
| -rw-r--r-- | backend/admin/handler.go | 314 |
1 files changed, 56 insertions, 258 deletions
diff --git a/backend/admin/handler.go b/backend/admin/handler.go index 09303ac..f115745 100644 --- a/backend/admin/handler.go +++ b/backend/admin/handler.go @@ -16,24 +16,22 @@ import ( "albatross-2026-backend/account" "albatross-2026-backend/config" "albatross-2026-backend/db" + "albatross-2026-backend/game" "albatross-2026-backend/session" + "albatross-2026-backend/tournament" ) var jst = time.FixedZone("Asia/Tokyo", 9*60*60) -type GameHub interface { - EnqueueTestTasks(ctx context.Context, submissionID, gameID, userID int, language, code string) error -} - type Handler struct { - q db.Querier - txm db.TxManager - hub GameHub - conf *config.Config + gameSvc *game.Service + tournamentSvc *tournament.Service + q db.Querier + conf *config.Config } -func NewHandler(q db.Querier, txm db.TxManager, hub GameHub, conf *config.Config) *Handler { - return &Handler{q: q, txm: txm, hub: hub, conf: conf} +func NewHandler(gameSvc *game.Service, tournamentSvc *tournament.Service, q db.Querier, conf *config.Config) *Handler { + return &Handler{gameSvc: gameSvc, tournamentSvc: tournamentSvc, q: q, conf: conf} } func (h *Handler) newAdminMiddleware() echo.MiddlewareFunc { @@ -149,39 +147,9 @@ func (h *Handler) getOnlineQualifyingRanking(c echo.Context) error { } func (h *Handler) postFix(c echo.Context) error { - rows, err := h.q.ListSubmissionIDs(c.Request().Context()) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - for _, submissionID := range rows { - as, err := h.q.AggregateTestcaseResults(c.Request().Context(), submissionID) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - err = h.q.UpdateSubmissionStatus(c.Request().Context(), db.UpdateSubmissionStatusParams{ - SubmissionID: submissionID, - Status: as, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - - rows2, err := h.q.ListGameStateIDs(c.Request().Context()) - if err != nil { + if err := h.gameSvc.FixSubmissionStatuses(c.Request().Context()); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - for _, r := range rows2 { - gameID := r.GameID - userID := r.UserID - err := h.q.SyncGameStateBestScoreSubmission(c.Request().Context(), db.SyncGameStateBestScoreSubmissionParams{ - GameID: gameID, - UserID: userID, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/dashboard") } @@ -519,31 +487,15 @@ func (h *Handler) postGameEdit(c echo.Context) error { mainPlayers = append(mainPlayers, mainPlayer2) } - ctx := c.Request().Context() - err = h.txm.RunInTx(ctx, func(qtx db.Querier) error { - if err := qtx.UpdateGame(ctx, db.UpdateGameParams{ - GameID: int32(gameID), - GameType: gameType, - IsPublic: isPublic, - DisplayName: displayName, - DurationSeconds: int32(durationSeconds), - StartedAt: changedStartedAt, - ProblemID: int32(problemID), - }); err != nil { - return err - } - if err := qtx.RemoveAllMainPlayers(ctx, int32(gameID)); err != nil { - return err - } - for _, userID := range mainPlayers { - if err := qtx.AddMainPlayer(ctx, db.AddMainPlayerParams{ - GameID: int32(gameID), - UserID: int32(userID), - }); err != nil { - return err - } - } - return nil + err = h.gameSvc.UpdateGameWithPlayers(c.Request().Context(), game.UpdateGameParams{ + GameID: gameID, + GameType: gameType, + IsPublic: isPublic, + DisplayName: displayName, + DurationSeconds: durationSeconds, + StartedAt: changedStartedAt, + ProblemID: problemID, + MainPlayerIDs: mainPlayers, }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -558,36 +510,16 @@ func (h *Handler) postGameStart(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Invalid game id") } - game, err := h.q.GetGameByID(c.Request().Context(), int32(gameID)) + err = h.gameSvc.StartGame(c.Request().Context(), gameID) if err != nil { - if errors.Is(err, pgx.ErrNoRows) { + if errors.Is(err, game.ErrNotFound) { return echo.NewHTTPError(http.StatusNotFound, "Game not found") } - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - testcases, err := h.q.ListTestcasesByProblemID(c.Request().Context(), game.ProblemID) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { + if errors.Is(err, game.ErrNoTestcases) { return echo.NewHTTPError(http.StatusBadRequest, "No testcases") } return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - if len(testcases) == 0 { - return echo.NewHTTPError(http.StatusBadRequest, "No testcases") - } - - startedAt := time.Now().Add(10 * time.Second) - - err = h.q.UpdateGameStartedAt(c.Request().Context(), db.UpdateGameStartedAtParams{ - GameID: int32(gameID), - StartedAt: pgtype.Timestamp{ - Time: startedAt, - Valid: true, - }, - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/games") } @@ -673,22 +605,6 @@ func (h *Handler) getSubmissionDetail(c echo.Context) error { }) } -func (h *Handler) rejudgeSubmission(ctx context.Context, submission db.Submission, language string) error { - err := h.txm.RunInTx(ctx, func(qtx db.Querier) error { - if err := qtx.DeleteTestcaseResultsBySubmissionID(ctx, submission.SubmissionID); err != nil { - return err - } - return qtx.UpdateSubmissionStatus(ctx, db.UpdateSubmissionStatusParams{ - SubmissionID: submission.SubmissionID, - Status: "running", - }) - }) - if err != nil { - return err - } - return h.hub.EnqueueTestTasks(ctx, int(submission.SubmissionID), int(submission.GameID), int(submission.UserID), language, submission.Code) -} - func (h *Handler) postSubmissionRejudge(c echo.Context) error { gameID, err := strconv.Atoi(c.Param("gameID")) if err != nil { @@ -710,7 +626,7 @@ func (h *Handler) postSubmissionRejudge(c echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - game, err := h.q.GetGameByID(ctx, int32(gameID)) + g, err := h.q.GetGameByID(ctx, int32(gameID)) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return echo.NewHTTPError(http.StatusNotFound) @@ -718,7 +634,7 @@ func (h *Handler) postSubmissionRejudge(c echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - if err := h.rejudgeSubmission(ctx, submission, game.Language); err != nil { + if err := h.gameSvc.RejudgeSubmission(ctx, submission.SubmissionID, int(submission.GameID), int(submission.UserID), g.Language, submission.Code); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } @@ -731,27 +647,14 @@ func (h *Handler) postSubmissionsRejudgeLatest(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Invalid game_id") } - ctx := c.Request().Context() - - game, err := h.q.GetGameByID(ctx, int32(gameID)) + err = h.gameSvc.RejudgeLatestSubmissionsByGame(c.Request().Context(), gameID) if err != nil { - if errors.Is(err, pgx.ErrNoRows) { + if errors.Is(err, game.ErrNotFound) { return echo.NewHTTPError(http.StatusNotFound) } return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - submissions, err := h.q.GetLatestSubmissionsByGameID(ctx, int32(gameID)) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - - for _, s := range submissions { - if err := h.rejudgeSubmission(ctx, s, game.Language); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%sadmin/games/%d/submissions", h.conf.BasePath, gameID)) } @@ -761,27 +664,14 @@ func (h *Handler) postSubmissionsRejudgeAll(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Invalid game_id") } - ctx := c.Request().Context() - - game, err := h.q.GetGameByID(ctx, int32(gameID)) + err = h.gameSvc.RejudgeAllSubmissionsByGame(c.Request().Context(), gameID) if err != nil { - if errors.Is(err, pgx.ErrNoRows) { + if errors.Is(err, game.ErrNotFound) { return echo.NewHTTPError(http.StatusNotFound) } return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - submissions, err := h.q.GetSubmissionsByGameID(ctx, int32(gameID)) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - - for _, s := range submissions { - if err := h.rejudgeSubmission(ctx, s, game.Language); err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - } - return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%sadmin/games/%d/submissions", h.conf.BasePath, gameID)) } @@ -1107,23 +997,6 @@ func (h *Handler) getTournamentNew(c echo.Context) error { }) } -func nextPowerOf2(n int) int { - p := 1 - for p < n { - p *= 2 - } - return p -} - -func log2Int(n int) int { - r := 0 - for n > 1 { - n /= 2 - r++ - } - return r -} - func (h *Handler) postTournamentNew(c echo.Context) error { displayName := c.FormValue("display_name") numParticipants, err := strconv.Atoi(c.FormValue("num_participants")) @@ -1131,34 +1004,7 @@ func (h *Handler) postTournamentNew(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Invalid num_participants") } - bracketSize := nextPowerOf2(numParticipants) - numRounds := log2Int(bracketSize) - - ctx := c.Request().Context() - err = h.txm.RunInTx(ctx, func(qtx db.Querier) error { - tournamentID, err := qtx.CreateTournament(ctx, db.CreateTournamentParams{ - DisplayName: displayName, - BracketSize: int32(bracketSize), - NumRounds: int32(numRounds), - }) - if err != nil { - return err - } - // Create match slots for all rounds - for round := range numRounds { - numPositions := bracketSize / (1 << (round + 1)) - for pos := range numPositions { - if err := qtx.CreateTournamentMatch(ctx, db.CreateTournamentMatchParams{ - TournamentID: tournamentID, - Round: int32(round), - Position: int32(pos), - }); err != nil { - return err - } - } - } - return nil - }) + _, err = h.tournamentSvc.CreateTournament(c.Request().Context(), displayName, numParticipants) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } @@ -1173,30 +1019,19 @@ func (h *Handler) getTournamentEdit(c echo.Context) error { } ctx := c.Request().Context() - tournament, err := h.q.GetTournamentByID(ctx, int32(tournamentID)) + editData, err := h.tournamentSvc.GetTournamentEditData(ctx, tournamentID) if err != nil { - if errors.Is(err, pgx.ErrNoRows) { + if errors.Is(err, game.ErrNotFound) { return echo.NewHTTPError(http.StatusNotFound) } return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - entryRows, err := h.q.ListTournamentEntries(ctx, int32(tournamentID)) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - seedUserMap := make(map[int]int) - for _, e := range entryRows { - seedUserMap[int(e.Seed)] = int(e.UserID) - } + t := editData.Tournament - matchRows, err := h.q.ListTournamentMatches(ctx, int32(tournamentID)) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } matchGameMap := make(map[int]int) var matches []echo.Map - for _, m := range matchRows { + for _, m := range editData.Matches { gameID := 0 if m.GameID != nil { gameID = int(*m.GameID) @@ -1235,7 +1070,7 @@ func (h *Handler) getTournamentEdit(c echo.Context) error { }) } - seeds := make([]int, tournament.BracketSize) + seeds := make([]int, t.BracketSize) for i := range seeds { seeds[i] = i + 1 } @@ -1244,13 +1079,13 @@ func (h *Handler) getTournamentEdit(c echo.Context) error { "BasePath": h.conf.BasePath, "Title": "Tournament Edit", "Tournament": echo.Map{ - "TournamentID": tournament.TournamentID, - "DisplayName": tournament.DisplayName, - "BracketSize": tournament.BracketSize, - "NumRounds": tournament.NumRounds, + "TournamentID": t.TournamentID, + "DisplayName": t.DisplayName, + "BracketSize": t.BracketSize, + "NumRounds": t.NumRounds, }, "Seeds": seeds, - "SeedUserMap": seedUserMap, + "SeedUserMap": editData.SeedUserMap, "Matches": matches, "MatchGameMap": matchGameMap, "Users": users, @@ -1265,9 +1100,11 @@ func (h *Handler) postTournamentEdit(c echo.Context) error { } ctx := c.Request().Context() - tournament, err := h.q.GetTournamentByID(ctx, int32(tournamentID)) + // We need the tournament to know bracket size for seed parsing, + // and matches for match game parsing. + editData, err := h.tournamentSvc.GetTournamentEditData(ctx, tournamentID) if err != nil { - if errors.Is(err, pgx.ErrNoRows) { + if errors.Is(err, game.ErrNotFound) { return echo.NewHTTPError(http.StatusNotFound) } return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) @@ -1276,12 +1113,8 @@ func (h *Handler) postTournamentEdit(c echo.Context) error { displayName := c.FormValue("display_name") // Parse seed → user assignments - type seedEntry struct { - seed int - userID int - } - var seedEntries []seedEntry - for seed := 1; seed <= int(tournament.BracketSize); seed++ { + var seedEntries []tournament.SeedEntry + for seed := 1; seed <= int(editData.Tournament.BracketSize); seed++ { raw := c.FormValue("seed_" + strconv.Itoa(seed)) if raw == "" || raw == "0" { continue @@ -1290,20 +1123,12 @@ func (h *Handler) postTournamentEdit(c echo.Context) error { if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid seed_"+strconv.Itoa(seed)) } - seedEntries = append(seedEntries, seedEntry{seed: seed, userID: userID}) + seedEntries = append(seedEntries, tournament.SeedEntry{Seed: seed, UserID: userID}) } // Parse match → game assignments - matchRows, err := h.q.ListTournamentMatches(ctx, int32(tournamentID)) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) - } - type matchGame struct { - matchID int - gameID *int32 - } - var matchGames []matchGame - for _, m := range matchRows { + var matchGames []tournament.MatchGame + for _, m := range editData.Matches { raw := c.FormValue("match_" + strconv.Itoa(int(m.TournamentMatchID))) var gameID *int32 if raw != "" && raw != "0" { @@ -1314,46 +1139,19 @@ func (h *Handler) postTournamentEdit(c echo.Context) error { gid32 := int32(gid) gameID = &gid32 } - matchGames = append(matchGames, matchGame{matchID: int(m.TournamentMatchID), gameID: gameID}) + matchGames = append(matchGames, tournament.MatchGame{MatchID: int(m.TournamentMatchID), GameID: gameID}) } - err = h.txm.RunInTx(ctx, func(qtx db.Querier) error { - if err := qtx.UpdateTournament(ctx, db.UpdateTournamentParams{ - TournamentID: int32(tournamentID), - DisplayName: displayName, - BracketSize: tournament.BracketSize, - NumRounds: tournament.NumRounds, - }); err != nil { - return err - } - - // Replace entries - if err := qtx.DeleteTournamentEntries(ctx, int32(tournamentID)); err != nil { - return err - } - for _, se := range seedEntries { - if err := qtx.CreateTournamentEntry(ctx, db.CreateTournamentEntryParams{ - TournamentID: int32(tournamentID), - UserID: int32(se.userID), - Seed: int32(se.seed), - }); err != nil { - return err - } - } - - // Update match game assignments - for _, mg := range matchGames { - if err := qtx.UpdateTournamentMatchGame(ctx, db.UpdateTournamentMatchGameParams{ - TournamentMatchID: int32(mg.matchID), - GameID: mg.gameID, - }); err != nil { - return err - } - } - - return nil + err = h.tournamentSvc.UpdateTournament(ctx, tournament.UpdateTournamentParams{ + TournamentID: tournamentID, + DisplayName: displayName, + SeedEntries: seedEntries, + MatchGames: matchGames, }) if err != nil { + if errors.Is(err, game.ErrNotFound) { + return echo.NewHTTPError(http.StatusNotFound) + } return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } |
