feat(tester): extend GetContestResponse
This commit is contained in:
parent
e6088953b9
commit
81d7aa2366
17 changed files with 539 additions and 238 deletions
|
@ -1,143 +0,0 @@
|
|||
package interceptors
|
||||
|
||||
//var defaultUser = &models.User{
|
||||
// UserId: nil,
|
||||
// Role: models.RoleSpectator.AsPointer(),
|
||||
// UpdatedAt: nil,
|
||||
//}
|
||||
//
|
||||
//func extractToken(ctx context.Context) string {
|
||||
// md, ok := metadata.FromIncomingContext(ctx)
|
||||
// if !ok {
|
||||
// return ""
|
||||
// }
|
||||
// tokens := md.Get("token")
|
||||
//
|
||||
// if len(tokens) == 0 {
|
||||
// return ""
|
||||
// }
|
||||
//
|
||||
// return tokens[0]
|
||||
//}
|
||||
//
|
||||
//func (s *TesterServer) readSessionAndReadUser(ctx context.Context, token string) (*models.User, error) {
|
||||
// // FIXME: possible bottle neck: should we cache it? (think of it in future)
|
||||
// // FIXME: maybe use single connection instead of multiple requests
|
||||
// userId, err := s.sessionClient.Read(ctx, &sessionv1.ReadSessionRequest{Token: token})
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// user, err := s.userService.ReadUserById(ctx, userId.GetUserId()) // FIXME: must be cached!
|
||||
// if err != nil {
|
||||
// if errors.Is(err, utils.ErrNotFound) {
|
||||
// user = &models.User{
|
||||
// UserId: utils.AsInt32P(userId.GetUserId()),
|
||||
// Role: models.RoleParticipant.AsPointer(),
|
||||
// }
|
||||
// err = s.userService.CreateUser(ctx, user)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// } else {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return user, nil
|
||||
//}
|
||||
//
|
||||
//func insertUser(ctx context.Context, user *models.User) context.Context {
|
||||
// return context.WithValue(ctx, "user", user)
|
||||
//}
|
||||
//
|
||||
//func (s *TesterServer) AuthUnaryInterceptor() grpc.UnaryServerInterceptor {
|
||||
// return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
// token := extractToken(ctx)
|
||||
// if token == "" {
|
||||
// return handler(insertUser(ctx, defaultUser), req)
|
||||
// }
|
||||
//
|
||||
// user, err := s.readSessionAndReadUser(ctx, token)
|
||||
// if err != nil {
|
||||
// return handler(insertUser(ctx, defaultUser), req)
|
||||
// }
|
||||
//
|
||||
// return handler(insertUser(ctx, user), req)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//type ssWrapper struct {
|
||||
// grpc.ServerStream
|
||||
// ctx context.Context
|
||||
//}
|
||||
//
|
||||
//func (s *ssWrapper) Context() context.Context {
|
||||
// return s.ctx
|
||||
//}
|
||||
//
|
||||
//func (s *TesterServer) AuthStreamInterceptor() grpc.StreamServerInterceptor {
|
||||
// return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
// ctx := ss.Context()
|
||||
//
|
||||
// token := extractToken(ctx)
|
||||
// if token == "" {
|
||||
// return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)})
|
||||
// }
|
||||
//
|
||||
// user, err := s.readSessionAndReadUser(ctx, token)
|
||||
// if err != nil {
|
||||
// return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)})
|
||||
// }
|
||||
//
|
||||
// return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, user)})
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func ToGrpcError(err error) error {
|
||||
// if err == nil {
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // should I use map instead?
|
||||
// switch {
|
||||
// case errors.Is(err, utils.ErrValidationFailed):
|
||||
// return status.Error(codes.InvalidArgument, err.Error())
|
||||
// case errors.Is(err, utils.ErrInternal):
|
||||
// return status.Error(codes.Internal, err.Error())
|
||||
// case errors.Is(err, utils.ErrExternal):
|
||||
// return status.Error(codes.Unavailable, err.Error())
|
||||
// case errors.Is(err, utils.ErrNoPermission):
|
||||
// return status.Error(codes.PermissionDenied, err.Error())
|
||||
// case errors.Is(err, utils.ErrUnknown):
|
||||
// return status.Error(codes.Unknown, err.Error())
|
||||
// case errors.Is(err, utils.ErrDeadlineExceeded):
|
||||
// return status.Error(codes.DeadlineExceeded, err.Error())
|
||||
// case errors.Is(err, utils.ErrNotFound):
|
||||
// return status.Error(codes.NotFound, err.Error())
|
||||
// case errors.Is(err, utils.ErrAlreadyExists):
|
||||
// return status.Error(codes.AlreadyExists, err.Error())
|
||||
// case errors.Is(err, utils.ErrConflict):
|
||||
// return status.Error(codes.Unimplemented, err.Error())
|
||||
// case errors.Is(err, utils.ErrUnimplemented):
|
||||
// return status.Error(codes.Unimplemented, err.Error())
|
||||
// case errors.Is(err, utils.ErrUnauthenticated):
|
||||
// return status.Error(codes.Unauthenticated, err.Error())
|
||||
// default:
|
||||
// return status.Error(codes.Unknown, err.Error())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (s *TesterServer) ErrUnwrappingUnaryInterceptor() grpc.UnaryServerInterceptor {
|
||||
// return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
// resp, err := handler(ctx, req)
|
||||
// return resp, ToGrpcError(err)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//func (s *TesterServer) ErrUnwrappingStreamInterceptor() grpc.StreamServerInterceptor {
|
||||
// return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
// err := handler(server, ss)
|
||||
// return ToGrpcError(err)
|
||||
// }
|
||||
//}
|
|
@ -3,16 +3,16 @@ package models
|
|||
import "time"
|
||||
|
||||
type Problem struct {
|
||||
Id *int32 `db:"id"`
|
||||
Title *string `db:"title"`
|
||||
Legend *string `db:"legend"`
|
||||
InputFormat *string `db:"input_format"`
|
||||
OutputFormat *string `db:"output_format"`
|
||||
Notes *string `db:"notes"`
|
||||
Tutorial *string `db:"tutorial"`
|
||||
LatexSummary *string `db:"latex_summary"`
|
||||
TimeLimit *int32 `db:"time_limit"`
|
||||
MemoryLimit *int32 `db:"memory_limit"`
|
||||
CreatedAt *time.Time `db:"created_at"`
|
||||
UpdatedAt *time.Time `db:"updated_at"`
|
||||
Id int32 `db:"id"`
|
||||
Title string `db:"title"`
|
||||
Legend string `db:"legend"`
|
||||
InputFormat string `db:"input_format"`
|
||||
OutputFormat string `db:"output_format"`
|
||||
Notes string `db:"notes"`
|
||||
Tutorial string `db:"tutorial"`
|
||||
LatexSummary string `db:"latex_summary"`
|
||||
TimeLimit int32 `db:"time_limit"`
|
||||
MemoryLimit int32 `db:"memory_limit"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
|
162
internal/models/session.go
Normal file
162
internal/models/session.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/open-policy-agent/opa/v1/rego"
|
||||
)
|
||||
|
||||
type JWT struct {
|
||||
SessionId string `json:"session_id"`
|
||||
UserId int32 `json:"user_id"`
|
||||
Role Role `json:"role"`
|
||||
ExpiresAt int64 `json:"exp"`
|
||||
IssuedAt int64 `json:"iat"`
|
||||
NotBefore int64 `json:"nbf"`
|
||||
Permissions []grant `json:"permissions"`
|
||||
}
|
||||
|
||||
func (j JWT) Valid() error {
|
||||
if uuid.Validate(j.SessionId) != nil {
|
||||
return errors.New("invalid session id")
|
||||
}
|
||||
if j.UserId == 0 {
|
||||
return errors.New("empty user id")
|
||||
}
|
||||
if j.ExpiresAt == 0 {
|
||||
return errors.New("empty expires at")
|
||||
}
|
||||
if j.IssuedAt == 0 {
|
||||
return errors.New("empty issued at")
|
||||
}
|
||||
if j.NotBefore == 0 {
|
||||
return errors.New("empty not before")
|
||||
}
|
||||
if len(j.Permissions) == 0 {
|
||||
return errors.New("empty permissions")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Role int32
|
||||
|
||||
const (
|
||||
RoleGuest Role = -1
|
||||
RoleStudent Role = 0
|
||||
RoleTeacher Role = 1
|
||||
RoleAdmin Role = 2
|
||||
)
|
||||
|
||||
func (r Role) String() string {
|
||||
switch r {
|
||||
case RoleGuest:
|
||||
return "guest"
|
||||
case RoleStudent:
|
||||
return "student"
|
||||
case RoleTeacher:
|
||||
return "teacher"
|
||||
case RoleAdmin:
|
||||
return "admin"
|
||||
}
|
||||
|
||||
panic("invalid role")
|
||||
}
|
||||
|
||||
type Action string
|
||||
|
||||
const (
|
||||
Create Action = "create"
|
||||
Read Action = "read"
|
||||
Update Action = "update"
|
||||
Delete Action = "delete"
|
||||
)
|
||||
|
||||
type Resource string
|
||||
|
||||
const (
|
||||
ResourceAnotherUser Resource = "another-user"
|
||||
ResourceMeUser Resource = "me-user"
|
||||
ResourceListUser Resource = "list-user"
|
||||
|
||||
ResourceOwnSession Resource = "own-session"
|
||||
)
|
||||
|
||||
type grant struct {
|
||||
Action Action `json:"action"`
|
||||
Resource Resource `json:"resource"`
|
||||
}
|
||||
|
||||
var Grants = map[string][]grant{
|
||||
RoleGuest.String(): {},
|
||||
RoleStudent.String(): {
|
||||
{Read, ResourceAnotherUser},
|
||||
{Read, ResourceMeUser},
|
||||
{Update, ResourceOwnSession},
|
||||
{Delete, ResourceOwnSession},
|
||||
},
|
||||
RoleTeacher.String(): {
|
||||
{Create, ResourceAnotherUser},
|
||||
{Read, ResourceAnotherUser},
|
||||
{Read, ResourceMeUser},
|
||||
{Read, ResourceListUser},
|
||||
{Update, ResourceOwnSession},
|
||||
{Delete, ResourceOwnSession},
|
||||
},
|
||||
RoleAdmin.String(): {
|
||||
{Create, ResourceAnotherUser},
|
||||
{Read, ResourceAnotherUser},
|
||||
{Read, ResourceMeUser},
|
||||
{Read, ResourceListUser},
|
||||
{Update, ResourceAnotherUser},
|
||||
{Update, ResourceOwnSession},
|
||||
{Delete, ResourceAnotherUser},
|
||||
{Delete, ResourceOwnSession},
|
||||
},
|
||||
}
|
||||
|
||||
const module = `package app.rbac
|
||||
|
||||
default allow := false
|
||||
|
||||
allow if {
|
||||
some grant in input.role_grants[input.role]
|
||||
|
||||
input.action == grant.action
|
||||
input.resource == grant.resource
|
||||
}
|
||||
`
|
||||
|
||||
var query rego.PreparedEvalQuery
|
||||
|
||||
func (r Role) HasPermission(action Action, resource Resource) bool {
|
||||
ctx := context.TODO()
|
||||
|
||||
input := map[string]interface{}{
|
||||
"action": action,
|
||||
"resource": resource,
|
||||
"role": r.String(),
|
||||
"role_grants": Grants,
|
||||
}
|
||||
|
||||
results, err := query.Eval(ctx, rego.EvalInput(input))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return results.Allowed()
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
ctx := context.TODO()
|
||||
|
||||
query, err = rego.New(
|
||||
rego.Query("data.app.rbac.allow"),
|
||||
rego.Module("ms-auth.rego", module),
|
||||
).PrepareForEval(ctx)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -1,13 +1,17 @@
|
|||
package models
|
||||
|
||||
//type Solution struct {
|
||||
// Id *int32 `db:"id"`
|
||||
// TaskId *int32 `db:"task_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"`
|
||||
// CreatedAt *time.Time `db:"created_at"`
|
||||
//}
|
||||
import "time"
|
||||
|
||||
type Solution struct {
|
||||
Id int32 `db:"id"`
|
||||
TaskId int32 `db:"task_id"`
|
||||
ParticipantId int32 `db:"participant_id"`
|
||||
Solution string `db:"solution"`
|
||||
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"`
|
||||
}
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
package models
|
||||
|
||||
//type Task struct {
|
||||
// Id *int32 `db:"id"`
|
||||
// ProblemId *int32 `db:"problem_id"`
|
||||
// ContestId *int32 `db:"contest_id"`
|
||||
// Position *int32 `db:"position"`
|
||||
// CreatedAt *time.Time `db:"created_at"`
|
||||
// UpdatedAt *time.Time `db:"updated_at"`
|
||||
//}
|
||||
import "time"
|
||||
|
||||
type Task struct {
|
||||
Id int32 `db:"id"`
|
||||
ProblemId int32 `db:"problem_id"`
|
||||
ContestId int32 `db:"contest_id"`
|
||||
Position int32 `db:"position"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
type RichTask struct {
|
||||
Id int32 `db:"id"`
|
||||
ProblemId int32 `db:"problem_id"`
|
||||
ContestId int32 `db:"contest_id"`
|
||||
Position int32 `db:"position"`
|
||||
Title string `db:"title"`
|
||||
MemoryLimit int32 `db:"memory_limit"`
|
||||
TimeLimit int32 `db:"time_limit"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
|
|
@ -53,14 +53,49 @@ func (h *TesterHandlers) GetContest(c *fiber.Ctx, id int32) error {
|
|||
return c.SendStatus(pkg.ToREST(err))
|
||||
}
|
||||
|
||||
return c.JSON(testerv1.GetContestResponse{
|
||||
//token, ok := c.Locals(TokenKey).(*models.JWT)
|
||||
//if !ok {
|
||||
// return c.SendStatus(fiber.StatusUnauthorized)
|
||||
//}
|
||||
|
||||
tasks, err := h.contestsUC.ReadRichTasks(c.Context(), id)
|
||||
if err != nil {
|
||||
return c.SendStatus(pkg.ToREST(err))
|
||||
}
|
||||
|
||||
resp := testerv1.GetContestResponse{
|
||||
Contest: testerv1.Contest{
|
||||
Id: *contest.Id,
|
||||
Id: id,
|
||||
Title: *contest.Title,
|
||||
CreatedAt: *contest.CreatedAt,
|
||||
UpdatedAt: *contest.UpdatedAt,
|
||||
},
|
||||
})
|
||||
Tasks: make([]struct {
|
||||
BestSolution testerv1.BestSolution `json:"best_solution"`
|
||||
Task testerv1.RichTask `json:"task"`
|
||||
}, len(tasks)),
|
||||
}
|
||||
|
||||
for i, task := range tasks {
|
||||
resp.Tasks[i] = struct {
|
||||
BestSolution testerv1.BestSolution `json:"best_solution"`
|
||||
Task testerv1.RichTask `json:"task"`
|
||||
}{
|
||||
BestSolution: testerv1.BestSolution{},
|
||||
Task: testerv1.RichTask{
|
||||
Id: task.Id,
|
||||
ProblemId: task.ProblemId,
|
||||
Position: task.Position,
|
||||
Title: task.Title,
|
||||
MemoryLimit: task.MemoryLimit,
|
||||
TimeLimit: task.TimeLimit,
|
||||
CreatedAt: task.CreatedAt,
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(resp)
|
||||
}
|
||||
|
||||
func (h *TesterHandlers) DeleteParticipant(c *fiber.Ctx, id int32, params testerv1.DeleteParticipantParams) error {
|
||||
|
@ -130,17 +165,17 @@ func (h *TesterHandlers) GetProblem(c *fiber.Ctx, id int32) error {
|
|||
|
||||
return c.JSON(
|
||||
testerv1.GetProblemResponse{Problem: testerv1.Problem{
|
||||
Id: *problem.Id,
|
||||
Legend: *problem.Legend,
|
||||
InputFormat: *problem.InputFormat,
|
||||
OutputFormat: *problem.OutputFormat,
|
||||
Notes: *problem.Notes,
|
||||
Tutorial: *problem.Tutorial,
|
||||
LatexSummary: *problem.LatexSummary,
|
||||
TimeLimit: *problem.TimeLimit,
|
||||
MemoryLimit: *problem.MemoryLimit,
|
||||
CreatedAt: *problem.CreatedAt,
|
||||
UpdatedAt: *problem.UpdatedAt,
|
||||
Id: problem.Id,
|
||||
Legend: problem.Legend,
|
||||
InputFormat: problem.InputFormat,
|
||||
OutputFormat: problem.OutputFormat,
|
||||
Notes: problem.Notes,
|
||||
Tutorial: problem.Tutorial,
|
||||
LatexSummary: problem.LatexSummary,
|
||||
TimeLimit: problem.TimeLimit,
|
||||
MemoryLimit: problem.MemoryLimit,
|
||||
CreatedAt: problem.CreatedAt,
|
||||
UpdatedAt: problem.UpdatedAt,
|
||||
}},
|
||||
)
|
||||
}
|
||||
|
|
71
internal/tester/delivery/rest/middlewares.go
Normal file
71
internal/tester/delivery/rest/middlewares.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenKey = "token"
|
||||
)
|
||||
|
||||
func AuthMiddleware(jwtSecret string) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
const op = "AuthMiddleware"
|
||||
|
||||
authHeader := c.Get("Authorization", "")
|
||||
if authHeader == "" {
|
||||
c.Locals(TokenKey, nil)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
authParts := strings.Split(authHeader, " ")
|
||||
if len(authParts) != 2 || strings.ToLower(authParts[0]) != "bearer" {
|
||||
c.Locals(TokenKey, nil)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
parsedToken, err := jwt.ParseWithClaims(authParts[1], &models.JWT{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
return []byte(jwtSecret), nil
|
||||
})
|
||||
if err != nil {
|
||||
c.Locals(TokenKey, nil)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
token, ok := parsedToken.Claims.(*models.JWT)
|
||||
if !ok {
|
||||
c.Locals(TokenKey, nil)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
err = token.Valid()
|
||||
if err != nil {
|
||||
c.Locals(TokenKey, nil)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
//ctx := c.Context()
|
||||
|
||||
// check if session exists
|
||||
//_, err = userUC.ReadSession(ctx, token.SessionId)
|
||||
//if err != nil {
|
||||
// if errors.Is(err, pkg.ErrNotFound) {
|
||||
// c.Locals(TokenKey, nil)
|
||||
// return c.Next()
|
||||
// }
|
||||
//
|
||||
// return c.SendStatus(pkg.ToREST(err))
|
||||
//}
|
||||
|
||||
c.Locals(TokenKey, token)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
|
@ -19,4 +19,5 @@ type ContestRepository interface {
|
|||
DeleteTask(ctx context.Context, taskId int32) error
|
||||
AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error)
|
||||
DeleteParticipant(ctx context.Context, participantId int32) error
|
||||
ReadRichTasks(ctx context.Context, contestId int32) ([]*models.RichTask, error)
|
||||
}
|
||||
|
|
|
@ -4,18 +4,15 @@ import (
|
|||
"context"
|
||||
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ContestRepository struct {
|
||||
db *sqlx.DB
|
||||
logger *zap.Logger
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewContestRepository(db *sqlx.DB, logger *zap.Logger) *ContestRepository {
|
||||
func NewContestRepository(db *sqlx.DB) *ContestRepository {
|
||||
return &ContestRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,3 +133,28 @@ func (r *ContestRepository) DeleteParticipant(ctx context.Context, participantId
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -4,18 +4,17 @@ import (
|
|||
"context"
|
||||
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ProblemRepository struct {
|
||||
db *sqlx.DB
|
||||
logger *zap.Logger
|
||||
db *sqlx.DB
|
||||
//logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewProblemRepository(db *sqlx.DB, logger *zap.Logger) *ProblemRepository {
|
||||
func NewProblemRepository(db *sqlx.DB) *ProblemRepository {
|
||||
return &ProblemRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
db: db,
|
||||
//logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,4 +19,5 @@ type ContestUseCase interface {
|
|||
DeleteTask(ctx context.Context, taskId int32) error
|
||||
AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error)
|
||||
DeleteParticipant(ctx context.Context, participantId int32) error
|
||||
ReadRichTasks(ctx context.Context, contestId int32) ([]*models.RichTask, error)
|
||||
}
|
||||
|
|
|
@ -45,3 +45,7 @@ func (uc *ContestUseCase) AddParticipant(ctx context.Context, contestId int32, u
|
|||
func (uc *ContestUseCase) DeleteParticipant(ctx context.Context, participantId int32) error {
|
||||
return uc.contestRepo.DeleteParticipant(ctx, participantId)
|
||||
}
|
||||
|
||||
func (uc *ContestUseCase) ReadRichTasks(ctx context.Context, contestId int32) ([]*models.RichTask, error) {
|
||||
return uc.contestRepo.ReadRichTasks(ctx, contestId)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue