From 8d739b386f2b555292fd8082c9de2199228737c9 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 6 Sep 2025 01:30:31 +0900 Subject: feat(backend): add admin pages for testcases --- backend/admin/handler.go | 201 +++++++++++++++++++++++++++++ backend/admin/templates/problem_edit.html | 6 + backend/admin/templates/testcase_edit.html | 36 ++++++ backend/admin/templates/testcase_new.html | 27 ++++ backend/admin/templates/testcases.html | 26 ++++ 5 files changed, 296 insertions(+) create mode 100644 backend/admin/templates/testcase_edit.html create mode 100644 backend/admin/templates/testcase_new.html create mode 100644 backend/admin/templates/testcases.html (limited to 'backend/admin') diff --git a/backend/admin/handler.go b/backend/admin/handler.go index 19e44e6..86ee3f7 100644 --- a/backend/admin/handler.go +++ b/backend/admin/handler.go @@ -73,6 +73,12 @@ func (h *Handler) RegisterHandlers(g *echo.Group) { g.POST("/problems/new", h.postProblemNew) g.GET("/problems/:problemID", h.getProblemEdit) g.POST("/problems/:problemID", h.postProblemEdit) + g.GET("/problems/:problemID/testcases", h.getTestcases) + g.GET("/problems/:problemID/testcases/new", h.getTestcaseNew) + g.POST("/problems/:problemID/testcases/new", h.postTestcaseNew) + g.GET("/problems/:problemID/testcases/:testcaseID", h.getTestcaseEdit) + g.POST("/problems/:problemID/testcases/:testcaseID", h.postTestcaseEdit) + g.POST("/problems/:problemID/testcases/:testcaseID/delete", h.postTestcaseDelete) } func (h *Handler) getDashboard(c echo.Context) error { @@ -605,3 +611,198 @@ func (h *Handler) postProblemEdit(c echo.Context) error { return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/problems") } + +func (h *Handler) getTestcases(c echo.Context) error { + problemID, err := strconv.Atoi(c.Param("problemID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem id") + } + + // Get problem info + problem, err := h.q.GetProblemByID(c.Request().Context(), int32(problemID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + // Get testcases for this problem + rows, err := h.q.ListTestcasesByProblemID(c.Request().Context(), int32(problemID)) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + testcases := make([]echo.Map, len(rows)) + for i, tc := range rows { + testcases[i] = echo.Map{ + "TestcaseID": tc.TestcaseID, + "ProblemID": tc.ProblemID, + "Stdin": tc.Stdin, + "Stdout": tc.Stdout, + } + } + + return c.Render(http.StatusOK, "testcases", echo.Map{ + "BasePath": h.conf.BasePath, + "Title": "Testcases for " + problem.Title, + "Problem": echo.Map{"ProblemID": problem.ProblemID, "Title": problem.Title}, + "Testcases": testcases, + }) +} + +func (h *Handler) getTestcaseNew(c echo.Context) error { + problemID, err := strconv.Atoi(c.Param("problemID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem id") + } + + // Get problem info + problem, err := h.q.GetProblemByID(c.Request().Context(), int32(problemID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.Render(http.StatusOK, "testcase_new", echo.Map{ + "BasePath": h.conf.BasePath, + "Title": "New Testcase for " + problem.Title, + "Problem": echo.Map{"ProblemID": problem.ProblemID, "Title": problem.Title}, + }) +} + +func (h *Handler) postTestcaseNew(c echo.Context) error { + problemID, err := strconv.Atoi(c.Param("problemID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem_id") + } + stdin := c.FormValue("stdin") + stdout := c.FormValue("stdout") + + _, err = h.q.CreateTestcase(c.Request().Context(), db.CreateTestcaseParams{ + ProblemID: int32(problemID), + Stdin: stdin, + Stdout: stdout, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/problems/"+strconv.Itoa(problemID)+"/testcases") +} + +func (h *Handler) getTestcaseEdit(c echo.Context) error { + problemID, err := strconv.Atoi(c.Param("problemID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem id") + } + testcaseID, err := strconv.Atoi(c.Param("testcaseID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid testcase id") + } + + // Get problem info + problem, err := h.q.GetProblemByID(c.Request().Context(), int32(problemID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + // Get testcase info and verify it belongs to this problem + testcase, err := h.q.GetTestcaseByID(c.Request().Context(), int32(testcaseID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + // Verify the testcase belongs to the specified problem + if testcase.ProblemID != int32(problemID) { + return echo.NewHTTPError(http.StatusNotFound) + } + + return c.Render(http.StatusOK, "testcase_edit", echo.Map{ + "BasePath": h.conf.BasePath, + "Title": "Edit Testcase for " + problem.Title, + "Problem": echo.Map{"ProblemID": problem.ProblemID, "Title": problem.Title}, + "Testcase": echo.Map{ + "TestcaseID": testcase.TestcaseID, + "ProblemID": testcase.ProblemID, + "Stdin": testcase.Stdin, + "Stdout": testcase.Stdout, + }, + }) +} + +func (h *Handler) postTestcaseEdit(c echo.Context) error { + problemID, err := strconv.Atoi(c.Param("problemID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem_id") + } + testcaseID, err := strconv.Atoi(c.Param("testcaseID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid testcase_id") + } + + // Verify the testcase belongs to this problem before updating + testcase, err := h.q.GetTestcaseByID(c.Request().Context(), int32(testcaseID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + if testcase.ProblemID != int32(problemID) { + return echo.NewHTTPError(http.StatusNotFound) + } + + stdin := c.FormValue("stdin") + stdout := c.FormValue("stdout") + + err = h.q.UpdateTestcase(c.Request().Context(), db.UpdateTestcaseParams{ + TestcaseID: int32(testcaseID), + ProblemID: int32(problemID), + Stdin: stdin, + Stdout: stdout, + }) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/problems/"+strconv.Itoa(problemID)+"/testcases") +} + +func (h *Handler) postTestcaseDelete(c echo.Context) error { + problemID, err := strconv.Atoi(c.Param("problemID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid problem_id") + } + testcaseID, err := strconv.Atoi(c.Param("testcaseID")) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid testcase_id") + } + + // Verify the testcase belongs to this problem before deleting + testcase, err := h.q.GetTestcaseByID(c.Request().Context(), int32(testcaseID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return echo.NewHTTPError(http.StatusNotFound) + } + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + if testcase.ProblemID != int32(problemID) { + return echo.NewHTTPError(http.StatusNotFound) + } + + err = h.q.DeleteTestcase(c.Request().Context(), int32(testcaseID)) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + return c.Redirect(http.StatusSeeOther, h.conf.BasePath+"admin/problems/"+strconv.Itoa(problemID)+"/testcases") +} diff --git a/backend/admin/templates/problem_edit.html b/backend/admin/templates/problem_edit.html index cc700f4..722473b 100644 --- a/backend/admin/templates/problem_edit.html +++ b/backend/admin/templates/problem_edit.html @@ -33,4 +33,10 @@ +
+ View Testcases +
+
+ Add New Testcase +
{{ end }} diff --git a/backend/admin/templates/testcase_edit.html b/backend/admin/templates/testcase_edit.html new file mode 100644 index 0000000..65e181e --- /dev/null +++ b/backend/admin/templates/testcase_edit.html @@ -0,0 +1,36 @@ +{{ template "base.html" . }} + +{{ define "breadcrumb" }} +Dashboard | Problems | {{ .Problem.Title }} | Testcases +{{ end }} + +{{ define "content" }} +

Edit Testcase for {{ .Problem.Title }}

+
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+
+{{ end }} diff --git a/backend/admin/templates/testcase_new.html b/backend/admin/templates/testcase_new.html new file mode 100644 index 0000000..828406a --- /dev/null +++ b/backend/admin/templates/testcase_new.html @@ -0,0 +1,27 @@ +{{ template "base.html" . }} + +{{ define "breadcrumb" }} +Dashboard | Problems | {{ .Problem.Title }} | Testcases +{{ end }} + +{{ define "content" }} +

New Testcase for {{ .Problem.Title }}

+
+
+ + + +
+
+ + +
+
+ + +
+
+ +
+
+{{ end }} diff --git a/backend/admin/templates/testcases.html b/backend/admin/templates/testcases.html new file mode 100644 index 0000000..c27a45f --- /dev/null +++ b/backend/admin/templates/testcases.html @@ -0,0 +1,26 @@ +{{ template "base.html" . }} + +{{ define "breadcrumb" }} +Dashboard | Problems | {{ .Problem.Title }} +{{ end }} + +{{ define "content" }} +

Testcases for {{ .Problem.Title }}

+
+ Create New Testcase +
+{{ range .Testcases }} +

{{ .TestcaseID }}

+
+ Edit +
+

Stdin

+
+
{{ .Stdin }}
+
+

Stdout

+
+
{{ .Stdout }}
+
+{{ end }} +{{ end }} -- cgit v1.2.3-70-g09d2