diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-09-17 00:45:46 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-09-17 00:45:46 +0900 |
| commit | 611d93ea8a5dcc7dd92ec412e8fb28078438b31b (patch) | |
| tree | 9b08446290e60af9bcda7547ece21e8dc6c40e76 | |
| parent | a49e0b2fc1a59b54043b9ca93828346c027973eb (diff) | |
| download | iosdc-japan-2025-albatross-611d93ea8a5dcc7dd92ec412e8fb28078438b31b.tar.gz iosdc-japan-2025-albatross-611d93ea8a5dcc7dd92ec412e8fb28078438b31b.tar.zst iosdc-japan-2025-albatross-611d93ea8a5dcc7dd92ec412e8fb28078438b31b.zip | |
feat(backend): add admin pages for submissions
| -rw-r--r-- | backend/admin/handler.go | 83 | ||||
| -rw-r--r-- | backend/admin/templates/game_edit.html | 3 | ||||
| -rw-r--r-- | backend/admin/templates/games.html | 1 | ||||
| -rw-r--r-- | backend/admin/templates/submission_detail.html | 37 | ||||
| -rw-r--r-- | backend/admin/templates/submissions.html | 35 | ||||
| -rw-r--r-- | backend/db/query.sql.go | 92 | ||||
| -rw-r--r-- | backend/query.sql | 18 |
7 files changed, 269 insertions, 0 deletions
diff --git a/backend/admin/handler.go b/backend/admin/handler.go index 2a50cb0..5316250 100644 --- a/backend/admin/handler.go +++ b/backend/admin/handler.go @@ -68,6 +68,8 @@ func (h *Handler) RegisterHandlers(g *echo.Group) { g.GET("/games/:gameID", h.getGameEdit) g.POST("/games/:gameID", h.postGameEdit) g.POST("/games/:gameID/start", h.postGameStart) + g.GET("/games/:gameID/submissions", h.getSubmissions) + g.GET("/games/:gameID/submissions/:submissionID", h.getSubmissionDetail) g.GET("/problems", h.getProblems) g.GET("/problems/new", h.getProblemNew) @@ -576,6 +578,87 @@ func (h *Handler) postGameStart(c echo.Context) error { return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/games") } +func (h *Handler) getSubmissions(c echo.Context) error { + gameID, err := strconv.Atoi(c.Param("gameID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid game_id") + } + + submissions, err := h.q.GetSubmissionsByGameID(c.Request().Context(), int32(gameID)) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + entries := make([]echo.Map, len(submissions)) + for i, r := range submissions { + entries[i] = echo.Map{ + "SubmissionID": r.SubmissionID, + "UserID": r.UserID, + "Status": r.Status, + "CodeSize": r.CodeSize, + "CreatedAt": r.CreatedAt.Time.In(jst).Format("2006-01-02T15:04"), + } + } + + return c.Render(http.StatusOK, "submissions", echo.Map{ + "BasePath": h.conf.BasePath, + "Title": "Submissions", + "GameID": gameID, + "Submissions": entries, + }) +} + +func (h *Handler) getSubmissionDetail(c echo.Context) error { + gameID, err := strconv.Atoi(c.Param("gameID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid game_id") + } + + submissionID, err := strconv.Atoi(c.Param("submissionID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid submission_id") + } + + submission, err := h.q.GetSubmissionByID(c.Request().Context(), int32(submissionID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + testcaseResultRows, err := h.q.GetTestcaseResultsBySubmissionID(c.Request().Context(), int32(submissionID)) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + testcaseResults := make([]echo.Map, len(testcaseResultRows)) + for i, r := range testcaseResultRows { + testcaseResults[i] = echo.Map{ + "TestcaseResultID": r.TestcaseResultID, + "TestcaseID": r.TestcaseID, + "Status": r.Status, + "CreatedAt": r.CreatedAt.Time.In(jst).Format("2006-01-02T15:04"), + "Stdout": r.Stdout, + "Stderr": r.Stderr, + } + } + + return c.Render(http.StatusOK, "submission_detail", echo.Map{ + "BasePath": h.conf.BasePath, + "Title": "Submission Detail", + "GameID": gameID, + "Submission": echo.Map{ + "SubmissionID": submission.SubmissionID, + "UserID": submission.UserID, + "Status": submission.Status, + "CodeSize": submission.CodeSize, + "CreatedAt": submission.CreatedAt.Time.In(jst).Format("2006-01-02T15:04"), + "Code": submission.Code, + }, + "TestcaseResults": testcaseResults, + }) +} + func (h *Handler) getProblems(c echo.Context) error { rows, err := h.q.ListProblems(c.Request().Context()) if err != nil { diff --git a/backend/admin/templates/game_edit.html b/backend/admin/templates/game_edit.html index b171343..a6900e8 100644 --- a/backend/admin/templates/game_edit.html +++ b/backend/admin/templates/game_edit.html @@ -66,4 +66,7 @@ <button type="submit" formaction="{{ .BasePath }}admin/games/{{ .Game.GameID }}/start">Start</button> </div> </form> +<div> + <a href="{{ .BasePath }}admin/games/{{ .Game.GameID }}/submissions">View Submissions</a> +</div> {{ end }} diff --git a/backend/admin/templates/games.html b/backend/admin/templates/games.html index d642f07..63f27b7 100644 --- a/backend/admin/templates/games.html +++ b/backend/admin/templates/games.html @@ -21,6 +21,7 @@ {{ if .IsPublic }} <li><a href="{{ $.BasePath }}golf/{{ .GameID }}/watch">Watch</a></li> {{ end }} + <li><a href="{{ $.BasePath }}admin/games/{{ .GameID }}/submissions">Submissions</a></li> </ul> </li> {{ end }} diff --git a/backend/admin/templates/submission_detail.html b/backend/admin/templates/submission_detail.html new file mode 100644 index 0000000..406c0b4 --- /dev/null +++ b/backend/admin/templates/submission_detail.html @@ -0,0 +1,37 @@ +{{ template "base.html" . }} + +{{ define "breadcrumb" }} +<a href="{{ .BasePath }}admin/dashboard">Dashboard</a> | +<a href="{{ .BasePath }}admin/games">Games</a> | +<a href="{{ .BasePath }}admin/games/{{ .GameID }}">Game {{ .GameID }}</a> | +<a href="{{ .BasePath }}admin/games/{{ .GameID }}/submissions">Submissions</a> +{{ end }} + +{{ define "content" }} +<h2>Submission {{ .Submission.SubmissionID }}</h2> + +<h3>Basics</h3> +<ul> + <li>User: {{ .Submission.UserID }}</li> + <li>Status: {{ .Submission.Status }}</li> + <li>Code Size: {{ .Submission.CodeSize }}</li> + <li>Created At: {{ .Submission.CreatedAt }}</li> +</ul> + +<h3>Code</h3> +<pre><code>{{ .Submission.Code }}</code></pre> + +<h3>Testcase Results</h3> +{{ range .TestcaseResults }} + <h4>Testcase Result {{ .TestcaseResultID }}</h4> + <ul> + <li>Testcase ID: {{ .TestcaseID }}</li> + <li>Status: {{ .Status }}</li> + <li>Created At: {{ .CreatedAt }}</li> + </ul> + <h5>Stdout</h5> + <pre><code>{{ .Stdout }}</code></pre> + <h5>Stderr</h5> + <pre><code>{{ .Stderr }}</code></pre> +{{ end }} +{{ end }} diff --git a/backend/admin/templates/submissions.html b/backend/admin/templates/submissions.html new file mode 100644 index 0000000..6870c2a --- /dev/null +++ b/backend/admin/templates/submissions.html @@ -0,0 +1,35 @@ +{{ template "base.html" . }} + +{{ define "breadcrumb" }} +<a href="{{ .BasePath }}admin/dashboard">Dashboard</a> | +<a href="{{ .BasePath }}admin/games">Games</a> | +<a href="{{ .BasePath }}admin/games/{{ .GameID }}">Game {{ .GameID }}</a> +{{ end }} + +{{ define "content" }} +<h2>Submissions for Game {{ .GameID }}</h2> +<table> + <thead> + <tr> + <th>ID</th> + <th>User</th> + <th>Status</th> + <th>Code Size</th> + <th>Created At</th> + <th>View</th> + </tr> + </thead> + <tbody> + {{ range .Submissions }} + <tr> + <td>{{ .SubmissionID }}</td> + <td>{{ .UserID }}</td> + <td>{{ .Status }}</td> + <td>{{ .CodeSize }}</td> + <td>{{ .CreatedAt }}</td> + <td><a href="{{ $.BasePath }}admin/games/{{ $.GameID }}/submissions/{{ .SubmissionID }}">View</a></td> + </tr> + {{ end }} + </tbody> +</table> +{{ end }} diff --git a/backend/db/query.sql.go b/backend/db/query.sql.go index 05c841d..e807be4 100644 --- a/backend/db/query.sql.go +++ b/backend/db/query.sql.go @@ -500,6 +500,63 @@ func (q *Queries) GetRanking(ctx context.Context, gameID int32) ([]GetRankingRow return items, nil } +const getSubmissionByID = `-- name: GetSubmissionByID :one +SELECT submission_id, game_id, user_id, code, code_size, status, created_at +FROM submissions +WHERE submission_id = $1 +LIMIT 1 +` + +func (q *Queries) GetSubmissionByID(ctx context.Context, submissionID int32) (Submission, error) { + row := q.db.QueryRow(ctx, getSubmissionByID, submissionID) + var i Submission + err := row.Scan( + &i.SubmissionID, + &i.GameID, + &i.UserID, + &i.Code, + &i.CodeSize, + &i.Status, + &i.CreatedAt, + ) + return i, err +} + +const getSubmissionsByGameID = `-- name: GetSubmissionsByGameID :many +SELECT submission_id, game_id, user_id, code, code_size, status, created_at +FROM submissions +WHERE game_id = $1 +ORDER BY created_at DESC +` + +func (q *Queries) GetSubmissionsByGameID(ctx context.Context, gameID int32) ([]Submission, error) { + rows, err := q.db.Query(ctx, getSubmissionsByGameID, gameID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Submission + for rows.Next() { + var i Submission + if err := rows.Scan( + &i.SubmissionID, + &i.GameID, + &i.UserID, + &i.Code, + &i.CodeSize, + &i.Status, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getTestcaseByID = `-- name: GetTestcaseByID :one SELECT testcase_id, problem_id, stdin, stdout FROM testcases WHERE testcase_id = $1 @@ -518,6 +575,41 @@ func (q *Queries) GetTestcaseByID(ctx context.Context, testcaseID int32) (Testca return i, err } +const getTestcaseResultsBySubmissionID = `-- name: GetTestcaseResultsBySubmissionID :many +SELECT testcase_result_id, submission_id, testcase_id, status, stdout, stderr, created_at +FROM testcase_results +WHERE submission_id = $1 +ORDER BY created_at +` + +func (q *Queries) GetTestcaseResultsBySubmissionID(ctx context.Context, submissionID int32) ([]TestcaseResult, error) { + rows, err := q.db.Query(ctx, getTestcaseResultsBySubmissionID, submissionID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TestcaseResult + for rows.Next() { + var i TestcaseResult + if err := rows.Scan( + &i.TestcaseResultID, + &i.SubmissionID, + &i.TestcaseID, + &i.Status, + &i.Stdout, + &i.Stderr, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getUserAuthByUsername = `-- name: GetUserAuthByUsername :one SELECT users.user_id, username, display_name, icon_path, is_admin, label, created_at, user_auth_id, user_auths.user_id, auth_type, password_hash FROM users JOIN user_auths ON users.user_id = user_auths.user_id diff --git a/backend/query.sql b/backend/query.sql index 9baf593..0d84652 100644 --- a/backend/query.sql +++ b/backend/query.sql @@ -258,3 +258,21 @@ WHERE testcase_id = $1; -- name: DeleteTestcase :exec DELETE FROM testcases WHERE testcase_id = $1; + +-- name: GetSubmissionsByGameID :many +SELECT * +FROM submissions +WHERE game_id = $1 +ORDER BY created_at DESC; + +-- name: GetSubmissionByID :one +SELECT * +FROM submissions +WHERE submission_id = $1 +LIMIT 1; + +-- name: GetTestcaseResultsBySubmissionID :many +SELECT * +FROM testcase_results +WHERE submission_id = $1 +ORDER BY created_at; |
