feat: merge auth&tester

This commit is contained in:
Vyacheslav1557 2025-04-22 20:44:52 +05:00
parent 0a2dea6c23
commit 441af4c6a2
72 changed files with 4910 additions and 2378 deletions

View file

@ -0,0 +1,202 @@
package rest
import (
"context"
testerv1 "git.sch9.ru/new_gate/ms-tester/contracts/tester/v1"
"git.sch9.ru/new_gate/ms-tester/internal/contests"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"git.sch9.ru/new_gate/ms-tester/internal/problems"
"git.sch9.ru/new_gate/ms-tester/pkg"
"github.com/gofiber/fiber/v2"
)
type Handlers struct {
problemsUC problems.UseCase
contestsUC contests.UseCase
jwtSecret string
}
func NewHandlers(problemsUC problems.UseCase, contestsUC contests.UseCase, jwtSecret string) *Handlers {
return &Handlers{
problemsUC: problemsUC,
contestsUC: contestsUC,
jwtSecret: jwtSecret,
}
}
const (
sessionKey = "session"
)
func sessionFromCtx(ctx context.Context) (*models.Session, error) {
const op = "sessionFromCtx"
session, ok := ctx.Value(sessionKey).(*models.Session)
if !ok {
return nil, pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "")
}
return session, nil
}
func (h *Handlers) CreateContest(c *fiber.Ctx) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
id, err := h.contestsUC.CreateContest(ctx, "Название контеста")
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(&testerv1.CreateContestResponse{
Id: id,
})
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) GetContest(c *fiber.Ctx, id int32) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
contest, err := h.contestsUC.GetContest(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
tasks, err := h.contestsUC.GetTasks(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
solutions := make([]*models.SolutionsListItem, 0)
participantId, err := h.contestsUC.GetParticipantId(ctx, contest.Id, session.UserId)
if err == nil { // Admin or Teacher may not participate in contest
solutions, err = h.contestsUC.GetBestSolutions(ctx, id, participantId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
}
return c.JSON(GetContestResponseDTO(contest, tasks, solutions))
case models.RoleStudent:
contest, err := h.contestsUC.GetContest(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
tasks, err := h.contestsUC.GetTasks(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
participantId, err := h.contestsUC.GetParticipantId(ctx, contest.Id, session.UserId)
solutions, err := h.contestsUC.GetBestSolutions(c.Context(), id, participantId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(GetContestResponseDTO(contest, tasks, solutions))
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) UpdateContest(c *fiber.Ctx, id int32) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
var req testerv1.UpdateContestRequest
err := c.BodyParser(&req)
if err != nil {
return err
}
err = h.contestsUC.UpdateContest(ctx, id, models.ContestUpdate{
Title: req.Title,
})
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.SendStatus(fiber.StatusOK)
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) DeleteContest(c *fiber.Ctx, id int32) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
err := h.contestsUC.DeleteContest(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.SendStatus(fiber.StatusOK)
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) ListContests(c *fiber.Ctx, params testerv1.ListContestsParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
filter := models.ContestsFilter{
Page: params.Page,
PageSize: params.PageSize,
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
contestsList, err := h.contestsUC.ListContests(ctx, filter)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(ListContestsResponseDTO(contestsList))
case models.RoleStudent:
filter.UserId = &session.UserId
contestsList, err := h.contestsUC.ListContests(ctx, filter)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(ListContestsResponseDTO(contestsList))
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}

View file

@ -0,0 +1,202 @@
package rest
import (
testerv1 "git.sch9.ru/new_gate/ms-tester/contracts/tester/v1"
"git.sch9.ru/new_gate/ms-tester/internal/models"
)
func GetContestResponseDTO(contest *models.Contest,
tasks []*models.TasksListItem,
solutions []*models.SolutionsListItem) *testerv1.GetContestResponse {
m := make(map[int32]*models.SolutionsListItem)
for i := 0; i < len(solutions); i++ {
m[solutions[i].TaskPosition] = solutions[i]
}
resp := testerv1.GetContestResponse{
Contest: ContestDTO(*contest),
Tasks: make([]struct {
Solution testerv1.SolutionsListItem `json:"solution"`
Task testerv1.TasksListItem `json:"task"`
}, len(tasks)),
}
for i, task := range tasks {
solution := testerv1.SolutionsListItem{}
if sol, ok := m[task.Position]; ok {
solution = SolutionsListItemDTO(*sol)
}
resp.Tasks[i] = struct {
Solution testerv1.SolutionsListItem `json:"solution"`
Task testerv1.TasksListItem `json:"task"`
}{
Solution: solution,
Task: TasksListItemDTO(*task),
}
}
return &resp
}
func ListContestsResponseDTO(contestsList *models.ContestsList) *testerv1.ListContestsResponse {
resp := testerv1.ListContestsResponse{
Contests: make([]testerv1.ContestsListItem, len(contestsList.Contests)),
Pagination: PaginationDTO(contestsList.Pagination),
}
for i, contest := range contestsList.Contests {
resp.Contests[i] = ContestsListItemDTO(*contest)
}
return &resp
}
func ListSolutionsResponseDTO(solutionsList *models.SolutionsList) *testerv1.ListSolutionsResponse {
resp := testerv1.ListSolutionsResponse{
Solutions: make([]testerv1.SolutionsListItem, len(solutionsList.Solutions)),
Pagination: PaginationDTO(solutionsList.Pagination),
}
for i, solution := range solutionsList.Solutions {
resp.Solutions[i] = SolutionsListItemDTO(*solution)
}
return &resp
}
func GetTaskResponseDTO(contest *models.Contest, tasks []*models.TasksListItem, task *models.Task) *testerv1.GetTaskResponse {
resp := testerv1.GetTaskResponse{
Contest: ContestDTO(*contest),
Tasks: make([]testerv1.TasksListItem, len(tasks)),
Task: *TaskDTO(task),
}
for i, t := range tasks {
resp.Tasks[i] = TasksListItemDTO(*t)
}
return &resp
}
func PaginationDTO(p models.Pagination) testerv1.Pagination {
return testerv1.Pagination{
Page: p.Page,
Total: p.Total,
}
}
func ContestDTO(c models.Contest) testerv1.Contest {
return testerv1.Contest{
Id: c.Id,
Title: c.Title,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}
func ContestsListItemDTO(c models.ContestsListItem) testerv1.ContestsListItem {
return testerv1.ContestsListItem{
Id: c.Id,
Title: c.Title,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}
func TasksListItemDTO(t models.TasksListItem) testerv1.TasksListItem {
return testerv1.TasksListItem{
Id: t.Id,
Position: t.Position,
Title: t.Title,
MemoryLimit: t.MemoryLimit,
ProblemId: t.ProblemId,
TimeLimit: t.TimeLimit,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
}
}
func TaskDTO(t *models.Task) *testerv1.Task {
return &testerv1.Task{
Id: t.Id,
Title: t.Title,
MemoryLimit: t.MemoryLimit,
TimeLimit: t.TimeLimit,
InputFormatHtml: t.InputFormatHtml,
LegendHtml: t.LegendHtml,
NotesHtml: t.NotesHtml,
OutputFormatHtml: t.OutputFormatHtml,
Position: t.Position,
ScoringHtml: t.ScoringHtml,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
}
}
func ParticipantsListItemDTO(p models.ParticipantsListItem) testerv1.ParticipantsListItem {
return testerv1.ParticipantsListItem{
Id: p.Id,
UserId: p.UserId,
Name: p.Name,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
}
}
func SolutionsListItemDTO(s models.SolutionsListItem) testerv1.SolutionsListItem {
return testerv1.SolutionsListItem{
Id: s.Id,
ParticipantId: s.ParticipantId,
ParticipantName: s.ParticipantName,
State: s.State,
Score: s.Score,
Penalty: s.Penalty,
TimeStat: s.TimeStat,
MemoryStat: s.MemoryStat,
Language: s.Language,
TaskId: s.TaskId,
TaskPosition: s.TaskPosition,
TaskTitle: s.TaskTitle,
ContestId: s.ContestId,
ContestTitle: s.ContestTitle,
CreatedAt: s.CreatedAt,
UpdatedAt: s.UpdatedAt,
}
}
func SolutionDTO(s models.Solution) testerv1.Solution {
return testerv1.Solution{
Id: s.Id,
ParticipantId: s.ParticipantId,
ParticipantName: s.ParticipantName,
Solution: s.Solution,
State: s.State,
Score: s.Score,
Penalty: s.Penalty,
TimeStat: s.TimeStat,
MemoryStat: s.MemoryStat,
Language: s.Language,
TaskId: s.TaskId,
TaskPosition: s.TaskPosition,
TaskTitle: s.TaskTitle,
ContestId: s.ContestId,
ContestTitle: s.ContestTitle,
CreatedAt: s.CreatedAt,
UpdatedAt: s.UpdatedAt,
}
}

View file

@ -0,0 +1,71 @@
package rest
import (
testerv1 "git.sch9.ru/new_gate/ms-tester/contracts/tester/v1"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"git.sch9.ru/new_gate/ms-tester/pkg"
"github.com/gofiber/fiber/v2"
)
func (h *Handlers) GetMonitor(c *fiber.Ctx, params testerv1.GetMonitorParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher, models.RoleStudent:
contest, err := h.contestsUC.GetContest(ctx, params.ContestId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
monitor, err := h.contestsUC.GetMonitor(ctx, params.ContestId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
tasks, err := h.contestsUC.GetTasks(ctx, params.ContestId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
resp := testerv1.GetMonitorResponse{
Contest: ContestDTO(*contest),
Tasks: make([]testerv1.TasksListItem, len(tasks)),
Participants: make([]testerv1.ParticipantsStat, len(monitor.Participants)),
SummaryPerProblem: make([]testerv1.ProblemStatSummary, len(monitor.Summary)),
}
for i, participant := range monitor.Participants {
resp.Participants[i] = testerv1.ParticipantsStat{
Id: participant.Id,
Name: participant.Name,
PenaltyInTotal: participant.PenaltyInTotal,
Solutions: make([]testerv1.SolutionsListItem, len(participant.Solutions)),
SolvedInTotal: participant.SolvedInTotal,
}
for j, solution := range participant.Solutions {
resp.Participants[i].Solutions[j] = SolutionsListItemDTO(*solution)
}
}
for i, problem := range monitor.Summary {
resp.SummaryPerProblem[i] = testerv1.ProblemStatSummary{
Id: problem.Id,
Success: problem.Success,
Total: problem.Total,
}
}
for i, task := range tasks {
resp.Tasks[i] = TasksListItemDTO(*task)
}
return c.JSON(resp)
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}

View file

@ -0,0 +1,116 @@
package rest
import (
testerv1 "git.sch9.ru/new_gate/ms-tester/contracts/tester/v1"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"git.sch9.ru/new_gate/ms-tester/pkg"
"github.com/gofiber/fiber/v2"
)
func (h *Handlers) CreateParticipant(c *fiber.Ctx, params testerv1.CreateParticipantParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
id, err := h.contestsUC.CreateParticipant(ctx, params.ContestId, params.UserId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(testerv1.CreateParticipantResponse{
Id: id,
})
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) UpdateParticipant(c *fiber.Ctx, params testerv1.UpdateParticipantParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
var req testerv1.UpdateParticipantRequest
err := c.BodyParser(&req)
if err != nil {
return err
}
err = h.contestsUC.UpdateParticipant(ctx, params.ParticipantId, models.ParticipantUpdate{
Name: req.Name,
})
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.SendStatus(fiber.StatusOK)
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) DeleteParticipant(c *fiber.Ctx, params testerv1.DeleteParticipantParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
err := h.contestsUC.DeleteParticipant(c.Context(), params.ParticipantId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.SendStatus(fiber.StatusOK)
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) ListParticipants(c *fiber.Ctx, params testerv1.ListParticipantsParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
participantsList, err := h.contestsUC.ListParticipants(c.Context(), models.ParticipantsFilter{
Page: params.Page,
PageSize: params.PageSize,
ContestId: params.ContestId,
})
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
resp := testerv1.ListParticipantsResponse{
Participants: make([]testerv1.ParticipantsListItem, len(participantsList.Participants)),
Pagination: PaginationDTO(participantsList.Pagination),
}
for i, participant := range participantsList.Participants {
resp.Participants[i] = ParticipantsListItemDTO(*participant)
}
return c.JSON(resp)
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}

View file

@ -0,0 +1,148 @@
package rest
import (
testerv1 "git.sch9.ru/new_gate/ms-tester/contracts/tester/v1"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"git.sch9.ru/new_gate/ms-tester/pkg"
"github.com/gofiber/fiber/v2"
"io"
)
const (
maxSolutionSize int64 = 10 * 1024 * 1024
)
func (h *Handlers) CreateSolution(c *fiber.Ctx, params testerv1.CreateSolutionParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher, models.RoleStudent:
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(ctx, &models.SolutionCreation{
UserId: session.UserId,
TaskId: params.TaskId,
Language: params.Language,
Solution: string(b),
})
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(testerv1.CreateSolutionResponse{
Id: id,
})
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) GetSolution(c *fiber.Ctx, id int32) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
solution, err := h.contestsUC.GetSolution(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(testerv1.GetSolutionResponse{Solution: SolutionDTO(*solution)})
case models.RoleStudent:
_, err := h.contestsUC.GetParticipantId3(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
solution, err := h.contestsUC.GetSolution(ctx, id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(testerv1.GetSolutionResponse{Solution: SolutionDTO(*solution)})
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) ListSolutions(c *fiber.Ctx, params testerv1.ListSolutionsParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
filter := 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,
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
solutionsList, err := h.contestsUC.ListSolutions(ctx, filter)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(ListSolutionsResponseDTO(solutionsList))
case models.RoleStudent:
if params.ContestId == nil {
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
participantId, err := h.contestsUC.GetParticipantId(ctx, *params.ContestId, session.UserId)
if err != nil {
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
// Student cannot view other users' solutions
if params.ParticipantId != nil && *params.ParticipantId != participantId {
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
filter.ParticipantId = &participantId
solutionsList, err := h.contestsUC.ListSolutions(ctx, filter)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(ListSolutionsResponseDTO(solutionsList))
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}

View file

@ -0,0 +1,105 @@
package rest
import (
testerv1 "git.sch9.ru/new_gate/ms-tester/contracts/tester/v1"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"git.sch9.ru/new_gate/ms-tester/pkg"
"github.com/gofiber/fiber/v2"
)
func (h *Handlers) CreateTask(c *fiber.Ctx, params testerv1.CreateTaskParams) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
id, err := h.contestsUC.CreateTask(ctx, params.ContestId, params.ProblemId)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(testerv1.CreateTaskResponse{
Id: id,
})
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) GetTask(c *fiber.Ctx, id int32) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
contest, err := h.contestsUC.GetContest(c.Context(), id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
tasks, err := h.contestsUC.GetTasks(c.Context(), id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
t, err := h.contestsUC.GetTask(c.Context(), id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(GetTaskResponseDTO(contest, tasks, t))
case models.RoleStudent:
_, err = h.contestsUC.GetParticipantId2(ctx, id, session.UserId)
if err != nil {
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
contest, err := h.contestsUC.GetContest(c.Context(), id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
tasks, err := h.contestsUC.GetTasks(c.Context(), id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
t, err := h.contestsUC.GetTask(c.Context(), id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.JSON(GetTaskResponseDTO(contest, tasks, t))
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}
func (h *Handlers) DeleteTask(c *fiber.Ctx, id int32) error {
ctx := c.Context()
session, err := sessionFromCtx(ctx)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
switch session.Role {
case models.RoleAdmin, models.RoleTeacher:
err := h.contestsUC.DeleteTask(c.Context(), id)
if err != nil {
return c.SendStatus(pkg.ToREST(err))
}
return c.SendStatus(fiber.StatusOK)
default:
return c.SendStatus(pkg.ToREST(pkg.NoPermission))
}
}