aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-28 15:04:21 +0900
committernsfisis <nsfisis@gmail.com>2026-02-28 15:04:47 +0900
commit2794e9de67781614edc17336b284266d3e4a740c (patch)
treea1fe37cc341f8ad5db90088f8c9bd096f8ea978c
parentcf72673a8b09cb679d8dd80650d7f972af78d6f8 (diff)
downloadphperkaigi-2026-albatross-2794e9de67781614edc17336b284266d3e4a740c.tar.gz
phperkaigi-2026-albatross-2794e9de67781614edc17336b284266d3e4a740c.tar.zst
phperkaigi-2026-albatross-2794e9de67781614edc17336b284266d3e4a740c.zip
feat(admin): allow admin users to view and submit code before game starts
Admin users can now access the gaming UI (problem description, code editor, submit button) even when a game has not started yet. Regular users still see the waiting screen as before. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-rw-r--r--backend/api/handler.go4
-rw-r--r--backend/game/service.go8
-rw-r--r--frontend/app/components/GolfPlayApp.tsx16
-rw-r--r--frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx4
4 files changed, 25 insertions, 7 deletions
diff --git a/backend/api/handler.go b/backend/api/handler.go
index 4d729f1..ad7d55a 100644
--- a/backend/api/handler.go
+++ b/backend/api/handler.go
@@ -236,7 +236,7 @@ func (h *Handler) GetGamePlaySubmissions(ctx context.Context, request GetGamePla
}
func (h *Handler) PostGamePlayCode(ctx context.Context, request PostGamePlayCodeRequestObject, user *db.User) (PostGamePlayCodeResponseObject, error) {
- err := h.gameSvc.SaveCode(ctx, request.GameID, user.UserID, request.Body.Code)
+ err := h.gameSvc.SaveCode(ctx, request.GameID, user.UserID, request.Body.Code, user.IsAdmin)
if err != nil {
if errors.Is(err, game.ErrNotFound) {
return PostGamePlayCode404JSONResponse{Message: "Game not found"}, nil
@@ -250,7 +250,7 @@ func (h *Handler) PostGamePlayCode(ctx context.Context, request PostGamePlayCode
}
func (h *Handler) PostGamePlaySubmit(ctx context.Context, request PostGamePlaySubmitRequestObject, user *db.User) (PostGamePlaySubmitResponseObject, error) {
- err := h.gameSvc.SubmitCode(ctx, request.GameID, user.UserID, request.Body.Code)
+ err := h.gameSvc.SubmitCode(ctx, request.GameID, user.UserID, request.Body.Code, user.IsAdmin)
if err != nil {
if errors.Is(err, game.ErrNotFound) {
return PostGamePlaySubmit404JSONResponse{}, nil
diff --git a/backend/game/service.go b/backend/game/service.go
index 13900e1..a054388 100644
--- a/backend/game/service.go
+++ b/backend/game/service.go
@@ -202,7 +202,7 @@ func (s *Service) GetGameByID(ctx context.Context, gameID int, isAdmin bool) (De
return game, nil
}
-func (s *Service) SaveCode(ctx context.Context, gameID int, userID int32, code string) error {
+func (s *Service) SaveCode(ctx context.Context, gameID int, userID int32, code string, isAdmin bool) error {
gameRow, err := s.q.GetGameByID(ctx, int32(gameID))
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
@@ -210,7 +210,7 @@ func (s *Service) SaveCode(ctx context.Context, gameID int, userID int32, code s
}
return err
}
- if !IsGameRunning(gameRow.StartedAt, gameRow.DurationSeconds) {
+ if !IsGameRunning(gameRow.StartedAt, gameRow.DurationSeconds) && !isAdmin {
return ErrGameNotRunning
}
return s.q.UpdateCode(ctx, db.UpdateCodeParams{
@@ -221,7 +221,7 @@ func (s *Service) SaveCode(ctx context.Context, gameID int, userID int32, code s
})
}
-func (s *Service) SubmitCode(ctx context.Context, gameID int, userID int32, code string) error {
+func (s *Service) SubmitCode(ctx context.Context, gameID int, userID int32, code string, isAdmin bool) error {
gameRow, err := s.q.GetGameByID(ctx, int32(gameID))
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
@@ -233,7 +233,7 @@ func (s *Service) SubmitCode(ctx context.Context, gameID int, userID int32, code
language := gameRow.Language
codeSize := s.hub.CalcCodeSize(code, language)
- if !IsGameRunning(gameRow.StartedAt, gameRow.DurationSeconds) {
+ if !IsGameRunning(gameRow.StartedAt, gameRow.DurationSeconds) && !isAdmin {
return ErrGameNotRunning
}
diff --git a/frontend/app/components/GolfPlayApp.tsx b/frontend/app/components/GolfPlayApp.tsx
index 6c77f8c..1c1e7ae 100644
--- a/frontend/app/components/GolfPlayApp.tsx
+++ b/frontend/app/components/GolfPlayApp.tsx
@@ -121,6 +121,22 @@ export default function GolfPlayApp({ game, player, initialGameState }: Props) {
if (gameStateKind === "loading") {
return <GolfPlayAppLoading />;
} else if (gameStateKind === "waiting") {
+ if (player.is_admin) {
+ return (
+ <GolfPlayAppGaming
+ gameDisplayName={game.display_name}
+ playerProfile={playerProfile}
+ problemTitle={game.problem.title}
+ problemDescription={game.problem.description}
+ problemLanguage={game.problem.language}
+ sampleCode={game.problem.sample_code}
+ initialCode={initialGameState.code}
+ onCodeChange={onCodeChange}
+ onCodeSubmit={onCodeSubmit}
+ isFinished={false}
+ />
+ );
+ }
return (
<GolfPlayAppWaiting
gameDisplayName={game.display_name}
diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
index 7d81c82..fa9a2b4 100644
--- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
+++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
@@ -43,7 +43,7 @@ export default function GolfPlayAppGaming({
onCodeSubmit,
isFinished,
}: Props) {
- const leftTimeSeconds = useAtomValue(gamingLeftTimeSecondsAtom)!;
+ const leftTimeSeconds = useAtomValue(gamingLeftTimeSecondsAtom);
const score = useAtomValue(scoreAtom);
const status = useAtomValue(statusAtom);
@@ -72,6 +72,8 @@ export default function GolfPlayAppGaming({
<div className="text-gray-100">{gameDisplayName}</div>
{isFinished ? (
<div className="text-2xl md:text-3xl">終了</div>
+ ) : leftTimeSeconds === null ? (
+ <div className="text-2xl md:text-3xl">未開始</div>
) : (
<LeftTime sec={leftTimeSeconds} />
)}