resolve conflicts
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"datarush/internal/task/config"
|
||||||
|
"datarush/internal/task/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := server.New(cfg)
|
||||||
|
|
||||||
|
if err := srv.Start(); err != nil {
|
||||||
|
log.Fatalf("failed to start server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
|
||||||
|
log.Println("shutting down task server...")
|
||||||
|
srv.Stop()
|
||||||
|
log.Println("task server stopped")
|
||||||
|
}
|
||||||
+35
-5
@@ -28,6 +28,10 @@ services:
|
|||||||
restart: false
|
restart: false
|
||||||
condition: service_started
|
condition: service_started
|
||||||
required: true
|
required: true
|
||||||
|
task:
|
||||||
|
restart: false
|
||||||
|
condition: service_started
|
||||||
|
required: true
|
||||||
env_file:
|
env_file:
|
||||||
- path: ./infrastructure/gw/.env.template
|
- path: ./infrastructure/gw/.env.template
|
||||||
required: true
|
required: true
|
||||||
@@ -109,6 +113,35 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
shm_size: 4mb
|
shm_size: 4mb
|
||||||
|
|
||||||
|
task:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile
|
||||||
|
args:
|
||||||
|
SERVICE: task
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
restart: false
|
||||||
|
condition: service_healthy
|
||||||
|
required: true
|
||||||
|
redis:
|
||||||
|
restart: false
|
||||||
|
condition: service_healthy
|
||||||
|
required: true
|
||||||
|
auth:
|
||||||
|
restart: false
|
||||||
|
condition: service_started
|
||||||
|
required: true
|
||||||
|
env_file:
|
||||||
|
- path: ./infrastructure/task/.env.template
|
||||||
|
required: true
|
||||||
|
- path: ./infrastructure/task/.env
|
||||||
|
required: false
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
restart: unless-stopped
|
||||||
|
shm_size: 4mb
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: docker.io/nginx:1.29-alpine
|
image: docker.io/nginx:1.29-alpine
|
||||||
configs:
|
configs:
|
||||||
@@ -149,7 +182,7 @@ services:
|
|||||||
- path: ./infrastructure/postgres/.env
|
- path: ./infrastructure/postgres/.env
|
||||||
required: false
|
required: false
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "pg_isready", "--dbname=postgres" ]
|
test: ["CMD", "pg_isready", "--dbname=postgres"]
|
||||||
interval: 1m30s
|
interval: 1m30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
@@ -258,7 +291,7 @@ services:
|
|||||||
image: docker.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
|
image: docker.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||||
command: server --console-address ":9001"
|
command: server --console-address ":9001"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "mc", "ready", "local" ]
|
test: ["CMD", "mc", "ready", "local"]
|
||||||
interval: 1m30s
|
interval: 1m30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
@@ -288,7 +321,6 @@ services:
|
|||||||
source: minio_data
|
source: minio_data
|
||||||
target: /data
|
target: /data
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
@@ -298,14 +330,12 @@ networks:
|
|||||||
enable_ipv6: true
|
enable_ipv6: true
|
||||||
internal: false
|
internal: false
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
pgadmin_data:
|
pgadmin_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
minio_data:
|
minio_data:
|
||||||
|
|
||||||
|
|
||||||
configs:
|
configs:
|
||||||
nginx_config:
|
nginx_config:
|
||||||
file: ./infrastructure/nginx/nginx.conf
|
file: ./infrastructure/nginx/nginx.conf
|
||||||
|
|||||||
@@ -138,8 +138,13 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
<<<<<<< HEAD
|
||||||
|
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
|
||||||
|
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
|
=======
|
||||||
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
|
>>>>>>> origin/feature/task
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
|
|
||||||
"datarush/internal/competition/config"
|
"datarush/internal/competition/config"
|
||||||
grpcHandlers "datarush/internal/competition/handler/grpc"
|
grpcHandlers "datarush/internal/competition/handler/grpc"
|
||||||
"datarush/internal/competition/middleware"
|
|
||||||
"datarush/internal/competition/repository/postgres"
|
"datarush/internal/competition/repository/postgres"
|
||||||
"datarush/internal/competition/service"
|
"datarush/internal/competition/service"
|
||||||
authpb "datarush/pkg/api/auth"
|
authpb "datarush/pkg/api/auth"
|
||||||
pb "datarush/pkg/api/competition"
|
pb "datarush/pkg/api/competition"
|
||||||
|
"datarush/pkg/interceptor"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
@@ -82,7 +82,7 @@ func (s *Server) Start() error {
|
|||||||
|
|
||||||
func (s *Server) registerGRPCServices() error {
|
func (s *Server) registerGRPCServices() error {
|
||||||
authClient := authpb.NewAuthServiceClient(s.authConn)
|
authClient := authpb.NewAuthServiceClient(s.authConn)
|
||||||
authInterceptor := middleware.NewAuthInterceptor(authClient)
|
authInterceptor := interceptor.NewAuthInterceptor(authClient)
|
||||||
|
|
||||||
s.grpcServer = grpc.NewServer(
|
s.grpcServer = grpc.NewServer(
|
||||||
grpc.UnaryInterceptor(authInterceptor.Unary()),
|
grpc.UnaryInterceptor(authInterceptor.Unary()),
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
GRPCPort int
|
||||||
|
GRPCEnableReflection bool
|
||||||
|
HTTPPort int
|
||||||
|
LogLevel string
|
||||||
|
DBHost string
|
||||||
|
DBPort int
|
||||||
|
DBUser string
|
||||||
|
DBPassword string
|
||||||
|
DBName string
|
||||||
|
AuthSvcAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
_ = godotenv.Load()
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
GRPCPort: mustGetInt("TASK_GRPC_PORT", 50053),
|
||||||
|
GRPCEnableReflection: mustGetBool("TASK_GRPC_ENABLE_REFLECTION", false),
|
||||||
|
HTTPPort: mustGetInt("TASK_HTTP_PORT", 8082),
|
||||||
|
LogLevel: getEnv("LOG_LEVEL", "info"),
|
||||||
|
DBHost: getEnv("POSTGRES_HOST", "localhost"),
|
||||||
|
DBPort: mustGetInt("POSTGRES_PORT", 5432),
|
||||||
|
DBUser: getEnv("POSTGRES_USERNAME", "postgres"),
|
||||||
|
DBPassword: getEnv("POSTGRES_PASSWORD", "postgres"),
|
||||||
|
DBName: getEnv("POSTGRES_DATABASE", "postgres"),
|
||||||
|
AuthSvcAddr: getEnv("AUTH_SVC_ADDR", "localhost:50051"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, def string) string {
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustGetInt(key string, def int) int {
|
||||||
|
val := getEnv(key, strconv.Itoa(def))
|
||||||
|
n, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid int for %s: %v", key, err)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustGetBool(key string, def bool) bool {
|
||||||
|
val := getEnv(key, strconv.FormatBool(def))
|
||||||
|
b, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid bool for %s: %v", key, err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) BuildPostgresConnStr() string {
|
||||||
|
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
||||||
|
c.DBHost, c.DBPort, c.DBUser, c.DBPassword, c.DBName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) BuildPostgresDSN() string {
|
||||||
|
return fmt.Sprintf("postgresql://%s:%s@%s/%s?sslmode=disable",
|
||||||
|
c.DBUser, c.DBPassword, net.JoinHostPort(c.DBHost, strconv.Itoa(c.DBPort)), c.DBName)
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/task"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskService interface {
|
||||||
|
CreateTask(ctx context.Context, req *pb.Task) (*pb.Task, error)
|
||||||
|
GetTask(ctx context.Context, req *pb.GetTaskRequest) (*pb.Task, error)
|
||||||
|
EditTask(ctx context.Context, req *pb.Task) (*pb.Task, error)
|
||||||
|
DeleteTask(ctx context.Context, req *pb.DeleteTaskRequest) (*emptypb.Empty, error)
|
||||||
|
ListCompetitionTasks(ctx context.Context, req *pb.ListCompetitionTasksRequest) (*pb.ListCompetitionTasksResponse, error)
|
||||||
|
GetTaskAttachments(ctx context.Context, req *pb.GetTaskAttachmentsRequest) (*pb.GetTaskAttachmentsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskHandler struct {
|
||||||
|
pb.UnimplementedTaskServiceServer
|
||||||
|
service TaskService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTaskHandler(service TaskService) *TaskHandler {
|
||||||
|
return &TaskHandler{service: service}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) CreateTask(ctx context.Context, req *pb.Task) (*pb.Task, error) {
|
||||||
|
return h.service.CreateTask(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) GetTask(ctx context.Context, req *pb.GetTaskRequest) (*pb.Task, error) {
|
||||||
|
return h.service.GetTask(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) EditTask(ctx context.Context, req *pb.Task) (*pb.Task, error) {
|
||||||
|
return h.service.EditTask(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) DeleteTask(ctx context.Context, req *pb.DeleteTaskRequest) (*emptypb.Empty, error) {
|
||||||
|
return h.service.DeleteTask(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) ListCompetitionTasks(ctx context.Context, req *pb.ListCompetitionTasksRequest) (*pb.ListCompetitionTasksResponse, error) {
|
||||||
|
return h.service.ListCompetitionTasks(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) GetTaskAttachments(ctx context.Context, req *pb.GetTaskAttachmentsRequest) (*pb.GetTaskAttachmentsResponse, error) {
|
||||||
|
return h.service.GetTaskAttachments(ctx, req)
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
|
"datarush/pkg/api/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTaskRepository(db *sqlx.DB) *TaskRepository {
|
||||||
|
return &TaskRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TaskRepository) CreateTask(ctx context.Context, t *task.Task) (*task.Task, error) {
|
||||||
|
query := `INSERT INTO tasks (competition_id, title, description, in_competition_position, max_points, max_attempts, type)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, created_at, updated_at`
|
||||||
|
|
||||||
|
var createdTask task.Task
|
||||||
|
err := r.db.QueryRowxContext(ctx, query, t.CompetitionId, t.Title, t.Description, t.InCompetitionPosition, t.MaxPoints, t.MaxAttempts, t.Type).StructScan(&createdTask)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
createdTask.CompetitionId = t.CompetitionId
|
||||||
|
createdTask.Title = t.Title
|
||||||
|
createdTask.Description = t.Description
|
||||||
|
createdTask.InCompetitionPosition = t.InCompetitionPosition
|
||||||
|
createdTask.MaxPoints = t.MaxPoints
|
||||||
|
createdTask.MaxAttempts = t.MaxAttempts
|
||||||
|
createdTask.Type = t.Type
|
||||||
|
return &createdTask, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TaskRepository) GetTask(ctx context.Context, id string) (*task.Task, error) {
|
||||||
|
var t task.Task
|
||||||
|
err := r.db.GetContext(ctx, &t, "SELECT * FROM tasks WHERE id = $1", id)
|
||||||
|
return &t, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TaskRepository) EditTask(ctx context.Context, t *task.Task) (*task.Task, error) {
|
||||||
|
query := `UPDATE tasks SET title = $1, description = $2, in_competition_position = $3, max_points = $4, max_attempts = $5, type = $6, updated_at = now()
|
||||||
|
WHERE id = $7 RETURNING updated_at`
|
||||||
|
|
||||||
|
var updatedTask task.Task
|
||||||
|
err := r.db.QueryRowxContext(ctx, query, t.Title, t.Description, t.InCompetitionPosition, t.MaxPoints, t.MaxAttempts, t.Type, t.Id).StructScan(&updatedTask)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.UpdatedAt = updatedTask.UpdatedAt
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TaskRepository) DeleteTask(ctx context.Context, id string) error {
|
||||||
|
_, err := r.db.ExecContext(ctx, "DELETE FROM tasks WHERE id = $1", id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TaskRepository) ListCompetitionTasks(ctx context.Context, competitionID string) ([]*task.Task, error) {
|
||||||
|
var tasks []*task.Task
|
||||||
|
err := r.db.SelectContext(ctx, &tasks, "SELECT * FROM tasks WHERE competition_id = $1", competitionID)
|
||||||
|
return tasks, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TaskRepository) GetTaskAttachments(ctx context.Context, taskID string, showPrivate bool) ([]*task.TaskAttachment, error) {
|
||||||
|
var attachments []*task.TaskAttachment
|
||||||
|
query := "SELECT * FROM task_attachments WHERE task_id = $1"
|
||||||
|
args := []interface{}{taskID}
|
||||||
|
|
||||||
|
if !showPrivate {
|
||||||
|
query += " AND is_public = true"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.SelectContext(ctx, &attachments, query, args...)
|
||||||
|
return attachments, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"datarush/internal/task/config"
|
||||||
|
grpcHandlers "datarush/internal/task/handler/grpc"
|
||||||
|
taskPostgresRepo "datarush/internal/task/repository/postgres"
|
||||||
|
"datarush/internal/task/service"
|
||||||
|
authpb "datarush/pkg/api/auth"
|
||||||
|
pb "datarush/pkg/api/task"
|
||||||
|
"datarush/pkg/interceptor"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
httpReadTimeout = 10 * time.Second
|
||||||
|
httpWriteTimeout = 10 * time.Second
|
||||||
|
httpIdleTimeout = 60 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
grpcServer *grpc.Server
|
||||||
|
config *config.Config
|
||||||
|
db *sqlx.DB
|
||||||
|
authConn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.Config) *Server {
|
||||||
|
return &Server{
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
db, err := sqlx.Connect("postgres", s.config.BuildPostgresConnStr())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to postgres: %w", err)
|
||||||
|
}
|
||||||
|
s.db = db
|
||||||
|
|
||||||
|
if err := s.registerGRPCServices(); err != nil {
|
||||||
|
return fmt.Errorf("failed to register gRPC services: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.config.GRPCPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen on grpc port: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("starting gRPC server on port %d", s.config.GRPCPort)
|
||||||
|
if err := s.grpcServer.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve gRPC: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) registerGRPCServices() error {
|
||||||
|
authClient := authpb.NewAuthServiceClient(s.authConn)
|
||||||
|
authInterceptor := interceptor.NewAuthInterceptor(authClient)
|
||||||
|
|
||||||
|
s.grpcServer = grpc.NewServer()
|
||||||
|
|
||||||
|
taskRepo := taskPostgresRepo.NewTaskRepository(s.db)
|
||||||
|
|
||||||
|
taskService := service.NewTaskService(taskRepo)
|
||||||
|
|
||||||
|
taskHandler := grpcHandlers.NewTaskHandler(taskService)
|
||||||
|
pb.RegisterTaskServiceServer(s.grpcServer, taskHandler)
|
||||||
|
|
||||||
|
if s.config.GRPCEnableReflection {
|
||||||
|
reflection.Register(s.grpcServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() {
|
||||||
|
log.Println("shutting down task server...")
|
||||||
|
|
||||||
|
if s.grpcServer != nil {
|
||||||
|
s.grpcServer.GracefulStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.db != nil {
|
||||||
|
if err := s.db.Close(); err != nil {
|
||||||
|
log.Printf("failed to close database: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("task server stopped")
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: internal/task/service/service.go
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -source=internal/task/service/service.go -destination=internal/task/service/mocks/mock_repository.go -package=mocks
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package mocks is a generated GoMock package.
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
task "datarush/pkg/api/task"
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockTaskRepository is a mock of TaskRepository interface.
|
||||||
|
type MockTaskRepository struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockTaskRepositoryMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockTaskRepositoryMockRecorder is the mock recorder for MockTaskRepository.
|
||||||
|
type MockTaskRepositoryMockRecorder struct {
|
||||||
|
mock *MockTaskRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockTaskRepository creates a new mock instance.
|
||||||
|
func NewMockTaskRepository(ctrl *gomock.Controller) *MockTaskRepository {
|
||||||
|
mock := &MockTaskRepository{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockTaskRepositoryMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockTaskRepository) EXPECT() *MockTaskRepositoryMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTask mocks base method.
|
||||||
|
func (m *MockTaskRepository) CreateTask(ctx context.Context, t *task.Task) (*task.Task, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "CreateTask", ctx, t)
|
||||||
|
ret0, _ := ret[0].(*task.Task)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTask indicates an expected call of CreateTask.
|
||||||
|
func (mr *MockTaskRepositoryMockRecorder) CreateTask(ctx, t any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTask", reflect.TypeOf((*MockTaskRepository)(nil).CreateTask), ctx, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTask mocks base method.
|
||||||
|
func (m *MockTaskRepository) DeleteTask(ctx context.Context, id string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteTask", ctx, id)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTask indicates an expected call of DeleteTask.
|
||||||
|
func (mr *MockTaskRepositoryMockRecorder) DeleteTask(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTask", reflect.TypeOf((*MockTaskRepository)(nil).DeleteTask), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTask mocks base method.
|
||||||
|
func (m *MockTaskRepository) EditTask(ctx context.Context, t *task.Task) (*task.Task, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "EditTask", ctx, t)
|
||||||
|
ret0, _ := ret[0].(*task.Task)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTask indicates an expected call of EditTask.
|
||||||
|
func (mr *MockTaskRepositoryMockRecorder) EditTask(ctx, t any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditTask", reflect.TypeOf((*MockTaskRepository)(nil).EditTask), ctx, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTask mocks base method.
|
||||||
|
func (m *MockTaskRepository) GetTask(ctx context.Context, id string) (*task.Task, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetTask", ctx, id)
|
||||||
|
ret0, _ := ret[0].(*task.Task)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTask indicates an expected call of GetTask.
|
||||||
|
func (mr *MockTaskRepositoryMockRecorder) GetTask(ctx, id any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTask", reflect.TypeOf((*MockTaskRepository)(nil).GetTask), ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskAttachments mocks base method.
|
||||||
|
func (m *MockTaskRepository) GetTaskAttachments(ctx context.Context, taskID string, showPrivate bool) ([]*task.TaskAttachment, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetTaskAttachments", ctx, taskID, showPrivate)
|
||||||
|
ret0, _ := ret[0].([]*task.TaskAttachment)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTaskAttachments indicates an expected call of GetTaskAttachments.
|
||||||
|
func (mr *MockTaskRepositoryMockRecorder) GetTaskAttachments(ctx, taskID, showPrivate any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTaskAttachments", reflect.TypeOf((*MockTaskRepository)(nil).GetTaskAttachments), ctx, taskID, showPrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCompetitionTasks mocks base method.
|
||||||
|
func (m *MockTaskRepository) ListCompetitionTasks(ctx context.Context, competitionID string) ([]*task.Task, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ListCompetitionTasks", ctx, competitionID)
|
||||||
|
ret0, _ := ret[0].([]*task.Task)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCompetitionTasks indicates an expected call of ListCompetitionTasks.
|
||||||
|
func (mr *MockTaskRepositoryMockRecorder) ListCompetitionTasks(ctx, competitionID any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCompetitionTasks", reflect.TypeOf((*MockTaskRepository)(nil).ListCompetitionTasks), ctx, competitionID)
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
pb "datarush/pkg/api/task"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskRepository interface {
|
||||||
|
CreateTask(ctx context.Context, t *pb.Task) (*pb.Task, error)
|
||||||
|
GetTask(ctx context.Context, id string) (*pb.Task, error)
|
||||||
|
EditTask(ctx context.Context, t *pb.Task) (*pb.Task, error)
|
||||||
|
DeleteTask(ctx context.Context, id string) error
|
||||||
|
ListCompetitionTasks(ctx context.Context, competitionID string) ([]*pb.Task, error)
|
||||||
|
GetTaskAttachments(ctx context.Context, taskID string, showPrivate bool) ([]*pb.TaskAttachment, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskService struct {
|
||||||
|
repo TaskRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTaskService(repo TaskRepository) *TaskService {
|
||||||
|
return &TaskService{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TaskService) CreateTask(ctx context.Context, req *pb.Task) (*pb.Task, error) {
|
||||||
|
return s.repo.CreateTask(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TaskService) GetTask(ctx context.Context, req *pb.GetTaskRequest) (*pb.Task, error) {
|
||||||
|
return s.repo.GetTask(ctx, req.TaskId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TaskService) EditTask(ctx context.Context, req *pb.Task) (*pb.Task, error) {
|
||||||
|
return s.repo.EditTask(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TaskService) DeleteTask(ctx context.Context, req *pb.DeleteTaskRequest) (*emptypb.Empty, error) {
|
||||||
|
err := s.repo.DeleteTask(ctx, req.TaskId)
|
||||||
|
return &emptypb.Empty{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TaskService) ListCompetitionTasks(ctx context.Context, req *pb.ListCompetitionTasksRequest) (*pb.ListCompetitionTasksResponse, error) {
|
||||||
|
tasks, err := s.repo.ListCompetitionTasks(ctx, req.CompetitionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.ListCompetitionTasksResponse{Tasks: tasks}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TaskService) GetTaskAttachments(ctx context.Context, req *pb.GetTaskAttachmentsRequest) (*pb.GetTaskAttachmentsResponse, error) {
|
||||||
|
showPrivate := false
|
||||||
|
if req.ShowPrivate != nil {
|
||||||
|
showPrivate = *req.ShowPrivate
|
||||||
|
}
|
||||||
|
attachments, err := s.repo.GetTaskAttachments(ctx, req.TaskId, showPrivate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.GetTaskAttachmentsResponse{Attachments: attachments}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"datarush/internal/task/service/mocks"
|
||||||
|
pb "datarush/pkg/api/task"
|
||||||
|
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTaskService(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
mockRepo := mocks.NewMockTaskRepository(ctrl)
|
||||||
|
service := NewTaskService(mockRepo)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("CreateTask", func(t *testing.T) {
|
||||||
|
task := &pb.Task{
|
||||||
|
CompetitionId: "comp1",
|
||||||
|
Title: "Test Task",
|
||||||
|
Description: "This is a test task",
|
||||||
|
InCompetitionPosition: 1,
|
||||||
|
MaxPoints: 100,
|
||||||
|
MaxAttempts: 10,
|
||||||
|
Type: pb.TaskType_TASK_TYPE_INPUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockRepo.EXPECT().CreateTask(ctx, task).Return(task, nil)
|
||||||
|
|
||||||
|
createdTask, err := service.CreateTask(ctx, task)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, task, createdTask)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetTask", func(t *testing.T) {
|
||||||
|
taskID := "task1"
|
||||||
|
req := &pb.GetTaskRequest{TaskId: taskID}
|
||||||
|
expectedTask := &pb.Task{
|
||||||
|
Id: taskID,
|
||||||
|
CompetitionId: "comp1",
|
||||||
|
Title: "Test Task",
|
||||||
|
Description: "This is a test task",
|
||||||
|
InCompetitionPosition: 1,
|
||||||
|
MaxPoints: 100,
|
||||||
|
MaxAttempts: 10,
|
||||||
|
Type: pb.TaskType_TASK_TYPE_INPUT,
|
||||||
|
CreatedAt: timestamppb.Now(),
|
||||||
|
UpdatedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
mockRepo.EXPECT().GetTask(ctx, taskID).Return(expectedTask, nil)
|
||||||
|
|
||||||
|
task, err := service.GetTask(ctx, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedTask, task)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EditTask", func(t *testing.T) {
|
||||||
|
task := &pb.Task{
|
||||||
|
Id: "task1",
|
||||||
|
CompetitionId: "comp1",
|
||||||
|
Title: "Updated Test Task",
|
||||||
|
Description: "This is an updated test task",
|
||||||
|
InCompetitionPosition: 1,
|
||||||
|
MaxPoints: 150,
|
||||||
|
MaxAttempts: 5,
|
||||||
|
Type: pb.TaskType_TASK_TYPE_CHECKER,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockRepo.EXPECT().EditTask(ctx, task).Return(task, nil)
|
||||||
|
|
||||||
|
updatedTask, err := service.EditTask(ctx, task)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, task, updatedTask)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteTask", func(t *testing.T) {
|
||||||
|
taskID := "task1"
|
||||||
|
req := &pb.DeleteTaskRequest{TaskId: taskID}
|
||||||
|
|
||||||
|
mockRepo.EXPECT().DeleteTask(ctx, taskID).Return(nil)
|
||||||
|
|
||||||
|
_, err := service.DeleteTask(ctx, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ListCompetitionTasks", func(t *testing.T) {
|
||||||
|
competitionID := "comp1"
|
||||||
|
req := &pb.ListCompetitionTasksRequest{CompetitionId: competitionID}
|
||||||
|
expectedTasks := []*pb.Task{
|
||||||
|
{Id: "task1", CompetitionId: competitionID, Title: "Task 1"},
|
||||||
|
{Id: "task2", CompetitionId: competitionID, Title: "Task 2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockRepo.EXPECT().ListCompetitionTasks(ctx, competitionID).Return(expectedTasks, nil)
|
||||||
|
|
||||||
|
resp, err := service.ListCompetitionTasks(ctx, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedTasks, resp.Tasks)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetTaskAttachments", func(t *testing.T) {
|
||||||
|
taskID := "task1"
|
||||||
|
showPrivate := true
|
||||||
|
req := &pb.GetTaskAttachmentsRequest{TaskId: taskID, ShowPrivate: &showPrivate}
|
||||||
|
expectedAttachments := []*pb.TaskAttachment{
|
||||||
|
{Id: "att1", FileUrl: "url1", IsPublic: true},
|
||||||
|
{Id: "att2", FileUrl: "url2", IsPublic: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockRepo.EXPECT().GetTaskAttachments(ctx, taskID, showPrivate).Return(expectedAttachments, nil)
|
||||||
|
|
||||||
|
resp, err := service.GetTaskAttachments(ctx, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedAttachments, resp.Attachments)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetTaskAttachments - show public only", func(t *testing.T) {
|
||||||
|
taskID := "task1"
|
||||||
|
showPrivate := false
|
||||||
|
req := &pb.GetTaskAttachmentsRequest{TaskId: taskID, ShowPrivate: &showPrivate}
|
||||||
|
expectedAttachments := []*pb.TaskAttachment{
|
||||||
|
{Id: "att1", FileUrl: "url1", IsPublic: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockRepo.EXPECT().GetTaskAttachments(ctx, taskID, showPrivate).Return(expectedAttachments, nil)
|
||||||
|
|
||||||
|
resp, err := service.GetTaskAttachments(ctx, req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedAttachments, resp.Attachments)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package middleware
|
package interceptor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
Reference in New Issue
Block a user