diff --git a/internal/models/problem.go b/internal/models/problem.go index 1f23be1..85a3577 100644 --- a/internal/models/problem.go +++ b/internal/models/problem.go @@ -25,3 +25,26 @@ type ProblemListItem struct { CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } + +type ProblemUpdate struct { + 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"` + MemoryLimit *int32 `db:"memory_limit"` + TimeLimit *int32 `db:"time_limit"` +} + +type ProblemStatement struct { + 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"` + TimeLimit int32 `db:"time_limit"` + MemoryLimit int32 `db:"memory_limit"` +} diff --git a/internal/tester/delivery.go b/internal/tester/delivery.go index 15266da..06789ca 100644 --- a/internal/tester/delivery.go +++ b/internal/tester/delivery.go @@ -19,4 +19,5 @@ type Handlers interface { 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 } diff --git a/internal/tester/delivery/rest/handlers.go b/internal/tester/delivery/rest/handlers.go index 208059a..9ee35de 100644 --- a/internal/tester/delivery/rest/handlers.go +++ b/internal/tester/delivery/rest/handlers.go @@ -1,6 +1,7 @@ package rest import ( + "git.sch9.ru/new_gate/ms-tester/internal/models" "git.sch9.ru/new_gate/ms-tester/internal/tester" "git.sch9.ru/new_gate/ms-tester/pkg" testerv1 "git.sch9.ru/new_gate/ms-tester/proto/tester/v1" @@ -220,6 +221,7 @@ func (h *TesterHandlers) GetProblem(c *fiber.Ctx, id int32) error { return c.JSON( testerv1.GetProblemResponse{Problem: testerv1.Problem{ Id: problem.Id, + Title: problem.Title, Legend: problem.Legend, InputFormat: problem.InputFormat, OutputFormat: problem.OutputFormat, @@ -263,3 +265,28 @@ func (h *TesterHandlers) ListParticipants(c *fiber.Ctx, params testerv1.ListPart return c.JSON(resp) } + +func (h *TesterHandlers) UpdateProblem(c *fiber.Ctx, id int32) error { + var req testerv1.UpdateProblemRequest + err := c.BodyParser(&req) + if err != nil { + return err + } + + err = h.problemsUC.UpdateProblem(c.Context(), id, models.ProblemUpdate{ + Title: req.Title, + Legend: req.Legend, + InputFormat: req.InputFormat, + OutputFormat: req.OutputFormat, + Notes: req.Notes, + Tutorial: req.Tutorial, + MemoryLimit: req.MemoryLimit, + TimeLimit: req.TimeLimit, + }) + + if err != nil { + return c.SendStatus(pkg.ToREST(err)) + } + + return c.SendStatus(fiber.StatusOK) +} diff --git a/internal/tester/pg_repository.go b/internal/tester/pg_repository.go index 1718f85..c922202 100644 --- a/internal/tester/pg_repository.go +++ b/internal/tester/pg_repository.go @@ -2,14 +2,33 @@ package tester import ( "context" + "database/sql" "git.sch9.ru/new_gate/ms-tester/internal/models" + "github.com/jmoiron/sqlx" ) +type Querier interface { + Rebind(query string) string + QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) + GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error + ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) + SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error +} + +type Tx interface { + Querier + Commit() error + Rollback() error +} + type ProblemPostgresRepository interface { - CreateProblem(ctx context.Context, title string) (int32, error) - ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) - DeleteProblem(ctx context.Context, id int32) error - ListProblems(ctx context.Context, page int32, pageSize int32) ([]*models.ProblemListItem, int32, error) + BeginTx(ctx context.Context) (Tx, error) + DB() Querier + CreateProblem(ctx context.Context, q Querier, title string) (int32, error) + ReadProblemById(ctx context.Context, q Querier, id int32) (*models.Problem, error) + DeleteProblem(ctx context.Context, q Querier, id int32) error + ListProblems(ctx context.Context, q Querier, page int32, pageSize int32) ([]*models.ProblemListItem, int32, error) + UpdateProblem(ctx context.Context, q Querier, id int32, heading models.ProblemUpdate) error } type ContestRepository interface { diff --git a/internal/tester/repository/pg_problems_repository.go b/internal/tester/repository/pg_problems_repository.go index 400b217..57045f5 100644 --- a/internal/tester/repository/pg_problems_repository.go +++ b/internal/tester/repository/pg_problems_repository.go @@ -3,28 +3,40 @@ package repository import ( "context" "git.sch9.ru/new_gate/ms-tester/internal/models" + "git.sch9.ru/new_gate/ms-tester/internal/tester" "github.com/jmoiron/sqlx" ) type ProblemRepository struct { - db *sqlx.DB - //logger *zap.Logger + _db *sqlx.DB } func NewProblemRepository(db *sqlx.DB) *ProblemRepository { return &ProblemRepository{ - db: db, - //logger: logger, + _db: db, } } +func (r *ProblemRepository) BeginTx(ctx context.Context) (tester.Tx, error) { + tx, err := r._db.BeginTxx(ctx, nil) + if err != nil { + return nil, err + } + + return tx, nil +} + +func (r *ProblemRepository) DB() tester.Querier { + return r._db +} + const createProblemQuery = "INSERT INTO problems (title) VALUES (?) RETURNING id" -func (r *ProblemRepository) CreateProblem(ctx context.Context, title string) (int32, error) { +func (r *ProblemRepository) CreateProblem(ctx context.Context, q tester.Querier, title string) (int32, error) { const op = "ProblemRepository.CreateProblem" - query := r.db.Rebind(createProblemQuery) - rows, err := r.db.QueryxContext(ctx, query, title) + query := q.Rebind(createProblemQuery) + rows, err := q.QueryxContext(ctx, query, title) if err != nil { return 0, handlePgErr(err, op) } @@ -42,12 +54,12 @@ func (r *ProblemRepository) CreateProblem(ctx context.Context, title string) (in const readProblemQuery = "SELECT * from problems WHERE id=? LIMIT 1" -func (r *ProblemRepository) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) { +func (r *ProblemRepository) ReadProblemById(ctx context.Context, q tester.Querier, id int32) (*models.Problem, error) { const op = "ProblemRepository.ReadProblemById" var problem models.Problem - query := r.db.Rebind(readProblemQuery) - err := r.db.GetContext(ctx, &problem, query, id) + query := q.Rebind(readProblemQuery) + err := q.GetContext(ctx, &problem, query, id) if err != nil { return nil, handlePgErr(err, op) } @@ -56,11 +68,11 @@ func (r *ProblemRepository) ReadProblemById(ctx context.Context, id int32) (*mod const deleteProblemQuery = "DELETE FROM problems WHERE id=?" -func (r *ProblemRepository) DeleteProblem(ctx context.Context, id int32) error { +func (r *ProblemRepository) DeleteProblem(ctx context.Context, q tester.Querier, id int32) error { const op = "ProblemRepository.DeleteProblem" - query := r.db.Rebind(deleteProblemQuery) - _, err := r.db.ExecContext(ctx, query, id) + query := q.Rebind(deleteProblemQuery) + _, err := q.ExecContext(ctx, query, id) if err != nil { return handlePgErr(err, op) } @@ -75,7 +87,7 @@ LIMIT ? OFFSET ?` CountProblemsQuery = "SELECT COUNT(*) FROM problems" ) -func (r *ProblemRepository) ListProblems(ctx context.Context, page int32, pageSize int32) ([]*models.ProblemListItem, int32, error) { +func (r *ProblemRepository) ListProblems(ctx context.Context, q tester.Querier, page int32, pageSize int32) ([]*models.ProblemListItem, int32, error) { const op = "ContestRepository.ListProblems" if pageSize > 20 || pageSize < 1 { @@ -83,19 +95,56 @@ func (r *ProblemRepository) ListProblems(ctx context.Context, page int32, pageSi } var problems []*models.ProblemListItem - query := r.db.Rebind(ListProblemsQuery) - err := r.db.SelectContext(ctx, &problems, query, pageSize, (page-1)*pageSize) + query := q.Rebind(ListProblemsQuery) + err := q.SelectContext(ctx, &problems, query, pageSize, (page-1)*pageSize) if err != nil { return nil, 0, handlePgErr(err, op) } - query = r.db.Rebind(CountProblemsQuery) + query = q.Rebind(CountProblemsQuery) var count int32 - err = r.db.GetContext(ctx, &count, query) + err = q.GetContext(ctx, &count, query) if err != nil { return nil, 0, handlePgErr(err, op) } return problems, count, nil } + +const ( + UpdateProblemQuery = `UPDATE problems +SET title = COALESCE(?, title), + legend = COALESCE(?, legend), + input_format = COALESCE(?, input_format), + output_format = COALESCE(?, output_format), + notes = COALESCE(?, notes), + tutorial = COALESCE(?, tutorial), + latex_summary = COALESCE(?, latex_summary), + time_limit = COALESCE(?, time_limit), + memory_limit = COALESCE(?, memory_limit) +WHERE id=?` +) + +func (r *ProblemRepository) UpdateProblem(ctx context.Context, q tester.Querier, id int32, problem models.ProblemUpdate) error { + const op = "ProblemRepository.UpdateProblem" + + query := q.Rebind(UpdateProblemQuery) + _, err := q.ExecContext(ctx, query, + problem.Title, + problem.Legend, + problem.InputFormat, + problem.OutputFormat, + problem.Notes, + problem.Tutorial, + problem.LatexSummary, + problem.TimeLimit, + problem.MemoryLimit, + id, + ) + if err != nil { + return handlePgErr(err, op) + } + + return nil +} diff --git a/internal/tester/usecase.go b/internal/tester/usecase.go index e706910..0c77d19 100644 --- a/internal/tester/usecase.go +++ b/internal/tester/usecase.go @@ -10,6 +10,7 @@ type ProblemUseCase interface { ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) DeleteProblem(ctx context.Context, id int32) error ListProblems(ctx context.Context, page int32, pageSize int32) ([]*models.ProblemListItem, int32, error) + UpdateProblem(ctx context.Context, id int32, problem models.ProblemUpdate) error } type ContestUseCase interface { diff --git a/internal/tester/usecase/problems_usecase.go b/internal/tester/usecase/problems_usecase.go index 0c01d68..4671152 100644 --- a/internal/tester/usecase/problems_usecase.go +++ b/internal/tester/usecase/problems_usecase.go @@ -2,18 +2,20 @@ package usecase import ( "context" + "errors" "git.sch9.ru/new_gate/ms-tester/internal/models" "git.sch9.ru/new_gate/ms-tester/internal/tester" + "git.sch9.ru/new_gate/ms-tester/pkg" ) type ProblemUseCase struct { problemRepo tester.ProblemPostgresRepository - //pandocClient pandoc.PandocClient + //pandocClient pkg.PandocClient } func NewProblemUseCase( problemRepo tester.ProblemPostgresRepository, - // pandocClient pandoc.PandocClient, + // pandocClient pkg.PandocClient, ) *ProblemUseCase { return &ProblemUseCase{ problemRepo: problemRepo, @@ -22,17 +24,102 @@ func NewProblemUseCase( } func (u *ProblemUseCase) CreateProblem(ctx context.Context, title string) (int32, error) { - return u.problemRepo.CreateProblem(ctx, title) + return u.problemRepo.CreateProblem(ctx, u.problemRepo.DB(), title) } func (u *ProblemUseCase) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) { - return u.problemRepo.ReadProblemById(ctx, id) + return u.problemRepo.ReadProblemById(ctx, u.problemRepo.DB(), id) } func (u *ProblemUseCase) DeleteProblem(ctx context.Context, id int32) error { - return u.problemRepo.DeleteProblem(ctx, id) + return u.problemRepo.DeleteProblem(ctx, u.problemRepo.DB(), id) } func (u *ProblemUseCase) ListProblems(ctx context.Context, page int32, pageSize int32) ([]*models.ProblemListItem, int32, error) { - return u.problemRepo.ListProblems(ctx, page, pageSize) + return u.problemRepo.ListProblems(ctx, u.problemRepo.DB(), page, pageSize) +} + +func isEmpty(p models.ProblemUpdate) bool { + return p.Title == nil && + p.Legend == nil && + p.InputFormat == nil && + p.OutputFormat == nil && + p.Notes == nil && + p.Tutorial == nil && + p.LatexSummary == nil && + p.MemoryLimit == nil && + p.TimeLimit == nil +} + +func build(p models.ProblemStatement) string { + return "" +} + +func (u *ProblemUseCase) UpdateProblem(ctx context.Context, id int32, problemUpdate models.ProblemUpdate) error { + if isEmpty(problemUpdate) { + return pkg.Wrap(pkg.ErrBadInput, nil, "UpdateProblem", "empty problem update") + } + + tx, err := u.problemRepo.BeginTx(ctx) + if err != nil { + return err + } + + problem, err := u.problemRepo.ReadProblemById(ctx, tx, id) + if err != nil { + return errors.Join(err, tx.Rollback()) + } + + statement := models.ProblemStatement{ + Title: problem.Title, + Legend: problem.Legend, + InputFormat: problem.InputFormat, + OutputFormat: problem.OutputFormat, + Notes: problem.Notes, + Tutorial: problem.Tutorial, + TimeLimit: problem.TimeLimit, + MemoryLimit: problem.MemoryLimit, + } + + if problemUpdate.Title != nil { + statement.Title = *problemUpdate.Title + } + if problemUpdate.Legend != nil { + statement.Legend = *problemUpdate.Legend + } + if problemUpdate.InputFormat != nil { + statement.InputFormat = *problemUpdate.InputFormat + } + if problemUpdate.OutputFormat != nil { + statement.OutputFormat = *problemUpdate.OutputFormat + } + if problemUpdate.Notes != nil { + statement.Notes = *problemUpdate.Notes + } + if problemUpdate.Tutorial != nil { + statement.Tutorial = *problemUpdate.Tutorial + } + if problemUpdate.TimeLimit != nil { + statement.TimeLimit = *problemUpdate.TimeLimit + } + if problemUpdate.MemoryLimit != nil { + statement.MemoryLimit = *problemUpdate.MemoryLimit + } + + builtStatement := build(statement) + if builtStatement != problem.LatexSummary { + problemUpdate.LatexSummary = &builtStatement + } + + err = u.problemRepo.UpdateProblem(ctx, tx, id, problemUpdate) + if err != nil { + return errors.Join(err, tx.Rollback()) + } + + err = tx.Commit() + if err != nil { + return err + } + + return nil } diff --git a/main.go b/main.go index db45284..e28941b 100644 --- a/main.go +++ b/main.go @@ -41,7 +41,7 @@ func main() { defer db.Close() logger.Info("successfully connected to postgres") - //pandocClient := pandoc.NewPandocClient(&http.Client{}, cfg.Pandoc) + //pandocClient := pkg.NewPandocClient(&http.Client{}, cfg.Pandoc) problemRepo := problemsRepository.NewProblemRepository(db) problemUC := testerUseCase.NewProblemUseCase(problemRepo) diff --git a/pkg/pandoc-client.go b/pkg/pandoc-client.go index 75a4adf..903dfff 100644 --- a/pkg/pandoc-client.go +++ b/pkg/pandoc-client.go @@ -54,10 +54,6 @@ func (client *Client) convert(ctx context.Context, text, from, to string) (strin defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return "", err - } - body, err = io.ReadAll(resp.Body) if err != nil { return "", err diff --git a/proto b/proto index 3913140..e5f7902 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 391314039942e437907d9d562d8973a80e290e4e +Subproject commit e5f7902c188a6b3daae9b147f687c4564cbfdfba