feat(tester): add solution endpoints
add CreateSolution&GetSolution&ListSolutions endpoints
This commit is contained in:
parent
af6e0b89f6
commit
ef696d2836
9 changed files with 316 additions and 8 deletions
|
@ -15,3 +15,36 @@ type Solution struct {
|
||||||
UpdatedAt time.Time `db:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SolutionCreation struct {
|
||||||
|
Solution string
|
||||||
|
TaskId int32
|
||||||
|
ParticipantId int32
|
||||||
|
Language int32
|
||||||
|
Penalty int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type SolutionsListItem struct {
|
||||||
|
Id int32 `db:"id"`
|
||||||
|
TaskId int32 `db:"task_id"`
|
||||||
|
ContestId int32 `db:"contest_id"`
|
||||||
|
ParticipantId int32 `db:"participant_id"`
|
||||||
|
State int32 `db:"state"`
|
||||||
|
Score int32 `db:"score"`
|
||||||
|
Penalty int32 `db:"penalty"`
|
||||||
|
TotalScore int32 `db:"total_score"`
|
||||||
|
Language int32 `db:"language"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SolutionsFilter struct {
|
||||||
|
Page int32
|
||||||
|
PageSize int32
|
||||||
|
ContestId *int32
|
||||||
|
ParticipantId *int32
|
||||||
|
TaskId *int32
|
||||||
|
Language *int32
|
||||||
|
State *int32
|
||||||
|
Order *int32
|
||||||
|
}
|
||||||
|
|
|
@ -10,16 +10,19 @@ type Handlers interface {
|
||||||
CreateContest(c *fiber.Ctx) error
|
CreateContest(c *fiber.Ctx) error
|
||||||
DeleteContest(c *fiber.Ctx, id int32) error
|
DeleteContest(c *fiber.Ctx, id int32) error
|
||||||
GetContest(c *fiber.Ctx, id int32) error
|
GetContest(c *fiber.Ctx, id int32) error
|
||||||
|
UpdateContest(c *fiber.Ctx, id int32) error
|
||||||
DeleteParticipant(c *fiber.Ctx, params testerv1.DeleteParticipantParams) error
|
DeleteParticipant(c *fiber.Ctx, params testerv1.DeleteParticipantParams) error
|
||||||
|
ListParticipants(c *fiber.Ctx, params testerv1.ListParticipantsParams) error
|
||||||
|
UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateParticipantParams) error
|
||||||
AddParticipant(c *fiber.Ctx, params testerv1.AddParticipantParams) error
|
AddParticipant(c *fiber.Ctx, params testerv1.AddParticipantParams) error
|
||||||
DeleteTask(c *fiber.Ctx, params testerv1.DeleteTaskParams) error
|
|
||||||
AddTask(c *fiber.Ctx, params testerv1.AddTaskParams) error
|
|
||||||
ListProblems(c *fiber.Ctx, params testerv1.ListProblemsParams) error
|
ListProblems(c *fiber.Ctx, params testerv1.ListProblemsParams) error
|
||||||
CreateProblem(c *fiber.Ctx) error
|
CreateProblem(c *fiber.Ctx) error
|
||||||
DeleteProblem(c *fiber.Ctx, id int32) error
|
DeleteProblem(c *fiber.Ctx, id int32) error
|
||||||
GetProblem(c *fiber.Ctx, id int32) error
|
GetProblem(c *fiber.Ctx, id int32) error
|
||||||
ListParticipants(c *fiber.Ctx, params testerv1.ListParticipantsParams) error
|
|
||||||
UpdateProblem(c *fiber.Ctx, id int32) error
|
UpdateProblem(c *fiber.Ctx, id int32) error
|
||||||
UpdateContest(c *fiber.Ctx, id int32) error
|
ListSolutions(c *fiber.Ctx, params testerv1.ListSolutionsParams) error
|
||||||
UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateParticipantParams) error
|
CreateSolution(c *fiber.Ctx, params testerv1.CreateSolutionParams) error
|
||||||
|
GetSolution(c *fiber.Ctx, id int32) error
|
||||||
|
DeleteTask(c *fiber.Ctx, params testerv1.DeleteTaskParams) error
|
||||||
|
AddTask(c *fiber.Ctx, params testerv1.AddTaskParams) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"git.sch9.ru/new_gate/ms-tester/pkg"
|
"git.sch9.ru/new_gate/ms-tester/pkg"
|
||||||
testerv1 "git.sch9.ru/new_gate/ms-tester/proto/tester/v1"
|
testerv1 "git.sch9.ru/new_gate/ms-tester/proto/tester/v1"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TesterHandlers struct {
|
type TesterHandlers struct {
|
||||||
|
@ -332,3 +333,112 @@ func (h *TesterHandlers) UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateP
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *TesterHandlers) ListSolutions(c *fiber.Ctx, params testerv1.ListSolutionsParams) error {
|
||||||
|
list, total, err := h.contestsUC.ListSolutions(c.Context(), models.SolutionsFilter{
|
||||||
|
ContestId: params.ContestId,
|
||||||
|
Page: params.Page,
|
||||||
|
PageSize: params.PageSize,
|
||||||
|
ParticipantId: params.ParticipantId,
|
||||||
|
TaskId: params.TaskId,
|
||||||
|
Language: params.Language,
|
||||||
|
Order: params.Order,
|
||||||
|
State: params.State,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(pkg.ToREST(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := testerv1.ListSolutionsResponse{
|
||||||
|
Solutions: make([]testerv1.SolutionListItem, len(list)),
|
||||||
|
Page: params.Page,
|
||||||
|
MaxPage: func() int32 {
|
||||||
|
if total%params.PageSize == 0 {
|
||||||
|
return total / params.PageSize
|
||||||
|
}
|
||||||
|
return total/params.PageSize + 1
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, solution := range list {
|
||||||
|
resp.Solutions[i] = testerv1.SolutionListItem{
|
||||||
|
Id: solution.Id,
|
||||||
|
TaskId: solution.TaskId,
|
||||||
|
ContestId: solution.ContestId,
|
||||||
|
ParticipantId: solution.ParticipantId,
|
||||||
|
Language: solution.Language,
|
||||||
|
Penalty: solution.Penalty,
|
||||||
|
Score: solution.Score,
|
||||||
|
State: solution.State,
|
||||||
|
TotalScore: solution.TotalScore,
|
||||||
|
CreatedAt: solution.CreatedAt,
|
||||||
|
UpdatedAt: solution.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxSolutionSize int64 = 10 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *TesterHandlers) CreateSolution(c *fiber.Ctx, params testerv1.CreateSolutionParams) error {
|
||||||
|
s, err := c.FormFile("solution")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Size == 0 || s.Size > maxSolutionSize {
|
||||||
|
return c.SendStatus(fiber.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := s.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := h.contestsUC.CreateSolution(c.Context(), &models.SolutionCreation{
|
||||||
|
TaskId: params.TaskId,
|
||||||
|
ParticipantId: 1,
|
||||||
|
Language: params.Language,
|
||||||
|
Penalty: 0,
|
||||||
|
Solution: string(b),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(pkg.ToREST(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(testerv1.CreateSolutionResponse{
|
||||||
|
Id: id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TesterHandlers) GetSolution(c *fiber.Ctx, id int32) error {
|
||||||
|
solution, err := h.contestsUC.ReadSolution(c.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
return c.SendStatus(pkg.ToREST(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(
|
||||||
|
testerv1.GetSolutionResponse{Solution: testerv1.Solution{
|
||||||
|
Id: solution.Id,
|
||||||
|
TaskId: solution.TaskId,
|
||||||
|
ParticipantId: solution.ParticipantId,
|
||||||
|
Solution: solution.Solution,
|
||||||
|
State: solution.State,
|
||||||
|
Score: solution.Score,
|
||||||
|
Penalty: solution.Penalty,
|
||||||
|
TotalScore: solution.TotalScore,
|
||||||
|
Language: solution.Language,
|
||||||
|
CreatedAt: solution.CreatedAt,
|
||||||
|
UpdatedAt: solution.UpdatedAt,
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -44,4 +44,7 @@ type ContestRepository interface {
|
||||||
ListParticipants(ctx context.Context, contestId int32, page int32, pageSize int32) ([]*models.ParticipantsListItem, int32, error)
|
ListParticipants(ctx context.Context, contestId int32, page int32, pageSize int32) ([]*models.ParticipantsListItem, int32, error)
|
||||||
UpdateContest(ctx context.Context, id int32, contestUpdate models.ContestUpdate) error
|
UpdateContest(ctx context.Context, id int32, contestUpdate models.ContestUpdate) error
|
||||||
UpdateParticipant(ctx context.Context, id int32, participantUpdate models.ParticipantUpdate) error
|
UpdateParticipant(ctx context.Context, id int32, participantUpdate models.ParticipantUpdate) error
|
||||||
|
ReadSolution(ctx context.Context, id int32) (*models.Solution, error)
|
||||||
|
CreateSolution(ctx context.Context, creation *models.SolutionCreation) (int32, error)
|
||||||
|
ListSolutions(ctx context.Context, filters models.SolutionsFilter) ([]*models.SolutionsListItem, int32, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContestRepository struct {
|
type ContestRepository struct {
|
||||||
|
@ -247,3 +249,145 @@ func (r *ContestRepository) UpdateParticipant(ctx context.Context, id int32, par
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
readSolutionQuery = "SELECT * FROM solutions WHERE id = ?"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *ContestRepository) ReadSolution(ctx context.Context, id int32) (*models.Solution, error) {
|
||||||
|
const op = "ContestRepository.ReadSolution"
|
||||||
|
|
||||||
|
query := r.db.Rebind(readSolutionQuery)
|
||||||
|
var solution models.Solution
|
||||||
|
err := r.db.GetContext(ctx, &solution, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, handlePgErr(err, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &solution, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
createSolutionQuery = `INSERT INTO solutions (task_id, participant_id, language, penalty, solution)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
RETURNING id`
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *ContestRepository) CreateSolution(ctx context.Context, creation *models.SolutionCreation) (int32, error) {
|
||||||
|
const op = "ContestRepository.CreateSolution"
|
||||||
|
|
||||||
|
query := r.db.Rebind(createSolutionQuery)
|
||||||
|
|
||||||
|
rows, err := r.db.QueryxContext(ctx,
|
||||||
|
query,
|
||||||
|
creation.TaskId,
|
||||||
|
creation.ParticipantId,
|
||||||
|
creation.Language,
|
||||||
|
creation.Penalty,
|
||||||
|
creation.Solution,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, handlePgErr(err, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
var id int32
|
||||||
|
rows.Next()
|
||||||
|
err = rows.Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, handlePgErr(err, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ContestRepository) ListSolutions(ctx context.Context, filters models.SolutionsFilter) ([]*models.SolutionsListItem, int32, error) {
|
||||||
|
const op = "ContestRepository.ListSolutions"
|
||||||
|
|
||||||
|
baseQuery := `
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.task_id,
|
||||||
|
t.contest_id,
|
||||||
|
s.participant_id,
|
||||||
|
s.state,
|
||||||
|
s.score,
|
||||||
|
s.penalty,
|
||||||
|
s.total_score,
|
||||||
|
s.language,
|
||||||
|
s.updated_at,
|
||||||
|
s.created_at
|
||||||
|
FROM solutions s
|
||||||
|
LEFT JOIN tasks t ON s.task_id = t.id
|
||||||
|
WHERE 1=1
|
||||||
|
`
|
||||||
|
|
||||||
|
var conditions []string
|
||||||
|
var args []interface{}
|
||||||
|
|
||||||
|
if filters.ContestId != nil {
|
||||||
|
conditions = append(conditions, "contest_id = ?")
|
||||||
|
args = append(args, *filters.ContestId)
|
||||||
|
}
|
||||||
|
if filters.ParticipantId != nil {
|
||||||
|
conditions = append(conditions, "participant_id = ?")
|
||||||
|
args = append(args, *filters.ParticipantId)
|
||||||
|
}
|
||||||
|
if filters.TaskId != nil {
|
||||||
|
conditions = append(conditions, "task_id = ?")
|
||||||
|
args = append(args, *filters.TaskId)
|
||||||
|
}
|
||||||
|
if filters.Language != nil {
|
||||||
|
conditions = append(conditions, "language = ?")
|
||||||
|
args = append(args, *filters.Language)
|
||||||
|
}
|
||||||
|
if filters.State != nil {
|
||||||
|
conditions = append(conditions, "state = ?")
|
||||||
|
args = append(args, *filters.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conditions) > 0 {
|
||||||
|
baseQuery += " AND " + strings.Join(conditions, " AND ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.Order != nil {
|
||||||
|
orderDirection := "ASC"
|
||||||
|
if *filters.Order < 0 {
|
||||||
|
orderDirection = "DESC"
|
||||||
|
}
|
||||||
|
baseQuery += fmt.Sprintf(" ORDER BY s.id %s", orderDirection)
|
||||||
|
}
|
||||||
|
|
||||||
|
countQuery := "SELECT COUNT(*) FROM (" + baseQuery + ") as count_table"
|
||||||
|
var totalCount int32
|
||||||
|
err := r.db.QueryRowxContext(ctx, r.db.Rebind(countQuery), args...).Scan(&totalCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, handlePgErr(err, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (filters.Page - 1) * filters.PageSize
|
||||||
|
baseQuery += " LIMIT ? OFFSET ?"
|
||||||
|
args = append(args, filters.PageSize, offset)
|
||||||
|
|
||||||
|
rows, err := r.db.QueryxContext(ctx, r.db.Rebind(baseQuery), args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, handlePgErr(err, op)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var solutions []*models.SolutionsListItem
|
||||||
|
for rows.Next() {
|
||||||
|
var solution models.SolutionsListItem
|
||||||
|
err = rows.StructScan(&solution)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, handlePgErr(err, op)
|
||||||
|
}
|
||||||
|
solutions = append(solutions, &solution)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, 0, handlePgErr(err, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
return solutions, totalCount, nil
|
||||||
|
}
|
||||||
|
|
|
@ -26,4 +26,7 @@ type ContestUseCase interface {
|
||||||
ListParticipants(ctx context.Context, contestId int32, page int32, pageSize int32) ([]*models.ParticipantsListItem, int32, error)
|
ListParticipants(ctx context.Context, contestId int32, page int32, pageSize int32) ([]*models.ParticipantsListItem, int32, error)
|
||||||
UpdateContest(ctx context.Context, id int32, contestUpdate models.ContestUpdate) error
|
UpdateContest(ctx context.Context, id int32, contestUpdate models.ContestUpdate) error
|
||||||
UpdateParticipant(ctx context.Context, id int32, participantUpdate models.ParticipantUpdate) error
|
UpdateParticipant(ctx context.Context, id int32, participantUpdate models.ParticipantUpdate) error
|
||||||
|
ReadSolution(ctx context.Context, id int32) (*models.Solution, error)
|
||||||
|
CreateSolution(ctx context.Context, creation *models.SolutionCreation) (int32, error)
|
||||||
|
ListSolutions(ctx context.Context, filters models.SolutionsFilter) ([]*models.SolutionsListItem, int32, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,3 +65,15 @@ func (uc *ContestUseCase) UpdateContest(ctx context.Context, id int32, contestUp
|
||||||
func (uc *ContestUseCase) UpdateParticipant(ctx context.Context, id int32, participantUpdate models.ParticipantUpdate) error {
|
func (uc *ContestUseCase) UpdateParticipant(ctx context.Context, id int32, participantUpdate models.ParticipantUpdate) error {
|
||||||
return uc.contestRepo.UpdateParticipant(ctx, id, participantUpdate)
|
return uc.contestRepo.UpdateParticipant(ctx, id, participantUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (uc *ContestUseCase) ReadSolution(ctx context.Context, id int32) (*models.Solution, error) {
|
||||||
|
return uc.contestRepo.ReadSolution(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *ContestUseCase) CreateSolution(ctx context.Context, creation *models.SolutionCreation) (int32, error) {
|
||||||
|
return uc.contestRepo.CreateSolution(ctx, creation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *ContestUseCase) ListSolutions(ctx context.Context, filters models.SolutionsFilter) ([]*models.SolutionsListItem, int32, error) {
|
||||||
|
return uc.contestRepo.ListSolutions(ctx, filters)
|
||||||
|
}
|
||||||
|
|
|
@ -105,9 +105,9 @@ CREATE TABLE IF NOT EXISTS solutions
|
||||||
participant_id integer REFERENCES participants (id) ON DELETE SET NULL,
|
participant_id integer REFERENCES participants (id) ON DELETE SET NULL,
|
||||||
solution varchar(1048576) NOT NULL,
|
solution varchar(1048576) NOT NULL,
|
||||||
state integer NOT NULL DEFAULT 1,
|
state integer NOT NULL DEFAULT 1,
|
||||||
score integer NOT NULL,
|
score integer NOT NULL DEFAULT 0,
|
||||||
penalty integer NOT NULL,
|
penalty integer NOT NULL,
|
||||||
total_score integer NOT NULL,
|
total_score integer NOT NULL DEFAULT 0,
|
||||||
language integer NOT NULL,
|
language integer NOT NULL,
|
||||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||||
created_at timestamptz NOT NULL DEFAULT now(),
|
created_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
|
2
proto
2
proto
|
@ -1 +1 @@
|
||||||
Subproject commit fb65ba8ce2220e7e47990f0f52214126839b3d78
|
Subproject commit 6a83a9832d5fed659f72e72c90b3954a9518e0ba
|
Loading…
Add table
Reference in a new issue