diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/api/generated.go | 151 | ||||
| -rw-r--r-- | backend/api/handler.go | 38 | ||||
| -rw-r--r-- | backend/api/handler_test.go | 166 | ||||
| -rw-r--r-- | backend/api/handler_wrapper.go | 10 | ||||
| -rw-r--r-- | backend/db/querier.go | 1 | ||||
| -rw-r--r-- | backend/db/query.sql.go | 39 | ||||
| -rw-r--r-- | backend/query.sql | 5 |
7 files changed, 376 insertions, 34 deletions
diff --git a/backend/api/generated.go b/backend/api/generated.go index a419123..aee6f3e 100644 --- a/backend/api/generated.go +++ b/backend/api/generated.go @@ -97,6 +97,16 @@ type RankingEntry struct { SubmittedAt int64 `json:"submitted_at"` } +// Submission defines model for Submission. +type Submission struct { + Code string `json:"code"` + CodeSize int `json:"code_size"` + CreatedAt int64 `json:"created_at"` + GameID int `json:"game_id"` + Status ExecutionStatus `json:"status"` + SubmissionID int `json:"submission_id"` +} + // Tournament defines model for Tournament. type Tournament struct { BracketSize int `json:"bracket_size"` @@ -177,6 +187,9 @@ type ServerInterface interface { // (GET /games/{game_id}/play/latest_state) GetGamePlayLatestState(ctx echo.Context, gameID int) error + // (GET /games/{game_id}/play/submissions) + GetGamePlaySubmissions(ctx echo.Context, gameID int) error + // (POST /games/{game_id}/play/submit) PostGamePlaySubmit(ctx echo.Context, gameID int) error @@ -261,6 +274,22 @@ func (w *ServerInterfaceWrapper) GetGamePlayLatestState(ctx echo.Context) error return err } +// GetGamePlaySubmissions converts echo context to params. +func (w *ServerInterfaceWrapper) GetGamePlaySubmissions(ctx echo.Context) error { + var err error + // ------------- Path parameter "game_id" ------------- + var gameID int + + err = runtime.BindStyledParameterWithOptions("simple", "game_id", ctx.Param("game_id"), &gameID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter game_id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetGamePlaySubmissions(ctx, gameID) + return err +} + // PostGamePlaySubmit converts echo context to params. func (w *ServerInterfaceWrapper) PostGamePlaySubmit(ctx echo.Context) error { var err error @@ -384,6 +413,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/games/:game_id", wrapper.GetGame) router.POST(baseURL+"/games/:game_id/play/code", wrapper.PostGamePlayCode) router.GET(baseURL+"/games/:game_id/play/latest_state", wrapper.GetGamePlayLatestState) + router.GET(baseURL+"/games/:game_id/play/submissions", wrapper.GetGamePlaySubmissions) router.POST(baseURL+"/games/:game_id/play/submit", wrapper.PostGamePlaySubmit) router.GET(baseURL+"/games/:game_id/watch/latest_states", wrapper.GetGameWatchLatestStates) router.GET(baseURL+"/games/:game_id/watch/ranking", wrapper.GetGameWatchRanking) @@ -566,6 +596,52 @@ func (response GetGamePlayLatestState404JSONResponse) VisitGetGamePlayLatestStat return json.NewEncoder(w).Encode(response) } +type GetGamePlaySubmissionsRequestObject struct { + GameID int `json:"game_id"` +} + +type GetGamePlaySubmissionsResponseObject interface { + VisitGetGamePlaySubmissionsResponse(w http.ResponseWriter) error +} + +type GetGamePlaySubmissions200JSONResponse struct { + Submissions []Submission `json:"submissions"` +} + +func (response GetGamePlaySubmissions200JSONResponse) VisitGetGamePlaySubmissionsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetGamePlaySubmissions401JSONResponse Error + +func (response GetGamePlaySubmissions401JSONResponse) VisitGetGamePlaySubmissionsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type GetGamePlaySubmissions403JSONResponse Error + +func (response GetGamePlaySubmissions403JSONResponse) VisitGetGamePlaySubmissionsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetGamePlaySubmissions404JSONResponse Error + +func (response GetGamePlaySubmissions404JSONResponse) VisitGetGamePlaySubmissionsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + type PostGamePlaySubmitRequestObject struct { GameID int `json:"game_id"` Body *PostGamePlaySubmitJSONRequestBody @@ -842,6 +918,9 @@ type StrictServerInterface interface { // (GET /games/{game_id}/play/latest_state) GetGamePlayLatestState(ctx context.Context, request GetGamePlayLatestStateRequestObject) (GetGamePlayLatestStateResponseObject, error) + // (GET /games/{game_id}/play/submissions) + GetGamePlaySubmissions(ctx context.Context, request GetGamePlaySubmissionsRequestObject) (GetGamePlaySubmissionsResponseObject, error) + // (POST /games/{game_id}/play/submit) PostGamePlaySubmit(ctx context.Context, request PostGamePlaySubmitRequestObject) (PostGamePlaySubmitResponseObject, error) @@ -980,6 +1059,31 @@ func (sh *strictHandler) GetGamePlayLatestState(ctx echo.Context, gameID int) er return nil } +// GetGamePlaySubmissions operation middleware +func (sh *strictHandler) GetGamePlaySubmissions(ctx echo.Context, gameID int) error { + var request GetGamePlaySubmissionsRequestObject + + request.GameID = gameID + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.GetGamePlaySubmissions(ctx.Request().Context(), request.(GetGamePlaySubmissionsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetGamePlaySubmissions") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(GetGamePlaySubmissionsResponseObject); ok { + return validResponse.VisitGetGamePlaySubmissionsResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + // PostGamePlaySubmit operation middleware func (sh *strictHandler) PostGamePlaySubmit(ctx echo.Context, gameID int) error { var request PostGamePlaySubmitRequestObject @@ -1164,29 +1268,30 @@ func (sh *strictHandler) GetTournament(ctx echo.Context, tournamentID int) error // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xaTW/bOBP+KwLf96jaThPswbfsIigKpICxTbGHohAoaWKzlUgth4rrGv7vC5L6FiXL", - "SbqLpL454nA4H88zw4/sSSTSTHDgCslyTzDaQErNzxsphdQ/MikykIqB+ZwCIl2D/ql2GZAlQSUZX5PD", - "wScS/s6ZhJgsP1eCX/xSUIRfIVLk4JOb7xDlign+UVGVG73A81RP44ID8YnMOddafYJ5FAEi8clWCr4O", - "KMctSOITxVIQuSK+8YElEIAx2UzWg9XfjCuQnCbFh9qi0nSfvKMp9J2NGWYJ3QW8GO1Ni3NJtR8BQiR4", - "jA0hvegapJZa0xQCFo8M2s978n8J92RJ/jev0zIvcjLXJt5puYNPGAZZHiYsaugMhUiAcj2cUsYDbTlI", - "YxJTkOIx/Z/QGlSoo1LSnf47kyJMID02fVWIHXyCikoFcUCVw2WffH+zFm/qr79d9bBTBqwZnabTfjsz", - "jjzUZnei4YJjFdkGDi8eLvTUPFHMTnXC5pYqQKXnayQ7EBQCqgAjISHAPEyZquPC8yShYQJkqWQO/qQ4", - "aazHbiiaRSbotfkpWDeW0C5Ju0kylpTr+oOeVsu5Ir+qsdVhHmAkWaaXd3qbUL7Oizo0AZW3pXiN50FC", - "Ik2zBILBQCumkgn1r7FMOcdvudVwor3oSKBuG26XSM02mVawZffKidE/Kf/G+PqGK7nrB7p0cwA1tZqC", - "BROLSIVGR3w7PHhEfShsqcHXQdxgGO9ELnXJ4MpBVUmjb6ACZD8GLD/aDoArWWibVHJrc2x2HNU3pSra", - "PErlBz3TpZLnaSBFPtiuVKVigCOdbLTle7W5FdfW6nXAaj/HszYAYgQYIHOOUzHbccpM9K3mcZtsnHs2", - "jTZ9hkG4A3fztuC+mMq0QjwYYZwVeXuaxrejGgWyTm1ujJr0HsWWyfhghLaMc5CBTsPJKKw0l6Y0DK5i", - "70rqpwItJ+4CWSR4kFG1cY9iQOOUcXe2ExpCMqkAj4TCDg7Y5wC2jU01p0fZyuTSvn6wtFrG74VZ0HZE", - "cp2EVEmB6JU7bm8LoXe9ek988gASDWLIYnY5W2ijRQacZowsyeVsMVvoNFG1MTGfa/pYHoGp1TohZov3", - "PiZL8g7MpksXDgmYCY5W+O1iYZsaV0WNp1mWsMjMnH9Fi1gLeDdlp1dac2TolVfHThYHwtfa45C7DXh6", - "JqDyNhQ9c+6BGOKZXuRqcXGSY6MbO3MKcphwbU5aHkMv5zRXGyHZj2r9y39z/XshQxbHwGda7uAXeJjv", - "i6p6OIYMgyVJU1DmCPR5TzQBDb50DzJMaRwz6pRZ8tWO9KrOl2eH3DSgOYB1xtVTcKUXv/r5i+v4I8gH", - "kF5EORfKu2c89lSdFog9CShyGcEQ3Oe6Os/LvXom0IH8lbAn0VVCd3/Ys9nPpIAx/XcR756A/oEzluug", - "6YZ62+iDm5pnPrxSPiTm9iXA8uplrCFoVtjbGntT80L6Q+XbWKy7t1BdAlkl52bxS5HDXolMaxcfrey5", - "YZwbxmvlxJaqaNPqGEdPl3/pKY2egS+qaZhfNI7NtQdNVi2Jk7pJn0iO9nI+5P5qXJL2XWESi4o3iJdC", - "oIZrk66CWk8sx66ESuVnvrxWviRiba97h3det0bkubZFGUXcChk7759Puxrm5eVZofEJ26hHOvO0J5uX", - "zqoKQSJXRyFk//3lZW9hrcPp6AH+A5AzxJ454vV7Gc73rSfc0cv1xgv6lGbefRv+z1q6ar38T3tBH3lj", - "PHfv19m9D4d/AgAA///ukgFcEykAAA==", + "H4sIAAAAAAAC/+xaTW/bOBP+KwLf96jaThPswbfsIigKpICxSbGHohAoaWKzlUgth4rrGv7vC5L6tChZ", + "TtJdOPHNlsjhfDzPzJDUlkQizQQHrpDMtwSjFaTU/LyRUkj9I5MiA6kYmMcpINIl6J9qkwGZE1SS8SXZ", + "7Xwi4e+cSYjJ/Es18KtfDhThN4gU2fnk5gdEuWKC3ymqciMXeJ7qaVxwID6ROedaqk8wjyJAJD5ZS8GX", + "AeW4Bkl8olgKIlfENzawBAIwKpvJ+mX1n3EFktOkeFBrVKrukw80ha6xMcMsoZuAF2870+JcUm1HgBAJ", + "HmNjkF50CVKPWtIUAhYPvLSPt+T/Eh7InPxvWodlWsRkqlW81+N2PmEYZHmYsKghMxQiAcr165QyHmjN", + "QRqVmIIUD8n/jFahQhyVkm70/0yKMIH00PRFMWznE1RUKogDqhwm++THu6V4Vz/97aqDndJhTe80jfbb", + "kXHEoVZ7zxsuOFaebeDw4vFCT80TxexUJ2xuqQJUer5GsgNBIaAKMBISAszDlKnaLzxPEhomQOZK5uCP", + "8pPGeuyGollkhFwbn4J1QwHdJ+l+kIwm5bp+r6XVci7PL2ps7TEPMJIs08s7rU0oX+ZFHhqByttyeI3n", + "XkIiTbMEgl5HK6aSEfmvsUw5x2+Z1TCiveiAo24bZpdIzVaZFrBmD8qJ0T8p/8748oYruek6ujSzBzW1", + "mIIFI5NIhUaHf/d48IT8UOhSg28Pcb1uvNPjEAtUuT3RsVy/CJD97LEnkkCPs+ZARXgiPQsvGOt6ZO+5", + "sT3eb6Tdgtu15ZVWLXtdLr4XudRZmStHNpQ0+g5qwJkHKy5wJQtpo6parY4lgKPApVRFqyeJ/KRnukTy", + "PA2kyHs7AlWJGBep9vhO+Wv5tbV67bDazuGo9eQJBOiBa45j08KeUWaibyUP62T93NFpkEUMg3AD7v7I", + "5o+LscmsGB4MJDU75P1xEt8PShTI9spf460J70FsmYj3emjNOAcZ6DAcjcJKcqlKQ+HK966gfi7QcmSj", + "zSLBg4yqlfstBjROGXdHO6EhJKNq3IAr7Mse/RzAtr6p5nQoW6lc6td1lhbL+IMwC9qmg1wnIVVSIHrl", + "psZbQ+hdLz4SnzyCtKWNzCaXk5lWWmTAacbInFxOZpOZDhNVK+PzqaaP5RGYXK0DYrrojzGZkw9g+lqd", + "OCRgJjjawe9nM1stuSpyPM2yhEVm5vQbWsRawLspOz7Tml1ZJ706NgvY475WG0nuV+DpmYDKW1H0zNYS", + "YognepGr2cVRhg0WZ7PRdKhwbTazHkMv5zRXKyHZz2r9y39z/QchQxbHwCd63M4v8DDdFll1dwgZBkuS", + "pqDMLvPLlmgCGnzpGmSY0mgp6pBZ8tWGdLLO1xeH3DigOYB1xtVzcKUXv/r1i2v/I8hHkF5EORfKe2A8", + "9lQdFog9CShyGUEf3Kc6O0/LTUAm0IH8hbCb/UVCN3/YFvlXUsCo/ruIN89Af8+uxrWXd0O9rfTOTc0z", + "H14pHxJzwBVgebo1VBA0K+yBmD0MO5H6UNk25Ov9g77OVto8PReLN0WO+vwEx3DjrjH8VLjRtnBU0944", + "YzvUujfFn7nz9rijxrVad3bsudk6N1uvlRNrqqJVq9s6WFL+0lMa/RaeVMNlftE4NkeGNFm0RhzViXWJ", + "5GjNzvXlrXFJ2mvPUSwqrkhPhUAN00Z1ZK0b4EM9WSn8zJfXypdELO1VSX/ndWuGvFRblFHEtZCx8+7m", + "uGsVXh48FxKf0UY90ZjnXXeeOqsqBIlcHYSQ/TrvtFtYa3A6ePj1CcgZYi/s8fquGafb1ucPgxdTja9P", + "xhTz/e8q/rOSrlpfzYz7+mTgfv5cvV9n9d7t/gkAAP//fXodcbItAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/backend/api/handler.go b/backend/api/handler.go index 8d0d9be..57ae973 100644 --- a/backend/api/handler.go +++ b/backend/api/handler.go @@ -376,6 +376,44 @@ func (h *Handler) GetGameWatchRanking(ctx context.Context, request GetGameWatchR }, nil } +func (h *Handler) GetGamePlaySubmissions(ctx context.Context, request GetGamePlaySubmissionsRequestObject, user *db.User) (GetGamePlaySubmissionsResponseObject, error) { + gameID := request.GameID + + _, err := h.q.GetGameByID(ctx, int32(gameID)) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return GetGamePlaySubmissions404JSONResponse{ + Message: "Game not found", + }, nil + } + return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + rows, err := h.q.GetSubmissionsByGameIDAndUserID(ctx, db.GetSubmissionsByGameIDAndUserIDParams{ + GameID: int32(gameID), + UserID: user.UserID, + }) + if err != nil { + return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + submissions := make([]Submission, len(rows)) + for i, row := range rows { + submissions[i] = Submission{ + SubmissionID: int(row.SubmissionID), + GameID: int(row.GameID), + Code: row.Code, + CodeSize: int(row.CodeSize), + Status: ExecutionStatus(row.Status), + CreatedAt: row.CreatedAt.Time.Unix(), + } + } + + return GetGamePlaySubmissions200JSONResponse{ + Submissions: submissions, + }, nil +} + func (h *Handler) PostGamePlayCode(ctx context.Context, request PostGamePlayCodeRequestObject, user *db.User) (PostGamePlayCodeResponseObject, error) { gameID := request.GameID diff --git a/backend/api/handler_test.go b/backend/api/handler_test.go index 2415710..a28d605 100644 --- a/backend/api/handler_test.go +++ b/backend/api/handler_test.go @@ -16,17 +16,18 @@ import ( // mockQuerier implements db.Querier for testing. type mockQuerier struct { db.Querier - getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) - listMainPlayersFunc func(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error) - listPublicGamesFunc func(ctx context.Context) ([]db.ListPublicGamesRow, error) - deleteSessionFunc func(ctx context.Context, sessionID string) error - getLatestStateFunc func(ctx context.Context, arg db.GetLatestStateParams) (db.GetLatestStateRow, error) - updateCodeFunc func(ctx context.Context, arg db.UpdateCodeParams) error - getRankingFunc func(ctx context.Context, gameID int32) ([]db.GetRankingRow, error) - getLatestStatesFunc func(ctx context.Context, gameID int32) ([]db.GetLatestStatesOfMainPlayersRow, error) - getTournamentByIDFunc func(ctx context.Context, tournamentID int32) (db.Tournament, error) - listTournamentEntriesFunc func(ctx context.Context, tournamentID int32) ([]db.ListTournamentEntriesRow, error) - listTournamentMatchesFunc func(ctx context.Context, tournamentID int32) ([]db.TournamentMatch, error) + getGameByIDFunc func(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) + listMainPlayersFunc func(ctx context.Context, gameIDs []int32) ([]db.ListMainPlayersRow, error) + listPublicGamesFunc func(ctx context.Context) ([]db.ListPublicGamesRow, error) + deleteSessionFunc func(ctx context.Context, sessionID string) error + getLatestStateFunc func(ctx context.Context, arg db.GetLatestStateParams) (db.GetLatestStateRow, error) + updateCodeFunc func(ctx context.Context, arg db.UpdateCodeParams) error + getRankingFunc func(ctx context.Context, gameID int32) ([]db.GetRankingRow, error) + getLatestStatesFunc func(ctx context.Context, gameID int32) ([]db.GetLatestStatesOfMainPlayersRow, error) + getTournamentByIDFunc func(ctx context.Context, tournamentID int32) (db.Tournament, error) + listTournamentEntriesFunc func(ctx context.Context, tournamentID int32) ([]db.ListTournamentEntriesRow, error) + listTournamentMatchesFunc func(ctx context.Context, tournamentID int32) ([]db.TournamentMatch, error) + getSubmissionsByGameIDAndUserIDFunc func(ctx context.Context, arg db.GetSubmissionsByGameIDAndUserIDParams) ([]db.Submission, error) } func (m *mockQuerier) GetGameByID(ctx context.Context, gameID int32) (db.GetGameByIDRow, error) { @@ -85,6 +86,13 @@ func (m *mockQuerier) GetLatestStatesOfMainPlayers(ctx context.Context, gameID i return nil, nil } +func (m *mockQuerier) GetSubmissionsByGameIDAndUserID(ctx context.Context, arg db.GetSubmissionsByGameIDAndUserIDParams) ([]db.Submission, error) { + if m.getSubmissionsByGameIDAndUserIDFunc != nil { + return m.getSubmissionsByGameIDAndUserIDFunc(ctx, arg) + } + return nil, nil +} + func (m *mockQuerier) GetTournamentByID(ctx context.Context, tournamentID int32) (db.Tournament, error) { if m.getTournamentByIDFunc != nil { return m.getTournamentByIDFunc(ctx, tournamentID) @@ -137,6 +145,142 @@ func (m *mockAuthenticator) Login(_ context.Context, _, _ string) (int, error) { return m.loginResult, m.loginErr } +func TestGetGamePlaySubmissions_GameNotFound(t *testing.T) { + h := Handler{ + q: &mockQuerier{}, + txm: &mockTxManager{}, + hub: &mockGameHub{}, + auth: &mockAuthenticator{}, + conf: &config.Config{}, + } + user := &db.User{UserID: 1} + resp, err := h.GetGamePlaySubmissions(context.Background(), GetGamePlaySubmissionsRequestObject{ + GameID: 999, + }, user) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if _, ok := resp.(GetGamePlaySubmissions404JSONResponse); !ok { + t.Errorf("expected 404 response, got %T", resp) + } +} + +func TestGetGamePlaySubmissions_Empty(t *testing.T) { + h := Handler{ + q: &mockQuerier{ + getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) { + return db.GetGameByIDRow{ + GameID: 1, + Language: "php", + }, nil + }, + }, + txm: &mockTxManager{}, + hub: &mockGameHub{}, + auth: &mockAuthenticator{}, + conf: &config.Config{}, + } + user := &db.User{UserID: 1} + resp, err := h.GetGamePlaySubmissions(context.Background(), GetGamePlaySubmissionsRequestObject{ + GameID: 1, + }, user) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + okResp, ok := resp.(GetGamePlaySubmissions200JSONResponse) + if !ok { + t.Fatalf("expected 200 response, got %T", resp) + } + if len(okResp.Submissions) != 0 { + t.Errorf("expected 0 submissions, got %d", len(okResp.Submissions)) + } +} + +func TestGetGamePlaySubmissions_WithSubmissions(t *testing.T) { + now := time.Now() + h := Handler{ + q: &mockQuerier{ + getGameByIDFunc: func(_ context.Context, _ int32) (db.GetGameByIDRow, error) { + return db.GetGameByIDRow{ + GameID: 1, + Language: "php", + }, nil + }, + getSubmissionsByGameIDAndUserIDFunc: func(_ context.Context, arg db.GetSubmissionsByGameIDAndUserIDParams) ([]db.Submission, error) { + if arg.GameID != 1 || arg.UserID != 42 { + t.Errorf("unexpected query params: game_id=%d, user_id=%d", arg.GameID, arg.UserID) + } + return []db.Submission{ + { + SubmissionID: 10, + GameID: 1, + UserID: 42, + Code: "<?php echo 1;", + CodeSize: 14, + Status: "success", + CreatedAt: pgtype.Timestamp{Time: now, Valid: true}, + }, + { + SubmissionID: 9, + GameID: 1, + UserID: 42, + Code: "<?php echo 'hello';", + CodeSize: 20, + Status: "wrong_answer", + CreatedAt: pgtype.Timestamp{Time: now.Add(-5 * time.Minute), Valid: true}, + }, + }, nil + }, + }, + txm: &mockTxManager{}, + hub: &mockGameHub{}, + auth: &mockAuthenticator{}, + conf: &config.Config{}, + } + user := &db.User{UserID: 42} + resp, err := h.GetGamePlaySubmissions(context.Background(), GetGamePlaySubmissionsRequestObject{ + GameID: 1, + }, user) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + okResp, ok := resp.(GetGamePlaySubmissions200JSONResponse) + if !ok { + t.Fatalf("expected 200 response, got %T", resp) + } + if len(okResp.Submissions) != 2 { + t.Fatalf("expected 2 submissions, got %d", len(okResp.Submissions)) + } + + s0 := okResp.Submissions[0] + if s0.SubmissionID != 10 { + t.Errorf("expected submission_id 10, got %d", s0.SubmissionID) + } + if s0.GameID != 1 { + t.Errorf("expected game_id 1, got %d", s0.GameID) + } + if s0.Code != "<?php echo 1;" { + t.Errorf("expected code '<?php echo 1;', got %q", s0.Code) + } + if s0.CodeSize != 14 { + t.Errorf("expected code_size 14, got %d", s0.CodeSize) + } + if s0.Status != Success { + t.Errorf("expected status 'success', got %q", s0.Status) + } + if s0.CreatedAt != now.Unix() { + t.Errorf("expected created_at %d, got %d", now.Unix(), s0.CreatedAt) + } + + s1 := okResp.Submissions[1] + if s1.SubmissionID != 9 { + t.Errorf("expected submission_id 9, got %d", s1.SubmissionID) + } + if s1.Status != WrongAnswer { + t.Errorf("expected status 'wrong_answer', got %q", s1.Status) + } +} + func TestPostGamePlaySubmit_GameNotFound(t *testing.T) { h := Handler{ q: &mockQuerier{}, diff --git a/backend/api/handler_wrapper.go b/backend/api/handler_wrapper.go index 28b89e4..48a0eef 100644 --- a/backend/api/handler_wrapper.go +++ b/backend/api/handler_wrapper.go @@ -47,6 +47,16 @@ func (h *HandlerWrapper) GetGamePlayLatestState(ctx context.Context, request Get return h.impl.GetGamePlayLatestState(ctx, request, user) } +func (h *HandlerWrapper) GetGamePlaySubmissions(ctx context.Context, request GetGamePlaySubmissionsRequestObject) (GetGamePlaySubmissionsResponseObject, error) { + user, ok := GetUserFromContext(ctx) + if !ok { + return GetGamePlaySubmissions401JSONResponse{ + Message: "Unauthorized", + }, nil + } + return h.impl.GetGamePlaySubmissions(ctx, request, user) +} + func (h *HandlerWrapper) GetGameWatchLatestStates(ctx context.Context, request GetGameWatchLatestStatesRequestObject) (GetGameWatchLatestStatesResponseObject, error) { user, ok := GetUserFromContext(ctx) if !ok { diff --git a/backend/db/querier.go b/backend/db/querier.go index 220a86c..692ea0d 100644 --- a/backend/db/querier.go +++ b/backend/db/querier.go @@ -37,6 +37,7 @@ type Querier interface { GetRanking(ctx context.Context, gameID int32) ([]GetRankingRow, error) GetSubmissionByID(ctx context.Context, submissionID int32) (Submission, error) GetSubmissionsByGameID(ctx context.Context, gameID int32) ([]Submission, error) + GetSubmissionsByGameIDAndUserID(ctx context.Context, arg GetSubmissionsByGameIDAndUserIDParams) ([]Submission, error) GetTestcaseByID(ctx context.Context, testcaseID int32) (Testcase, error) GetTestcaseResultsBySubmissionID(ctx context.Context, submissionID int32) ([]TestcaseResult, error) GetTournamentByID(ctx context.Context, tournamentID int32) (Tournament, error) diff --git a/backend/db/query.sql.go b/backend/db/query.sql.go index 02f1abf..80cbc16 100644 --- a/backend/db/query.sql.go +++ b/backend/db/query.sql.go @@ -705,6 +705,45 @@ func (q *Queries) GetSubmissionsByGameID(ctx context.Context, gameID int32) ([]S return items, nil } +const getSubmissionsByGameIDAndUserID = `-- name: GetSubmissionsByGameIDAndUserID :many +SELECT submission_id, game_id, user_id, code, code_size, status, created_at FROM submissions +WHERE game_id = $1 AND user_id = $2 +ORDER BY created_at DESC +` + +type GetSubmissionsByGameIDAndUserIDParams struct { + GameID int32 + UserID int32 +} + +func (q *Queries) GetSubmissionsByGameIDAndUserID(ctx context.Context, arg GetSubmissionsByGameIDAndUserIDParams) ([]Submission, error) { + rows, err := q.db.Query(ctx, getSubmissionsByGameIDAndUserID, arg.GameID, arg.UserID) + 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 diff --git a/backend/query.sql b/backend/query.sql index 45ac46f..c791c27 100644 --- a/backend/query.sql +++ b/backend/query.sql @@ -259,6 +259,11 @@ WHERE testcase_id = $1; DELETE FROM testcases WHERE testcase_id = $1; +-- name: GetSubmissionsByGameIDAndUserID :many +SELECT * FROM submissions +WHERE game_id = $1 AND user_id = $2 +ORDER BY created_at DESC; + -- name: GetSubmissionsByGameID :many SELECT * FROM submissions |
