diff --git a/internal/contests/delivery.go b/internal/contests/delivery.go index bbb81f3..874d361 100644 --- a/internal/contests/delivery.go +++ b/internal/contests/delivery.go @@ -10,4 +10,8 @@ type ContestHandlers interface { CreateContest(ctx context.Context, req *contestv1.CreateContestRequest) (*contestv1.CreateContestResponse, error) ReadContest(ctx context.Context, req *contestv1.ReadContestRequest) (*contestv1.ReadContestResponse, error) DeleteContest(ctx context.Context, req *contestv1.DeleteContestRequest) (*emptypb.Empty, error) + AddTask(ctx context.Context, req *contestv1.AddTaskRequest) (*contestv1.AddTaskResponse, error) + DeleteTask(ctx context.Context, req *contestv1.DeleteTaskRequest) (*emptypb.Empty, error) + AddParticipant(ctx context.Context, req *contestv1.AddParticipantRequest) (*contestv1.AddParticipantResponse, error) + DeleteParticipant(ctx context.Context, req *contestv1.DeleteParticipantRequest) (*emptypb.Empty, error) } diff --git a/internal/contests/delivery/grpc/handlers.go b/internal/contests/delivery/grpc/handlers.go index 67141e4..36047a4 100644 --- a/internal/contests/delivery/grpc/handlers.go +++ b/internal/contests/delivery/grpc/handlers.go @@ -49,3 +49,35 @@ func (h *ContestHandlers) DeleteContest(ctx context.Context, req *contestv1.Dele } return &emptypb.Empty{}, nil } + +func (h *ContestHandlers) AddTask(ctx context.Context, req *contestv1.AddTaskRequest) (*contestv1.AddTaskResponse, error) { + id, err := h.contestUC.AddTask(ctx, req.GetContestId(), req.GetProblemId()) + if err != nil { + return nil, err + } + return &contestv1.AddTaskResponse{Id: id}, nil +} + +func (h *ContestHandlers) DeleteTask(ctx context.Context, req *contestv1.DeleteTaskRequest) (*emptypb.Empty, error) { + err := h.contestUC.DeleteTask(ctx, req.GetTaskId()) + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (h *ContestHandlers) AddParticipant(ctx context.Context, req *contestv1.AddParticipantRequest) (*contestv1.AddParticipantResponse, error) { + id, err := h.contestUC.AddParticipant(ctx, req.GetContestId(), req.GetUserId()) + if err != nil { + return nil, err + } + return &contestv1.AddParticipantResponse{Id: id}, nil +} + +func (h *ContestHandlers) DeleteParticipant(ctx context.Context, req *contestv1.DeleteParticipantRequest) (*emptypb.Empty, error) { + err := h.contestUC.DeleteParticipant(ctx, req.GetParticipantId()) + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} diff --git a/internal/contests/pg_repository.go b/internal/contests/pg_repository.go index 1dc1368..0443e32 100644 --- a/internal/contests/pg_repository.go +++ b/internal/contests/pg_repository.go @@ -9,4 +9,8 @@ type ContestRepository interface { CreateContest(ctx context.Context, title string) (int32, error) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) DeleteContest(ctx context.Context, id int32) error + AddTask(ctx context.Context, contestId int32, taskId int32) (int32, error) + DeleteTask(ctx context.Context, taskId int32) error + AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error) + DeleteParticipant(ctx context.Context, participantId int32) error } diff --git a/internal/contests/repository/pg_repository.go b/internal/contests/repository/pg_repository.go index e3f1c82..48ff720 100644 --- a/internal/contests/repository/pg_repository.go +++ b/internal/contests/repository/pg_repository.go @@ -68,6 +68,65 @@ func (r *ContestRepository) DeleteContest(ctx context.Context, id int32) error { return nil } +const addTaskQuery = "INSERT INTO task (problem_id, contest_id, position) VALUES (?, ?,COALESCE(SELECT MAX(position) FROM task WHERE contest_id = ? ,0) + 1) RETURNING id" + +func (r *ContestRepository) AddTask(ctx context.Context, contestId int32, problem_id int32) (int32, error) { + query := r.db.Rebind(addTaskQuery) + rows, err := r.db.QueryxContext(ctx, query, problem_id, contestId, contestId) + if err != nil { + return 0, handlePgErr(err) + } + defer rows.Close() + var id int32 + rows.Next() + err = rows.Scan(&id) + if err != nil { + return 0, handlePgErr(err) + } + return id, nil +} + +const deleteTaskQuery = "DELETE FROM task WHERE id=?" + +func (r *ContestRepository) DeleteTask(ctx context.Context, taskId int32) error { + query := r.db.Rebind(deleteTaskQuery) + _, err := r.db.ExecContext(ctx, query, taskId) + if err != nil { + return handlePgErr(err) + } + return nil +} + +const addParticipantQuery = "INSERT INTO participant (user_id ,contest_id, name) VALUES (?, ?, ?) RETURNING id" + +func (r *ContestRepository) AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error) { + query := r.db.Rebind(addParticipantQuery) + name := "" + rows, err := r.db.QueryxContext(ctx, query, contestId, userId, name) + if err != nil { + return 0, handlePgErr(err) + } + 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 participant WHERE id=?" + +func (r *ContestRepository) DeleteParticipant(ctx context.Context, participantId int32) error { + query := r.db.Rebind(deleteParticipantQuery) + _, err := r.db.ExecContext(ctx, query, participantId) + if err != nil { + return handlePgErr(err) + } + return nil +} + func handlePgErr(err error) error { var pgErr *pgconn.PgError if !errors.As(err, &pgErr) { diff --git a/internal/contests/repository/pg_repository_test.go b/internal/contests/repository/pg_repository_test.go index beee36a..be2cccd 100644 --- a/internal/contests/repository/pg_repository_test.go +++ b/internal/contests/repository/pg_repository_test.go @@ -56,3 +56,99 @@ func TestContestRepository_DeleteContest(t *testing.T) { require.NoError(t, err) }) } + +func TestContestRepository_AddTask(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + defer sqlxDB.Close() + + contestRepo := NewContestRepository(sqlxDB, zap.NewNop()) + + t.Run("valid task additional", func(t *testing.T) { + taskId := int32(1) + contestId := int32(1) + + rows := sqlmock.NewRows([]string{"id"}).AddRow(1) + + mock.ExpectQuery(sqlxDB.Rebind(addTaskQuery)).WithArgs(taskId, contestId, contestId).WillReturnRows(rows) + + id, err := contestRepo.AddTask(context.Background(), contestId, taskId) + + require.NoError(t, err) + require.Equal(t, int32(1), id) + + }) +} + +func TestContestRepository_DeleteTask(t *testing.T) { + t.Parallel() + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + defer sqlxDB.Close() + contestRepo := NewContestRepository(sqlxDB, zap.NewNop()) + t.Run("valid task deletion", func(t *testing.T) { + id := int32(1) + rows := sqlmock.NewResult(1, 1) + + mock.ExpectExec(sqlxDB.Rebind(deleteTaskQuery)).WithArgs(id).WillReturnResult(rows) + + err = contestRepo.DeleteTask(context.Background(), id) + require.NoError(t, err) + }) +} + +func TestContestRepository_AddParticipant(t *testing.T) { + t.Parallel() + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + defer sqlxDB.Close() + contestRepo := NewContestRepository(sqlxDB, zap.NewNop()) + + t.Run("valid participant addition", func(t *testing.T) { + contestId := int32(1) + userId := int32(1) + name := "" + + rows := sqlmock.NewRows([]string{"id"}).AddRow(1) + + mock.ExpectQuery(sqlxDB.Rebind(addParticipantQuery)).WithArgs(contestId, userId, name).WillReturnRows(rows) + + id, err := contestRepo.AddParticipant(context.Background(), contestId, userId) + + require.NoError(t, err) + require.Equal(t, int32(1), id) + }) +} + +func TestContestRepository_DeleteParticipant(t *testing.T) { + t.Parallel() + db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) + require.NoError(t, err) + defer db.Close() + + sqlxDB := sqlx.NewDb(db, "sqlmock") + defer sqlxDB.Close() + + contestRepo := NewContestRepository(sqlxDB, zap.NewNop()) + + t.Run("valid participant deletion", func(t *testing.T) { + id := int32(1) + rows := sqlmock.NewResult(1, 1) + + mock.ExpectExec(sqlxDB.Rebind(deleteParticipantQuery)).WithArgs(id).WillReturnResult(rows) + + err = contestRepo.DeleteParticipant(context.Background(), id) + require.NoError(t, err) + }) +} diff --git a/internal/contests/usecase.go b/internal/contests/usecase.go index c076721..7892518 100644 --- a/internal/contests/usecase.go +++ b/internal/contests/usecase.go @@ -9,4 +9,8 @@ type ContestUseCase interface { CreateContest(ctx context.Context, title string) (int32, error) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) DeleteContest(ctx context.Context, id int32) error + AddTask(ctx context.Context, contestId int32, taskId int32) (int32, error) + DeleteTask(ctx context.Context, taskId int32) error + AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error) + DeleteParticipant(ctx context.Context, participantId int32) error } diff --git a/internal/contests/usecase/usecase.go b/internal/contests/usecase/usecase.go index 5437e17..633bab3 100644 --- a/internal/contests/usecase/usecase.go +++ b/internal/contests/usecase/usecase.go @@ -29,3 +29,19 @@ func (uc *ContestUseCase) ReadContestById(ctx context.Context, id int32) (*model func (uc *ContestUseCase) DeleteContest(ctx context.Context, id int32) error { return uc.contestRepo.DeleteContest(ctx, id) } + +func (uc *ContestUseCase) AddTask(ctx context.Context, contestId int32, taskId int32) (id int32, err error) { + return uc.contestRepo.AddTask(ctx, contestId, taskId) +} + +func (uc *ContestUseCase) DeleteTask(ctx context.Context, taskId int32) error { + return uc.contestRepo.DeleteTask(ctx, taskId) +} + +func (uc *ContestUseCase) AddParticipant(ctx context.Context, contestId int32, userId int32) (id int32, err error) { + return uc.contestRepo.AddParticipant(ctx, contestId, userId) +} + +func (uc *ContestUseCase) DeleteParticipant(ctx context.Context, participantId int32) error { + return uc.contestRepo.DeleteParticipant(ctx, participantId) +}