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"`
|
||||
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
|
||||
DeleteContest(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
|
||||
ListParticipants(c *fiber.Ctx, params testerv1.ListParticipantsParams) error
|
||||
UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateParticipantParams) 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
|
||||
CreateProblem(c *fiber.Ctx) error
|
||||
DeleteProblem(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
|
||||
UpdateContest(c *fiber.Ctx, id int32) error
|
||||
UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateParticipantParams) error
|
||||
ListSolutions(c *fiber.Ctx, params testerv1.ListSolutionsParams) 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"
|
||||
testerv1 "git.sch9.ru/new_gate/ms-tester/proto/tester/v1"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"io"
|
||||
)
|
||||
|
||||
type TesterHandlers struct {
|
||||
|
@ -332,3 +333,112 @@ func (h *TesterHandlers) UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateP
|
|||
|
||||
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)
|
||||
UpdateContest(ctx context.Context, id int32, contestUpdate models.ContestUpdate) 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ContestRepository struct {
|
||||
|
@ -247,3 +249,145 @@ func (r *ContestRepository) UpdateParticipant(ctx context.Context, id int32, par
|
|||
|
||||
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)
|
||||
UpdateContest(ctx context.Context, id int32, contestUpdate models.ContestUpdate) 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 {
|
||||
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,
|
||||
solution varchar(1048576) NOT NULL,
|
||||
state integer NOT NULL DEFAULT 1,
|
||||
score integer NOT NULL,
|
||||
score integer NOT NULL DEFAULT 0,
|
||||
penalty integer NOT NULL,
|
||||
total_score integer NOT NULL,
|
||||
total_score integer NOT NULL DEFAULT 0,
|
||||
language integer NOT NULL,
|
||||
updated_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