From acfe7275eb41f59f66acd7a256de73abbe39ae56 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sat, 20 Dec 2025 01:59:53 +0300 Subject: [PATCH] chore(api)!: added new fields to contract --- api/proto/task.proto | 4 ++- internal/gw/domain/models.go | 9 +++-- internal/gw/utils/converter.go | 11 +++++++ .../task/repository/postgres/repository.go | 13 ++++---- internal/task/service/service_test.go | 9 +++-- pkg/api/task/task.pb.go | 33 +++++++++++++++---- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/api/proto/task.proto b/api/proto/task.proto index 73f4453..5d8cf44 100644 --- a/api/proto/task.proto +++ b/api/proto/task.proto @@ -32,6 +32,7 @@ message Task { int32 max_points = 6; int32 max_attempts = 7; TaskType type = 8; + string answer_url = 11; google.protobuf.Timestamp created_at = 9; google.protobuf.Timestamp updated_at = 10; } @@ -39,7 +40,8 @@ message Task { message TaskAttachment { string id = 1; string file_url = 2; - bool is_public = 3; + string mountpoint = 3; + bool is_public = 4; } message GetTaskRequest { diff --git a/internal/gw/domain/models.go b/internal/gw/domain/models.go index 406572a..83b175b 100644 --- a/internal/gw/domain/models.go +++ b/internal/gw/domain/models.go @@ -68,6 +68,7 @@ type TaskRequest struct { InCompetitionPosition int32 `json:"in_competition_position" validate:"required"` MaxPoints int32 `json:"max_points,omitempty"` MaxAttempts int32 `json:"max_attempts,omitempty"` + AnswerURL string `json:"answer_url,omitempty"` Type string `json:"type" validate:"required,oneof=input checker review"` } @@ -79,6 +80,7 @@ type TaskResponse struct { InCompetitionPosition int32 `json:"in_competition_position"` MaxPoints int32 `json:"max_points,omitempty"` MaxAttempts int32 `json:"max_attempts,omitempty"` + AnswerURL string `json:"answer_url,omitempty"` Type string `json:"type"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -89,9 +91,10 @@ type ListTasksResponse struct { } type TaskAttachmentResponse struct { - ID string `json:"id"` - FileURL string `json:"file_url"` - IsPublic bool `json:"is_public"` + ID string `json:"id"` + FileURL string `json:"file_url"` + Mountpoint string `json:"mountpoint,omitempty"` + IsPublic bool `json:"is_public"` } type SubmissionResponse struct { diff --git a/internal/gw/utils/converter.go b/internal/gw/utils/converter.go index 697aecd..5d395f5 100644 --- a/internal/gw/utils/converter.go +++ b/internal/gw/utils/converter.go @@ -49,12 +49,22 @@ func TaskProtoToHTTP(t *taskpb.Task) *domain.TaskResponse { InCompetitionPosition: t.InCompetitionPosition, MaxPoints: t.MaxPoints, MaxAttempts: t.MaxAttempts, + AnswerURL: t.AnswerUrl, Type: TaskTypeToString(t.Type), CreatedAt: t.CreatedAt.AsTime(), UpdatedAt: t.UpdatedAt.AsTime(), } } +func TaskAttachmentProtoToHTTP(a *taskpb.TaskAttachment) *domain.TaskAttachmentResponse { + return &domain.TaskAttachmentResponse{ + ID: a.Id, + FileURL: a.FileUrl, + Mountpoint: a.Mountpoint, + IsPublic: a.IsPublic, + } +} + func SubmissionProtoToHTTP(s *subpb.Submission) *domain.SubmissionResponse { return &domain.SubmissionResponse{ ID: s.Id, @@ -150,6 +160,7 @@ func TaskHTTPToProto(req *domain.TaskRequest) *taskpb.Task { InCompetitionPosition: req.InCompetitionPosition, MaxPoints: req.MaxPoints, MaxAttempts: req.MaxAttempts, + AnswerUrl: req.AnswerURL, Type: StringToTaskType(req.Type), } } diff --git a/internal/task/repository/postgres/repository.go b/internal/task/repository/postgres/repository.go index cec043c..c15d2f7 100644 --- a/internal/task/repository/postgres/repository.go +++ b/internal/task/repository/postgres/repository.go @@ -17,11 +17,11 @@ func NewTaskRepository(db *sqlx.DB) *TaskRepository { } 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` + query := `INSERT INTO tasks (competition_id, title, description, in_competition_position, max_points, max_attempts, type, answer_url) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 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). + err := r.db.QueryRowxContext(ctx, query, t.CompetitionId, t.Title, t.Description, t.InCompetitionPosition, t.MaxPoints, t.MaxAttempts, t.Type, t.AnswerUrl). StructScan(&createdTask) if err != nil { return nil, err @@ -33,6 +33,7 @@ func (r *TaskRepository) CreateTask(ctx context.Context, t *task.Task) (*task.Ta createdTask.MaxPoints = t.MaxPoints createdTask.MaxAttempts = t.MaxAttempts createdTask.Type = t.Type + createdTask.AnswerUrl = t.AnswerUrl return &createdTask, nil } @@ -43,11 +44,11 @@ func (r *TaskRepository) GetTask(ctx context.Context, id string) (*task.Task, er } 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` + query := `UPDATE tasks SET title = $1, description = $2, in_competition_position = $3, max_points = $4, max_attempts = $5, type = $6, answer_url = $7, updated_at = now() + WHERE id = $8 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). + err := r.db.QueryRowxContext(ctx, query, t.Title, t.Description, t.InCompetitionPosition, t.MaxPoints, t.MaxAttempts, t.Type, t.AnswerUrl, t.Id). StructScan(&updatedTask) if err != nil { return nil, err diff --git a/internal/task/service/service_test.go b/internal/task/service/service_test.go index e77de82..9d11373 100644 --- a/internal/task/service/service_test.go +++ b/internal/task/service/service_test.go @@ -30,6 +30,7 @@ func TestTaskService(t *testing.T) { InCompetitionPosition: 1, MaxPoints: 100, MaxAttempts: 10, + AnswerUrl: "https://example.com/answer", Type: pb.TaskType_TASK_TYPE_INPUT, } @@ -51,6 +52,7 @@ func TestTaskService(t *testing.T) { InCompetitionPosition: 1, MaxPoints: 100, MaxAttempts: 10, + AnswerUrl: "https://example.com/answer", Type: pb.TaskType_TASK_TYPE_INPUT, CreatedAt: timestamppb.Now(), UpdatedAt: timestamppb.Now(), @@ -72,6 +74,7 @@ func TestTaskService(t *testing.T) { InCompetitionPosition: 1, MaxPoints: 150, MaxAttempts: 5, + AnswerUrl: "https://example.com/answer-updated", Type: pb.TaskType_TASK_TYPE_CHECKER, } @@ -112,8 +115,8 @@ func TestTaskService(t *testing.T) { showPrivate := true req := &pb.GetTaskAttachmentsRequest{TaskId: taskID, ShowPrivate: &showPrivate} expectedAttachments := []*pb.TaskAttachment{ - {Id: "att1", FileUrl: "url1", IsPublic: true}, - {Id: "att2", FileUrl: "url2", IsPublic: false}, + {Id: "att1", FileUrl: "url1", Mountpoint: "mp1", IsPublic: true}, + {Id: "att2", FileUrl: "url2", Mountpoint: "mp2", IsPublic: false}, } mockRepo.EXPECT().GetTaskAttachments(ctx, taskID, showPrivate).Return(expectedAttachments, nil) @@ -128,7 +131,7 @@ func TestTaskService(t *testing.T) { showPrivate := false req := &pb.GetTaskAttachmentsRequest{TaskId: taskID, ShowPrivate: &showPrivate} expectedAttachments := []*pb.TaskAttachment{ - {Id: "att1", FileUrl: "url1", IsPublic: true}, + {Id: "att1", FileUrl: "url1", Mountpoint: "mp1", IsPublic: true}, } mockRepo.EXPECT().GetTaskAttachments(ctx, taskID, showPrivate).Return(expectedAttachments, nil) diff --git a/pkg/api/task/task.pb.go b/pkg/api/task/task.pb.go index 1e0b9b5..87e97f3 100644 --- a/pkg/api/task/task.pb.go +++ b/pkg/api/task/task.pb.go @@ -85,6 +85,7 @@ type Task struct { MaxPoints int32 `protobuf:"varint,6,opt,name=max_points,json=maxPoints,proto3" json:"max_points,omitempty"` MaxAttempts int32 `protobuf:"varint,7,opt,name=max_attempts,json=maxAttempts,proto3" json:"max_attempts,omitempty"` Type TaskType `protobuf:"varint,8,opt,name=type,proto3,enum=task.TaskType" json:"type,omitempty"` + AnswerUrl string `protobuf:"bytes,11,opt,name=answer_url,json=answerUrl,proto3" json:"answer_url,omitempty"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` unknownFields protoimpl.UnknownFields @@ -177,6 +178,13 @@ func (x *Task) GetType() TaskType { return TaskType_TASK_TYPE_UNSPECIFIED } +func (x *Task) GetAnswerUrl() string { + if x != nil { + return x.AnswerUrl + } + return "" +} + func (x *Task) GetCreatedAt() *timestamppb.Timestamp { if x != nil { return x.CreatedAt @@ -195,7 +203,8 @@ type TaskAttachment struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` FileUrl string `protobuf:"bytes,2,opt,name=file_url,json=fileUrl,proto3" json:"file_url,omitempty"` - IsPublic bool `protobuf:"varint,3,opt,name=is_public,json=isPublic,proto3" json:"is_public,omitempty"` + Mountpoint string `protobuf:"bytes,3,opt,name=mountpoint,proto3" json:"mountpoint,omitempty"` + IsPublic bool `protobuf:"varint,4,opt,name=is_public,json=isPublic,proto3" json:"is_public,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -244,6 +253,13 @@ func (x *TaskAttachment) GetFileUrl() string { return "" } +func (x *TaskAttachment) GetMountpoint() string { + if x != nil { + return x.Mountpoint + } + return "" +} + func (x *TaskAttachment) GetIsPublic() bool { if x != nil { return x.IsPublic @@ -527,7 +543,7 @@ var File_api_proto_task_proto protoreflect.FileDescriptor const file_api_proto_task_proto_rawDesc = "" + "\n" + - "\x14api/proto/task.proto\x12\x04task\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\"\x89\x03\n" + + "\x14api/proto/task.proto\x12\x04task\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\"\xa8\x03\n" + "\x04Task\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12%\n" + "\x0ecompetition_id\x18\x02 \x01(\tR\rcompetitionId\x12\x14\n" + @@ -537,16 +553,21 @@ const file_api_proto_task_proto_rawDesc = "" + "\n" + "max_points\x18\x06 \x01(\x05R\tmaxPoints\x12!\n" + "\fmax_attempts\x18\a \x01(\x05R\vmaxAttempts\x12\"\n" + - "\x04type\x18\b \x01(\x0e2\x0e.task.TaskTypeR\x04type\x129\n" + + "\x04type\x18\b \x01(\x0e2\x0e.task.TaskTypeR\x04type\x12\x1d\n" + + "\n" + + "answer_url\x18\v \x01(\tR\tanswerUrl\x129\n" + "\n" + "created_at\x18\t \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + "\n" + "updated_at\x18\n" + - " \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"X\n" + + " \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"x\n" + "\x0eTaskAttachment\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x19\n" + - "\bfile_url\x18\x02 \x01(\tR\afileUrl\x12\x1b\n" + - "\tis_public\x18\x03 \x01(\bR\bisPublic\")\n" + + "\bfile_url\x18\x02 \x01(\tR\afileUrl\x12\x1e\n" + + "\n" + + "mountpoint\x18\x03 \x01(\tR\n" + + "mountpoint\x12\x1b\n" + + "\tis_public\x18\x04 \x01(\bR\bisPublic\")\n" + "\x0eGetTaskRequest\x12\x17\n" + "\atask_id\x18\x01 \x01(\tR\x06taskId\",\n" + "\x11DeleteTaskRequest\x12\x17\n" +