feat(tester): add solution endpoints

add CreateSolution&GetSolution&ListSolutions endpoints
This commit is contained in:
Vyacheslav1557 2025-03-27 00:04:52 +05:00
parent af6e0b89f6
commit ef696d2836
9 changed files with 316 additions and 8 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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,
}},
)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}