ms-tester/internal/tester/repository/pg_contests_repository.go
Vyacheslav1557 ef696d2836 feat(tester): add solution endpoints
add CreateSolution&GetSolution&ListSolutions endpoints
2025-03-27 00:04:52 +05:00

393 lines
10 KiB
Go

package repository
import (
"context"
"fmt"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"github.com/jmoiron/sqlx"
"strings"
)
type ContestRepository struct {
db *sqlx.DB
}
func NewContestRepository(db *sqlx.DB) *ContestRepository {
return &ContestRepository{
db: db,
}
}
const createContestQuery = "INSERT INTO contests (title) VALUES (?) RETURNING id"
func (r *ContestRepository) CreateContest(ctx context.Context, title string) (int32, error) {
const op = "ContestRepository.CreateContest"
query := r.db.Rebind(createContestQuery)
rows, err := r.db.QueryxContext(ctx, query, title)
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
}
const readContestByIdQuery = "SELECT * from contests WHERE id=? LIMIT 1"
func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) {
const op = "ContestRepository.ReadContestById"
var contest models.Contest
query := r.db.Rebind(readContestByIdQuery)
err := r.db.GetContext(ctx, &contest, query, id)
if err != nil {
return nil, handlePgErr(err, op)
}
return &contest, nil
}
const deleteContestQuery = "DELETE FROM contests WHERE id=?"
func (r *ContestRepository) DeleteContest(ctx context.Context, id int32) error {
const op = "ContestRepository.DeleteContest"
query := r.db.Rebind(deleteContestQuery)
_, err := r.db.ExecContext(ctx, query, id)
if err != nil {
return handlePgErr(err, op)
}
return nil
}
const addTaskQuery = `INSERT INTO tasks (problem_id, contest_id, position)
VALUES (?, ?, COALESCE((SELECT MAX(position) FROM tasks WHERE contest_id = ?), 0) + 1)
RETURNING id
`
func (r *ContestRepository) AddTask(ctx context.Context, contestId int32, problemId int32) (int32, error) {
const op = "ContestRepository.AddTask"
query := r.db.Rebind(addTaskQuery)
rows, err := r.db.QueryxContext(ctx, query, problemId, contestId, contestId)
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
}
const deleteTaskQuery = "DELETE FROM tasks WHERE id=?"
func (r *ContestRepository) DeleteTask(ctx context.Context, taskId int32) error {
const op = "ContestRepository.DeleteTask"
query := r.db.Rebind(deleteTaskQuery)
_, err := r.db.ExecContext(ctx, query, taskId)
if err != nil {
return handlePgErr(err, op)
}
return nil
}
const addParticipantQuery = "INSERT INTO participants (user_id ,contest_id, name) VALUES (?, ?, ?) RETURNING id"
func (r *ContestRepository) AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error) {
const op = "ContestRepository.AddParticipant"
query := r.db.Rebind(addParticipantQuery)
name := ""
rows, err := r.db.QueryxContext(ctx, query, contestId, userId, name)
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, err
}
return id, nil
}
const deleteParticipantQuery = "DELETE FROM participants WHERE id=?"
func (r *ContestRepository) DeleteParticipant(ctx context.Context, participantId int32) error {
const op = "ContestRepository.DeleteParticipant"
query := r.db.Rebind(deleteParticipantQuery)
_, err := r.db.ExecContext(ctx, query, participantId)
if err != nil {
return handlePgErr(err, op)
}
return nil
}
const readTasksQuery = `SELECT tasks.id,
problem_id,
contest_id,
position,
title,
memory_limit,
time_limit,
tasks.created_at,
tasks.updated_at
FROM tasks
INNER JOIN problems ON tasks.problem_id = problems.id
WHERE contest_id = ? ORDER BY position`
func (r *ContestRepository) ReadRichTasks(ctx context.Context, contestId int32) ([]*models.RichTask, error) {
const op = "ContestRepository.ReadTasks"
var tasks []*models.RichTask
query := r.db.Rebind(readTasksQuery)
err := r.db.SelectContext(ctx, &tasks, query, contestId)
if err != nil {
return nil, handlePgErr(err, op)
}
return tasks, nil
}
const (
readContestsListQuery = `SELECT id, title, created_at, updated_at FROM contests LIMIT ? OFFSET ?`
countContestsQuery = "SELECT COUNT(*) FROM contests"
)
func (r *ContestRepository) ListContests(ctx context.Context, page int32, pageSize int32) ([]*models.ContestsListItem, int32, error) {
const op = "ContestRepository.ReadTasks"
var tasks []*models.ContestsListItem
query := r.db.Rebind(readContestsListQuery)
err := r.db.SelectContext(ctx, &tasks, query, pageSize, (page-1)*pageSize)
if err != nil {
return nil, 0, handlePgErr(err, op)
}
query = r.db.Rebind(countContestsQuery)
var count int32
err = r.db.GetContext(ctx, &count, query)
if err != nil {
return nil, 0, handlePgErr(err, op)
}
return tasks, count, nil
}
const (
readParticipantsListQuery = `SELECT id, user_id, name, created_at, updated_at FROM participants WHERE contest_id = ? LIMIT ? OFFSET ?`
countParticipantsQuery = "SELECT COUNT(*) FROM participants WHERE contest_id = ?"
)
func (r *ContestRepository) ListParticipants(ctx context.Context, contestId int32, page int32, pageSize int32) ([]*models.ParticipantsListItem, int32, error) {
const op = "ContestRepository.ReadParticipants"
if pageSize > 20 {
pageSize = 1
}
var participants []*models.ParticipantsListItem
query := r.db.Rebind(readParticipantsListQuery)
err := r.db.SelectContext(ctx, &participants, query, contestId, pageSize, (page-1)*pageSize)
if err != nil {
return nil, 0, handlePgErr(err, op)
}
query = r.db.Rebind(countParticipantsQuery)
var count int32
err = r.db.GetContext(ctx, &count, query, contestId)
if err != nil {
return nil, 0, handlePgErr(err, op)
}
return participants, count, nil
}
const (
updateContestQuery = "UPDATE contests SET title = COALESCE(?, title) WHERE id = ?"
)
func (r *ContestRepository) UpdateContest(ctx context.Context, id int32, contestUpdate models.ContestUpdate) error {
const op = "ContestRepository.UpdateContest"
query := r.db.Rebind(updateContestQuery)
_, err := r.db.ExecContext(ctx, query, contestUpdate.Title, id)
if err != nil {
return handlePgErr(err, op)
}
return nil
}
const (
updateParticipantQuery = "UPDATE participants SET name = COALESCE(?, name) WHERE id = ?"
)
func (r *ContestRepository) UpdateParticipant(ctx context.Context, id int32, participantUpdate models.ParticipantUpdate) error {
const op = "ContestRepository.UpdateParticipant"
query := r.db.Rebind(updateParticipantQuery)
_, err := r.db.ExecContext(ctx, query, participantUpdate.Name, id)
if err != nil {
return handlePgErr(err, op)
}
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
}