resolve conflicts
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
# Change all vars before going to production and remove all comments (!)
|
|
||||||
# Below all environment variables and default values
|
|
||||||
|
|
||||||
GRPC_PORT=50051
|
|
||||||
GRPC_ENABLE_REFLECTION=false
|
|
||||||
HTTP_HANDLER_ENABLE=false
|
|
||||||
HTTP_PORT=8080
|
|
||||||
LOG_LEVEL=info
|
|
||||||
POSTGRES_HOST=localhost
|
|
||||||
POSTGRES_PORT=5432
|
|
||||||
POSTGRES_USERNAME=postgres
|
|
||||||
POSTGRES_PASSWORD=postgres
|
|
||||||
POSTGRES_DATABASE=postgres
|
|
||||||
REDIS_URI=redis://localhost:6379
|
|
||||||
+39
-18
@@ -185,14 +185,6 @@ cache:
|
|||||||
when: manual
|
when: manual
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
|
||||||
build-auth:
|
|
||||||
<<: *build-config
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: $BASE_IMAGE_NAME/auth
|
|
||||||
CONTAINERFILE: Containerfile
|
|
||||||
BUILDTARGET: runtime
|
|
||||||
SERVICE_NAME: auth
|
|
||||||
|
|
||||||
build-migrate:
|
build-migrate:
|
||||||
<<: *build-config
|
<<: *build-config
|
||||||
variables:
|
variables:
|
||||||
@@ -201,6 +193,22 @@ build-migrate:
|
|||||||
BUILDTARGET: runtime
|
BUILDTARGET: runtime
|
||||||
SERVICE_NAME: migrate
|
SERVICE_NAME: migrate
|
||||||
|
|
||||||
|
build-gw:
|
||||||
|
<<: *build-config
|
||||||
|
variables:
|
||||||
|
IMAGE_NAME: $BASE_IMAGE_NAME/gw
|
||||||
|
CONTAINERFILE: Containerfile
|
||||||
|
BUILDTARGET: runtime
|
||||||
|
SERVICE_NAME: gw
|
||||||
|
|
||||||
|
build-auth:
|
||||||
|
<<: *build-config
|
||||||
|
variables:
|
||||||
|
IMAGE_NAME: $BASE_IMAGE_NAME/auth
|
||||||
|
CONTAINERFILE: Containerfile
|
||||||
|
BUILDTARGET: runtime
|
||||||
|
SERVICE_NAME: auth
|
||||||
|
|
||||||
golangci-lint:
|
golangci-lint:
|
||||||
stage: lint
|
stage: lint
|
||||||
image: docker.io/golangci/golangci-lint:latest-alpine
|
image: docker.io/golangci/golangci-lint:latest-alpine
|
||||||
@@ -249,14 +257,6 @@ go-test:
|
|||||||
sast-filesystem:
|
sast-filesystem:
|
||||||
<<: *trivy-fs-scan
|
<<: *trivy-fs-scan
|
||||||
|
|
||||||
sast-image-auth:
|
|
||||||
<<: *trivy-image-scan
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: $BASE_IMAGE_NAME/auth
|
|
||||||
IMAGE_TYPE: auth
|
|
||||||
dependencies:
|
|
||||||
- build-auth
|
|
||||||
|
|
||||||
sast-image-migrate:
|
sast-image-migrate:
|
||||||
<<: *trivy-image-scan
|
<<: *trivy-image-scan
|
||||||
variables:
|
variables:
|
||||||
@@ -265,16 +265,37 @@ sast-image-migrate:
|
|||||||
dependencies:
|
dependencies:
|
||||||
- build-migrate
|
- build-migrate
|
||||||
|
|
||||||
tag-auth:
|
sast-image-gw:
|
||||||
<<: *tag-config
|
<<: *trivy-image-scan
|
||||||
|
variables:
|
||||||
|
IMAGE_NAME: $BASE_IMAGE_NAME/gw
|
||||||
|
IMAGE_TYPE: gw
|
||||||
|
dependencies:
|
||||||
|
- build-gw
|
||||||
|
|
||||||
|
sast-image-auth:
|
||||||
|
<<: *trivy-image-scan
|
||||||
variables:
|
variables:
|
||||||
IMAGE_NAME: $BASE_IMAGE_NAME/auth
|
IMAGE_NAME: $BASE_IMAGE_NAME/auth
|
||||||
|
IMAGE_TYPE: auth
|
||||||
|
dependencies:
|
||||||
|
- build-auth
|
||||||
|
|
||||||
tag-migrate:
|
tag-migrate:
|
||||||
<<: *tag-config
|
<<: *tag-config
|
||||||
variables:
|
variables:
|
||||||
IMAGE_NAME: $BASE_IMAGE_NAME/migrate
|
IMAGE_NAME: $BASE_IMAGE_NAME/migrate
|
||||||
|
|
||||||
|
tag-gw:
|
||||||
|
<<: *tag-config
|
||||||
|
variables:
|
||||||
|
IMAGE_NAME: $BASE_IMAGE_NAME/gw
|
||||||
|
|
||||||
|
tag-auth:
|
||||||
|
<<: *tag-config
|
||||||
|
variables:
|
||||||
|
IMAGE_NAME: $BASE_IMAGE_NAME/auth
|
||||||
|
|
||||||
# webhook-backend-deploy:
|
# webhook-backend-deploy:
|
||||||
# <<: *webhook-config
|
# <<: *webhook-config
|
||||||
# stage: deploy
|
# stage: deploy
|
||||||
|
|||||||
+2
-2
@@ -44,7 +44,7 @@ ARG SERVICE
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=build --chown=1000:1000 /out/${SERVICE} /app/bin/${SERVICE}
|
COPY --from=build --chown=1000:1000 /out/${SERVICE} /app/bin
|
||||||
|
|
||||||
EXPOSE 8080 50051
|
EXPOSE 8080 50051
|
||||||
|
|
||||||
@@ -54,6 +54,6 @@ LABEL org.opencontainers.image.created="${BUILD_TIME}" \
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
ENTRYPOINT ["/app/bin/${SERVICE}"]
|
ENTRYPOINT ["/app/bin"]
|
||||||
|
|
||||||
CMD []
|
CMD []
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
# Go parameters
|
|
||||||
GOCMD=go
|
GOCMD=go
|
||||||
GOBUILD=$(GOCMD) build -trimpath -ldflags="-s -w"
|
GOBUILD=$(GOCMD) build -trimpath -ldflags="-s -w"
|
||||||
GOTEST=$(GOCMD) test
|
GOTEST=$(GOCMD) test
|
||||||
GODOWNLOAD=$(GOCMD) mod download
|
|
||||||
BINARY_NAME=datarush
|
BASE_BINARY_NAME=datarush
|
||||||
MIGRATE_BINARY_NAME=datarush-migrate
|
GW_BINARY_NAME=$(BASE_BINARY_NAME)-gw
|
||||||
|
MIGRATE_BINARY_NAME=$(BASE_BINARY_NAME)-migrate
|
||||||
|
AUTH_BINARY_NAME=$(BASE_BINARY_NAME)-auth
|
||||||
|
|
||||||
BINARY_DIR=bin
|
BINARY_DIR=bin
|
||||||
|
|
||||||
# Protobuf parameters
|
|
||||||
PROTOC=protoc
|
PROTOC=protoc
|
||||||
PROTO_DIR=api/proto
|
PROTO_DIR=api/proto
|
||||||
PROTO_FILE=$(PROTO_DIR)/auth.proto $(PROTO_DIR)/competition.proto $(PROTO_DIR)/results.proto $(PROTO_DIR)/review.proto $(PROTO_DIR)/submission.proto $(PROTO_DIR)/task.proto $(PROTO_DIR)/user.proto
|
PROTO_FILE=${PROTO_DIR}/achievements.proto $(PROTO_DIR)/auth.proto $(PROTO_DIR)/competition.proto $(PROTO_DIR)/results.proto $(PROTO_DIR)/review.proto $(PROTO_DIR)/submission.proto $(PROTO_DIR)/task.proto $(PROTO_DIR)/user.proto
|
||||||
PROTO_OUT=.
|
PROTO_OUT=.
|
||||||
|
|
||||||
.PHONY: install i generate gen generate-gw test build run migrate lint fmt format clean help codegen examples
|
.PHONY: install i generate gen generate-gw test build run migrate lint fmt format clean help codegen examples
|
||||||
|
|
||||||
install:
|
install:
|
||||||
$(GODOWNLOAD)
|
$(GOCMD) mod download
|
||||||
|
$(GOCMD) mod tidy
|
||||||
|
|
||||||
i: install
|
i: install
|
||||||
|
|
||||||
@@ -35,16 +37,23 @@ generate-gw:
|
|||||||
test:
|
test:
|
||||||
$(GOTEST) ./...
|
$(GOTEST) ./...
|
||||||
|
|
||||||
build:
|
build-gw:
|
||||||
$(GOBUILD) -o ./$(BINARY_DIR)/$(BINARY_NAME) ./cmd/server
|
$(GOBUILD) -o ./$(BINARY_DIR)/$(GW_BINARY_NAME) ./cmd/gw
|
||||||
chmod +x ./$(BINARY_DIR)/$(BINARY_NAME)
|
chmod +x ./$(BINARY_DIR)/$(GW_BINARY_NAME)
|
||||||
|
|
||||||
build-migrate:
|
build-migrate:
|
||||||
$(GOBUILD) -o ./$(BINARY_DIR)/$(MIGRATE_BINARY_NAME) ./cmd/migrate
|
$(GOBUILD) -o ./$(BINARY_DIR)/$(MIGRATE_BINARY_NAME) ./cmd/migrate
|
||||||
chmod +x ./$(BINARY_DIR)/$(MIGRATE_BINARY_NAME)
|
chmod +x ./$(BINARY_DIR)/$(MIGRATE_BINARY_NAME)
|
||||||
|
|
||||||
run: build
|
build-auth:
|
||||||
./$(BINARY_DIR)/$(BINARY_NAME)
|
$(GOBUILD) -o ./$(BINARY_DIR)/$(AUTH_BINARY_NAME) ./cmd/auth
|
||||||
|
chmod +x ./$(BINARY_DIR)/$(AUTH_BINARY_NAME)
|
||||||
|
|
||||||
|
build: build-gw build-migrate build-auth
|
||||||
|
|
||||||
|
run:
|
||||||
|
./$(BINARY_DIR)/$(AUTH_BINARY_NAME) &
|
||||||
|
./$(BINARY_DIR)/$(GW_BINARY_NAME)
|
||||||
|
|
||||||
migrate: build-migrate
|
migrate: build-migrate
|
||||||
@cmd=$(word 2,$(MAKECMDGOALS)); \
|
@cmd=$(word 2,$(MAKECMDGOALS)); \
|
||||||
@@ -65,28 +74,15 @@ format: fmt
|
|||||||
clean:
|
clean:
|
||||||
rm -rf bin/*
|
rm -rf bin/*
|
||||||
|
|
||||||
codegen:
|
|
||||||
@if [ -z "$(SERVICE)" ]; then \
|
|
||||||
echo "Usage: make codegen SERVICE=<service-name>"; \
|
|
||||||
echo "Example: make codegen SERVICE=auth"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
$(GOBUILD) -o ./$(BINARY_DIR)/codegen ./cmd/codegen
|
|
||||||
./$(BINARY_DIR)/codegen $(SERVICE) ./
|
|
||||||
|
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Available commands:"
|
@echo "Help:"
|
||||||
@echo " install - Install all deps using go mod download"
|
@echo " install - Install all deps using go mod download"
|
||||||
@echo " i"
|
@echo " i"
|
||||||
@echo " generate - Generate gRPC code"
|
@echo " generate - Generate gRPC code"
|
||||||
@echo " gen"
|
@echo " gen"
|
||||||
@echo " protoc"
|
@echo " protoc"
|
||||||
@echo " codegen - Generate service template"
|
|
||||||
@echo " Usage: make codegen SERVICE=<name>"
|
|
||||||
@echo " examples - Show integration examples"
|
|
||||||
@echo " test - Run tests"
|
@echo " test - Run tests"
|
||||||
@echo " build - Build the binary"
|
@echo " build - Build all binaries"
|
||||||
@echo " run - Run the application"
|
@echo " run - Run the application"
|
||||||
@echo " lint - Run golangci-lint linter"
|
@echo " lint - Run golangci-lint linter"
|
||||||
@echo " format - Run golangci-lint formatter"
|
@echo " format - Run golangci-lint formatter"
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ message GetCompetitionResultsRequest {
|
|||||||
string competition_id = 3;
|
string competition_id = 3;
|
||||||
}
|
}
|
||||||
message GetCompetitionResultsResponse {
|
message GetCompetitionResultsResponse {
|
||||||
repeated UserResult results = 1;
|
int32 total_count = 1;
|
||||||
int32 total_count = 2;
|
int32 next_page_token = 2;
|
||||||
int32 next_page_token = 3;
|
|
||||||
|
repeated UserResult results = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetUserCompetitionResultsRequest {
|
message GetUserCompetitionResultsRequest {
|
||||||
|
|||||||
+21
-7
@@ -8,6 +8,7 @@ import "google/protobuf/empty.proto";
|
|||||||
option go_package = "pkg/api/review";
|
option go_package = "pkg/api/review";
|
||||||
|
|
||||||
service ReviewService {
|
service ReviewService {
|
||||||
|
rpc ValidateReviewToken(ValidateReviewTokenRequest) returns (ValidateReviewTokenResponse);
|
||||||
rpc ListSubmissionsForReview(ListSubmissionsForReviewRequest) returns (ListSubmissionsForReviewResponse);
|
rpc ListSubmissionsForReview(ListSubmissionsForReviewRequest) returns (ListSubmissionsForReviewResponse);
|
||||||
rpc GetSubmissionForReview(GetSubmissionForReviewRequest) returns (SubmissionForReview);
|
rpc GetSubmissionForReview(GetSubmissionForReviewRequest) returns (SubmissionForReview);
|
||||||
rpc EvaluateSubmission(EvaluateSubmissionRequest) returns (EvaluateSubmissionResponse);
|
rpc EvaluateSubmission(EvaluateSubmissionRequest) returns (EvaluateSubmissionResponse);
|
||||||
@@ -48,11 +49,21 @@ message SubmissionForReview {
|
|||||||
optional google.protobuf.Timestamp checked_at = 8;
|
optional google.protobuf.Timestamp checked_at = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ValidateReviewTokenRequest {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
message ValidateReviewTokenResponse {
|
||||||
|
bool is_valid = 1;
|
||||||
|
string reviewer_id = 2;
|
||||||
|
string competition_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message ListSubmissionsForReviewRequest {
|
message ListSubmissionsForReviewRequest {
|
||||||
int32 page_size = 1;
|
int32 page_size = 1;
|
||||||
int32 page_token = 2;
|
int32 page_token = 2;
|
||||||
|
|
||||||
optional ReviewStatus status = 3;
|
string token = 3;
|
||||||
|
optional ReviewStatus status = 4;
|
||||||
}
|
}
|
||||||
message ListSubmissionsForReviewResponse {
|
message ListSubmissionsForReviewResponse {
|
||||||
int32 total_count = 1;
|
int32 total_count = 1;
|
||||||
@@ -62,14 +73,16 @@ message ListSubmissionsForReviewResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message GetSubmissionForReviewRequest {
|
message GetSubmissionForReviewRequest {
|
||||||
string submission_id = 1;
|
string token = 1;
|
||||||
|
string submission_id = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvaluateSubmissionRequest {
|
message EvaluateSubmissionRequest {
|
||||||
string submission_id = 1;
|
string token = 1;
|
||||||
int32 earned_points = 2;
|
string submission_id = 2;
|
||||||
string reviewer_comment = 3;
|
int32 earned_points = 3;
|
||||||
repeated CriteriaMark marks = 4;
|
string reviewer_comment = 4;
|
||||||
|
repeated CriteriaMark marks = 5;
|
||||||
}
|
}
|
||||||
message EvaluateSubmissionResponse {
|
message EvaluateSubmissionResponse {
|
||||||
string submission_id = 1;
|
string submission_id = 1;
|
||||||
@@ -78,5 +91,6 @@ message EvaluateSubmissionResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ReleaseSubmissionRequest {
|
message ReleaseSubmissionRequest {
|
||||||
string submission_id = 1;
|
string token = 1;
|
||||||
|
string submission_id = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,16 @@ message Submission {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message SubmitTaskRequest {
|
message SubmitTaskRequest {
|
||||||
string competition_id = 1;
|
string user_id = 1;
|
||||||
string task_id = 2;
|
string competition_id = 2;
|
||||||
string file_url = 3;
|
string task_id = 3;
|
||||||
|
string file_url = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetSubmissionsHistoryRequest {
|
message GetSubmissionsHistoryRequest {
|
||||||
string competition_id = 1;
|
string user_id = 1;
|
||||||
string task_id = 2;
|
string competition_id = 2;
|
||||||
|
string task_id = 3;
|
||||||
}
|
}
|
||||||
message GetSubmissionsHistoryResponse {
|
message GetSubmissionsHistoryResponse {
|
||||||
repeated Submission submissions = 1;
|
repeated Submission submissions = 1;
|
||||||
@@ -62,8 +64,8 @@ message ListSubmissionsRequest {
|
|||||||
SubmissionStatus status = 6;
|
SubmissionStatus status = 6;
|
||||||
}
|
}
|
||||||
message ListSubmissionsResponse {
|
message ListSubmissionsResponse {
|
||||||
int32 total_count = 2;
|
int32 total_count = 1;
|
||||||
int32 next_page_token = 3;
|
int32 next_page_token = 2;
|
||||||
|
|
||||||
repeated Submission submissions = 1;
|
repeated Submission submissions = 3;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"datarush/internal/competition/config"
|
||||||
|
"datarush/internal/competition/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 competition server...")
|
||||||
|
srv.Stop()
|
||||||
|
log.Println("competition server stopped")
|
||||||
|
}
|
||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"datarush/internal/gw/config"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/handler"
|
||||||
|
"datarush/internal/gw/middleware"
|
||||||
|
"datarush/internal/gw/router"
|
||||||
|
"datarush/internal/gw/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
clientFactory := grpc_client.NewClientFactory()
|
||||||
|
defer clientFactory.Close()
|
||||||
|
|
||||||
|
authClient, err := grpc_client.NewAuthClient(ctx, cfg.GRPC.AuthServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create auth client: %v", err)
|
||||||
|
authClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
userClient, err := grpc_client.NewUserClient(ctx, cfg.GRPC.UserServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create user client: %v", err)
|
||||||
|
userClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
competitionClient, err := grpc_client.NewCompetitionClient(ctx, cfg.GRPC.CompetitionServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create competition client: %v", err)
|
||||||
|
competitionClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
taskClient, err := grpc_client.NewTaskClient(ctx, cfg.GRPC.TaskServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create task client: %v", err)
|
||||||
|
taskClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
submissionClient, err := grpc_client.NewSubmissionClient(ctx, cfg.GRPC.SubmissionServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create submission client: %v", err)
|
||||||
|
submissionClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resultsClient, err := grpc_client.NewResultsClient(ctx, cfg.GRPC.ResultsServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create results client: %v", err)
|
||||||
|
resultsClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reviewClient, err := grpc_client.NewReviewClient(ctx, cfg.GRPC.ReviewServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create review client: %v", err)
|
||||||
|
reviewClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
achievementsClient, err := grpc_client.NewAchievementsClient(ctx, cfg.GRPC.AchievementsServiceAddr, clientFactory)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Warning: Failed to create achievements client: %v", err)
|
||||||
|
achievementsClient = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s3Storage, err := storage.NewS3Storage(storage.S3Config{
|
||||||
|
AccessKeyID: cfg.S3.AccessKeyID,
|
||||||
|
SecretAccessKey: cfg.S3.SecretAccessKey,
|
||||||
|
Region: cfg.S3.Region,
|
||||||
|
Bucket: cfg.S3.Bucket,
|
||||||
|
Endpoint: cfg.S3.Endpoint,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create S3 storage: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authMiddleware := middleware.NewAuthMiddleware(authClient)
|
||||||
|
|
||||||
|
var authHandler handler.AuthHandler
|
||||||
|
if authClient != nil {
|
||||||
|
authHandler = *handler.NewAuthHandler(authClient, userClient)
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: AuthHandler not initialized due to missing authClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
var competitionHandler handler.CompetitionHandler
|
||||||
|
if competitionClient != nil {
|
||||||
|
competitionHandler = *handler.NewCompetitionHandler(competitionClient, userClient)
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: CompetitionHandler not initialized due to missing competitionClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskHandler handler.TaskHandler
|
||||||
|
if taskClient != nil {
|
||||||
|
taskHandler = *handler.NewTaskHandler(taskClient)
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: TaskHandler not initialized due to missing taskClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
var submissionHandler handler.SubmissionHandler
|
||||||
|
if submissionClient != nil {
|
||||||
|
submissionHandler = *handler.NewSubmissionHandler(submissionClient, s3Storage)
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: SubmissionHandler not initialized due to missing submissionClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultsHandler handler.ResultsHandler
|
||||||
|
if resultsClient != nil {
|
||||||
|
resultsHandler = *handler.NewResultsHandler(resultsClient)
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: ResultsHandler not initialized due to missing resultsClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
var reviewHandler handler.ReviewHandler
|
||||||
|
if reviewClient != nil {
|
||||||
|
reviewHandler = *handler.NewReviewHandler(reviewClient)
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: ReviewHandler not initialized due to missing reviewClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
var achievementsHandler handler.AchievementsHandler
|
||||||
|
if achievementsClient != nil {
|
||||||
|
achievementsHandler = *handler.NewAchievementsHandler(achievementsClient)
|
||||||
|
} else {
|
||||||
|
log.Printf("Warning: AchievementsHandler not initialized due to missing achievementsClient")
|
||||||
|
}
|
||||||
|
|
||||||
|
pingHandler := handler.NewPingHandler()
|
||||||
|
|
||||||
|
rt := router.NewRouter(
|
||||||
|
&authHandler,
|
||||||
|
&competitionHandler,
|
||||||
|
&taskHandler,
|
||||||
|
&submissionHandler,
|
||||||
|
&resultsHandler,
|
||||||
|
&reviewHandler,
|
||||||
|
&achievementsHandler,
|
||||||
|
pingHandler,
|
||||||
|
authMiddleware,
|
||||||
|
)
|
||||||
|
|
||||||
|
httpHandler := rt.Setup()
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port)
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: httpHandler,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
IdleTimeout: 60 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Printf("Starting API Gateway on %s", addr)
|
||||||
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
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 server...")
|
||||||
|
|
||||||
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := server.Shutdown(shutdownCtx); err != nil {
|
||||||
|
log.Fatalf("Server forced to shutdown: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Server exited")
|
||||||
|
}
|
||||||
+1
-1
@@ -7,7 +7,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"datarush/internal/lms/config"
|
"datarush/internal/migrate/config"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
}
|
|
||||||
+152
-51
@@ -1,42 +1,12 @@
|
|||||||
name: datarush
|
name: datarush
|
||||||
|
|
||||||
services:
|
services:
|
||||||
auth:
|
gw:
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Containerfile
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
restart: false
|
|
||||||
condition: service_healthy
|
|
||||||
required: true
|
|
||||||
env_file:
|
|
||||||
- path: ./infrastructure/auth/.env.template
|
|
||||||
required: true
|
|
||||||
- path: ./infrastructure/auth/.env
|
|
||||||
required: false
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
target: 8081
|
|
||||||
published: 13443
|
|
||||||
host_ip: 127.0.0.1
|
|
||||||
protocol: tcp
|
|
||||||
app_protocol: http
|
|
||||||
- name: grpc
|
|
||||||
target: 50052
|
|
||||||
published: 13444
|
|
||||||
host_ip: 127.0.0.1
|
|
||||||
protocol: tcp
|
|
||||||
app_protocol: http
|
|
||||||
networks:
|
|
||||||
- default
|
|
||||||
restart: unless-stopped
|
|
||||||
shm_size: 4mb
|
|
||||||
|
|
||||||
core:
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Containerfile
|
dockerfile: Containerfile
|
||||||
|
args:
|
||||||
|
SERVICE: gw
|
||||||
depends_on:
|
depends_on:
|
||||||
migrate:
|
migrate:
|
||||||
restart: false
|
restart: false
|
||||||
@@ -54,23 +24,15 @@ services:
|
|||||||
restart: false
|
restart: false
|
||||||
condition: service_started
|
condition: service_started
|
||||||
required: true
|
required: true
|
||||||
env_file:
|
competition:
|
||||||
- path: ./infrastructure/core/.env.template
|
restart: false
|
||||||
|
condition: service_started
|
||||||
required: true
|
required: true
|
||||||
- path: ./infrastructure/core/.env
|
env_file:
|
||||||
ports:
|
- path: ./infrastructure/gw/.env.template
|
||||||
- name: http
|
required: true
|
||||||
target: 8080
|
- path: ./infrastructure/gw/.env
|
||||||
published: 13440
|
required: false
|
||||||
host_ip: 127.0.0.1
|
|
||||||
protocol: tcp
|
|
||||||
app_protocol: http
|
|
||||||
- name: grpc
|
|
||||||
target: 50051
|
|
||||||
published: 13441
|
|
||||||
host_ip: 127.0.0.1
|
|
||||||
protocol: tcp
|
|
||||||
app_protocol: http
|
|
||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -80,20 +42,102 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Containerfile
|
dockerfile: Containerfile
|
||||||
|
args:
|
||||||
|
SERVICE: migrate
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
restart: false
|
restart: false
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
required: true
|
required: true
|
||||||
env_file:
|
env_file:
|
||||||
- path: ./infrastructure/core/.env.template
|
- path: ./infrastructure/migrate/.env.template
|
||||||
required: true
|
required: true
|
||||||
- path: ./infrastructure/core/.env
|
- path: ./infrastructure/migrate/.env
|
||||||
|
required: false
|
||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
restart: no
|
restart: no
|
||||||
shm_size: 4mb
|
shm_size: 4mb
|
||||||
|
|
||||||
|
auth:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile
|
||||||
|
args:
|
||||||
|
SERVICE: auth
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
restart: false
|
||||||
|
condition: service_healthy
|
||||||
|
required: true
|
||||||
|
env_file:
|
||||||
|
- path: ./infrastructure/auth/.env.template
|
||||||
|
required: true
|
||||||
|
- path: ./infrastructure/auth/.env
|
||||||
|
required: false
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
restart: unless-stopped
|
||||||
|
shm_size: 4mb
|
||||||
|
|
||||||
|
competition:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile
|
||||||
|
args:
|
||||||
|
SERVICE: competition
|
||||||
|
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/competition/.env.template
|
||||||
|
required: true
|
||||||
|
- path: ./infrastructure/competition/.env
|
||||||
|
required: false
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
restart: unless-stopped
|
||||||
|
shm_size: 4mb
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: docker.io/nginx:1.29-alpine
|
||||||
|
configs:
|
||||||
|
- source: nginx_config
|
||||||
|
target: /etc/nginx/nginx.conf
|
||||||
|
depends_on:
|
||||||
|
gw:
|
||||||
|
restart: false
|
||||||
|
condition: service_started
|
||||||
|
required: true
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-O", "-", "http://localhost:80/healthz"]
|
||||||
|
interval: 1m30s
|
||||||
|
timeout: 5s
|
||||||
|
start_period: 5s
|
||||||
|
start_interval: 2s
|
||||||
|
retries: 5
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
target: 80
|
||||||
|
published: 8080
|
||||||
|
host_ip: 0.0.0.0
|
||||||
|
protocol: tcp
|
||||||
|
app_protocol: http
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
restart: unless-stopped
|
||||||
|
shm_size: 4mb
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: docker.io/postgres:17-alpine
|
image: docker.io/postgres:17-alpine
|
||||||
configs:
|
configs:
|
||||||
@@ -190,6 +234,60 @@ services:
|
|||||||
target: /data
|
target: /data
|
||||||
read_only: false
|
read_only: false
|
||||||
|
|
||||||
|
minio-init:
|
||||||
|
image: docker.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||||
|
entrypoint: >
|
||||||
|
/bin/sh -c "
|
||||||
|
sleep 5;
|
||||||
|
/usr/bin/mc alias set minio http://minio:9000 admin password;
|
||||||
|
/usr/bin/mc mb minio/datarush;
|
||||||
|
exit 0;
|
||||||
|
"
|
||||||
|
env_file:
|
||||||
|
- path: ./infrastructure/minio/.env.template
|
||||||
|
required: true
|
||||||
|
- path: ./infrastructure/minio/.env
|
||||||
|
required: false
|
||||||
|
restart: no
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: minio_data
|
||||||
|
target: /data
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: docker.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||||
|
command: server --console-address ":9001"
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "mc", "ready", "local" ]
|
||||||
|
interval: 1m30s
|
||||||
|
timeout: 5s
|
||||||
|
start_period: 5s
|
||||||
|
start_interval: 2s
|
||||||
|
retries: 5
|
||||||
|
env_file:
|
||||||
|
- path: ./infrastructure/minio/.env.template
|
||||||
|
required: true
|
||||||
|
- path: ./infrastructure/minio/.env
|
||||||
|
required: false
|
||||||
|
ports:
|
||||||
|
- name: api
|
||||||
|
target: 9000
|
||||||
|
published: 8005
|
||||||
|
host_ip: 127.0.0.1
|
||||||
|
protocol: tcp
|
||||||
|
app_protocol: http
|
||||||
|
- name: console
|
||||||
|
target: 9001
|
||||||
|
published: 8006
|
||||||
|
host_ip: 127.0.0.1
|
||||||
|
protocol: tcp
|
||||||
|
app_protocol: http
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: minio_data
|
||||||
|
target: /data
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
@@ -205,9 +303,12 @@ volumes:
|
|||||||
postgres_data:
|
postgres_data:
|
||||||
pgadmin_data:
|
pgadmin_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
|
minio_data:
|
||||||
|
|
||||||
|
|
||||||
configs:
|
configs:
|
||||||
|
nginx_config:
|
||||||
|
file: ./infrastructure/nginx/nginx.conf
|
||||||
postgres_config:
|
postgres_config:
|
||||||
file: ./infrastructure/postgres/postgresql.conf
|
file: ./infrastructure/postgres/postgresql.conf
|
||||||
pgadmin_servers_config:
|
pgadmin_servers_config:
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ toolchain go1.24.9
|
|||||||
require (
|
require (
|
||||||
github.com/Masterminds/squirrel v1.5.4
|
github.com/Masterminds/squirrel v1.5.4
|
||||||
github.com/go-playground/validator/v10 v10.28.0
|
github.com/go-playground/validator/v10 v10.28.0
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.32.6
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.0
|
github.com/golang-migrate/migrate/v4 v4.19.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/mux v1.7.4
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
@@ -17,18 +22,31 @@ require (
|
|||||||
github.com/redis/go-redis/v9 v9.16.0
|
github.com/redis/go-redis/v9 v9.16.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
go.uber.org/mock v0.6.0
|
go.uber.org/mock v0.6.0
|
||||||
|
github.com/redis/go-redis/v9 v9.17.2
|
||||||
golang.org/x/crypto v0.42.0
|
golang.org/x/crypto v0.42.0
|
||||||
google.golang.org/grpc v1.76.0
|
google.golang.org/grpc v1.76.0
|
||||||
google.golang.org/protobuf v1.36.10
|
google.golang.org/protobuf v1.36.10
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||||
|
github.com/aws/smithy-go v1.24.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
|||||||
@@ -6,6 +6,50 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8
|
|||||||
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 h1:tcFliCWne+zOuUfKNRn8JdFBuWPDuISDH08wD2ULkhk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8/go.mod h1:JTnlBSot91steJeti4ryyu/tLd4Sk84O5W22L7O2EQU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 h1:ZSIPAkAsCCjYrhqfw2+lNzWDzxzHXEckFkTePL5RSWQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14/go.mod h1:AyGgqiKv9ECM6IZeNQtdT8NnMvUb3/2wokeq2Fgryto=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 h1:BBYoNQt2kUZUUK4bIPsKrCcjVPUMNsgQpNAwhznK/zo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18/go.mod h1:NS55eQ4YixUJPTC+INxi2/jCqe1y2Uw3rnh9wEOVJxY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17/go.mod h1:4nYOrY41Lrbk2170/BGkcJKBhws9Pfn8MG3aGqjjeFI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 h1:HfVVR1vItaG6le+Bpw6P4midjBDMKnjMyZnw9MXYUcE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17/go.mod h1:YqMdV+gEKCQ59NrB7rzrJdALeBIsYiVi8Inj3+KcqHI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11/go.mod h1:fmgDANqTUCxciViKl9hb/zD5LFbvPINFRgWhDbR+vZo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||||
|
github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||||
|
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||||
|
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@@ -17,6 +61,7 @@ github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ
|
|||||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
@@ -39,14 +84,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
|
||||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
@@ -57,10 +94,13 @@ github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9Knoi
|
|||||||
github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0=
|
github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||||
|
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -68,6 +108,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
|||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
@@ -80,8 +122,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq
|
|||||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
@@ -100,10 +140,11 @@ 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=
|
||||||
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
|
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||||
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
@@ -148,5 +189,6 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
AUTH_GRPC_PORT=50052
|
AUTH_GRPC_PORT=50051
|
||||||
AUTH_HTTP_PORT=8081
|
AUTH_HTTP_PORT=8080
|
||||||
AUTH_GRPC_ENABLE_REFLECTION=true
|
AUTH_GRPC_ENABLE_REFLECTION=true
|
||||||
|
|
||||||
POSTGRES_HOST=postgres
|
POSTGRES_HOST=postgres
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
COMPETITION_GRPC_PORT=50051
|
||||||
|
COMPETITION_GRPC_ENABLE_REFLECTION=true
|
||||||
|
COMPETITION_HTTP_PORT=8080
|
||||||
|
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_USERNAME=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
POSTGRES_DATABASE=postgres
|
||||||
|
|
||||||
|
AUTH_SVC_ADDR=auth:50051
|
||||||
|
|
||||||
|
REDIS_ADDR=redis:6379
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
REDIS_DB=0
|
||||||
|
|
||||||
|
CACHE_ENABLED=true
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
GRPC_ENABLE_REFLECTION=true
|
|
||||||
HTTP_HANDLER_ENABLE=true
|
|
||||||
|
|
||||||
POSTGRES_HOST=postgres
|
|
||||||
POSTGRES_PORT=5432
|
|
||||||
POSTGRES_USERNAME=postgres
|
|
||||||
POSTGRES_PASSWORD=postgres
|
|
||||||
POSTGRES_DATABASE=postgres
|
|
||||||
|
|
||||||
REDIS_URI=redis://redis:6379
|
|
||||||
AUTH_GRPC_ADDR=auth:50052
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
SERVER_PORT=8080
|
||||||
|
SERVER_HOST=0.0.0.0
|
||||||
|
|
||||||
|
AUTH_SERVICE_ADDR=auth:50051
|
||||||
|
USER_SERVICE_ADDR=user:50051
|
||||||
|
COMPETITION_SERVICE_ADDR=competition:50051
|
||||||
|
TASK_SERVICE_ADDR=task:50051
|
||||||
|
SUBMISSION_SERVICE_ADDR=submission:50051
|
||||||
|
RESULTS_SERVICE_ADDR=results:50051
|
||||||
|
REVIEW_SERVICE_ADDR=review:50051
|
||||||
|
ACHIEVEMENTS_SERVICE_ADDR=achievements:50051
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID=admin
|
||||||
|
AWS_SECRET_ACCESS_KEY=password
|
||||||
|
AWS_REGION=
|
||||||
|
S3_BUCKET=datarush
|
||||||
|
S3_ENDPOINT=http://localhost:9000
|
||||||
|
|
||||||
|
JWT_SECRET=your_jwt_secret_here
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
POSTGRES_HOST=localhost
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_USERNAME=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
POSTGRES_DATABASE=postgres
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
MINIO_ROOT_USER=admin
|
||||||
|
MINIO_ROOT_PASSWORD=password
|
||||||
|
MINIO_VOLUMES=/data
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
ssl_session_cache shared:SSL:50m;
|
||||||
|
ssl_session_timeout 1d;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
|
||||||
|
ssl_ecdh_curve X25519:secp521r1:secp384r1;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
resolver 127.0.0.11 valid=30s;
|
||||||
|
resolver_timeout 5s;
|
||||||
|
server_names_hash_bucket_size 128;
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-XSS-Protection "0";
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||||
|
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https:; font-src 'self' https:; frame-ancestors 'none'; form-action 'self'; object-src 'none'; base-uri 'self';" always;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 75;
|
||||||
|
client_body_timeout 15;
|
||||||
|
client_header_timeout 15;
|
||||||
|
reset_timedout_connection on;
|
||||||
|
send_timeout 15;
|
||||||
|
|
||||||
|
client_body_buffer_size 128k;
|
||||||
|
client_header_buffer_size 4k;
|
||||||
|
client_max_body_size 100M;
|
||||||
|
large_client_header_buffers 4 16k;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types
|
||||||
|
text/plain
|
||||||
|
text/css
|
||||||
|
text/xml
|
||||||
|
text/javascript
|
||||||
|
application/json
|
||||||
|
application/javascript
|
||||||
|
application/x-javascript
|
||||||
|
application/xml
|
||||||
|
application/xml+rss
|
||||||
|
font/woff
|
||||||
|
font/woff2
|
||||||
|
image/svg+xml;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
upstream gw {
|
||||||
|
zone gw 64k;
|
||||||
|
server gw:8080 resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
zone minio 64k;
|
||||||
|
server minio:9000 resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream minio-ui {
|
||||||
|
zone minio-ui 64k;
|
||||||
|
server minio:9001 resolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://gw;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name minio;
|
||||||
|
|
||||||
|
http2 on;
|
||||||
|
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
client_max_body_size 0;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /minio/ui/ {
|
||||||
|
rewrite ^/minio/ui/(.*) /$1 break;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
real_ip_header X-Real-IP;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio-ui;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ func Load() (*Config, error) {
|
|||||||
_ = godotenv.Load()
|
_ = godotenv.Load()
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
GRPCPort: mustGetInt("AUTH_GRPC_PORT", 50052),
|
GRPCPort: mustGetInt("AUTH_GRPC_PORT", 50051),
|
||||||
GRPCEnableReflection: mustGetBool("AUTH_GRPC_ENABLE_REFLECTION", false),
|
GRPCEnableReflection: mustGetBool("AUTH_GRPC_ENABLE_REFLECTION", false),
|
||||||
HTTPPort: mustGetInt("AUTH_HTTP_PORT", 8081),
|
HTTPPort: mustGetInt("AUTH_HTTP_PORT", 8081),
|
||||||
LogLevel: getEnv("LOG_LEVEL", "info"),
|
LogLevel: getEnv("LOG_LEVEL", "info"),
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ func (h *AuthHandler) SignIn(ctx context.Context, req *pb.SignInRequest) (*pb.Si
|
|||||||
return &pb.SignInResponse{Token: token}, nil
|
return &pb.SignInResponse{Token: token}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) ValidateToken(ctx context.Context, req *pb.ValidateTokenRequest) (*pb.ValidateTokenResponse, error) {
|
func (h *AuthHandler) ValidateToken(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ValidateTokenRequest,
|
||||||
|
) (*pb.ValidateTokenResponse, error) {
|
||||||
token := req.Token
|
token := req.Token
|
||||||
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SignUpRequest struct {
|
type SignUpRequest struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
Username string `json:"username" binding:"required"`
|
Username string `json:"username" binding:"required"`
|
||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignInRequest struct {
|
type SignInRequest struct {
|
||||||
Email string `json:"email" binding:"required,email"`
|
Email string `json:"email" binding:"required,email"`
|
||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -10,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"datarush/internal/auth/config"
|
"datarush/internal/auth/config"
|
||||||
grpcHandlers "datarush/internal/auth/handler/grpc"
|
grpcHandlers "datarush/internal/auth/handler/grpc"
|
||||||
httpHandlers "datarush/internal/auth/handler/http"
|
|
||||||
authPostgresRepo "datarush/internal/auth/repository/postgres"
|
authPostgresRepo "datarush/internal/auth/repository/postgres"
|
||||||
"datarush/internal/auth/service"
|
"datarush/internal/auth/service"
|
||||||
|
|
||||||
@@ -64,10 +62,6 @@ func (s *Server) Start() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := s.startHTTPServer(); err != nil {
|
|
||||||
return fmt.Errorf("failed to start HTTP server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,60 +82,9 @@ func (s *Server) registerGRPCServices() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) startHTTPServer() error {
|
|
||||||
userRepo := authPostgresRepo.NewUserRepository(s.db)
|
|
||||||
|
|
||||||
authService := service.NewAuthService(userRepo, s.config.JWTSecret)
|
|
||||||
|
|
||||||
authHandler := httpHandlers.NewAuthHandler(authService)
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
|
|
||||||
mux.HandleFunc("/api/v1/sign-up", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
authHandler.SignUp(w, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc("/api/v1/sign-in", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
authHandler.SignIn(w, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
s.httpServer = &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%d", s.config.HTTPPort),
|
|
||||||
Handler: mux,
|
|
||||||
ReadTimeout: httpReadTimeout,
|
|
||||||
WriteTimeout: httpWriteTimeout,
|
|
||||||
IdleTimeout: httpIdleTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Printf("starting HTTP server on port %d", s.config.HTTPPort)
|
|
||||||
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatalf("failed to start HTTP server: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Stop() {
|
func (s *Server) Stop() {
|
||||||
log.Println("shutting down auth server...")
|
log.Println("shutting down auth server...")
|
||||||
|
|
||||||
if s.httpServer != nil {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := s.httpServer.Shutdown(ctx); err != nil {
|
|
||||||
log.Printf("failed to shutdown HTTP server: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.grpcServer != nil {
|
if s.grpcServer != nil {
|
||||||
s.grpcServer.GracefulStop()
|
s.grpcServer.GracefulStop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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
|
||||||
|
RedisAddr string
|
||||||
|
RedisPassword string
|
||||||
|
RedisDB int
|
||||||
|
CacheEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
_ = godotenv.Load()
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
GRPCPort: mustGetInt("COMPETITION_GRPC_PORT", 50051),
|
||||||
|
GRPCEnableReflection: mustGetBool("COMPETITION_GRPC_ENABLE_REFLECTION", false),
|
||||||
|
HTTPPort: mustGetInt("COMPETITION_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"),
|
||||||
|
RedisAddr: getEnv("REDIS_ADDR", "localhost:6379"),
|
||||||
|
RedisPassword: getEnv("REDIS_PASSWORD", ""),
|
||||||
|
RedisDB: mustGetInt("REDIS_DB", 0),
|
||||||
|
CacheEnabled: mustGetBool("CACHE_ENABLED", true),
|
||||||
|
}, 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,63 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/competition"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompetitionService interface {
|
||||||
|
CreateCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error)
|
||||||
|
GetCompetition(ctx context.Context, req *pb.GetCompetitionRequest) (*pb.Competition, error)
|
||||||
|
EditCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error)
|
||||||
|
DeleteCompetition(ctx context.Context, req *pb.DeleteCompetitionRequest) (*emptypb.Empty, error)
|
||||||
|
ListCompetitions(ctx context.Context, req *pb.ListCompetitionsRequest) (*pb.ListCompetitionsResponse, error)
|
||||||
|
ChangeCompetitionState(ctx context.Context, req *pb.ChangeCompetitionStateRequest) (*pb.Competition, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompetitionHandler struct {
|
||||||
|
pb.UnimplementedCompetitionServiceServer
|
||||||
|
service CompetitionService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompetitionHandler(service CompetitionService) *CompetitionHandler {
|
||||||
|
return &CompetitionHandler{service: service}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) CreateCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error) {
|
||||||
|
return h.service.CreateCompetition(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) GetCompetition(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.GetCompetitionRequest,
|
||||||
|
) (*pb.Competition, error) {
|
||||||
|
return h.service.GetCompetition(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) EditCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error) {
|
||||||
|
return h.service.EditCompetition(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) DeleteCompetition(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.DeleteCompetitionRequest,
|
||||||
|
) (*emptypb.Empty, error) {
|
||||||
|
return h.service.DeleteCompetition(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) ListCompetitions(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ListCompetitionsRequest,
|
||||||
|
) (*pb.ListCompetitionsResponse, error) {
|
||||||
|
return h.service.ListCompetitions(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) ChangeCompetitionState(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ChangeCompetitionStateRequest,
|
||||||
|
) (*pb.Competition, error) {
|
||||||
|
return h.service.ChangeCompetitionState(ctx, req)
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
authpb "datarush/pkg/api/auth"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserIDKey contextKey = "user_id"
|
||||||
|
authHeader = "authorization"
|
||||||
|
bearerScheme = "bearer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthInterceptor struct {
|
||||||
|
authClient authpb.AuthServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthInterceptor(authClient authpb.AuthServiceClient) *AuthInterceptor {
|
||||||
|
return &AuthInterceptor{authClient: authClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *AuthInterceptor) Unary() grpc.UnaryServerInterceptor {
|
||||||
|
return func(
|
||||||
|
ctx context.Context,
|
||||||
|
req interface{},
|
||||||
|
info *grpc.UnaryServerInfo,
|
||||||
|
handler grpc.UnaryHandler,
|
||||||
|
) (interface{}, error) {
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "metadata is not provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeaders := md.Get(authHeader)
|
||||||
|
if len(authHeaders) == 0 {
|
||||||
|
return nil, status.Error(codes.Unauthenticated, "authorization token is not provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
header := authHeaders[0]
|
||||||
|
parts := strings.Split(header, " ")
|
||||||
|
if len(parts) != 2 || !strings.EqualFold(parts[0], bearerScheme) {
|
||||||
|
return nil, status.Errorf(codes.Unauthenticated, "invalid authorization header format")
|
||||||
|
}
|
||||||
|
|
||||||
|
token := parts[1]
|
||||||
|
|
||||||
|
validateResp, err := i.authClient.ValidateToken(ctx, &authpb.ValidateTokenRequest{
|
||||||
|
Token: token,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unauthenticated, "failed to validate token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newCtx := context.WithValue(ctx, UserIDKey, validateResp.GetUserId())
|
||||||
|
|
||||||
|
return handler(newCtx, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/competition"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListCompetitionsOptions struct {
|
||||||
|
Page int
|
||||||
|
PageSize int
|
||||||
|
State *pb.CompetitionState
|
||||||
|
IsParticipating *bool
|
||||||
|
SearchQuery *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompetitionRepository interface {
|
||||||
|
Create(ctx context.Context, competition *pb.Competition) (*pb.Competition, error)
|
||||||
|
Get(ctx context.Context, id uuid.UUID) (*pb.Competition, error)
|
||||||
|
Update(ctx context.Context, competition *pb.Competition) (*pb.Competition, error)
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, opts ListCompetitionsOptions) ([]*pb.Competition, int, error)
|
||||||
|
ChangeState(ctx context.Context, id uuid.UUID, state pb.CompetitionState) (*pb.Competition, error)
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"datarush/internal/competition/repository"
|
||||||
|
pb "datarush/pkg/api/competition"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
competitionCachePrefix = "competition:"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompetitionRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
redisClient *redis.Client
|
||||||
|
cacheEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompetitionRepository(
|
||||||
|
db *sqlx.DB,
|
||||||
|
redisClient *redis.Client,
|
||||||
|
cacheEnabled bool,
|
||||||
|
) repository.CompetitionRepository {
|
||||||
|
return &CompetitionRepository{
|
||||||
|
db: db,
|
||||||
|
redisClient: redisClient,
|
||||||
|
cacheEnabled: cacheEnabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompetitionRepository) cacheKey(id string) string {
|
||||||
|
return competitionCachePrefix + id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompetitionRepository) Create(ctx context.Context, c *pb.Competition) (*pb.Competition, error) {
|
||||||
|
query := `INSERT INTO competitions (state, title, description, image_url, start_time, end_time, type, participation_type)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at, updated_at`
|
||||||
|
|
||||||
|
var createdCompetition pb.Competition
|
||||||
|
err := r.db.QueryRowxContext(ctx, query,
|
||||||
|
c.State,
|
||||||
|
c.Title,
|
||||||
|
c.Description,
|
||||||
|
c.ImageUrl,
|
||||||
|
c.StartTime.AsTime(),
|
||||||
|
c.EndTime.AsTime(),
|
||||||
|
c.Type,
|
||||||
|
c.ParticipationType,
|
||||||
|
).StructScan(&createdCompetition)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Id = createdCompetition.Id
|
||||||
|
c.CreatedAt = createdCompetition.CreatedAt
|
||||||
|
c.UpdatedAt = createdCompetition.UpdatedAt
|
||||||
|
|
||||||
|
if r.cacheEnabled {
|
||||||
|
data, err := json.Marshal(c)
|
||||||
|
if err == nil {
|
||||||
|
r.redisClient.Set(ctx, r.cacheKey(c.Id), data, 10*time.Minute).Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompetitionRepository) Get(ctx context.Context, id uuid.UUID) (*pb.Competition, error) {
|
||||||
|
if r.cacheEnabled {
|
||||||
|
val, err := r.redisClient.Get(ctx, r.cacheKey(id.String())).Result()
|
||||||
|
if err == nil {
|
||||||
|
var competition pb.Competition
|
||||||
|
if json.Unmarshal([]byte(val), &competition) == nil {
|
||||||
|
return &competition, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `SELECT id, state, title, description, image_url, start_time, end_time, type, participation_type, created_at, updated_at FROM competitions WHERE id = $1`
|
||||||
|
var competition pb.Competition
|
||||||
|
err := r.db.GetContext(ctx, &competition, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.cacheEnabled {
|
||||||
|
data, err := json.Marshal(&competition)
|
||||||
|
if err == nil {
|
||||||
|
r.redisClient.Set(ctx, r.cacheKey(id.String()), data, 10*time.Minute).Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &competition, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompetitionRepository) Update(ctx context.Context, c *pb.Competition) (*pb.Competition, error) {
|
||||||
|
query := `UPDATE competitions SET
|
||||||
|
state = $2, title = $3, description = $4, image_url = $5, start_time = $6, end_time = $7, type = $8, participation_type = $9, updated_at = now()
|
||||||
|
WHERE id = $1 RETURNING updated_at`
|
||||||
|
|
||||||
|
var updatedCompetition pb.Competition
|
||||||
|
err := r.db.QueryRowxContext(ctx, query,
|
||||||
|
c.Id,
|
||||||
|
c.State,
|
||||||
|
c.Title,
|
||||||
|
c.Description,
|
||||||
|
c.ImageUrl,
|
||||||
|
c.StartTime.AsTime(),
|
||||||
|
c.EndTime.AsTime(),
|
||||||
|
c.Type,
|
||||||
|
c.ParticipationType,
|
||||||
|
).StructScan(&updatedCompetition)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.UpdatedAt = updatedCompetition.UpdatedAt
|
||||||
|
|
||||||
|
if r.cacheEnabled {
|
||||||
|
r.redisClient.Del(ctx, r.cacheKey(c.Id)).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompetitionRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
query := `DELETE FROM competitions WHERE id = $1`
|
||||||
|
_, err := r.db.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.cacheEnabled {
|
||||||
|
r.redisClient.Del(ctx, r.cacheKey(id.String())).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompetitionRepository) List(
|
||||||
|
ctx context.Context,
|
||||||
|
opts repository.ListCompetitionsOptions,
|
||||||
|
) ([]*pb.Competition, int, error) {
|
||||||
|
var args []interface{}
|
||||||
|
var whereClauses []string
|
||||||
|
argId := 1
|
||||||
|
|
||||||
|
if opts.State != nil {
|
||||||
|
whereClauses = append(whereClauses, fmt.Sprintf("state = $%d", argId))
|
||||||
|
args = append(args, *opts.State)
|
||||||
|
argId++
|
||||||
|
}
|
||||||
|
if opts.SearchQuery != nil {
|
||||||
|
whereClauses = append(whereClauses, fmt.Sprintf("title ILIKE $%d", argId))
|
||||||
|
args = append(args, "%"+*opts.SearchQuery+"%")
|
||||||
|
argId++
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.IsParticipating != nil {
|
||||||
|
userID, ok := ctx.Value("user_id").(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, 0, fmt.Errorf("user not authenticated or user_id not in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *opts.IsParticipating {
|
||||||
|
whereClauses = append(
|
||||||
|
whereClauses,
|
||||||
|
fmt.Sprintf("id IN (SELECT competition_id FROM user_competitions WHERE user_id = $%d)", argId),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
whereClauses = append(whereClauses, fmt.Sprintf("id NOT IN (SELECT competition_id FROM user_competitions WHERE user_id = $%d)", argId))
|
||||||
|
}
|
||||||
|
args = append(args, userID)
|
||||||
|
argId++
|
||||||
|
}
|
||||||
|
|
||||||
|
where := ""
|
||||||
|
if len(whereClauses) > 0 {
|
||||||
|
where = "WHERE " + strings.Join(whereClauses, " AND ")
|
||||||
|
}
|
||||||
|
|
||||||
|
countQuery := "SELECT COUNT(*) FROM competitions " + where
|
||||||
|
var total int
|
||||||
|
if err := r.db.GetContext(ctx, &total, countQuery, args...); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(
|
||||||
|
`SELECT id, state, title, description, image_url, start_time, end_time, type, participation_type, created_at, updated_at FROM competitions %s ORDER BY created_at DESC LIMIT $%d OFFSET $%d`,
|
||||||
|
where,
|
||||||
|
argId,
|
||||||
|
argId+1,
|
||||||
|
)
|
||||||
|
args = append(args, opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||||
|
|
||||||
|
var competitions []*pb.Competition
|
||||||
|
err := r.db.SelectContext(ctx, &competitions, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return competitions, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompetitionRepository) ChangeState(
|
||||||
|
ctx context.Context,
|
||||||
|
id uuid.UUID,
|
||||||
|
state pb.CompetitionState,
|
||||||
|
) (*pb.Competition, error) {
|
||||||
|
query := `UPDATE competitions SET state = $1, updated_at = $2 WHERE id = $3 RETURNING updated_at`
|
||||||
|
now := time.Now()
|
||||||
|
var competition pb.Competition
|
||||||
|
err := r.db.QueryRowxContext(ctx, query, state, now, id).StructScan(&competition)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.cacheEnabled {
|
||||||
|
r.redisClient.Del(ctx, r.cacheKey(id.String())).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Get(ctx, id)
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"datarush/internal/competition/config"
|
||||||
|
grpcHandlers "datarush/internal/competition/handler/grpc"
|
||||||
|
"datarush/internal/competition/middleware"
|
||||||
|
"datarush/internal/competition/repository/postgres"
|
||||||
|
"datarush/internal/competition/service"
|
||||||
|
authpb "datarush/pkg/api/auth"
|
||||||
|
pb "datarush/pkg/api/competition"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"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
|
||||||
|
redisClient *redis.Client
|
||||||
|
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
|
||||||
|
|
||||||
|
s.redisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: s.config.RedisAddr,
|
||||||
|
Password: s.config.RedisPassword,
|
||||||
|
DB: s.config.RedisDB,
|
||||||
|
})
|
||||||
|
|
||||||
|
authConn, err := grpc.Dial(s.config.AuthSvcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to connect to auth service: %w", err)
|
||||||
|
}
|
||||||
|
s.authConn = authConn
|
||||||
|
|
||||||
|
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 := middleware.NewAuthInterceptor(authClient)
|
||||||
|
|
||||||
|
s.grpcServer = grpc.NewServer(
|
||||||
|
grpc.UnaryInterceptor(authInterceptor.Unary()),
|
||||||
|
)
|
||||||
|
|
||||||
|
compRepo := postgres.NewCompetitionRepository(s.db, s.redisClient, s.config.CacheEnabled)
|
||||||
|
compService := service.NewCompetitionService(compRepo)
|
||||||
|
compHandler := grpcHandlers.NewCompetitionHandler(compService)
|
||||||
|
|
||||||
|
pb.RegisterCompetitionServiceServer(s.grpcServer, compHandler)
|
||||||
|
|
||||||
|
if s.config.GRPCEnableReflection {
|
||||||
|
reflection.Register(s.grpcServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() {
|
||||||
|
log.Println("shutting down competition server...")
|
||||||
|
|
||||||
|
if s.grpcServer != nil {
|
||||||
|
s.grpcServer.GracefulStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.authConn != nil {
|
||||||
|
if err := s.authConn.Close(); err != nil {
|
||||||
|
log.Printf("failed to close auth service connection: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.db != nil {
|
||||||
|
if err := s.db.Close(); err != nil {
|
||||||
|
log.Printf("failed to close database: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.redisClient != nil {
|
||||||
|
if err := s.redisClient.Close(); err != nil {
|
||||||
|
log.Printf("failed to close redis client: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("competition server stopped")
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"datarush/internal/competition/repository"
|
||||||
|
pb "datarush/pkg/api/competition"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompetitionService struct {
|
||||||
|
repo repository.CompetitionRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompetitionService(repo repository.CompetitionRepository) *CompetitionService {
|
||||||
|
return &CompetitionService{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CompetitionService) CreateCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error) {
|
||||||
|
return s.repo.Create(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CompetitionService) GetCompetition(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.GetCompetitionRequest,
|
||||||
|
) (*pb.Competition, error) {
|
||||||
|
id, err := uuid.Parse(req.CompetitionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.repo.Get(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CompetitionService) EditCompetition(ctx context.Context, req *pb.Competition) (*pb.Competition, error) {
|
||||||
|
return s.repo.Update(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CompetitionService) DeleteCompetition(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.DeleteCompetitionRequest,
|
||||||
|
) (*emptypb.Empty, error) {
|
||||||
|
id, err := uuid.Parse(req.CompetitionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.repo.Delete(ctx, id)
|
||||||
|
return &emptypb.Empty{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CompetitionService) ListCompetitions(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ListCompetitionsRequest,
|
||||||
|
) (*pb.ListCompetitionsResponse, error) {
|
||||||
|
opts := repository.ListCompetitionsOptions{
|
||||||
|
Page: int(req.PageToken),
|
||||||
|
PageSize: int(req.PageSize),
|
||||||
|
State: req.State,
|
||||||
|
IsParticipating: req.IsParticipating,
|
||||||
|
SearchQuery: req.SearchQuery,
|
||||||
|
}
|
||||||
|
|
||||||
|
competitions, total, err := s.repo.List(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextPageToken int32
|
||||||
|
if (opts.Page+1)*opts.PageSize < total {
|
||||||
|
nextPageToken = int32(opts.Page + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.ListCompetitionsResponse{
|
||||||
|
Competitions: competitions,
|
||||||
|
TotalCount: int32(total),
|
||||||
|
NextPageToken: nextPageToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CompetitionService) ChangeCompetitionState(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ChangeCompetitionStateRequest,
|
||||||
|
) (*pb.Competition, error) {
|
||||||
|
id, err := uuid.Parse(req.CompetitionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.repo.ChangeState(ctx, id, req.State)
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server ServerConfig
|
||||||
|
GRPC GRPCConfig
|
||||||
|
S3 S3Config
|
||||||
|
Auth AuthConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Port string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GRPCConfig struct {
|
||||||
|
AuthServiceAddr string
|
||||||
|
UserServiceAddr string
|
||||||
|
CompetitionServiceAddr string
|
||||||
|
TaskServiceAddr string
|
||||||
|
SubmissionServiceAddr string
|
||||||
|
ResultsServiceAddr string
|
||||||
|
ReviewServiceAddr string
|
||||||
|
AchievementsServiceAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
type S3Config struct {
|
||||||
|
AccessKeyID string
|
||||||
|
SecretAccessKey string
|
||||||
|
Region string
|
||||||
|
Bucket string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthConfig struct {
|
||||||
|
JWTSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
cfg := &Config{
|
||||||
|
Server: ServerConfig{
|
||||||
|
Port: getEnv("SERVER_PORT", "8080"),
|
||||||
|
Host: getEnv("SERVER_HOST", "0.0.0.0"),
|
||||||
|
},
|
||||||
|
GRPC: GRPCConfig{
|
||||||
|
AuthServiceAddr: getEnvRequired("AUTH_SERVICE_ADDR"),
|
||||||
|
UserServiceAddr: getEnvRequired("USER_SERVICE_ADDR"),
|
||||||
|
CompetitionServiceAddr: getEnvRequired("COMPETITION_SERVICE_ADDR"),
|
||||||
|
TaskServiceAddr: getEnvRequired("TASK_SERVICE_ADDR"),
|
||||||
|
SubmissionServiceAddr: getEnvRequired("SUBMISSION_SERVICE_ADDR"),
|
||||||
|
ResultsServiceAddr: getEnvRequired("RESULTS_SERVICE_ADDR"),
|
||||||
|
ReviewServiceAddr: getEnvRequired("REVIEW_SERVICE_ADDR"),
|
||||||
|
AchievementsServiceAddr: getEnvRequired("ACHIEVEMENTS_SERVICE_ADDR"),
|
||||||
|
},
|
||||||
|
S3: S3Config{
|
||||||
|
AccessKeyID: getEnvRequired("AWS_ACCESS_KEY_ID"),
|
||||||
|
SecretAccessKey: getEnvRequired("AWS_SECRET_ACCESS_KEY"),
|
||||||
|
Region: getEnv("AWS_REGION", ""),
|
||||||
|
Bucket: getEnvRequired("S3_BUCKET"),
|
||||||
|
Endpoint: getEnv("S3_ENDPOINT", ""),
|
||||||
|
},
|
||||||
|
Auth: AuthConfig{
|
||||||
|
JWTSecret: getEnv("JWT_SECRET", ""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, defaultValue string) string {
|
||||||
|
value := os.Getenv(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvRequired(key string) string {
|
||||||
|
value := os.Getenv(key)
|
||||||
|
if value == "" {
|
||||||
|
panic(fmt.Sprintf("required environment variable %s is not set", key))
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvAsInt(key string, defaultValue int) int {
|
||||||
|
valueStr := os.Getenv(key)
|
||||||
|
if valueStr == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
value, err := strconv.Atoi(valueStr)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnauthorized = errors.New("unauthorized")
|
||||||
|
ErrForbidden = errors.New("forbidden")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrBadRequest = errors.New("bad request")
|
||||||
|
ErrInternalServer = errors.New("internal server error")
|
||||||
|
ErrConflict = errors.New("conflict")
|
||||||
|
ErrInvalidToken = errors.New("invalid token")
|
||||||
|
ErrMissingAuthHeader = errors.New("missing authorization header")
|
||||||
|
ErrInvalidFile = errors.New("invalid file")
|
||||||
|
ErrFileTooLarge = errors.New("file too large")
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppError struct {
|
||||||
|
Err error
|
||||||
|
Message string
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AppError) Error() string {
|
||||||
|
if e.Message != "" {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
if e.Err != nil {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
return "unknown error"
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAppError(err error, message string, statusCode int) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Err: err,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: statusCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBadRequestError(message string) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Err: ErrBadRequest,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnauthorizedError(message string) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Err: ErrUnauthorized,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: http.StatusUnauthorized,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewForbiddenError(message string) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Err: ErrForbidden,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: http.StatusForbidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotFoundError(message string) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Err: ErrNotFound,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConflictError(message string) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Err: ErrConflict,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: http.StatusConflict,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInternalServerError(message string) *AppError {
|
||||||
|
return &AppError{
|
||||||
|
Err: ErrInternalServer,
|
||||||
|
Message: message,
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Details string `json:"details,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewErrorResponse(err error, message string) *ErrorResponse {
|
||||||
|
errMsg := "internal server error"
|
||||||
|
if err != nil {
|
||||||
|
errMsg = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ErrorResponse{
|
||||||
|
Error: errMsg,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GRPCErrorToHTTPStatus(err error) int {
|
||||||
|
if err == nil {
|
||||||
|
return http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
errMsg := err.Error()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case contains(errMsg, "not found"):
|
||||||
|
return http.StatusNotFound
|
||||||
|
case contains(errMsg, "already exists"), contains(errMsg, "conflict"):
|
||||||
|
return http.StatusConflict
|
||||||
|
case contains(errMsg, "invalid"), contains(errMsg, "bad request"):
|
||||||
|
return http.StatusBadRequest
|
||||||
|
case contains(errMsg, "unauthorized"), contains(errMsg, "unauthenticated"):
|
||||||
|
return http.StatusUnauthorized
|
||||||
|
case contains(errMsg, "forbidden"), contains(errMsg, "permission denied"):
|
||||||
|
return http.StatusForbidden
|
||||||
|
default:
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && (s == substr || fmt.Sprintf("%s", s) != s)
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type SignUpRequest struct {
|
||||||
|
Email string `json:"email" validate:"required,email"`
|
||||||
|
Username string `json:"username" validate:"required,min=3,max=50"`
|
||||||
|
Password string `json:"password" validate:"required,min=6"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignInRequest struct {
|
||||||
|
Email string `json:"email" validate:"required,email"`
|
||||||
|
Password string `json:"password" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FullName *string `json:"full_name,omitempty"`
|
||||||
|
AvatarURL *string `json:"avatar_url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompetitionRequest struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Title string `json:"title" validate:"required,max=200"`
|
||||||
|
Description string `json:"description" validate:"required"`
|
||||||
|
ImageURL *string `json:"image_url,omitempty"`
|
||||||
|
StartTime time.Time `json:"start_time" validate:"required"`
|
||||||
|
EndTime time.Time `json:"end_time" validate:"required"`
|
||||||
|
Type string `json:"type" validate:"required,oneof=educative competitive"`
|
||||||
|
ParticipationType string `json:"participation_type" validate:"required,oneof=individual team"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompetitionResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
State string `json:"state"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ImageURL *string `json:"image_url,omitempty"`
|
||||||
|
StartTime time.Time `json:"start_time"`
|
||||||
|
EndTime time.Time `json:"end_time"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
ParticipationType string `json:"participation_type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListCompetitionsResponse struct {
|
||||||
|
TotalCount int32 `json:"total_count"`
|
||||||
|
NextPageToken int32 `json:"next_page_token"`
|
||||||
|
Competitions []CompetitionResponse `json:"competitions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeCompetitionStateRequest struct {
|
||||||
|
State string `json:"state" validate:"required,oneof=draft not_started started finished archived"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskRequest struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
CompetitionID string `json:"competition_id,omitempty"`
|
||||||
|
Title string `json:"title" validate:"required,max=50"`
|
||||||
|
Description string `json:"description" validate:"required"`
|
||||||
|
InCompetitionPosition int32 `json:"in_competition_position" validate:"required"`
|
||||||
|
MaxPoints int32 `json:"max_points,omitempty"`
|
||||||
|
MaxAttempts int32 `json:"max_attempts,omitempty"`
|
||||||
|
Type string `json:"type" validate:"required,oneof=input checker review"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CompetitionID string `json:"competition_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
InCompetitionPosition int32 `json:"in_competition_position"`
|
||||||
|
MaxPoints int32 `json:"max_points,omitempty"`
|
||||||
|
MaxAttempts int32 `json:"max_attempts,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListTasksResponse struct {
|
||||||
|
Tasks []TaskResponse `json:"tasks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskAttachmentResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
FileURL string `json:"file_url"`
|
||||||
|
IsPublic bool `json:"is_public"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmissionResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
CompetitionID string `json:"competition_id"`
|
||||||
|
TaskID string `json:"task_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
EarnedPoints int32 `json:"earned_points"`
|
||||||
|
SubmittedAt time.Time `json:"submitted_at"`
|
||||||
|
CheckedAt time.Time `json:"checked_at,omitempty"`
|
||||||
|
FileURL string `json:"file_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmitTaskResponse struct {
|
||||||
|
SubmissionID string `json:"submission_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmissionHistoryResponse struct {
|
||||||
|
Submissions []SubmissionResponse `json:"submissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskStatusResponse struct {
|
||||||
|
TaskID string `json:"task_id"`
|
||||||
|
TaskTitle string `json:"task_title"`
|
||||||
|
EarnedPoints int32 `json:"earned_points"`
|
||||||
|
MaxPoints int32 `json:"max_points"`
|
||||||
|
Position *int32 `json:"position,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserResultResponse struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
TotalScore int32 `json:"total_score"`
|
||||||
|
OverallPosition int32 `json:"overall_position"`
|
||||||
|
TaskStatuses []TaskStatusResponse `json:"task_statuses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompetitionResultsResponse struct {
|
||||||
|
Results []UserResultResponse `json:"results"`
|
||||||
|
TotalCount int32 `json:"total_count"`
|
||||||
|
NextPageToken int32 `json:"next_page_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CriteriaMarkRequest struct {
|
||||||
|
Slug string `json:"slug" validate:"required"`
|
||||||
|
Mark float64 `json:"mark" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EvaluateSubmissionRequest struct {
|
||||||
|
EarnedPoints int32 `json:"earned_points" validate:"required"`
|
||||||
|
ReviewerComment string `json:"reviewer_comment"`
|
||||||
|
Marks []CriteriaMarkRequest `json:"marks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmissionSummaryResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CompetitionID string `json:"competition_id"`
|
||||||
|
TaskID string `json:"task_id"`
|
||||||
|
CompetitionTitle string `json:"competition_title"`
|
||||||
|
TaskTitle string `json:"task_title"`
|
||||||
|
SubmittedAt time.Time `json:"submitted_at"`
|
||||||
|
ReviewStatus string `json:"review_status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmissionForReviewResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CompetitionID string `json:"competition_id"`
|
||||||
|
TaskID string `json:"task_id"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ReviewStatus string `json:"review_status"`
|
||||||
|
SubmittedAt time.Time `json:"submitted_at"`
|
||||||
|
CheckedAt *time.Time `json:"checked_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListSubmissionsForReviewResponse struct {
|
||||||
|
TotalCount int32 `json:"total_count"`
|
||||||
|
NextPageToken int32 `json:"next_page_token"`
|
||||||
|
Submissions []SubmissionSummaryResponse `json:"submissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EvaluateSubmissionResponse struct {
|
||||||
|
SubmissionID string `json:"submission_id"`
|
||||||
|
FinalScore int32 `json:"final_score"`
|
||||||
|
NewStatus string `json:"new_status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AchievementResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IconURL string `json:"icon_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListAchievementsResponse struct {
|
||||||
|
Achievements []AchievementResponse `json:"achievements"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAchievementResponse struct {
|
||||||
|
AchievementID string `json:"achievement_id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
EarnedAt time.Time `json:"earned_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListUserAchievementsResponse struct {
|
||||||
|
Achievements []UserAchievementResponse `json:"achievements"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PingResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/achievements"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementsClient struct {
|
||||||
|
client pb.AchievementsServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementsClient(ctx context.Context, address string, factory *ClientFactory) (*AchievementsClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create achievements client: %w", err)
|
||||||
|
}
|
||||||
|
return &AchievementsClient{client: pb.NewAchievementsServiceClient(conn), conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AchievementsClient) GetAchievement(ctx context.Context, achievementID string) (*pb.Achievement, error) {
|
||||||
|
req := &pb.GetAchievementRequest{Id: achievementID}
|
||||||
|
resp, err := c.client.GetAchievement(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get achievement failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AchievementsClient) ListAchievements(ctx context.Context) ([]*pb.Achievement, error) {
|
||||||
|
resp, err := c.client.ListAchievements(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list achievements failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp.Achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AchievementsClient) GetUserAchievements(ctx context.Context, userID string) ([]*pb.AchievementUser, error) {
|
||||||
|
req := &pb.GetUserAchievementsRequest{UserId: userID}
|
||||||
|
resp, err := c.client.GetUserAchievements(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get user achievements failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp.UserAchievements, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/auth"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthClient struct {
|
||||||
|
client pb.AuthServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthClient(ctx context.Context, address string, factory *ClientFactory) (*AuthClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create auth client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AuthClient{
|
||||||
|
client: pb.NewAuthServiceClient(conn),
|
||||||
|
conn: conn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthClient) SignUp(ctx context.Context, email, username, password string) (string, error) {
|
||||||
|
req := &pb.SignUpRequest{
|
||||||
|
Email: email,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.SignUp(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("sign up failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthClient) SignIn(ctx context.Context, email, password string) (string, error) {
|
||||||
|
req := &pb.SignInRequest{
|
||||||
|
Email: email,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.SignIn(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("sign in failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthClient) ValidateToken(ctx context.Context, token string) (string, error) {
|
||||||
|
req := &pb.ValidateTokenRequest{
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.ValidateToken(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("token validation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.UserId, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientFactory struct {
|
||||||
|
connections map[string]*grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientFactory() *ClientFactory {
|
||||||
|
return &ClientFactory{
|
||||||
|
connections: make(map[string]*grpc.ClientConn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ClientFactory) GetConnection(ctx context.Context, address string) (*grpc.ClientConn, error) {
|
||||||
|
if conn, ok := f.connections[address]; ok {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn, err := grpc.DialContext(ctx, address,
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithBlock(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to %s: %w", address, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.connections[address] = conn
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ClientFactory) Close() error {
|
||||||
|
for addr, conn := range f.connections {
|
||||||
|
if err := conn.Close(); err != nil {
|
||||||
|
return fmt.Errorf("failed to close connection to %s: %w", addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/competition"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompetitionClient struct {
|
||||||
|
client pb.CompetitionServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompetitionClient(ctx context.Context, address string, factory *ClientFactory) (*CompetitionClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create competition client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CompetitionClient{
|
||||||
|
client: pb.NewCompetitionServiceClient(conn),
|
||||||
|
conn: conn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompetitionClient) CreateCompetition(
|
||||||
|
ctx context.Context,
|
||||||
|
competition *pb.Competition,
|
||||||
|
) (*pb.Competition, error) {
|
||||||
|
resp, err := c.client.CreateCompetition(ctx, competition)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create competition failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompetitionClient) GetCompetition(ctx context.Context, competitionID string) (*pb.Competition, error) {
|
||||||
|
req := &pb.GetCompetitionRequest{
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.GetCompetition(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get competition failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompetitionClient) EditCompetition(ctx context.Context, competition *pb.Competition) (*pb.Competition, error) {
|
||||||
|
resp, err := c.client.EditCompetition(ctx, competition)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("edit competition failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompetitionClient) DeleteCompetition(ctx context.Context, competitionID string) error {
|
||||||
|
req := &pb.DeleteCompetitionRequest{
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.client.DeleteCompetition(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("delete competition failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompetitionClient) ListCompetitions(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ListCompetitionsRequest,
|
||||||
|
) (*pb.ListCompetitionsResponse, error) {
|
||||||
|
resp, err := c.client.ListCompetitions(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list competitions failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompetitionClient) ChangeCompetitionState(
|
||||||
|
ctx context.Context,
|
||||||
|
competitionID string,
|
||||||
|
state pb.CompetitionState,
|
||||||
|
) (*pb.Competition, error) {
|
||||||
|
req := &pb.ChangeCompetitionStateRequest{
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
State: state,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.ChangeCompetitionState(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("change competition state failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/results"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResultsClient struct {
|
||||||
|
client pb.ResultsServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResultsClient(ctx context.Context, address string, factory *ClientFactory) (*ResultsClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create results client: %w", err)
|
||||||
|
}
|
||||||
|
return &ResultsClient{client: pb.NewResultsServiceClient(conn), conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ResultsClient) GetCompetitionResults(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.GetCompetitionResultsRequest,
|
||||||
|
) (*pb.GetCompetitionResultsResponse, error) {
|
||||||
|
resp, err := c.client.GetCompetitionResults(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get competition results failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ResultsClient) GetUserCompetitionResults(
|
||||||
|
ctx context.Context,
|
||||||
|
competitionID, userID string,
|
||||||
|
) (*pb.UserResult, error) {
|
||||||
|
req := &pb.GetUserCompetitionResultsRequest{
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
UserId: userID,
|
||||||
|
}
|
||||||
|
resp, err := c.client.GetUserCompetitionResults(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get user competition results failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ResultsClient) RecalculateResults(ctx context.Context, competitionID string) error {
|
||||||
|
req := &pb.RecalculateResultsRequest{CompetitionId: competitionID}
|
||||||
|
_, err := c.client.RecalculateResults(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("recalculate results failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/review"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReviewClient struct {
|
||||||
|
client pb.ReviewServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReviewClient(ctx context.Context, address string, factory *ClientFactory) (*ReviewClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create review client: %w", err)
|
||||||
|
}
|
||||||
|
return &ReviewClient{client: pb.NewReviewServiceClient(conn), conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ReviewClient) ValidateReviewToken(ctx context.Context, token string) (*pb.ValidateReviewTokenResponse, error) {
|
||||||
|
req := &pb.ValidateReviewTokenRequest{Token: token}
|
||||||
|
resp, err := c.client.ValidateReviewToken(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("validate review token failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ReviewClient) ListSubmissionsForReview(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ListSubmissionsForReviewRequest,
|
||||||
|
) (*pb.ListSubmissionsForReviewResponse, error) {
|
||||||
|
resp, err := c.client.ListSubmissionsForReview(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list submissions for review failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ReviewClient) GetSubmissionForReview(
|
||||||
|
ctx context.Context,
|
||||||
|
token, submissionID string,
|
||||||
|
) (*pb.SubmissionForReview, error) {
|
||||||
|
req := &pb.GetSubmissionForReviewRequest{
|
||||||
|
Token: token,
|
||||||
|
SubmissionId: submissionID,
|
||||||
|
}
|
||||||
|
resp, err := c.client.GetSubmissionForReview(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get submission for review failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ReviewClient) EvaluateSubmission(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.EvaluateSubmissionRequest,
|
||||||
|
) (*pb.EvaluateSubmissionResponse, error) {
|
||||||
|
resp, err := c.client.EvaluateSubmission(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("evaluate submission failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ReviewClient) ReleaseSubmission(ctx context.Context, token, submissionID string) error {
|
||||||
|
req := &pb.ReleaseSubmissionRequest{
|
||||||
|
Token: token,
|
||||||
|
SubmissionId: submissionID,
|
||||||
|
}
|
||||||
|
_, err := c.client.ReleaseSubmission(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("release submission failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/submission"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubmissionClient struct {
|
||||||
|
client pb.SubmissionServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubmissionClient(ctx context.Context, address string, factory *ClientFactory) (*SubmissionClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create submission client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SubmissionClient{
|
||||||
|
client: pb.NewSubmissionServiceClient(conn),
|
||||||
|
conn: conn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SubmissionClient) SubmitTask(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, competitionID, taskID, fileURL string,
|
||||||
|
) (*pb.Submission, error) {
|
||||||
|
req := &pb.SubmitTaskRequest{
|
||||||
|
UserId: userID,
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
TaskId: taskID,
|
||||||
|
FileUrl: fileURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.SubmitTask(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("submit task failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SubmissionClient) GetSubmissionsHistory(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, competitionID, taskID string,
|
||||||
|
) ([]*pb.Submission, error) {
|
||||||
|
req := &pb.GetSubmissionsHistoryRequest{
|
||||||
|
UserId: userID,
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
TaskId: taskID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.GetSubmissionsHistory(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get submissions history failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp.Submissions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SubmissionClient) GetSubmission(ctx context.Context, submissionID string) (*pb.Submission, error) {
|
||||||
|
req := &pb.GetSubmissionRequest{
|
||||||
|
SubmissionId: submissionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.GetSubmission(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get submission failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SubmissionClient) ListSubmissions(
|
||||||
|
ctx context.Context,
|
||||||
|
req *pb.ListSubmissionsRequest,
|
||||||
|
) (*pb.ListSubmissionsResponse, error) {
|
||||||
|
resp, err := c.client.ListSubmissions(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list submissions failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/task"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskClient struct {
|
||||||
|
client pb.TaskServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTaskClient(ctx context.Context, address string, factory *ClientFactory) (*TaskClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create task client: %w", err)
|
||||||
|
}
|
||||||
|
return &TaskClient{client: pb.NewTaskServiceClient(conn), conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TaskClient) CreateTask(ctx context.Context, task *pb.Task) (*pb.Task, error) {
|
||||||
|
resp, err := c.client.CreateTask(ctx, task)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create task failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TaskClient) GetTask(ctx context.Context, taskID string) (*pb.Task, error) {
|
||||||
|
req := &pb.GetTaskRequest{TaskId: taskID}
|
||||||
|
resp, err := c.client.GetTask(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get task failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TaskClient) EditTask(ctx context.Context, task *pb.Task) (*pb.Task, error) {
|
||||||
|
resp, err := c.client.EditTask(ctx, task)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("edit task failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TaskClient) DeleteTask(ctx context.Context, taskID string) error {
|
||||||
|
req := &pb.DeleteTaskRequest{TaskId: taskID}
|
||||||
|
_, err := c.client.DeleteTask(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("delete task failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TaskClient) ListCompetitionTasks(ctx context.Context, competitionID string) ([]*pb.Task, error) {
|
||||||
|
req := &pb.ListCompetitionTasksRequest{CompetitionId: competitionID}
|
||||||
|
resp, err := c.client.ListCompetitionTasks(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list tasks failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp.Tasks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TaskClient) GetTaskAttachments(
|
||||||
|
ctx context.Context,
|
||||||
|
taskID string,
|
||||||
|
showPrivate bool,
|
||||||
|
) ([]*pb.TaskAttachment, error) {
|
||||||
|
req := &pb.GetTaskAttachmentsRequest{
|
||||||
|
TaskId: taskID,
|
||||||
|
ShowPrivate: &showPrivate,
|
||||||
|
}
|
||||||
|
resp, err := c.client.GetTaskAttachments(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get task attachments failed: %w", err)
|
||||||
|
}
|
||||||
|
return resp.Attachments, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package grpc_client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
pb "datarush/pkg/api/user"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserClient struct {
|
||||||
|
client pb.UserServiceClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserClient(ctx context.Context, address string, factory *ClientFactory) (*UserClient, error) {
|
||||||
|
conn, err := factory.GetConnection(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create user client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UserClient{
|
||||||
|
client: pb.NewUserServiceClient(conn),
|
||||||
|
conn: conn,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserClient) GetProfile(ctx context.Context, userID string) (*pb.User, error) {
|
||||||
|
req := &pb.GetProfileRequest{
|
||||||
|
UserId: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.GetProfile(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get profile failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserClient) RegisterForCompetition(ctx context.Context, competitionID string) error {
|
||||||
|
req := &pb.RegisterForCompetitionRequest{
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.client.RegisterForCompetition(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("register for competition failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserClient) UnregisterFromCompetition(ctx context.Context, competitionID string) error {
|
||||||
|
req := &pb.UnregisterFromCompetitionRequest{
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.client.UnregisterFromCompetition(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unregister from competition failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserClient) ListUserCompetitions(ctx context.Context, userID string) ([]string, error) {
|
||||||
|
req := &pb.ListUserCompetitionsRequest{
|
||||||
|
UserId: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.ListUserCompetitions(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list user competitions failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.CompetitionIds, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementsHandler struct {
|
||||||
|
achievementsClient *grpc_client.AchievementsClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementsHandler(achievementsClient *grpc_client.AchievementsClient) *AchievementsHandler {
|
||||||
|
return &AchievementsHandler{achievementsClient: achievementsClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementsHandler) ListAchievements(w http.ResponseWriter, r *http.Request) {
|
||||||
|
achievements, err := h.achievementsClient.ListAchievements(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to list achievements"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]domain.AchievementResponse, len(achievements))
|
||||||
|
for i, ach := range achievements {
|
||||||
|
response[i] = *utils.AchievementProtoToHTTP(ach)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.ListAchievementsResponse{
|
||||||
|
Achievements: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementsHandler) GetAchievement(w http.ResponseWriter, r *http.Request) {
|
||||||
|
achievementID := getPathParam(r, "achievement_id")
|
||||||
|
|
||||||
|
achievement, err := h.achievementsClient.GetAchievement(r.Context(), achievementID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewNotFoundError("achievement not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.AchievementProtoToHTTP(achievement))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AchievementsHandler) GetUserAchievements(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID := getPathParam(r, "user_id")
|
||||||
|
|
||||||
|
achievements, err := h.achievementsClient.GetUserAchievements(r.Context(), userID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to get user achievements"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]domain.UserAchievementResponse, len(achievements))
|
||||||
|
for i, ach := range achievements {
|
||||||
|
response[i] = domain.UserAchievementResponse{
|
||||||
|
AchievementID: ach.Achievement.Id,
|
||||||
|
UserID: userID,
|
||||||
|
EarnedAt: ach.ReceivedAt.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.ListUserAchievementsResponse{
|
||||||
|
Achievements: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthHandler struct {
|
||||||
|
authClient *grpc_client.AuthClient
|
||||||
|
userClient *grpc_client.UserClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthHandler(authClient *grpc_client.AuthClient, userClient *grpc_client.UserClient) *AuthHandler {
|
||||||
|
return &AuthHandler{
|
||||||
|
authClient: authClient,
|
||||||
|
userClient: userClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AuthHandler) SignUp(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req domain.SignUpRequest
|
||||||
|
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := h.authClient.SignUp(r.Context(), req.Email, req.Username, req.Password)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("sign up failed"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusCreated, &domain.TokenResponse{Token: token})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AuthHandler) SignIn(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req domain.SignInRequest
|
||||||
|
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := h.authClient.SignIn(r.Context(), req.Email, req.Password)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewUnauthorizedError("invalid credentials"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.TokenResponse{Token: token})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AuthHandler) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, err := getUserIDFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := h.userClient.GetProfile(r.Context(), userID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to get user profile"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.UserProtoToHTTP(user))
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/middleware"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUserIDFromContext(ctx context.Context) (string, error) {
|
||||||
|
return middleware.GetUserIDFromContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPathParam(r *http.Request, key string) string {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
return vars[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueryParam(r *http.Request, key string) string {
|
||||||
|
return r.URL.Query().Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueryParamInt(r *http.Request, key string, defaultValue int) int {
|
||||||
|
val := r.URL.Query().Get(key)
|
||||||
|
if val == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
intVal, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return intVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueryParamInt32(r *http.Request, key string, defaultValue int32) int32 {
|
||||||
|
return int32(getQueryParamInt(r, key, int(defaultValue)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueryParamBool(r *http.Request, key string) *bool {
|
||||||
|
val := r.URL.Query().Get(key)
|
||||||
|
if val == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
boolVal, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &boolVal
|
||||||
|
}
|
||||||
|
|
||||||
|
type PingHandler struct{}
|
||||||
|
|
||||||
|
func NewPingHandler() *PingHandler {
|
||||||
|
return &PingHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PingHandler) Ping(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response := &domain.PingResponse{
|
||||||
|
Message: "pong",
|
||||||
|
Status: "ok",
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/utils"
|
||||||
|
|
||||||
|
comppb "datarush/pkg/api/competition"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompetitionHandler struct {
|
||||||
|
competitionClient *grpc_client.CompetitionClient
|
||||||
|
userClient *grpc_client.UserClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompetitionHandler(
|
||||||
|
competitionClient *grpc_client.CompetitionClient,
|
||||||
|
userClient *grpc_client.UserClient,
|
||||||
|
) *CompetitionHandler {
|
||||||
|
return &CompetitionHandler{
|
||||||
|
competitionClient: competitionClient,
|
||||||
|
userClient: userClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) CreateCompetition(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req domain.CompetitionRequest
|
||||||
|
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
competition := utils.CompetitionHTTPToProto(&req)
|
||||||
|
resp, err := h.competitionClient.CreateCompetition(r.Context(), competition)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to create competition"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusCreated, utils.CompetitionProtoToHTTP(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) GetCompetition(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
competition, err := h.competitionClient.GetCompetition(r.Context(), competitionID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewNotFoundError("competition not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.CompetitionProtoToHTTP(competition))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) UpdateCompetition(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
var req domain.CompetitionRequest
|
||||||
|
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ID = competitionID
|
||||||
|
competition := utils.CompetitionHTTPToProto(&req)
|
||||||
|
resp, err := h.competitionClient.EditCompetition(r.Context(), competition)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to update competition"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.CompetitionProtoToHTTP(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) DeleteCompetition(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
if err := h.competitionClient.DeleteCompetition(r.Context(), competitionID); err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to delete competition"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
func (h *CompetitionHandler) ListCompetitions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pageSize := getQueryParamInt32(r, "page_size", 20)
|
||||||
|
pageToken := getQueryParamInt32(r, "page_token", 0)
|
||||||
|
state := getQueryParam(r, "state")
|
||||||
|
searchQuery := getQueryParam(r, "search_query")
|
||||||
|
isParticipating := getQueryParamBool(r, "is_participating")
|
||||||
|
|
||||||
|
req := &comppb.ListCompetitionsRequest{
|
||||||
|
PageSize: pageSize,
|
||||||
|
PageToken: pageToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != "" {
|
||||||
|
s := utils.StringToCompetitionState(state)
|
||||||
|
req.State = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
if searchQuery != "" {
|
||||||
|
req.SearchQuery = &searchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
if isParticipating != nil {
|
||||||
|
req.IsParticipating = isParticipating
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.competitionClient.ListCompetitions(r.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to list competitions"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
competitions := make([]domain.CompetitionResponse, len(resp.Competitions))
|
||||||
|
for i, comp := range resp.Competitions {
|
||||||
|
competitions[i] = *utils.CompetitionProtoToHTTP(comp)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.ListCompetitionsResponse{
|
||||||
|
TotalCount: resp.TotalCount,
|
||||||
|
NextPageToken: resp.NextPageToken,
|
||||||
|
Competitions: competitions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) ChangeCompetitionState(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
var req domain.ChangeCompetitionStateRequest
|
||||||
|
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state := utils.StringToCompetitionState(req.State)
|
||||||
|
competition, err := h.competitionClient.ChangeCompetitionState(r.Context(), competitionID, state)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to change competition state"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.CompetitionProtoToHTTP(competition))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompetitionHandler) JoinCompetition(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
if err := h.userClient.RegisterForCompetition(r.Context(), competitionID); err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to join competition"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/utils"
|
||||||
|
|
||||||
|
resultspb "datarush/pkg/api/results"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResultsHandler struct {
|
||||||
|
resultsClient *grpc_client.ResultsClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResultsHandler(resultsClient *grpc_client.ResultsClient) *ResultsHandler {
|
||||||
|
return &ResultsHandler{resultsClient: resultsClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResultsHandler) GetCompetitionResults(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
pageSize := getQueryParamInt32(r, "page_size", 20)
|
||||||
|
pageToken := getQueryParamInt32(r, "page_token", 0)
|
||||||
|
|
||||||
|
req := &resultspb.GetCompetitionResultsRequest{
|
||||||
|
CompetitionId: competitionID,
|
||||||
|
PageSize: pageSize,
|
||||||
|
PageToken: pageToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.resultsClient.GetCompetitionResults(r.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to get competition results"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]domain.UserResultResponse, len(resp.Results))
|
||||||
|
for i, result := range resp.Results {
|
||||||
|
results[i] = *utils.UserResultProtoToHTTP(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.CompetitionResultsResponse{
|
||||||
|
Results: results,
|
||||||
|
TotalCount: resp.TotalCount,
|
||||||
|
NextPageToken: resp.NextPageToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResultsHandler) GetMyResults(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, err := getUserIDFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
result, err := h.resultsClient.GetUserCompetitionResults(r.Context(), competitionID, userID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to get user results"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.UserResultProtoToHTTP(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ResultsHandler) RecalculateResults(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
if err := h.resultsClient.RecalculateResults(r.Context(), competitionID); err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to recalculate results"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/utils"
|
||||||
|
|
||||||
|
reviewpb "datarush/pkg/api/review"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReviewHandler struct {
|
||||||
|
reviewClient *grpc_client.ReviewClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReviewHandler(reviewClient *grpc_client.ReviewClient) *ReviewHandler {
|
||||||
|
return &ReviewHandler{reviewClient: reviewClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ReviewHandler) ListSubmissionsForReview(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := getPathParam(r, "token")
|
||||||
|
pageSize := getQueryParamInt32(r, "page_size", 20)
|
||||||
|
pageToken := getQueryParamInt32(r, "page_token", 0)
|
||||||
|
statusStr := getQueryParam(r, "status")
|
||||||
|
|
||||||
|
req := &reviewpb.ListSubmissionsForReviewRequest{
|
||||||
|
Token: token,
|
||||||
|
PageSize: pageSize,
|
||||||
|
PageToken: pageToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusStr != "" {
|
||||||
|
status := stringToReviewStatus(statusStr)
|
||||||
|
req.Status = &status
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.reviewClient.ListSubmissionsForReview(r.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewUnauthorizedError("invalid review token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submissions := make([]domain.SubmissionSummaryResponse, len(resp.Submissions))
|
||||||
|
for i, sub := range resp.Submissions {
|
||||||
|
submissions[i] = *utils.SubmissionSummaryProtoToHTTP(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.ListSubmissionsForReviewResponse{
|
||||||
|
TotalCount: resp.TotalCount,
|
||||||
|
NextPageToken: resp.NextPageToken,
|
||||||
|
Submissions: submissions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ReviewHandler) GetSubmissionForReview(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := getPathParam(r, "token")
|
||||||
|
submissionID := getPathParam(r, "submission_id")
|
||||||
|
|
||||||
|
submission, err := h.reviewClient.GetSubmissionForReview(r.Context(), token, submissionID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewNotFoundError("submission not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.SubmissionForReviewProtoToHTTP(submission))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ReviewHandler) EvaluateSubmission(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := getPathParam(r, "token")
|
||||||
|
submissionID := getPathParam(r, "submission_id")
|
||||||
|
|
||||||
|
var reqBody domain.EvaluateSubmissionRequest
|
||||||
|
if err := utils.DecodeJSON(r, &reqBody); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
marks := make([]*reviewpb.CriteriaMark, len(reqBody.Marks))
|
||||||
|
for i, m := range reqBody.Marks {
|
||||||
|
marks[i] = &reviewpb.CriteriaMark{
|
||||||
|
Slug: m.Slug,
|
||||||
|
Mark: m.Mark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &reviewpb.EvaluateSubmissionRequest{
|
||||||
|
Token: token,
|
||||||
|
SubmissionId: submissionID,
|
||||||
|
EarnedPoints: reqBody.EarnedPoints,
|
||||||
|
ReviewerComment: reqBody.ReviewerComment,
|
||||||
|
Marks: marks,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.reviewClient.EvaluateSubmission(r.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to evaluate submission"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.EvaluateSubmissionResponse{
|
||||||
|
SubmissionID: resp.SubmissionId,
|
||||||
|
FinalScore: resp.FinalScore,
|
||||||
|
NewStatus: utils.ReviewStatusToString(resp.NewStatus),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ReviewHandler) ReleaseSubmission(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := getPathParam(r, "token")
|
||||||
|
submissionID := getPathParam(r, "submission_id")
|
||||||
|
|
||||||
|
if err := h.reviewClient.ReleaseSubmission(r.Context(), token, submissionID); err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to release submission"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToReviewStatus(s string) reviewpb.ReviewStatus {
|
||||||
|
switch s {
|
||||||
|
case "pending":
|
||||||
|
return reviewpb.ReviewStatus_REVIEW_STATUS_PENDING
|
||||||
|
case "in_review":
|
||||||
|
return reviewpb.ReviewStatus_REVIEW_STATUS_IN_REVIEW
|
||||||
|
case "completed":
|
||||||
|
return reviewpb.ReviewStatus_REVIEW_STATUS_COMPLETED
|
||||||
|
case "rejected":
|
||||||
|
return reviewpb.ReviewStatus_REVIEW_STATUS_REJECTED
|
||||||
|
default:
|
||||||
|
return reviewpb.ReviewStatus_REVIEW_STATUS_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/storage"
|
||||||
|
"datarush/internal/gw/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubmissionHandler struct {
|
||||||
|
submissionClient *grpc_client.SubmissionClient
|
||||||
|
s3Storage *storage.S3Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubmissionHandler(
|
||||||
|
submissionClient *grpc_client.SubmissionClient,
|
||||||
|
s3Storage *storage.S3Storage,
|
||||||
|
) *SubmissionHandler {
|
||||||
|
return &SubmissionHandler{
|
||||||
|
submissionClient: submissionClient,
|
||||||
|
s3Storage: s3Storage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SubmissionHandler) SubmitTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, err := getUserIDFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
taskID := getPathParam(r, "task_id")
|
||||||
|
|
||||||
|
if err := r.ParseMultipartForm(32 << 20); err != nil { // 32 MB max
|
||||||
|
utils.RespondError(w, domain.NewBadRequestError("failed to parse form"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, header, err := r.FormFile("content")
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewBadRequestError("missing or invalid file"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
fileURL, err := h.s3Storage.UploadFile(r.Context(), file, header)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to upload file"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submission, err := h.submissionClient.SubmitTask(r.Context(), userID, competitionID, taskID, fileURL)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to submit task"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusCreated, &domain.SubmitTaskResponse{
|
||||||
|
SubmissionID: submission.Id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SubmissionHandler) GetSubmissionHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userID, err := getUserIDFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewUnauthorizedError("unauthorized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
taskID := getPathParam(r, "task_id")
|
||||||
|
|
||||||
|
submissions, err := h.submissionClient.GetSubmissionsHistory(r.Context(), userID, competitionID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to get submission history"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]domain.SubmissionResponse, len(submissions))
|
||||||
|
for i, sub := range submissions {
|
||||||
|
response[i] = *utils.SubmissionProtoToHTTP(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.SubmissionHistoryResponse{
|
||||||
|
Submissions: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
"datarush/internal/gw/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskHandler struct {
|
||||||
|
taskClient *grpc_client.TaskClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTaskHandler(taskClient *grpc_client.TaskClient) *TaskHandler {
|
||||||
|
return &TaskHandler{taskClient: taskClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
var req domain.TaskRequest
|
||||||
|
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.CompetitionID = competitionID
|
||||||
|
task := utils.TaskHTTPToProto(&req)
|
||||||
|
resp, err := h.taskClient.CreateTask(r.Context(), task)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to create task"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusCreated, utils.TaskProtoToHTTP(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
taskID := getPathParam(r, "task_id")
|
||||||
|
|
||||||
|
task, err := h.taskClient.GetTask(r.Context(), taskID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewNotFoundError("task not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.TaskProtoToHTTP(task))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
taskID := getPathParam(r, "task_id")
|
||||||
|
|
||||||
|
var req domain.TaskRequest
|
||||||
|
if err := utils.DecodeJSON(r, &req); err != nil {
|
||||||
|
utils.RespondError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.ID = taskID
|
||||||
|
req.CompetitionID = competitionID
|
||||||
|
task := utils.TaskHTTPToProto(&req)
|
||||||
|
resp, err := h.taskClient.EditTask(r.Context(), task)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to update task"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, utils.TaskProtoToHTTP(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
|
||||||
|
taskID := getPathParam(r, "task_id")
|
||||||
|
|
||||||
|
if err := h.taskClient.DeleteTask(r.Context(), taskID); err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to delete task"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
competitionID := getPathParam(r, "competition_id")
|
||||||
|
|
||||||
|
tasks, err := h.taskClient.ListCompetitionTasks(r.Context(), competitionID)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondError(w, domain.NewInternalServerError("failed to list tasks"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]domain.TaskResponse, len(tasks))
|
||||||
|
for i, task := range tasks {
|
||||||
|
response[i] = *utils.TaskProtoToHTTP(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.RespondJSON(w, http.StatusOK, &domain.ListTasksResponse{Tasks: response})
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
"datarush/internal/gw/grpc_client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserIDKey contextKey = "user_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthMiddleware struct {
|
||||||
|
authClient *grpc_client.AuthClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthMiddleware(authClient *grpc_client.AuthClient) *AuthMiddleware {
|
||||||
|
return &AuthMiddleware{
|
||||||
|
authClient: authClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *AuthMiddleware) Authenticate(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
respondWithError(w, domain.NewUnauthorizedError("missing authorization header"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(authHeader, " ")
|
||||||
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||||
|
respondWithError(w, domain.NewUnauthorizedError("invalid authorization header format"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := parts[1]
|
||||||
|
|
||||||
|
userID, err := m.authClient.ValidateToken(r.Context(), token)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(w, domain.NewUnauthorizedError("invalid token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), UserIDKey, userID)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserIDFromContext(ctx context.Context) (string, error) {
|
||||||
|
userID, ok := ctx.Value(UserIDKey).(string)
|
||||||
|
if !ok || userID == "" {
|
||||||
|
return "", domain.ErrUnauthorized
|
||||||
|
}
|
||||||
|
return userID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func respondWithError(w http.ResponseWriter, err *domain.AppError) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(err.StatusCode)
|
||||||
|
|
||||||
|
response := domain.NewErrorResponse(err.Err, err.Message)
|
||||||
|
w.Write([]byte(`{"error":"` + response.Error + `","message":"` + response.Message + `"}`))
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CORSMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||||
|
w.Header().Set("Access-Control-Max-Age", "3600")
|
||||||
|
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
written int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) WriteHeader(code int) {
|
||||||
|
rw.statusCode = code
|
||||||
|
rw.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Write(b []byte) (int, error) {
|
||||||
|
n, err := rw.ResponseWriter.Write(b)
|
||||||
|
rw.written += int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoggingMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
wrapped := &responseWriter{
|
||||||
|
ResponseWriter: w,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(wrapped, r)
|
||||||
|
|
||||||
|
duration := time.Since(start)
|
||||||
|
log.Printf(
|
||||||
|
"%s %s %d %s %s",
|
||||||
|
r.Method,
|
||||||
|
r.RequestURI,
|
||||||
|
wrapped.statusCode,
|
||||||
|
duration,
|
||||||
|
r.RemoteAddr,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/handler"
|
||||||
|
"datarush/internal/gw/middleware"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
authHandler *handler.AuthHandler
|
||||||
|
competitionHandler *handler.CompetitionHandler
|
||||||
|
taskHandler *handler.TaskHandler
|
||||||
|
submissionHandler *handler.SubmissionHandler
|
||||||
|
resultsHandler *handler.ResultsHandler
|
||||||
|
reviewHandler *handler.ReviewHandler
|
||||||
|
achievementsHandler *handler.AchievementsHandler
|
||||||
|
pingHandler *handler.PingHandler
|
||||||
|
authMiddleware *middleware.AuthMiddleware
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(
|
||||||
|
authHandler *handler.AuthHandler,
|
||||||
|
competitionHandler *handler.CompetitionHandler,
|
||||||
|
taskHandler *handler.TaskHandler,
|
||||||
|
submissionHandler *handler.SubmissionHandler,
|
||||||
|
resultsHandler *handler.ResultsHandler,
|
||||||
|
reviewHandler *handler.ReviewHandler,
|
||||||
|
achievementsHandler *handler.AchievementsHandler,
|
||||||
|
pingHandler *handler.PingHandler,
|
||||||
|
authMiddleware *middleware.AuthMiddleware,
|
||||||
|
) *Router {
|
||||||
|
return &Router{
|
||||||
|
authHandler: authHandler,
|
||||||
|
competitionHandler: competitionHandler,
|
||||||
|
taskHandler: taskHandler,
|
||||||
|
submissionHandler: submissionHandler,
|
||||||
|
resultsHandler: resultsHandler,
|
||||||
|
reviewHandler: reviewHandler,
|
||||||
|
achievementsHandler: achievementsHandler,
|
||||||
|
pingHandler: pingHandler,
|
||||||
|
authMiddleware: authMiddleware,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *Router) Setup() http.Handler {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
r.Use(middleware.LoggingMiddleware)
|
||||||
|
r.Use(middleware.CORSMiddleware)
|
||||||
|
|
||||||
|
api := r.PathPrefix("/api/v1").Subrouter()
|
||||||
|
|
||||||
|
api.HandleFunc("/ping", rt.pingHandler.Ping).Methods(http.MethodGet)
|
||||||
|
api.HandleFunc("/sign-up", rt.authHandler.SignUp).Methods(http.MethodPost)
|
||||||
|
api.HandleFunc("/sign-in", rt.authHandler.SignIn).Methods(http.MethodPost)
|
||||||
|
|
||||||
|
protected := api.PathPrefix("").Subrouter()
|
||||||
|
protected.Use(rt.authMiddleware.Authenticate)
|
||||||
|
|
||||||
|
protected.HandleFunc("/me", rt.authHandler.GetMe).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
protected.HandleFunc("/competitions", rt.competitionHandler.CreateCompetition).Methods(http.MethodPost)
|
||||||
|
protected.HandleFunc("/competitions", rt.competitionHandler.ListCompetitions).Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}", rt.competitionHandler.GetCompetition).Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}", rt.competitionHandler.UpdateCompetition).
|
||||||
|
Methods(http.MethodPut)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}", rt.competitionHandler.DeleteCompetition).
|
||||||
|
Methods(http.MethodDelete)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/state", rt.competitionHandler.ChangeCompetitionState).
|
||||||
|
Methods(http.MethodPatch)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/join", rt.competitionHandler.JoinCompetition).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/tasks", rt.taskHandler.CreateTask).Methods(http.MethodPost)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/tasks", rt.taskHandler.ListTasks).Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/tasks/{task_id}", rt.taskHandler.GetTask).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/tasks/{task_id}", rt.taskHandler.UpdateTask).
|
||||||
|
Methods(http.MethodPut)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/tasks/{task_id}", rt.taskHandler.DeleteTask).
|
||||||
|
Methods(http.MethodDelete)
|
||||||
|
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/tasks/{task_id}/submit", rt.submissionHandler.SubmitTask).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/tasks/{task_id}/history", rt.submissionHandler.GetSubmissionHistory).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/results", rt.resultsHandler.GetCompetitionResults).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/results/me", rt.resultsHandler.GetMyResults).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/competitions/{competition_id}/results/recalculate", rt.resultsHandler.RecalculateResults).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
protected.HandleFunc("/achievements", rt.achievementsHandler.ListAchievements).Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/achievements/{achievement_id}", rt.achievementsHandler.GetAchievement).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
protected.HandleFunc("/users/{user_id}/achievements", rt.achievementsHandler.GetUserAchievements).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
|
api.HandleFunc("/review/{token}/submissions", rt.reviewHandler.ListSubmissionsForReview).Methods(http.MethodGet)
|
||||||
|
api.HandleFunc("/review/{token}/submissions/{submission_id}", rt.reviewHandler.GetSubmissionForReview).
|
||||||
|
Methods(http.MethodGet)
|
||||||
|
api.HandleFunc("/review/{token}/submissions/{submission_id}/evaluate", rt.reviewHandler.EvaluateSubmission).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
api.HandleFunc("/review/{token}/submissions/{submission_id}/release", rt.reviewHandler.ReleaseSubmission).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type S3Storage struct {
|
||||||
|
client *s3.Client
|
||||||
|
bucket string
|
||||||
|
region string
|
||||||
|
endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
type S3Config struct {
|
||||||
|
AccessKeyID string
|
||||||
|
SecretAccessKey string
|
||||||
|
Region string
|
||||||
|
Bucket string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS3Storage(cfg S3Config) (*S3Storage, error) {
|
||||||
|
var loadOpts []func(*config.LoadOptions) error
|
||||||
|
|
||||||
|
if cfg.Region != "" {
|
||||||
|
loadOpts = append(loadOpts, config.WithRegion(cfg.Region))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Endpoint != "" {
|
||||||
|
customResolver := aws.EndpointResolverWithOptionsFunc(
|
||||||
|
func(service, region string, options ...interface{}) (aws.Endpoint, error) {
|
||||||
|
return aws.Endpoint{
|
||||||
|
URL: cfg.Endpoint,
|
||||||
|
SigningRegion: cfg.Region,
|
||||||
|
HostnameImmutable: true,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
loadOpts = append(loadOpts, config.WithEndpointResolverWithOptions(customResolver))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.AccessKeyID != "" || cfg.SecretAccessKey != "" {
|
||||||
|
loadOpts = append(loadOpts, config.WithCredentialsProvider(
|
||||||
|
credentials.NewStaticCredentialsProvider(cfg.AccessKeyID, cfg.SecretAccessKey, ""),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
awsCfg, err := config.LoadDefaultConfig(context.TODO(), loadOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load AWS config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var client *s3.Client
|
||||||
|
if cfg.Endpoint != "" {
|
||||||
|
client = s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
||||||
|
o.UsePathStyle = true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
client = s3.NewFromConfig(awsCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &S3Storage{
|
||||||
|
client: client,
|
||||||
|
bucket: cfg.Bucket,
|
||||||
|
region: cfg.Region,
|
||||||
|
endpoint: cfg.Endpoint,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) UploadFile(ctx context.Context, file multipart.File, header *multipart.FileHeader) (string, error) {
|
||||||
|
fileBytes, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := filepath.Ext(header.Filename)
|
||||||
|
key := fmt.Sprintf("submissions/%s/%s%s",
|
||||||
|
time.Now().Format("2006/01/02"),
|
||||||
|
uuid.New().String(),
|
||||||
|
ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err = s.client.PutObject(ctx, &s3.PutObjectInput{
|
||||||
|
Bucket: aws.String(s.bucket),
|
||||||
|
Key: aws.String(key),
|
||||||
|
Body: bytes.NewReader(fileBytes),
|
||||||
|
ContentType: aws.String(header.Header.Get("Content-Type")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to upload file to S3: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.buildObjectURL(key), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) UploadFileFromBytes(
|
||||||
|
ctx context.Context,
|
||||||
|
content []byte,
|
||||||
|
filename string,
|
||||||
|
contentType string,
|
||||||
|
) (string, error) {
|
||||||
|
ext := filepath.Ext(filename)
|
||||||
|
key := fmt.Sprintf("submissions/%s/%s%s",
|
||||||
|
time.Now().Format("2006/01/02"),
|
||||||
|
uuid.New().String(),
|
||||||
|
ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err := s.client.PutObject(ctx, &s3.PutObjectInput{
|
||||||
|
Bucket: aws.String(s.bucket),
|
||||||
|
Key: aws.String(key),
|
||||||
|
Body: bytes.NewReader(content),
|
||||||
|
ContentType: aws.String(contentType),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to upload file to S3: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.buildObjectURL(key), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) DeleteFile(ctx context.Context, fileURL string) error {
|
||||||
|
key := s.extractKeyFromURL(fileURL)
|
||||||
|
|
||||||
|
_, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
||||||
|
Bucket: aws.String(s.bucket),
|
||||||
|
Key: aws.String(key),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete file from S3: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) buildObjectURL(key string) string {
|
||||||
|
if s.endpoint != "" {
|
||||||
|
ep := strings.TrimRight(s.endpoint, "/")
|
||||||
|
return fmt.Sprintf("%s/%s/%s", ep, s.bucket, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%s", s.bucket, s.region, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3Storage) extractKeyFromURL(urlStr string) string {
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return urlStr
|
||||||
|
}
|
||||||
|
|
||||||
|
path := strings.TrimPrefix(u.Path, "/")
|
||||||
|
|
||||||
|
if strings.HasPrefix(u.Host, s.bucket+".") {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(path, s.bucket+"/") {
|
||||||
|
return strings.TrimPrefix(path, s.bucket+"/")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.endpoint != "" {
|
||||||
|
ep := strings.TrimPrefix(strings.TrimRight(s.endpoint, "/"), "http://")
|
||||||
|
ep = strings.TrimPrefix(ep, "https://")
|
||||||
|
if strings.HasPrefix(u.Host, ep) {
|
||||||
|
if strings.HasPrefix(path, s.bucket+"/") {
|
||||||
|
return strings.TrimPrefix(path, s.bucket+"/")
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
|
||||||
|
achievepb "datarush/pkg/api/achievements"
|
||||||
|
comppb "datarush/pkg/api/competition"
|
||||||
|
resultspb "datarush/pkg/api/results"
|
||||||
|
reviewpb "datarush/pkg/api/review"
|
||||||
|
subpb "datarush/pkg/api/submission"
|
||||||
|
taskpb "datarush/pkg/api/task"
|
||||||
|
userpb "datarush/pkg/api/user"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserProtoToHTTP(u *userpb.User) *domain.UserResponse {
|
||||||
|
return &domain.UserResponse{
|
||||||
|
ID: u.Id,
|
||||||
|
Username: u.Username,
|
||||||
|
Email: u.Email,
|
||||||
|
FullName: u.FullName,
|
||||||
|
AvatarURL: u.AvatarUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompetitionProtoToHTTP(c *comppb.Competition) *domain.CompetitionResponse {
|
||||||
|
return &domain.CompetitionResponse{
|
||||||
|
ID: c.Id,
|
||||||
|
State: CompetitionStateToString(c.State),
|
||||||
|
Title: c.Title,
|
||||||
|
Description: c.Description,
|
||||||
|
ImageURL: c.ImageUrl,
|
||||||
|
StartTime: c.StartTime.AsTime(),
|
||||||
|
EndTime: c.EndTime.AsTime(),
|
||||||
|
Type: CompetitionTypeToString(c.Type),
|
||||||
|
ParticipationType: ParticipationTypeToString(c.ParticipationType),
|
||||||
|
CreatedAt: c.CreatedAt.AsTime(),
|
||||||
|
UpdatedAt: c.UpdatedAt.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TaskProtoToHTTP(t *taskpb.Task) *domain.TaskResponse {
|
||||||
|
return &domain.TaskResponse{
|
||||||
|
ID: t.Id,
|
||||||
|
CompetitionID: t.CompetitionId,
|
||||||
|
Title: t.Title,
|
||||||
|
Description: t.Description,
|
||||||
|
InCompetitionPosition: t.InCompetitionPosition,
|
||||||
|
MaxPoints: t.MaxPoints,
|
||||||
|
MaxAttempts: t.MaxAttempts,
|
||||||
|
Type: TaskTypeToString(t.Type),
|
||||||
|
CreatedAt: t.CreatedAt.AsTime(),
|
||||||
|
UpdatedAt: t.UpdatedAt.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubmissionProtoToHTTP(s *subpb.Submission) *domain.SubmissionResponse {
|
||||||
|
return &domain.SubmissionResponse{
|
||||||
|
ID: s.Id,
|
||||||
|
UserID: s.UserId,
|
||||||
|
CompetitionID: s.CompetitionId,
|
||||||
|
TaskID: s.TaskId,
|
||||||
|
Status: SubmissionStatusToString(s.Status),
|
||||||
|
EarnedPoints: s.EarnedPoints,
|
||||||
|
SubmittedAt: s.SubmittedAt.AsTime(),
|
||||||
|
CheckedAt: s.CheckedAt.AsTime(),
|
||||||
|
FileURL: s.FileUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserResultProtoToHTTP(r *resultspb.UserResult) *domain.UserResultResponse {
|
||||||
|
taskStatuses := make([]domain.TaskStatusResponse, len(r.TaskStatuses))
|
||||||
|
for i, ts := range r.TaskStatuses {
|
||||||
|
taskStatuses[i] = domain.TaskStatusResponse{
|
||||||
|
TaskID: ts.TaskId,
|
||||||
|
TaskTitle: ts.TaskTitle,
|
||||||
|
EarnedPoints: ts.EarnedPoints,
|
||||||
|
MaxPoints: ts.MaxPoints,
|
||||||
|
Position: ts.Position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.UserResultResponse{
|
||||||
|
UserID: r.UserId,
|
||||||
|
Username: r.Username,
|
||||||
|
TotalScore: r.TotalScore,
|
||||||
|
OverallPosition: r.OverallPosition,
|
||||||
|
TaskStatuses: taskStatuses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubmissionSummaryProtoToHTTP(s *reviewpb.SubmissionSummary) *domain.SubmissionSummaryResponse {
|
||||||
|
return &domain.SubmissionSummaryResponse{
|
||||||
|
ID: s.Id,
|
||||||
|
CompetitionID: s.CompetitionId,
|
||||||
|
TaskID: s.TaskId,
|
||||||
|
CompetitionTitle: s.CompetitionTitle,
|
||||||
|
TaskTitle: s.TaskTitle,
|
||||||
|
SubmittedAt: s.SubmittedAt.AsTime(),
|
||||||
|
ReviewStatus: ReviewStatusToString(s.ReviewStatus),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubmissionForReviewProtoToHTTP(s *reviewpb.SubmissionForReview) *domain.SubmissionForReviewResponse {
|
||||||
|
resp := &domain.SubmissionForReviewResponse{
|
||||||
|
ID: s.Id,
|
||||||
|
CompetitionID: s.CompetitionId,
|
||||||
|
TaskID: s.TaskId,
|
||||||
|
Content: s.Content,
|
||||||
|
Description: s.Description,
|
||||||
|
ReviewStatus: ReviewStatusToString(s.ReviewStatus),
|
||||||
|
SubmittedAt: s.SubmittedAt.AsTime(),
|
||||||
|
}
|
||||||
|
if s.CheckedAt != nil {
|
||||||
|
t := s.CheckedAt.AsTime()
|
||||||
|
resp.CheckedAt = &t
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func AchievementProtoToHTTP(a *achievepb.Achievement) *domain.AchievementResponse {
|
||||||
|
return &domain.AchievementResponse{
|
||||||
|
ID: a.Id,
|
||||||
|
Name: a.Name,
|
||||||
|
Description: a.Description,
|
||||||
|
IconURL: a.IconUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompetitionHTTPToProto(req *domain.CompetitionRequest) *comppb.Competition {
|
||||||
|
return &comppb.Competition{
|
||||||
|
Id: req.ID,
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
ImageUrl: req.ImageURL,
|
||||||
|
StartTime: timestamppb.New(req.StartTime),
|
||||||
|
EndTime: timestamppb.New(req.EndTime),
|
||||||
|
Type: StringToCompetitionType(req.Type),
|
||||||
|
ParticipationType: StringToParticipationType(req.ParticipationType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TaskHTTPToProto(req *domain.TaskRequest) *taskpb.Task {
|
||||||
|
return &taskpb.Task{
|
||||||
|
Id: req.ID,
|
||||||
|
CompetitionId: req.CompetitionID,
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
InCompetitionPosition: req.InCompetitionPosition,
|
||||||
|
MaxPoints: req.MaxPoints,
|
||||||
|
MaxAttempts: req.MaxAttempts,
|
||||||
|
Type: StringToTaskType(req.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompetitionStateToString(state comppb.CompetitionState) string {
|
||||||
|
switch state {
|
||||||
|
case comppb.CompetitionState_COMPETITION_STATE_DRAFT:
|
||||||
|
return "draft"
|
||||||
|
case comppb.CompetitionState_COMPETITION_STATE_NOT_STARTED:
|
||||||
|
return "not_started"
|
||||||
|
case comppb.CompetitionState_COMPETITION_STATE_STARTED:
|
||||||
|
return "started"
|
||||||
|
case comppb.CompetitionState_COMPETITION_STATE_FINISHED:
|
||||||
|
return "finished"
|
||||||
|
case comppb.CompetitionState_COMPETITION_STATE_ARCHIVED:
|
||||||
|
return "archived"
|
||||||
|
default:
|
||||||
|
return "unspecified"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToCompetitionState(state string) comppb.CompetitionState {
|
||||||
|
switch state {
|
||||||
|
case "draft":
|
||||||
|
return comppb.CompetitionState_COMPETITION_STATE_DRAFT
|
||||||
|
case "not_started":
|
||||||
|
return comppb.CompetitionState_COMPETITION_STATE_NOT_STARTED
|
||||||
|
case "started":
|
||||||
|
return comppb.CompetitionState_COMPETITION_STATE_STARTED
|
||||||
|
case "finished":
|
||||||
|
return comppb.CompetitionState_COMPETITION_STATE_FINISHED
|
||||||
|
case "archived":
|
||||||
|
return comppb.CompetitionState_COMPETITION_STATE_ARCHIVED
|
||||||
|
default:
|
||||||
|
return comppb.CompetitionState_COMPETITION_STATE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompetitionTypeToString(t comppb.CompetitionType) string {
|
||||||
|
switch t {
|
||||||
|
case comppb.CompetitionType_COMPETITION_TYPE_EDUCATIVE:
|
||||||
|
return "educative"
|
||||||
|
case comppb.CompetitionType_COMPETITION_TYPE_COMPETETIVE:
|
||||||
|
return "competitive"
|
||||||
|
default:
|
||||||
|
return "unspecified"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToCompetitionType(t string) comppb.CompetitionType {
|
||||||
|
switch t {
|
||||||
|
case "educative":
|
||||||
|
return comppb.CompetitionType_COMPETITION_TYPE_EDUCATIVE
|
||||||
|
case "competitive":
|
||||||
|
return comppb.CompetitionType_COMPETITION_TYPE_COMPETETIVE
|
||||||
|
default:
|
||||||
|
return comppb.CompetitionType_COMPETITION_TYPE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParticipationTypeToString(t comppb.ParticipationType) string {
|
||||||
|
switch t {
|
||||||
|
case comppb.ParticipationType_PARTICIPATION_TYPE_INDIVIDUAL:
|
||||||
|
return "individual"
|
||||||
|
case comppb.ParticipationType_PARTICIPATION_TYPE_TEAM:
|
||||||
|
return "team"
|
||||||
|
default:
|
||||||
|
return "unspecified"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToParticipationType(t string) comppb.ParticipationType {
|
||||||
|
switch t {
|
||||||
|
case "individual":
|
||||||
|
return comppb.ParticipationType_PARTICIPATION_TYPE_INDIVIDUAL
|
||||||
|
case "team":
|
||||||
|
return comppb.ParticipationType_PARTICIPATION_TYPE_TEAM
|
||||||
|
default:
|
||||||
|
return comppb.ParticipationType_PARTICIPATION_TYPE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TaskTypeToString(t taskpb.TaskType) string {
|
||||||
|
switch t {
|
||||||
|
case taskpb.TaskType_TASK_TYPE_INPUT:
|
||||||
|
return "input"
|
||||||
|
case taskpb.TaskType_TASK_TYPE_CHECKER:
|
||||||
|
return "checker"
|
||||||
|
case taskpb.TaskType_TASK_TYPE_REVIEW:
|
||||||
|
return "review"
|
||||||
|
default:
|
||||||
|
return "unspecified"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToTaskType(t string) taskpb.TaskType {
|
||||||
|
switch t {
|
||||||
|
case "input":
|
||||||
|
return taskpb.TaskType_TASK_TYPE_INPUT
|
||||||
|
case "checker":
|
||||||
|
return taskpb.TaskType_TASK_TYPE_CHECKER
|
||||||
|
case "review":
|
||||||
|
return taskpb.TaskType_TASK_TYPE_REVIEW
|
||||||
|
default:
|
||||||
|
return taskpb.TaskType_TASK_TYPE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubmissionStatusToString(s subpb.SubmissionStatus) string {
|
||||||
|
switch s {
|
||||||
|
case subpb.SubmissionStatus_SUBMISSION_STATUS_PENDING:
|
||||||
|
return "pending"
|
||||||
|
case subpb.SubmissionStatus_SUBMISSION_STATUS_SENT_FOR_CHECK:
|
||||||
|
return "sent"
|
||||||
|
case subpb.SubmissionStatus_SUBMISSION_STATUS_CHECKING:
|
||||||
|
return "checking"
|
||||||
|
case subpb.SubmissionStatus_SUBMISSION_STATUS_CHECKED:
|
||||||
|
return "checked"
|
||||||
|
case subpb.SubmissionStatus_SUBMISSION_STATUS_FAILED:
|
||||||
|
return "failed"
|
||||||
|
default:
|
||||||
|
return "unspecified"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReviewStatusToString(s reviewpb.ReviewStatus) string {
|
||||||
|
switch s {
|
||||||
|
case reviewpb.ReviewStatus_REVIEW_STATUS_PENDING:
|
||||||
|
return "pending"
|
||||||
|
case reviewpb.ReviewStatus_REVIEW_STATUS_IN_REVIEW:
|
||||||
|
return "in_review"
|
||||||
|
case reviewpb.ReviewStatus_REVIEW_STATUS_COMPLETED:
|
||||||
|
return "completed"
|
||||||
|
case reviewpb.ReviewStatus_REVIEW_STATUS_REJECTED:
|
||||||
|
return "rejected"
|
||||||
|
default:
|
||||||
|
return "unspecified"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"datarush/internal/gw/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RespondJSON(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
|
||||||
|
if data != nil {
|
||||||
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||||
|
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RespondError(w http.ResponseWriter, err error) {
|
||||||
|
if appErr, ok := err.(*domain.AppError); ok {
|
||||||
|
RespondJSON(w, appErr.StatusCode, domain.NewErrorResponse(appErr.Err, appErr.Message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
RespondJSON(w, http.StatusInternalServerError, domain.NewErrorResponse(err, "internal server error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeJSON(r *http.Request, v interface{}) error {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(v); err != nil {
|
||||||
|
return domain.NewBadRequestError("invalid JSON body")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package domain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidID = errors.New("invalid uuid")
|
|
||||||
)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package domain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrOrderAlreadyExist = errors.New("order already exist")
|
|
||||||
ErrOrderNotFound = errors.New("order not found")
|
|
||||||
ErrInvalidOrderData = errors.New("invalid order data")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Order struct {
|
|
||||||
ID uuid.UUID `db:"id" json:"id" validate:"required"`
|
|
||||||
Item string `db:"item" json:"item" validate:"required"`
|
|
||||||
Quantity int32 `db:"quantity" json:"quantity" validate:"required,gt=0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOrder(id uuid.UUID, item string, quantity int32) (*Order, error) {
|
|
||||||
order := &Order{
|
|
||||||
ID: id,
|
|
||||||
Item: item,
|
|
||||||
Quantity: quantity,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := order.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Order) Validate() error {
|
|
||||||
validate := validator.New()
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: %w", ErrInvalidOrderData, validate.Struct(o))
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package gateway
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
authPb "datarush/pkg/api/auth"
|
|
||||||
|
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
httpReadTimeout = 10 * time.Second
|
|
||||||
httpWriteTimeout = 10 * time.Second
|
|
||||||
httpIdleTimeout = 60 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartGateway(grpcPort, httpPort int, authGrpcAddr string) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
gwmux := runtime.NewServeMux()
|
|
||||||
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
|
||||||
|
|
||||||
// Register auth service
|
|
||||||
if err := authPb.RegisterAuthServiceHandlerFromEndpoint(ctx, gwmux, authGrpcAddr, opts); err != nil {
|
|
||||||
return fmt.Errorf("failed to register auth service: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%d", httpPort),
|
|
||||||
Handler: gwmux,
|
|
||||||
ReadTimeout: httpReadTimeout,
|
|
||||||
WriteTimeout: httpWriteTimeout,
|
|
||||||
IdleTimeout: httpIdleTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("starting gRPC-Gateway on port %d", httpPort)
|
|
||||||
return srv.ListenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetGRPCListener(port int) (net.Listener, error) {
|
|
||||||
return net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"datarush/internal/lms/domain"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mapError(err error) error {
|
|
||||||
if errors.Is(err, domain.ErrOrderNotFound) {
|
|
||||||
return status.Error(codes.NotFound, err.Error())
|
|
||||||
}
|
|
||||||
if errors.Is(err, domain.ErrOrderAlreadyExist) {
|
|
||||||
return status.Error(codes.AlreadyExists, err.Error())
|
|
||||||
}
|
|
||||||
if errors.Is(err, domain.ErrInvalidOrderData) || errors.Is(err, domain.ErrInvalidID) {
|
|
||||||
return status.Error(codes.InvalidArgument, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("internal server error: %v", err)
|
|
||||||
return status.Error(codes.Internal, "internal server error")
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HealthHandler struct {
|
|
||||||
DB *sqlx.DB
|
|
||||||
Redis *redis.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHealthHandler(db *sqlx.DB, redisDB *redis.Client) *HealthHandler {
|
|
||||||
return &HealthHandler{
|
|
||||||
DB: db,
|
|
||||||
Redis: redisDB,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type HealthResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Details map[string]string `json:"details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
details := map[string]string{}
|
|
||||||
|
|
||||||
if err := h.DB.PingContext(ctx); err != nil {
|
|
||||||
details["postgres"] = "unhealthy: " + err.Error()
|
|
||||||
} else {
|
|
||||||
details["postgres"] = "ok"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.Redis.Ping(ctx).Err(); err != nil {
|
|
||||||
details["redis"] = "unhealthy: " + err.Error()
|
|
||||||
} else {
|
|
||||||
details["redis"] = "ok"
|
|
||||||
}
|
|
||||||
|
|
||||||
status := "ok"
|
|
||||||
for _, v := range details {
|
|
||||||
if v != "ok" {
|
|
||||||
status = "unhealthy"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := HealthResponse{Status: status, Details: details}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
if status != "ok" {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(resp)
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package inmemory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"datarush/internal/lms/domain"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrderRepository struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
orders map[string]*domain.Order
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOrderRepository() *OrderRepository {
|
|
||||||
return &OrderRepository{
|
|
||||||
orders: make(map[string]*domain.Order),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Create(ctx context.Context, order *domain.Order) error {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
if _, ok := r.orders[order.ID.String()]; ok {
|
|
||||||
return domain.ErrOrderAlreadyExist
|
|
||||||
}
|
|
||||||
r.orders[order.ID.String()] = order
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Get(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.RLock()
|
|
||||||
defer r.mu.RUnlock()
|
|
||||||
|
|
||||||
order, ok := r.orders[id.String()]
|
|
||||||
if !ok {
|
|
||||||
return nil, domain.ErrOrderNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Update(ctx context.Context, order *domain.Order) error {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
if _, ok := r.orders[order.ID.String()]; !ok {
|
|
||||||
return domain.ErrOrderNotFound
|
|
||||||
}
|
|
||||||
r.orders[order.ID.String()] = order
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
if _, ok := r.orders[id.String()]; !ok {
|
|
||||||
return domain.ErrOrderNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(r.orders, id.String())
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) List(ctx context.Context) ([]*domain.Order, error) {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.RLock()
|
|
||||||
defer r.mu.RUnlock()
|
|
||||||
|
|
||||||
orders := make([]*domain.Order, 0, len(r.orders))
|
|
||||||
for _, order := range r.orders {
|
|
||||||
orders = append(orders, order)
|
|
||||||
}
|
|
||||||
|
|
||||||
return orders, nil
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"datarush/internal/lms/domain"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrderRepository interface {
|
|
||||||
Create(ctx context.Context, order *domain.Order) error
|
|
||||||
Get(ctx context.Context, id uuid.UUID) (*domain.Order, error)
|
|
||||||
Update(ctx context.Context, order *domain.Order) error
|
|
||||||
Delete(ctx context.Context, id uuid.UUID) error
|
|
||||||
List(ctx context.Context) ([]*domain.Order, error)
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"datarush/internal/lms/domain"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
orderCachePrefix = "order:"
|
|
||||||
cacheTTL = 5 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrderRepository struct {
|
|
||||||
db *sqlx.DB
|
|
||||||
redisClient *redis.Client
|
|
||||||
cacheEnable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
CacheEnable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOrderRepository(db *sqlx.DB, redisClient *redis.Client, config *Config) *OrderRepository {
|
|
||||||
if config == nil {
|
|
||||||
config = &Config{
|
|
||||||
CacheEnable: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &OrderRepository{
|
|
||||||
db: db,
|
|
||||||
redisClient: redisClient,
|
|
||||||
cacheEnable: config.CacheEnable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) cacheKey(id string) string {
|
|
||||||
return orderCachePrefix + id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Create(ctx context.Context, order *domain.Order) error {
|
|
||||||
tx, err := r.db.BeginTxx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
query := `
|
|
||||||
insert into orders (id, item, quantity)
|
|
||||||
values (:id, :item, :quantity)
|
|
||||||
`
|
|
||||||
|
|
||||||
if _, err := tx.NamedExecContext(ctx, query, order); err != nil {
|
|
||||||
return fmt.Errorf("create order: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.cacheEnable {
|
|
||||||
if err := r.setCacheWithRetry(ctx, order); err != nil {
|
|
||||||
log.Printf("warn: cache set error for order %s: %v", order.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Get(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
|
|
||||||
if r.cacheEnable {
|
|
||||||
if order, err := r.getFromCache(ctx, id.String()); err == nil {
|
|
||||||
return order, nil
|
|
||||||
} else if !errors.Is(err, redis.Nil) {
|
|
||||||
log.Printf("warn: cache get error for order %s: %v", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
select id, item, quantity
|
|
||||||
from orders
|
|
||||||
where id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
var order domain.Order
|
|
||||||
if err := r.db.GetContext(ctx, &order, query, id); err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return nil, domain.ErrOrderNotFound
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("get order by id: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.cacheEnable {
|
|
||||||
if err := r.setCacheWithRetry(ctx, &order); err != nil {
|
|
||||||
log.Printf("warn: cache set error for order %s: %v", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Update(ctx context.Context, order *domain.Order) error {
|
|
||||||
tx, err := r.db.BeginTxx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
query := `
|
|
||||||
update orders
|
|
||||||
set item = :item, quantity = :quantity
|
|
||||||
where id = :id
|
|
||||||
`
|
|
||||||
|
|
||||||
result, err := tx.NamedExecContext(ctx, query, order)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("update order: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsAffected, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get rows affected: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAffected == 0 {
|
|
||||||
return domain.ErrOrderNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.cacheEnable {
|
|
||||||
if err := r.setCacheWithRetry(ctx, order); err != nil {
|
|
||||||
log.Printf("warn: cache set error for order %s: %v", order.ID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
|
||||||
tx, err := r.db.BeginTxx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
delete from orders
|
|
||||||
where id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
result, err := tx.ExecContext(ctx, query, id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("delete order: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowsAffected, err := result.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get rows affected: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowsAffected == 0 {
|
|
||||||
return domain.ErrOrderNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.invalidateCache(ctx, id.String())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) List(ctx context.Context) ([]*domain.Order, error) {
|
|
||||||
const query = `
|
|
||||||
select id, item, quantity
|
|
||||||
from orders
|
|
||||||
order by id
|
|
||||||
`
|
|
||||||
|
|
||||||
var orders []*domain.Order
|
|
||||||
if err := r.db.SelectContext(ctx, &orders, query); err != nil {
|
|
||||||
return nil, fmt.Errorf("list orders: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return orders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) getFromCache(ctx context.Context, id string) (*domain.Order, error) {
|
|
||||||
data, err := r.redisClient.Get(ctx, r.cacheKey(id)).Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var order domain.Order
|
|
||||||
if err := json.Unmarshal(data, &order); err != nil {
|
|
||||||
r.redisClient.Del(ctx, r.cacheKey(id))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) setCacheWithRetry(ctx context.Context, order *domain.Order) error {
|
|
||||||
data, err := json.Marshal(order)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := r.cacheKey(order.ID.String())
|
|
||||||
|
|
||||||
err = r.redisClient.Set(ctx, key, data, cacheTTL).Err()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OrderRepository) invalidateCache(_ context.Context, id string) {
|
|
||||||
if !r.cacheEnable {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), r.redisClient.Options().ReadTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := r.redisClient.Del(ctx, r.cacheKey(id)).Err(); err != nil {
|
|
||||||
log.Printf("warn: cache invalidation failed for order %s: %v", id, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"datarush/internal/lms/config"
|
|
||||||
"datarush/internal/lms/interceptor"
|
|
||||||
|
|
||||||
httpHandlers "datarush/internal/lms/handler/http"
|
|
||||||
|
|
||||||
authPb "datarush/pkg/api/auth"
|
|
||||||
|
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
_ "github.com/lib/pq" // postgres driver
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
"google.golang.org/grpc/reflection"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
httpReadTimeout = 10 * time.Second
|
|
||||||
httpWriteTimeout = 10 * time.Second
|
|
||||||
httpIdleTimeout = 60 * time.Second
|
|
||||||
redisRetryCount = 2
|
|
||||||
redisMinRetryBackoff = 50 * time.Millisecond
|
|
||||||
redisMaxRetryBackoff = 200 * time.Millisecond
|
|
||||||
redisDialTimeout = 1 * time.Second
|
|
||||||
redisDialerRetries = 3
|
|
||||||
redisTimeout = 2 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
grpcServer *grpc.Server
|
|
||||||
config *config.Config
|
|
||||||
db *sqlx.DB
|
|
||||||
redisDB *redis.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(cfg *config.Config) *Server {
|
|
||||||
loggerInterceptor := interceptor.NewLoggerInterceptor()
|
|
||||||
|
|
||||||
grpcServer := grpc.NewServer(
|
|
||||||
grpc.UnaryInterceptor(loggerInterceptor.Unary()),
|
|
||||||
grpc.StreamInterceptor(loggerInterceptor.Stream()),
|
|
||||||
)
|
|
||||||
|
|
||||||
return &Server{
|
|
||||||
grpcServer: grpcServer,
|
|
||||||
config: cfg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHTTPHandler(s *Server, grpcServerEndpoint *string) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
gwmux := runtime.NewServeMux()
|
|
||||||
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
|
||||||
|
|
||||||
// Register auth service
|
|
||||||
if err := registerAuthService(ctx, gwmux, s.config.AuthGRPCAddr, opts); err != nil {
|
|
||||||
log.Printf("failed to register auth service: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.Handle("/healthz", httpHandlers.NewHealthHandler(s.db, s.redisDB))
|
|
||||||
mux.Handle("/", gwmux)
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%d", s.config.HTTPPort),
|
|
||||||
Handler: mux,
|
|
||||||
ReadTimeout: httpReadTimeout,
|
|
||||||
WriteTimeout: httpWriteTimeout,
|
|
||||||
IdleTimeout: httpIdleTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv.ListenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerAuthService(ctx context.Context, gwmux *runtime.ServeMux, authAddr string, opts []grpc.DialOption) error {
|
|
||||||
if err := authPb.RegisterAuthServiceHandlerFromEndpoint(ctx, gwmux, authAddr, opts); err != nil {
|
|
||||||
return fmt.Errorf("register auth service handler: %w", err)
|
|
||||||
}
|
|
||||||
log.Printf("registered auth service from %s", authAddr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerAuthHandlerFromEndpoint(ctx context.Context, gwmux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error {
|
|
||||||
// We'll import the proto and register it here
|
|
||||||
// For now, this is a placeholder that will be called from gateway integration
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func getDatabase(cfg config.Config) (*sqlx.DB, error) {
|
|
||||||
db, err := sqlx.Connect("postgres", cfg.BuildPostgresConnStr())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("connect to database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRedis(cfg config.Config) (*redis.Client, error) {
|
|
||||||
conn, err := redis.ParseURL(cfg.RedisURI)
|
|
||||||
client := redis.NewClient(&redis.Options{
|
|
||||||
Addr: conn.Addr,
|
|
||||||
MaxRetries: redisRetryCount,
|
|
||||||
MinRetryBackoff: redisMinRetryBackoff,
|
|
||||||
MaxRetryBackoff: redisMaxRetryBackoff,
|
|
||||||
DialTimeout: redisDialTimeout,
|
|
||||||
DialerRetries: redisDialerRetries,
|
|
||||||
DialerRetryTimeout: redisDialTimeout,
|
|
||||||
ReadTimeout: redisTimeout,
|
|
||||||
WriteTimeout: redisTimeout,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parse Redis URI: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Ping(context.Background()).Result()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("connect to Redis server: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) RegisterServices() {
|
|
||||||
db, err := getDatabase(*s.config)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
s.db = db
|
|
||||||
|
|
||||||
redisDB, err := getRedis(*s.config)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
s.redisDB = redisDB
|
|
||||||
|
|
||||||
if s.config.GRPCEnableReflection {
|
|
||||||
reflection.Register(s.grpcServer)
|
|
||||||
log.Println("gRPC server will start with reflection")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
|
||||||
addr := fmt.Sprintf(":%d", s.config.GRPCPort)
|
|
||||||
lis, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to listen: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.config.EnableHTTPHandler {
|
|
||||||
go func() {
|
|
||||||
log.Printf("starting HTTP gateway on port %d", s.config.HTTPPort)
|
|
||||||
if err := runHTTPHandler(s, &addr); err != nil {
|
|
||||||
log.Printf("HTTP gateway failed: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("starting gRPC server on port %d", s.config.GRPCPort)
|
|
||||||
|
|
||||||
if err := s.grpcServer.Serve(lis); err != nil {
|
|
||||||
return fmt.Errorf("failed to serve: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Stop() {
|
|
||||||
s.grpcServer.GracefulStop()
|
|
||||||
log.Println("gRPC server stopped gracefully")
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"datarush/internal/lms/domain"
|
|
||||||
"datarush/internal/lms/repository"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OrderService struct {
|
|
||||||
repo repository.OrderRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOrderService(repo repository.OrderRepository) *OrderService {
|
|
||||||
return &OrderService{
|
|
||||||
repo: repo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OrderService) Create(ctx context.Context, item string, quantity int32) (*domain.Order, error) {
|
|
||||||
order, err := domain.NewOrder(uuid.New(), item, quantity)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.repo.Create(ctx, order); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OrderService) Get(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
|
|
||||||
return s.repo.Get(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OrderService) Update(ctx context.Context, id uuid.UUID, item string, quantity int32) (*domain.Order, error) {
|
|
||||||
order, err := domain.NewOrder(id, item, quantity)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.repo.Update(ctx, order); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OrderService) Delete(ctx context.Context, id uuid.UUID) error {
|
|
||||||
return s.repo.Delete(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *OrderService) List(ctx context.Context) ([]*domain.Order, error) {
|
|
||||||
return s.repo.List(ctx)
|
|
||||||
}
|
|
||||||
@@ -22,25 +22,17 @@ type Config struct {
|
|||||||
DBPassword string
|
DBPassword string
|
||||||
DBName string
|
DBName string
|
||||||
RedisURI string
|
RedisURI string
|
||||||
AuthGRPCAddr string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() (*Config, error) {
|
func Load() (*Config, error) {
|
||||||
_ = godotenv.Load()
|
_ = godotenv.Load()
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
GRPCPort: mustGetInt("GRPC_PORT", 50051), //nolint:mnd // false-positive
|
|
||||||
GRPCEnableReflection: mustGetBool("GRPC_ENABLE_REFLECTION", false),
|
|
||||||
EnableHTTPHandler: mustGetBool("HTTP_HANDLER_ENABLE", false),
|
|
||||||
HTTPPort: mustGetInt("HTTP_PORT", 8080), //nolint:mnd // false-positive
|
|
||||||
LogLevel: getEnv("LOG_LEVEL", "info"),
|
|
||||||
DBHost: getEnv("POSTGRES_HOST", "localhost"),
|
DBHost: getEnv("POSTGRES_HOST", "localhost"),
|
||||||
DBPort: mustGetInt("POSTGRES_PORT", 5432), //nolint:mnd // false-positive
|
DBPort: mustGetInt("POSTGRES_PORT", 5432), //nolint:mnd // false-positive
|
||||||
DBUser: getEnv("POSTGRES_USERNAME", "postgres"),
|
DBUser: getEnv("POSTGRES_USERNAME", "postgres"),
|
||||||
DBPassword: getEnv("POSTGRES_PASSWORD", "postgres"),
|
DBPassword: getEnv("POSTGRES_PASSWORD", "postgres"),
|
||||||
DBName: getEnv("POSTGRES_DATABASE", "postgres"),
|
DBName: getEnv("POSTGRES_DATABASE", "postgres"),
|
||||||
RedisURI: getEnv("REDIS_URI", "redis://localhost:6379"),
|
|
||||||
AuthGRPCAddr: getEnv("AUTH_GRPC_ADDR", "localhost:50052"),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v6.33.2
|
||||||
|
// source: api/proto/achievements.proto
|
||||||
|
|
||||||
|
package achievements
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Achievement struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
|
||||||
|
IconUrl string `protobuf:"bytes,4,opt,name=icon_url,json=iconUrl,proto3" json:"icon_url,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Achievement) Reset() {
|
||||||
|
*x = Achievement{}
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Achievement) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Achievement) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Achievement) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Achievement.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Achievement) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_achievements_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Achievement) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Achievement) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Achievement) GetDescription() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Description
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Achievement) GetIconUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.IconUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type AchievementUser struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Achievement *Achievement `protobuf:"bytes,1,opt,name=achievement,proto3" json:"achievement,omitempty"`
|
||||||
|
ReceivedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AchievementUser) Reset() {
|
||||||
|
*x = AchievementUser{}
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AchievementUser) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*AchievementUser) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *AchievementUser) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use AchievementUser.ProtoReflect.Descriptor instead.
|
||||||
|
func (*AchievementUser) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_achievements_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AchievementUser) GetAchievement() *Achievement {
|
||||||
|
if x != nil {
|
||||||
|
return x.Achievement
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AchievementUser) GetReceivedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReceivedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetAchievementRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetAchievementRequest) Reset() {
|
||||||
|
*x = GetAchievementRequest{}
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetAchievementRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetAchievementRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetAchievementRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetAchievementRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetAchievementRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_achievements_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetAchievementRequest) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteAchievementRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
AchievementId string `protobuf:"bytes,1,opt,name=achievement_id,json=achievementId,proto3" json:"achievement_id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteAchievementRequest) Reset() {
|
||||||
|
*x = DeleteAchievementRequest{}
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteAchievementRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteAchievementRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteAchievementRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeleteAchievementRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteAchievementRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_achievements_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteAchievementRequest) GetAchievementId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AchievementId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListAchievementsResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Achievements []*Achievement `protobuf:"bytes,1,rep,name=achievements,proto3" json:"achievements,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListAchievementsResponse) Reset() {
|
||||||
|
*x = ListAchievementsResponse{}
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListAchievementsResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListAchievementsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListAchievementsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListAchievementsResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListAchievementsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_achievements_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListAchievementsResponse) GetAchievements() []*Achievement {
|
||||||
|
if x != nil {
|
||||||
|
return x.Achievements
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserAchievementsRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsRequest) Reset() {
|
||||||
|
*x = GetUserAchievementsRequest{}
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetUserAchievementsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetUserAchievementsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetUserAchievementsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_achievements_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsRequest) GetUserId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserAchievementsResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UserAchievements []*AchievementUser `protobuf:"bytes,1,rep,name=user_achievements,json=userAchievements,proto3" json:"user_achievements,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsResponse) Reset() {
|
||||||
|
*x = GetUserAchievementsResponse{}
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetUserAchievementsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_achievements_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetUserAchievementsResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetUserAchievementsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_achievements_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserAchievementsResponse) GetUserAchievements() []*AchievementUser {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserAchievements
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_api_proto_achievements_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_api_proto_achievements_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x1capi/proto/achievements.proto\x12\fachievements\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\"n\n" +
|
||||||
|
"\vAchievement\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
||||||
|
"\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
|
||||||
|
"\vdescription\x18\x03 \x01(\tR\vdescription\x12\x19\n" +
|
||||||
|
"\bicon_url\x18\x04 \x01(\tR\aiconUrl\"\x8b\x01\n" +
|
||||||
|
"\x0fAchievementUser\x12;\n" +
|
||||||
|
"\vachievement\x18\x01 \x01(\v2\x19.achievements.AchievementR\vachievement\x12;\n" +
|
||||||
|
"\vreceived_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\n" +
|
||||||
|
"receivedAt\"'\n" +
|
||||||
|
"\x15GetAchievementRequest\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\tR\x02id\"A\n" +
|
||||||
|
"\x18DeleteAchievementRequest\x12%\n" +
|
||||||
|
"\x0eachievement_id\x18\x01 \x01(\tR\rachievementId\"Y\n" +
|
||||||
|
"\x18ListAchievementsResponse\x12=\n" +
|
||||||
|
"\fachievements\x18\x01 \x03(\v2\x19.achievements.AchievementR\fachievements\"5\n" +
|
||||||
|
"\x1aGetUserAchievementsRequest\x12\x17\n" +
|
||||||
|
"\auser_id\x18\x01 \x01(\tR\x06userId\"i\n" +
|
||||||
|
"\x1bGetUserAchievementsResponse\x12J\n" +
|
||||||
|
"\x11user_achievements\x18\x01 \x03(\v2\x1d.achievements.AchievementUserR\x10userAchievements2\x90\x04\n" +
|
||||||
|
"\x13AchievementsService\x12I\n" +
|
||||||
|
"\x11CreateAchievement\x12\x19.achievements.Achievement\x1a\x19.achievements.Achievement\x12P\n" +
|
||||||
|
"\x0eGetAchievement\x12#.achievements.GetAchievementRequest\x1a\x19.achievements.Achievement\x12G\n" +
|
||||||
|
"\x0fEditAchievement\x12\x19.achievements.Achievement\x1a\x19.achievements.Achievement\x12S\n" +
|
||||||
|
"\x11DeleteAchievement\x12&.achievements.DeleteAchievementRequest\x1a\x16.google.protobuf.Empty\x12R\n" +
|
||||||
|
"\x10ListAchievements\x12\x16.google.protobuf.Empty\x1a&.achievements.ListAchievementsResponse\x12j\n" +
|
||||||
|
"\x13GetUserAchievements\x12(.achievements.GetUserAchievementsRequest\x1a).achievements.GetUserAchievementsResponseB\x16Z\x14pkg/api/achievementsb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_api_proto_achievements_proto_rawDescOnce sync.Once
|
||||||
|
file_api_proto_achievements_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_api_proto_achievements_proto_rawDescGZIP() []byte {
|
||||||
|
file_api_proto_achievements_proto_rawDescOnce.Do(func() {
|
||||||
|
file_api_proto_achievements_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_achievements_proto_rawDesc), len(file_api_proto_achievements_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_api_proto_achievements_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_api_proto_achievements_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||||
|
var file_api_proto_achievements_proto_goTypes = []any{
|
||||||
|
(*Achievement)(nil), // 0: achievements.Achievement
|
||||||
|
(*AchievementUser)(nil), // 1: achievements.AchievementUser
|
||||||
|
(*GetAchievementRequest)(nil), // 2: achievements.GetAchievementRequest
|
||||||
|
(*DeleteAchievementRequest)(nil), // 3: achievements.DeleteAchievementRequest
|
||||||
|
(*ListAchievementsResponse)(nil), // 4: achievements.ListAchievementsResponse
|
||||||
|
(*GetUserAchievementsRequest)(nil), // 5: achievements.GetUserAchievementsRequest
|
||||||
|
(*GetUserAchievementsResponse)(nil), // 6: achievements.GetUserAchievementsResponse
|
||||||
|
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
|
||||||
|
(*emptypb.Empty)(nil), // 8: google.protobuf.Empty
|
||||||
|
}
|
||||||
|
var file_api_proto_achievements_proto_depIdxs = []int32{
|
||||||
|
0, // 0: achievements.AchievementUser.achievement:type_name -> achievements.Achievement
|
||||||
|
7, // 1: achievements.AchievementUser.received_at:type_name -> google.protobuf.Timestamp
|
||||||
|
0, // 2: achievements.ListAchievementsResponse.achievements:type_name -> achievements.Achievement
|
||||||
|
1, // 3: achievements.GetUserAchievementsResponse.user_achievements:type_name -> achievements.AchievementUser
|
||||||
|
0, // 4: achievements.AchievementsService.CreateAchievement:input_type -> achievements.Achievement
|
||||||
|
2, // 5: achievements.AchievementsService.GetAchievement:input_type -> achievements.GetAchievementRequest
|
||||||
|
0, // 6: achievements.AchievementsService.EditAchievement:input_type -> achievements.Achievement
|
||||||
|
3, // 7: achievements.AchievementsService.DeleteAchievement:input_type -> achievements.DeleteAchievementRequest
|
||||||
|
8, // 8: achievements.AchievementsService.ListAchievements:input_type -> google.protobuf.Empty
|
||||||
|
5, // 9: achievements.AchievementsService.GetUserAchievements:input_type -> achievements.GetUserAchievementsRequest
|
||||||
|
0, // 10: achievements.AchievementsService.CreateAchievement:output_type -> achievements.Achievement
|
||||||
|
0, // 11: achievements.AchievementsService.GetAchievement:output_type -> achievements.Achievement
|
||||||
|
0, // 12: achievements.AchievementsService.EditAchievement:output_type -> achievements.Achievement
|
||||||
|
8, // 13: achievements.AchievementsService.DeleteAchievement:output_type -> google.protobuf.Empty
|
||||||
|
4, // 14: achievements.AchievementsService.ListAchievements:output_type -> achievements.ListAchievementsResponse
|
||||||
|
6, // 15: achievements.AchievementsService.GetUserAchievements:output_type -> achievements.GetUserAchievementsResponse
|
||||||
|
10, // [10:16] is the sub-list for method output_type
|
||||||
|
4, // [4:10] is the sub-list for method input_type
|
||||||
|
4, // [4:4] is the sub-list for extension type_name
|
||||||
|
4, // [4:4] is the sub-list for extension extendee
|
||||||
|
0, // [0:4] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_api_proto_achievements_proto_init() }
|
||||||
|
func file_api_proto_achievements_proto_init() {
|
||||||
|
if File_api_proto_achievements_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_achievements_proto_rawDesc), len(file_api_proto_achievements_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 7,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_api_proto_achievements_proto_goTypes,
|
||||||
|
DependencyIndexes: file_api_proto_achievements_proto_depIdxs,
|
||||||
|
MessageInfos: file_api_proto_achievements_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_api_proto_achievements_proto = out.File
|
||||||
|
file_api_proto_achievements_proto_goTypes = nil
|
||||||
|
file_api_proto_achievements_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
|
// - protoc v6.33.2
|
||||||
|
// source: api/proto/achievements.proto
|
||||||
|
|
||||||
|
package achievements
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
AchievementsService_CreateAchievement_FullMethodName = "/achievements.AchievementsService/CreateAchievement"
|
||||||
|
AchievementsService_GetAchievement_FullMethodName = "/achievements.AchievementsService/GetAchievement"
|
||||||
|
AchievementsService_EditAchievement_FullMethodName = "/achievements.AchievementsService/EditAchievement"
|
||||||
|
AchievementsService_DeleteAchievement_FullMethodName = "/achievements.AchievementsService/DeleteAchievement"
|
||||||
|
AchievementsService_ListAchievements_FullMethodName = "/achievements.AchievementsService/ListAchievements"
|
||||||
|
AchievementsService_GetUserAchievements_FullMethodName = "/achievements.AchievementsService/GetUserAchievements"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AchievementsServiceClient is the client API for AchievementsService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type AchievementsServiceClient interface {
|
||||||
|
CreateAchievement(ctx context.Context, in *Achievement, opts ...grpc.CallOption) (*Achievement, error)
|
||||||
|
GetAchievement(ctx context.Context, in *GetAchievementRequest, opts ...grpc.CallOption) (*Achievement, error)
|
||||||
|
EditAchievement(ctx context.Context, in *Achievement, opts ...grpc.CallOption) (*Achievement, error)
|
||||||
|
DeleteAchievement(ctx context.Context, in *DeleteAchievementRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
ListAchievements(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListAchievementsResponse, error)
|
||||||
|
GetUserAchievements(ctx context.Context, in *GetUserAchievementsRequest, opts ...grpc.CallOption) (*GetUserAchievementsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type achievementsServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementsServiceClient(cc grpc.ClientConnInterface) AchievementsServiceClient {
|
||||||
|
return &achievementsServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *achievementsServiceClient) CreateAchievement(ctx context.Context, in *Achievement, opts ...grpc.CallOption) (*Achievement, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(Achievement)
|
||||||
|
err := c.cc.Invoke(ctx, AchievementsService_CreateAchievement_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *achievementsServiceClient) GetAchievement(ctx context.Context, in *GetAchievementRequest, opts ...grpc.CallOption) (*Achievement, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(Achievement)
|
||||||
|
err := c.cc.Invoke(ctx, AchievementsService_GetAchievement_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *achievementsServiceClient) EditAchievement(ctx context.Context, in *Achievement, opts ...grpc.CallOption) (*Achievement, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(Achievement)
|
||||||
|
err := c.cc.Invoke(ctx, AchievementsService_EditAchievement_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *achievementsServiceClient) DeleteAchievement(ctx context.Context, in *DeleteAchievementRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, AchievementsService_DeleteAchievement_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *achievementsServiceClient) ListAchievements(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ListAchievementsResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListAchievementsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AchievementsService_ListAchievements_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *achievementsServiceClient) GetUserAchievements(ctx context.Context, in *GetUserAchievementsRequest, opts ...grpc.CallOption) (*GetUserAchievementsResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(GetUserAchievementsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AchievementsService_GetUserAchievements_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AchievementsServiceServer is the server API for AchievementsService service.
|
||||||
|
// All implementations must embed UnimplementedAchievementsServiceServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type AchievementsServiceServer interface {
|
||||||
|
CreateAchievement(context.Context, *Achievement) (*Achievement, error)
|
||||||
|
GetAchievement(context.Context, *GetAchievementRequest) (*Achievement, error)
|
||||||
|
EditAchievement(context.Context, *Achievement) (*Achievement, error)
|
||||||
|
DeleteAchievement(context.Context, *DeleteAchievementRequest) (*emptypb.Empty, error)
|
||||||
|
ListAchievements(context.Context, *emptypb.Empty) (*ListAchievementsResponse, error)
|
||||||
|
GetUserAchievements(context.Context, *GetUserAchievementsRequest) (*GetUserAchievementsResponse, error)
|
||||||
|
mustEmbedUnimplementedAchievementsServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedAchievementsServiceServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedAchievementsServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedAchievementsServiceServer) CreateAchievement(context.Context, *Achievement) (*Achievement, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method CreateAchievement not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAchievementsServiceServer) GetAchievement(context.Context, *GetAchievementRequest) (*Achievement, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetAchievement not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAchievementsServiceServer) EditAchievement(context.Context, *Achievement) (*Achievement, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method EditAchievement not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAchievementsServiceServer) DeleteAchievement(context.Context, *DeleteAchievementRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method DeleteAchievement not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAchievementsServiceServer) ListAchievements(context.Context, *emptypb.Empty) (*ListAchievementsResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ListAchievements not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAchievementsServiceServer) GetUserAchievements(context.Context, *GetUserAchievementsRequest) (*GetUserAchievementsResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetUserAchievements not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedAchievementsServiceServer) mustEmbedUnimplementedAchievementsServiceServer() {}
|
||||||
|
func (UnimplementedAchievementsServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeAchievementsServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to AchievementsServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeAchievementsServiceServer interface {
|
||||||
|
mustEmbedUnimplementedAchievementsServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterAchievementsServiceServer(s grpc.ServiceRegistrar, srv AchievementsServiceServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedAchievementsServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&AchievementsService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AchievementsService_CreateAchievement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(Achievement)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AchievementsServiceServer).CreateAchievement(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AchievementsService_CreateAchievement_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AchievementsServiceServer).CreateAchievement(ctx, req.(*Achievement))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AchievementsService_GetAchievement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetAchievementRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AchievementsServiceServer).GetAchievement(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AchievementsService_GetAchievement_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AchievementsServiceServer).GetAchievement(ctx, req.(*GetAchievementRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AchievementsService_EditAchievement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(Achievement)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AchievementsServiceServer).EditAchievement(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AchievementsService_EditAchievement_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AchievementsServiceServer).EditAchievement(ctx, req.(*Achievement))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AchievementsService_DeleteAchievement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DeleteAchievementRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AchievementsServiceServer).DeleteAchievement(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AchievementsService_DeleteAchievement_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AchievementsServiceServer).DeleteAchievement(ctx, req.(*DeleteAchievementRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AchievementsService_ListAchievements_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AchievementsServiceServer).ListAchievements(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AchievementsService_ListAchievements_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AchievementsServiceServer).ListAchievements(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _AchievementsService_GetUserAchievements_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetUserAchievementsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AchievementsServiceServer).GetUserAchievements(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AchievementsService_GetUserAchievements_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AchievementsServiceServer).GetUserAchievements(ctx, req.(*GetUserAchievementsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AchievementsService_ServiceDesc is the grpc.ServiceDesc for AchievementsService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var AchievementsService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "achievements.AchievementsService",
|
||||||
|
HandlerType: (*AchievementsServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "CreateAchievement",
|
||||||
|
Handler: _AchievementsService_CreateAchievement_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetAchievement",
|
||||||
|
Handler: _AchievementsService_GetAchievement_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "EditAchievement",
|
||||||
|
Handler: _AchievementsService_EditAchievement_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DeleteAchievement",
|
||||||
|
Handler: _AchievementsService_DeleteAchievement_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListAchievements",
|
||||||
|
Handler: _AchievementsService_ListAchievements_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetUserAchievements",
|
||||||
|
Handler: _AchievementsService_GetUserAchievements_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "api/proto/achievements.proto",
|
||||||
|
}
|
||||||
@@ -236,9 +236,9 @@ func (x *GetCompetitionResultsRequest) GetCompetitionId() string {
|
|||||||
|
|
||||||
type GetCompetitionResultsResponse struct {
|
type GetCompetitionResultsResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Results []*UserResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"`
|
TotalCount int32 `protobuf:"varint,1,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
|
||||||
TotalCount int32 `protobuf:"varint,2,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
|
NextPageToken int32 `protobuf:"varint,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
|
||||||
NextPageToken int32 `protobuf:"varint,3,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
|
Results []*UserResult `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -273,13 +273,6 @@ func (*GetCompetitionResultsResponse) Descriptor() ([]byte, []int) {
|
|||||||
return file_api_proto_results_proto_rawDescGZIP(), []int{3}
|
return file_api_proto_results_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetCompetitionResultsResponse) GetResults() []*UserResult {
|
|
||||||
if x != nil {
|
|
||||||
return x.Results
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *GetCompetitionResultsResponse) GetTotalCount() int32 {
|
func (x *GetCompetitionResultsResponse) GetTotalCount() int32 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.TotalCount
|
return x.TotalCount
|
||||||
@@ -294,6 +287,13 @@ func (x *GetCompetitionResultsResponse) GetNextPageToken() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *GetCompetitionResultsResponse) GetResults() []*UserResult {
|
||||||
|
if x != nil {
|
||||||
|
return x.Results
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type GetUserCompetitionResultsRequest struct {
|
type GetUserCompetitionResultsRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
CompetitionId string `protobuf:"bytes,1,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"`
|
CompetitionId string `protobuf:"bytes,1,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"`
|
||||||
@@ -462,11 +462,11 @@ const file_api_proto_results_proto_rawDesc = "" +
|
|||||||
"\n" +
|
"\n" +
|
||||||
"page_token\x18\x02 \x01(\x05R\tpageToken\x12%\n" +
|
"page_token\x18\x02 \x01(\x05R\tpageToken\x12%\n" +
|
||||||
"\x0ecompetition_id\x18\x03 \x01(\tR\rcompetitionId\"\x97\x01\n" +
|
"\x0ecompetition_id\x18\x03 \x01(\tR\rcompetitionId\"\x97\x01\n" +
|
||||||
"\x1dGetCompetitionResultsResponse\x12-\n" +
|
"\x1dGetCompetitionResultsResponse\x12\x1f\n" +
|
||||||
"\aresults\x18\x01 \x03(\v2\x13.results.UserResultR\aresults\x12\x1f\n" +
|
"\vtotal_count\x18\x01 \x01(\x05R\n" +
|
||||||
"\vtotal_count\x18\x02 \x01(\x05R\n" +
|
|
||||||
"totalCount\x12&\n" +
|
"totalCount\x12&\n" +
|
||||||
"\x0fnext_page_token\x18\x03 \x01(\x05R\rnextPageToken\"b\n" +
|
"\x0fnext_page_token\x18\x02 \x01(\x05R\rnextPageToken\x12-\n" +
|
||||||
|
"\aresults\x18\x03 \x03(\v2\x13.results.UserResultR\aresults\"b\n" +
|
||||||
" GetUserCompetitionResultsRequest\x12%\n" +
|
" GetUserCompetitionResultsRequest\x12%\n" +
|
||||||
"\x0ecompetition_id\x18\x01 \x01(\tR\rcompetitionId\x12\x17\n" +
|
"\x0ecompetition_id\x18\x01 \x01(\tR\rcompetitionId\x12\x17\n" +
|
||||||
"\auser_id\x18\x02 \x01(\tR\x06userId\"P\n" +
|
"\auser_id\x18\x02 \x01(\tR\x06userId\"P\n" +
|
||||||
|
|||||||
+217
-65
@@ -322,18 +322,123 @@ func (x *SubmissionForReview) GetCheckedAt() *timestamppb.Timestamp {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidateReviewTokenRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenRequest) Reset() {
|
||||||
|
*x = ValidateReviewTokenRequest{}
|
||||||
|
mi := &file_api_proto_review_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ValidateReviewTokenRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_review_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ValidateReviewTokenRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ValidateReviewTokenRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_review_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenRequest) GetToken() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Token
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidateReviewTokenResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
IsValid bool `protobuf:"varint,1,opt,name=is_valid,json=isValid,proto3" json:"is_valid,omitempty"`
|
||||||
|
ReviewerId string `protobuf:"bytes,2,opt,name=reviewer_id,json=reviewerId,proto3" json:"reviewer_id,omitempty"`
|
||||||
|
CompetitionId string `protobuf:"bytes,3,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenResponse) Reset() {
|
||||||
|
*x = ValidateReviewTokenResponse{}
|
||||||
|
mi := &file_api_proto_review_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ValidateReviewTokenResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_review_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ValidateReviewTokenResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ValidateReviewTokenResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_review_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenResponse) GetIsValid() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsValid
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenResponse) GetReviewerId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReviewerId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ValidateReviewTokenResponse) GetCompetitionId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CompetitionId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type ListSubmissionsForReviewRequest struct {
|
type ListSubmissionsForReviewRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
|
PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
|
||||||
PageToken int32 `protobuf:"varint,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
|
PageToken int32 `protobuf:"varint,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
|
||||||
Status *ReviewStatus `protobuf:"varint,3,opt,name=status,proto3,enum=review.ReviewStatus,oneof" json:"status,omitempty"`
|
Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"`
|
||||||
|
Status *ReviewStatus `protobuf:"varint,4,opt,name=status,proto3,enum=review.ReviewStatus,oneof" json:"status,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSubmissionsForReviewRequest) Reset() {
|
func (x *ListSubmissionsForReviewRequest) Reset() {
|
||||||
*x = ListSubmissionsForReviewRequest{}
|
*x = ListSubmissionsForReviewRequest{}
|
||||||
mi := &file_api_proto_review_proto_msgTypes[3]
|
mi := &file_api_proto_review_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -345,7 +450,7 @@ func (x *ListSubmissionsForReviewRequest) String() string {
|
|||||||
func (*ListSubmissionsForReviewRequest) ProtoMessage() {}
|
func (*ListSubmissionsForReviewRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListSubmissionsForReviewRequest) ProtoReflect() protoreflect.Message {
|
func (x *ListSubmissionsForReviewRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_api_proto_review_proto_msgTypes[3]
|
mi := &file_api_proto_review_proto_msgTypes[5]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -358,7 +463,7 @@ func (x *ListSubmissionsForReviewRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ListSubmissionsForReviewRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListSubmissionsForReviewRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*ListSubmissionsForReviewRequest) Descriptor() ([]byte, []int) {
|
func (*ListSubmissionsForReviewRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_api_proto_review_proto_rawDescGZIP(), []int{3}
|
return file_api_proto_review_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSubmissionsForReviewRequest) GetPageSize() int32 {
|
func (x *ListSubmissionsForReviewRequest) GetPageSize() int32 {
|
||||||
@@ -375,6 +480,13 @@ func (x *ListSubmissionsForReviewRequest) GetPageToken() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ListSubmissionsForReviewRequest) GetToken() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Token
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *ListSubmissionsForReviewRequest) GetStatus() ReviewStatus {
|
func (x *ListSubmissionsForReviewRequest) GetStatus() ReviewStatus {
|
||||||
if x != nil && x.Status != nil {
|
if x != nil && x.Status != nil {
|
||||||
return *x.Status
|
return *x.Status
|
||||||
@@ -393,7 +505,7 @@ type ListSubmissionsForReviewResponse struct {
|
|||||||
|
|
||||||
func (x *ListSubmissionsForReviewResponse) Reset() {
|
func (x *ListSubmissionsForReviewResponse) Reset() {
|
||||||
*x = ListSubmissionsForReviewResponse{}
|
*x = ListSubmissionsForReviewResponse{}
|
||||||
mi := &file_api_proto_review_proto_msgTypes[4]
|
mi := &file_api_proto_review_proto_msgTypes[6]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -405,7 +517,7 @@ func (x *ListSubmissionsForReviewResponse) String() string {
|
|||||||
func (*ListSubmissionsForReviewResponse) ProtoMessage() {}
|
func (*ListSubmissionsForReviewResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListSubmissionsForReviewResponse) ProtoReflect() protoreflect.Message {
|
func (x *ListSubmissionsForReviewResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_api_proto_review_proto_msgTypes[4]
|
mi := &file_api_proto_review_proto_msgTypes[6]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -418,7 +530,7 @@ func (x *ListSubmissionsForReviewResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ListSubmissionsForReviewResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListSubmissionsForReviewResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*ListSubmissionsForReviewResponse) Descriptor() ([]byte, []int) {
|
func (*ListSubmissionsForReviewResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_api_proto_review_proto_rawDescGZIP(), []int{4}
|
return file_api_proto_review_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSubmissionsForReviewResponse) GetTotalCount() int32 {
|
func (x *ListSubmissionsForReviewResponse) GetTotalCount() int32 {
|
||||||
@@ -444,14 +556,15 @@ func (x *ListSubmissionsForReviewResponse) GetSubmissions() []*SubmissionSummary
|
|||||||
|
|
||||||
type GetSubmissionForReviewRequest struct {
|
type GetSubmissionForReviewRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
SubmissionId string `protobuf:"bytes,1,opt,name=submission_id,json=submissionId,proto3" json:"submission_id,omitempty"`
|
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
|
||||||
|
SubmissionId string `protobuf:"bytes,2,opt,name=submission_id,json=submissionId,proto3" json:"submission_id,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetSubmissionForReviewRequest) Reset() {
|
func (x *GetSubmissionForReviewRequest) Reset() {
|
||||||
*x = GetSubmissionForReviewRequest{}
|
*x = GetSubmissionForReviewRequest{}
|
||||||
mi := &file_api_proto_review_proto_msgTypes[5]
|
mi := &file_api_proto_review_proto_msgTypes[7]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -463,7 +576,7 @@ func (x *GetSubmissionForReviewRequest) String() string {
|
|||||||
func (*GetSubmissionForReviewRequest) ProtoMessage() {}
|
func (*GetSubmissionForReviewRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetSubmissionForReviewRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetSubmissionForReviewRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_api_proto_review_proto_msgTypes[5]
|
mi := &file_api_proto_review_proto_msgTypes[7]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -476,7 +589,14 @@ func (x *GetSubmissionForReviewRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use GetSubmissionForReviewRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetSubmissionForReviewRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetSubmissionForReviewRequest) Descriptor() ([]byte, []int) {
|
func (*GetSubmissionForReviewRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_api_proto_review_proto_rawDescGZIP(), []int{5}
|
return file_api_proto_review_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetSubmissionForReviewRequest) GetToken() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Token
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetSubmissionForReviewRequest) GetSubmissionId() string {
|
func (x *GetSubmissionForReviewRequest) GetSubmissionId() string {
|
||||||
@@ -488,17 +608,18 @@ func (x *GetSubmissionForReviewRequest) GetSubmissionId() string {
|
|||||||
|
|
||||||
type EvaluateSubmissionRequest struct {
|
type EvaluateSubmissionRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
SubmissionId string `protobuf:"bytes,1,opt,name=submission_id,json=submissionId,proto3" json:"submission_id,omitempty"`
|
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
|
||||||
EarnedPoints int32 `protobuf:"varint,2,opt,name=earned_points,json=earnedPoints,proto3" json:"earned_points,omitempty"`
|
SubmissionId string `protobuf:"bytes,2,opt,name=submission_id,json=submissionId,proto3" json:"submission_id,omitempty"`
|
||||||
ReviewerComment string `protobuf:"bytes,3,opt,name=reviewer_comment,json=reviewerComment,proto3" json:"reviewer_comment,omitempty"`
|
EarnedPoints int32 `protobuf:"varint,3,opt,name=earned_points,json=earnedPoints,proto3" json:"earned_points,omitempty"`
|
||||||
Marks []*CriteriaMark `protobuf:"bytes,4,rep,name=marks,proto3" json:"marks,omitempty"`
|
ReviewerComment string `protobuf:"bytes,4,opt,name=reviewer_comment,json=reviewerComment,proto3" json:"reviewer_comment,omitempty"`
|
||||||
|
Marks []*CriteriaMark `protobuf:"bytes,5,rep,name=marks,proto3" json:"marks,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *EvaluateSubmissionRequest) Reset() {
|
func (x *EvaluateSubmissionRequest) Reset() {
|
||||||
*x = EvaluateSubmissionRequest{}
|
*x = EvaluateSubmissionRequest{}
|
||||||
mi := &file_api_proto_review_proto_msgTypes[6]
|
mi := &file_api_proto_review_proto_msgTypes[8]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -510,7 +631,7 @@ func (x *EvaluateSubmissionRequest) String() string {
|
|||||||
func (*EvaluateSubmissionRequest) ProtoMessage() {}
|
func (*EvaluateSubmissionRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *EvaluateSubmissionRequest) ProtoReflect() protoreflect.Message {
|
func (x *EvaluateSubmissionRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_api_proto_review_proto_msgTypes[6]
|
mi := &file_api_proto_review_proto_msgTypes[8]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -523,7 +644,14 @@ func (x *EvaluateSubmissionRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use EvaluateSubmissionRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use EvaluateSubmissionRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*EvaluateSubmissionRequest) Descriptor() ([]byte, []int) {
|
func (*EvaluateSubmissionRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_api_proto_review_proto_rawDescGZIP(), []int{6}
|
return file_api_proto_review_proto_rawDescGZIP(), []int{8}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EvaluateSubmissionRequest) GetToken() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Token
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *EvaluateSubmissionRequest) GetSubmissionId() string {
|
func (x *EvaluateSubmissionRequest) GetSubmissionId() string {
|
||||||
@@ -565,7 +693,7 @@ type EvaluateSubmissionResponse struct {
|
|||||||
|
|
||||||
func (x *EvaluateSubmissionResponse) Reset() {
|
func (x *EvaluateSubmissionResponse) Reset() {
|
||||||
*x = EvaluateSubmissionResponse{}
|
*x = EvaluateSubmissionResponse{}
|
||||||
mi := &file_api_proto_review_proto_msgTypes[7]
|
mi := &file_api_proto_review_proto_msgTypes[9]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -577,7 +705,7 @@ func (x *EvaluateSubmissionResponse) String() string {
|
|||||||
func (*EvaluateSubmissionResponse) ProtoMessage() {}
|
func (*EvaluateSubmissionResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *EvaluateSubmissionResponse) ProtoReflect() protoreflect.Message {
|
func (x *EvaluateSubmissionResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_api_proto_review_proto_msgTypes[7]
|
mi := &file_api_proto_review_proto_msgTypes[9]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -590,7 +718,7 @@ func (x *EvaluateSubmissionResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use EvaluateSubmissionResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use EvaluateSubmissionResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*EvaluateSubmissionResponse) Descriptor() ([]byte, []int) {
|
func (*EvaluateSubmissionResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_api_proto_review_proto_rawDescGZIP(), []int{7}
|
return file_api_proto_review_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *EvaluateSubmissionResponse) GetSubmissionId() string {
|
func (x *EvaluateSubmissionResponse) GetSubmissionId() string {
|
||||||
@@ -616,14 +744,15 @@ func (x *EvaluateSubmissionResponse) GetNewStatus() ReviewStatus {
|
|||||||
|
|
||||||
type ReleaseSubmissionRequest struct {
|
type ReleaseSubmissionRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
SubmissionId string `protobuf:"bytes,1,opt,name=submission_id,json=submissionId,proto3" json:"submission_id,omitempty"`
|
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
|
||||||
|
SubmissionId string `protobuf:"bytes,2,opt,name=submission_id,json=submissionId,proto3" json:"submission_id,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ReleaseSubmissionRequest) Reset() {
|
func (x *ReleaseSubmissionRequest) Reset() {
|
||||||
*x = ReleaseSubmissionRequest{}
|
*x = ReleaseSubmissionRequest{}
|
||||||
mi := &file_api_proto_review_proto_msgTypes[8]
|
mi := &file_api_proto_review_proto_msgTypes[10]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -635,7 +764,7 @@ func (x *ReleaseSubmissionRequest) String() string {
|
|||||||
func (*ReleaseSubmissionRequest) ProtoMessage() {}
|
func (*ReleaseSubmissionRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ReleaseSubmissionRequest) ProtoReflect() protoreflect.Message {
|
func (x *ReleaseSubmissionRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_api_proto_review_proto_msgTypes[8]
|
mi := &file_api_proto_review_proto_msgTypes[10]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -648,7 +777,14 @@ func (x *ReleaseSubmissionRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use ReleaseSubmissionRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ReleaseSubmissionRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*ReleaseSubmissionRequest) Descriptor() ([]byte, []int) {
|
func (*ReleaseSubmissionRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_api_proto_review_proto_rawDescGZIP(), []int{8}
|
return file_api_proto_review_proto_rawDescGZIP(), []int{10}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ReleaseSubmissionRequest) GetToken() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Token
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ReleaseSubmissionRequest) GetSubmissionId() string {
|
func (x *ReleaseSubmissionRequest) GetSubmissionId() string {
|
||||||
@@ -685,40 +821,52 @@ const file_api_proto_review_proto_rawDesc = "" +
|
|||||||
"\fsubmitted_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\vsubmittedAt\x12>\n" +
|
"\fsubmitted_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\vsubmittedAt\x12>\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"checked_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampH\x00R\tcheckedAt\x88\x01\x01B\r\n" +
|
"checked_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampH\x00R\tcheckedAt\x88\x01\x01B\r\n" +
|
||||||
"\v_checked_at\"\x9b\x01\n" +
|
"\v_checked_at\"2\n" +
|
||||||
|
"\x1aValidateReviewTokenRequest\x12\x14\n" +
|
||||||
|
"\x05token\x18\x01 \x01(\tR\x05token\"\x80\x01\n" +
|
||||||
|
"\x1bValidateReviewTokenResponse\x12\x19\n" +
|
||||||
|
"\bis_valid\x18\x01 \x01(\bR\aisValid\x12\x1f\n" +
|
||||||
|
"\vreviewer_id\x18\x02 \x01(\tR\n" +
|
||||||
|
"reviewerId\x12%\n" +
|
||||||
|
"\x0ecompetition_id\x18\x03 \x01(\tR\rcompetitionId\"\xb1\x01\n" +
|
||||||
"\x1fListSubmissionsForReviewRequest\x12\x1b\n" +
|
"\x1fListSubmissionsForReviewRequest\x12\x1b\n" +
|
||||||
"\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" +
|
"\tpage_size\x18\x01 \x01(\x05R\bpageSize\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"page_token\x18\x02 \x01(\x05R\tpageToken\x121\n" +
|
"page_token\x18\x02 \x01(\x05R\tpageToken\x12\x14\n" +
|
||||||
"\x06status\x18\x03 \x01(\x0e2\x14.review.ReviewStatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
"\x05token\x18\x03 \x01(\tR\x05token\x121\n" +
|
||||||
|
"\x06status\x18\x04 \x01(\x0e2\x14.review.ReviewStatusH\x00R\x06status\x88\x01\x01B\t\n" +
|
||||||
"\a_status\"\xa8\x01\n" +
|
"\a_status\"\xa8\x01\n" +
|
||||||
" ListSubmissionsForReviewResponse\x12\x1f\n" +
|
" ListSubmissionsForReviewResponse\x12\x1f\n" +
|
||||||
"\vtotal_count\x18\x01 \x01(\x05R\n" +
|
"\vtotal_count\x18\x01 \x01(\x05R\n" +
|
||||||
"totalCount\x12&\n" +
|
"totalCount\x12&\n" +
|
||||||
"\x0fnext_page_token\x18\x02 \x01(\x05R\rnextPageToken\x12;\n" +
|
"\x0fnext_page_token\x18\x02 \x01(\x05R\rnextPageToken\x12;\n" +
|
||||||
"\vsubmissions\x18\x03 \x03(\v2\x19.review.SubmissionSummaryR\vsubmissions\"D\n" +
|
"\vsubmissions\x18\x03 \x03(\v2\x19.review.SubmissionSummaryR\vsubmissions\"Z\n" +
|
||||||
"\x1dGetSubmissionForReviewRequest\x12#\n" +
|
"\x1dGetSubmissionForReviewRequest\x12\x14\n" +
|
||||||
"\rsubmission_id\x18\x01 \x01(\tR\fsubmissionId\"\xbc\x01\n" +
|
"\x05token\x18\x01 \x01(\tR\x05token\x12#\n" +
|
||||||
"\x19EvaluateSubmissionRequest\x12#\n" +
|
"\rsubmission_id\x18\x02 \x01(\tR\fsubmissionId\"\xd2\x01\n" +
|
||||||
"\rsubmission_id\x18\x01 \x01(\tR\fsubmissionId\x12#\n" +
|
"\x19EvaluateSubmissionRequest\x12\x14\n" +
|
||||||
"\rearned_points\x18\x02 \x01(\x05R\fearnedPoints\x12)\n" +
|
"\x05token\x18\x01 \x01(\tR\x05token\x12#\n" +
|
||||||
"\x10reviewer_comment\x18\x03 \x01(\tR\x0freviewerComment\x12*\n" +
|
"\rsubmission_id\x18\x02 \x01(\tR\fsubmissionId\x12#\n" +
|
||||||
"\x05marks\x18\x04 \x03(\v2\x14.review.CriteriaMarkR\x05marks\"\x97\x01\n" +
|
"\rearned_points\x18\x03 \x01(\x05R\fearnedPoints\x12)\n" +
|
||||||
|
"\x10reviewer_comment\x18\x04 \x01(\tR\x0freviewerComment\x12*\n" +
|
||||||
|
"\x05marks\x18\x05 \x03(\v2\x14.review.CriteriaMarkR\x05marks\"\x97\x01\n" +
|
||||||
"\x1aEvaluateSubmissionResponse\x12#\n" +
|
"\x1aEvaluateSubmissionResponse\x12#\n" +
|
||||||
"\rsubmission_id\x18\x01 \x01(\tR\fsubmissionId\x12\x1f\n" +
|
"\rsubmission_id\x18\x01 \x01(\tR\fsubmissionId\x12\x1f\n" +
|
||||||
"\vfinal_score\x18\x02 \x01(\x05R\n" +
|
"\vfinal_score\x18\x02 \x01(\x05R\n" +
|
||||||
"finalScore\x123\n" +
|
"finalScore\x123\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"new_status\x18\x03 \x01(\x0e2\x14.review.ReviewStatusR\tnewStatus\"?\n" +
|
"new_status\x18\x03 \x01(\x0e2\x14.review.ReviewStatusR\tnewStatus\"U\n" +
|
||||||
"\x18ReleaseSubmissionRequest\x12#\n" +
|
"\x18ReleaseSubmissionRequest\x12\x14\n" +
|
||||||
"\rsubmission_id\x18\x01 \x01(\tR\fsubmissionId*\x9e\x01\n" +
|
"\x05token\x18\x01 \x01(\tR\x05token\x12#\n" +
|
||||||
|
"\rsubmission_id\x18\x02 \x01(\tR\fsubmissionId*\x9e\x01\n" +
|
||||||
"\fReviewStatus\x12\x1d\n" +
|
"\fReviewStatus\x12\x1d\n" +
|
||||||
"\x19REVIEW_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" +
|
"\x19REVIEW_STATUS_UNSPECIFIED\x10\x00\x12\x19\n" +
|
||||||
"\x15REVIEW_STATUS_PENDING\x10\x01\x12\x1b\n" +
|
"\x15REVIEW_STATUS_PENDING\x10\x01\x12\x1b\n" +
|
||||||
"\x17REVIEW_STATUS_IN_REVIEW\x10\x02\x12\x1b\n" +
|
"\x17REVIEW_STATUS_IN_REVIEW\x10\x02\x12\x1b\n" +
|
||||||
"\x17REVIEW_STATUS_COMPLETED\x10\x03\x12\x1a\n" +
|
"\x17REVIEW_STATUS_COMPLETED\x10\x03\x12\x1a\n" +
|
||||||
"\x16REVIEW_STATUS_REJECTED\x10\x042\x88\x03\n" +
|
"\x16REVIEW_STATUS_REJECTED\x10\x042\xe8\x03\n" +
|
||||||
"\rReviewService\x12m\n" +
|
"\rReviewService\x12^\n" +
|
||||||
|
"\x13ValidateReviewToken\x12\".review.ValidateReviewTokenRequest\x1a#.review.ValidateReviewTokenResponse\x12m\n" +
|
||||||
"\x18ListSubmissionsForReview\x12'.review.ListSubmissionsForReviewRequest\x1a(.review.ListSubmissionsForReviewResponse\x12\\\n" +
|
"\x18ListSubmissionsForReview\x12'.review.ListSubmissionsForReviewRequest\x1a(.review.ListSubmissionsForReviewResponse\x12\\\n" +
|
||||||
"\x16GetSubmissionForReview\x12%.review.GetSubmissionForReviewRequest\x1a\x1b.review.SubmissionForReview\x12[\n" +
|
"\x16GetSubmissionForReview\x12%.review.GetSubmissionForReviewRequest\x1a\x1b.review.SubmissionForReview\x12[\n" +
|
||||||
"\x12EvaluateSubmission\x12!.review.EvaluateSubmissionRequest\x1a\".review.EvaluateSubmissionResponse\x12M\n" +
|
"\x12EvaluateSubmission\x12!.review.EvaluateSubmissionRequest\x1a\".review.EvaluateSubmissionResponse\x12M\n" +
|
||||||
@@ -737,41 +885,45 @@ func file_api_proto_review_proto_rawDescGZIP() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_api_proto_review_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
var file_api_proto_review_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
var file_api_proto_review_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
var file_api_proto_review_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||||
var file_api_proto_review_proto_goTypes = []any{
|
var file_api_proto_review_proto_goTypes = []any{
|
||||||
(ReviewStatus)(0), // 0: review.ReviewStatus
|
(ReviewStatus)(0), // 0: review.ReviewStatus
|
||||||
(*CriteriaMark)(nil), // 1: review.CriteriaMark
|
(*CriteriaMark)(nil), // 1: review.CriteriaMark
|
||||||
(*SubmissionSummary)(nil), // 2: review.SubmissionSummary
|
(*SubmissionSummary)(nil), // 2: review.SubmissionSummary
|
||||||
(*SubmissionForReview)(nil), // 3: review.SubmissionForReview
|
(*SubmissionForReview)(nil), // 3: review.SubmissionForReview
|
||||||
(*ListSubmissionsForReviewRequest)(nil), // 4: review.ListSubmissionsForReviewRequest
|
(*ValidateReviewTokenRequest)(nil), // 4: review.ValidateReviewTokenRequest
|
||||||
(*ListSubmissionsForReviewResponse)(nil), // 5: review.ListSubmissionsForReviewResponse
|
(*ValidateReviewTokenResponse)(nil), // 5: review.ValidateReviewTokenResponse
|
||||||
(*GetSubmissionForReviewRequest)(nil), // 6: review.GetSubmissionForReviewRequest
|
(*ListSubmissionsForReviewRequest)(nil), // 6: review.ListSubmissionsForReviewRequest
|
||||||
(*EvaluateSubmissionRequest)(nil), // 7: review.EvaluateSubmissionRequest
|
(*ListSubmissionsForReviewResponse)(nil), // 7: review.ListSubmissionsForReviewResponse
|
||||||
(*EvaluateSubmissionResponse)(nil), // 8: review.EvaluateSubmissionResponse
|
(*GetSubmissionForReviewRequest)(nil), // 8: review.GetSubmissionForReviewRequest
|
||||||
(*ReleaseSubmissionRequest)(nil), // 9: review.ReleaseSubmissionRequest
|
(*EvaluateSubmissionRequest)(nil), // 9: review.EvaluateSubmissionRequest
|
||||||
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
(*EvaluateSubmissionResponse)(nil), // 10: review.EvaluateSubmissionResponse
|
||||||
(*emptypb.Empty)(nil), // 11: google.protobuf.Empty
|
(*ReleaseSubmissionRequest)(nil), // 11: review.ReleaseSubmissionRequest
|
||||||
|
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
|
||||||
|
(*emptypb.Empty)(nil), // 13: google.protobuf.Empty
|
||||||
}
|
}
|
||||||
var file_api_proto_review_proto_depIdxs = []int32{
|
var file_api_proto_review_proto_depIdxs = []int32{
|
||||||
10, // 0: review.SubmissionSummary.submitted_at:type_name -> google.protobuf.Timestamp
|
12, // 0: review.SubmissionSummary.submitted_at:type_name -> google.protobuf.Timestamp
|
||||||
0, // 1: review.SubmissionSummary.review_status:type_name -> review.ReviewStatus
|
0, // 1: review.SubmissionSummary.review_status:type_name -> review.ReviewStatus
|
||||||
0, // 2: review.SubmissionForReview.review_status:type_name -> review.ReviewStatus
|
0, // 2: review.SubmissionForReview.review_status:type_name -> review.ReviewStatus
|
||||||
10, // 3: review.SubmissionForReview.submitted_at:type_name -> google.protobuf.Timestamp
|
12, // 3: review.SubmissionForReview.submitted_at:type_name -> google.protobuf.Timestamp
|
||||||
10, // 4: review.SubmissionForReview.checked_at:type_name -> google.protobuf.Timestamp
|
12, // 4: review.SubmissionForReview.checked_at:type_name -> google.protobuf.Timestamp
|
||||||
0, // 5: review.ListSubmissionsForReviewRequest.status:type_name -> review.ReviewStatus
|
0, // 5: review.ListSubmissionsForReviewRequest.status:type_name -> review.ReviewStatus
|
||||||
2, // 6: review.ListSubmissionsForReviewResponse.submissions:type_name -> review.SubmissionSummary
|
2, // 6: review.ListSubmissionsForReviewResponse.submissions:type_name -> review.SubmissionSummary
|
||||||
1, // 7: review.EvaluateSubmissionRequest.marks:type_name -> review.CriteriaMark
|
1, // 7: review.EvaluateSubmissionRequest.marks:type_name -> review.CriteriaMark
|
||||||
0, // 8: review.EvaluateSubmissionResponse.new_status:type_name -> review.ReviewStatus
|
0, // 8: review.EvaluateSubmissionResponse.new_status:type_name -> review.ReviewStatus
|
||||||
4, // 9: review.ReviewService.ListSubmissionsForReview:input_type -> review.ListSubmissionsForReviewRequest
|
4, // 9: review.ReviewService.ValidateReviewToken:input_type -> review.ValidateReviewTokenRequest
|
||||||
6, // 10: review.ReviewService.GetSubmissionForReview:input_type -> review.GetSubmissionForReviewRequest
|
6, // 10: review.ReviewService.ListSubmissionsForReview:input_type -> review.ListSubmissionsForReviewRequest
|
||||||
7, // 11: review.ReviewService.EvaluateSubmission:input_type -> review.EvaluateSubmissionRequest
|
8, // 11: review.ReviewService.GetSubmissionForReview:input_type -> review.GetSubmissionForReviewRequest
|
||||||
9, // 12: review.ReviewService.ReleaseSubmission:input_type -> review.ReleaseSubmissionRequest
|
9, // 12: review.ReviewService.EvaluateSubmission:input_type -> review.EvaluateSubmissionRequest
|
||||||
5, // 13: review.ReviewService.ListSubmissionsForReview:output_type -> review.ListSubmissionsForReviewResponse
|
11, // 13: review.ReviewService.ReleaseSubmission:input_type -> review.ReleaseSubmissionRequest
|
||||||
3, // 14: review.ReviewService.GetSubmissionForReview:output_type -> review.SubmissionForReview
|
5, // 14: review.ReviewService.ValidateReviewToken:output_type -> review.ValidateReviewTokenResponse
|
||||||
8, // 15: review.ReviewService.EvaluateSubmission:output_type -> review.EvaluateSubmissionResponse
|
7, // 15: review.ReviewService.ListSubmissionsForReview:output_type -> review.ListSubmissionsForReviewResponse
|
||||||
11, // 16: review.ReviewService.ReleaseSubmission:output_type -> google.protobuf.Empty
|
3, // 16: review.ReviewService.GetSubmissionForReview:output_type -> review.SubmissionForReview
|
||||||
13, // [13:17] is the sub-list for method output_type
|
10, // 17: review.ReviewService.EvaluateSubmission:output_type -> review.EvaluateSubmissionResponse
|
||||||
9, // [9:13] is the sub-list for method input_type
|
13, // 18: review.ReviewService.ReleaseSubmission:output_type -> google.protobuf.Empty
|
||||||
|
14, // [14:19] is the sub-list for method output_type
|
||||||
|
9, // [9:14] is the sub-list for method input_type
|
||||||
9, // [9:9] is the sub-list for extension type_name
|
9, // [9:9] is the sub-list for extension type_name
|
||||||
9, // [9:9] is the sub-list for extension extendee
|
9, // [9:9] is the sub-list for extension extendee
|
||||||
0, // [0:9] is the sub-list for field type_name
|
0, // [0:9] is the sub-list for field type_name
|
||||||
@@ -783,14 +935,14 @@ func file_api_proto_review_proto_init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
file_api_proto_review_proto_msgTypes[2].OneofWrappers = []any{}
|
file_api_proto_review_proto_msgTypes[2].OneofWrappers = []any{}
|
||||||
file_api_proto_review_proto_msgTypes[3].OneofWrappers = []any{}
|
file_api_proto_review_proto_msgTypes[5].OneofWrappers = []any{}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_review_proto_rawDesc), len(file_api_proto_review_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_review_proto_rawDesc), len(file_api_proto_review_proto_rawDesc)),
|
||||||
NumEnums: 1,
|
NumEnums: 1,
|
||||||
NumMessages: 9,
|
NumMessages: 11,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
const _ = grpc.SupportPackageIsVersion9
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ReviewService_ValidateReviewToken_FullMethodName = "/review.ReviewService/ValidateReviewToken"
|
||||||
ReviewService_ListSubmissionsForReview_FullMethodName = "/review.ReviewService/ListSubmissionsForReview"
|
ReviewService_ListSubmissionsForReview_FullMethodName = "/review.ReviewService/ListSubmissionsForReview"
|
||||||
ReviewService_GetSubmissionForReview_FullMethodName = "/review.ReviewService/GetSubmissionForReview"
|
ReviewService_GetSubmissionForReview_FullMethodName = "/review.ReviewService/GetSubmissionForReview"
|
||||||
ReviewService_EvaluateSubmission_FullMethodName = "/review.ReviewService/EvaluateSubmission"
|
ReviewService_EvaluateSubmission_FullMethodName = "/review.ReviewService/EvaluateSubmission"
|
||||||
@@ -30,6 +31,7 @@ const (
|
|||||||
//
|
//
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
type ReviewServiceClient interface {
|
type ReviewServiceClient interface {
|
||||||
|
ValidateReviewToken(ctx context.Context, in *ValidateReviewTokenRequest, opts ...grpc.CallOption) (*ValidateReviewTokenResponse, error)
|
||||||
ListSubmissionsForReview(ctx context.Context, in *ListSubmissionsForReviewRequest, opts ...grpc.CallOption) (*ListSubmissionsForReviewResponse, error)
|
ListSubmissionsForReview(ctx context.Context, in *ListSubmissionsForReviewRequest, opts ...grpc.CallOption) (*ListSubmissionsForReviewResponse, error)
|
||||||
GetSubmissionForReview(ctx context.Context, in *GetSubmissionForReviewRequest, opts ...grpc.CallOption) (*SubmissionForReview, error)
|
GetSubmissionForReview(ctx context.Context, in *GetSubmissionForReviewRequest, opts ...grpc.CallOption) (*SubmissionForReview, error)
|
||||||
EvaluateSubmission(ctx context.Context, in *EvaluateSubmissionRequest, opts ...grpc.CallOption) (*EvaluateSubmissionResponse, error)
|
EvaluateSubmission(ctx context.Context, in *EvaluateSubmissionRequest, opts ...grpc.CallOption) (*EvaluateSubmissionResponse, error)
|
||||||
@@ -44,6 +46,16 @@ func NewReviewServiceClient(cc grpc.ClientConnInterface) ReviewServiceClient {
|
|||||||
return &reviewServiceClient{cc}
|
return &reviewServiceClient{cc}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *reviewServiceClient) ValidateReviewToken(ctx context.Context, in *ValidateReviewTokenRequest, opts ...grpc.CallOption) (*ValidateReviewTokenResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ValidateReviewTokenResponse)
|
||||||
|
err := c.cc.Invoke(ctx, ReviewService_ValidateReviewToken_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *reviewServiceClient) ListSubmissionsForReview(ctx context.Context, in *ListSubmissionsForReviewRequest, opts ...grpc.CallOption) (*ListSubmissionsForReviewResponse, error) {
|
func (c *reviewServiceClient) ListSubmissionsForReview(ctx context.Context, in *ListSubmissionsForReviewRequest, opts ...grpc.CallOption) (*ListSubmissionsForReviewResponse, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(ListSubmissionsForReviewResponse)
|
out := new(ListSubmissionsForReviewResponse)
|
||||||
@@ -88,6 +100,7 @@ func (c *reviewServiceClient) ReleaseSubmission(ctx context.Context, in *Release
|
|||||||
// All implementations must embed UnimplementedReviewServiceServer
|
// All implementations must embed UnimplementedReviewServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
type ReviewServiceServer interface {
|
type ReviewServiceServer interface {
|
||||||
|
ValidateReviewToken(context.Context, *ValidateReviewTokenRequest) (*ValidateReviewTokenResponse, error)
|
||||||
ListSubmissionsForReview(context.Context, *ListSubmissionsForReviewRequest) (*ListSubmissionsForReviewResponse, error)
|
ListSubmissionsForReview(context.Context, *ListSubmissionsForReviewRequest) (*ListSubmissionsForReviewResponse, error)
|
||||||
GetSubmissionForReview(context.Context, *GetSubmissionForReviewRequest) (*SubmissionForReview, error)
|
GetSubmissionForReview(context.Context, *GetSubmissionForReviewRequest) (*SubmissionForReview, error)
|
||||||
EvaluateSubmission(context.Context, *EvaluateSubmissionRequest) (*EvaluateSubmissionResponse, error)
|
EvaluateSubmission(context.Context, *EvaluateSubmissionRequest) (*EvaluateSubmissionResponse, error)
|
||||||
@@ -102,6 +115,9 @@ type ReviewServiceServer interface {
|
|||||||
// pointer dereference when methods are called.
|
// pointer dereference when methods are called.
|
||||||
type UnimplementedReviewServiceServer struct{}
|
type UnimplementedReviewServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedReviewServiceServer) ValidateReviewToken(context.Context, *ValidateReviewTokenRequest) (*ValidateReviewTokenResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ValidateReviewToken not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedReviewServiceServer) ListSubmissionsForReview(context.Context, *ListSubmissionsForReviewRequest) (*ListSubmissionsForReviewResponse, error) {
|
func (UnimplementedReviewServiceServer) ListSubmissionsForReview(context.Context, *ListSubmissionsForReviewRequest) (*ListSubmissionsForReviewResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method ListSubmissionsForReview not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListSubmissionsForReview not implemented")
|
||||||
}
|
}
|
||||||
@@ -135,6 +151,24 @@ func RegisterReviewServiceServer(s grpc.ServiceRegistrar, srv ReviewServiceServe
|
|||||||
s.RegisterService(&ReviewService_ServiceDesc, srv)
|
s.RegisterService(&ReviewService_ServiceDesc, srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _ReviewService_ValidateReviewToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ValidateReviewTokenRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ReviewServiceServer).ValidateReviewToken(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: ReviewService_ValidateReviewToken_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ReviewServiceServer).ValidateReviewToken(ctx, req.(*ValidateReviewTokenRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _ReviewService_ListSubmissionsForReview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _ReviewService_ListSubmissionsForReview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ListSubmissionsForReviewRequest)
|
in := new(ListSubmissionsForReviewRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -214,6 +248,10 @@ var ReviewService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
ServiceName: "review.ReviewService",
|
ServiceName: "review.ReviewService",
|
||||||
HandlerType: (*ReviewServiceServer)(nil),
|
HandlerType: (*ReviewServiceServer)(nil),
|
||||||
Methods: []grpc.MethodDesc{
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "ValidateReviewToken",
|
||||||
|
Handler: _ReviewService_ValidateReviewToken_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "ListSubmissionsForReview",
|
MethodName: "ListSubmissionsForReview",
|
||||||
Handler: _ReviewService_ListSubmissionsForReview_Handler,
|
Handler: _ReviewService_ListSubmissionsForReview_Handler,
|
||||||
|
|||||||
@@ -190,9 +190,10 @@ func (x *Submission) GetFileUrl() string {
|
|||||||
|
|
||||||
type SubmitTaskRequest struct {
|
type SubmitTaskRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
CompetitionId string `protobuf:"bytes,1,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"`
|
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||||
TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
|
CompetitionId string `protobuf:"bytes,2,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"`
|
||||||
FileUrl string `protobuf:"bytes,3,opt,name=file_url,json=fileUrl,proto3" json:"file_url,omitempty"`
|
TaskId string `protobuf:"bytes,3,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
|
||||||
|
FileUrl string `protobuf:"bytes,4,opt,name=file_url,json=fileUrl,proto3" json:"file_url,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -227,6 +228,13 @@ func (*SubmitTaskRequest) Descriptor() ([]byte, []int) {
|
|||||||
return file_api_proto_submission_proto_rawDescGZIP(), []int{1}
|
return file_api_proto_submission_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *SubmitTaskRequest) GetUserId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *SubmitTaskRequest) GetCompetitionId() string {
|
func (x *SubmitTaskRequest) GetCompetitionId() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.CompetitionId
|
return x.CompetitionId
|
||||||
@@ -250,8 +258,9 @@ func (x *SubmitTaskRequest) GetFileUrl() string {
|
|||||||
|
|
||||||
type GetSubmissionsHistoryRequest struct {
|
type GetSubmissionsHistoryRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
CompetitionId string `protobuf:"bytes,1,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"`
|
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||||
TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
|
CompetitionId string `protobuf:"bytes,2,opt,name=competition_id,json=competitionId,proto3" json:"competition_id,omitempty"`
|
||||||
|
TaskId string `protobuf:"bytes,3,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -286,6 +295,13 @@ func (*GetSubmissionsHistoryRequest) Descriptor() ([]byte, []int) {
|
|||||||
return file_api_proto_submission_proto_rawDescGZIP(), []int{2}
|
return file_api_proto_submission_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *GetSubmissionsHistoryRequest) GetUserId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *GetSubmissionsHistoryRequest) GetCompetitionId() string {
|
func (x *GetSubmissionsHistoryRequest) GetCompetitionId() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.CompetitionId
|
return x.CompetitionId
|
||||||
@@ -474,9 +490,9 @@ func (x *ListSubmissionsRequest) GetStatus() SubmissionStatus {
|
|||||||
|
|
||||||
type ListSubmissionsResponse struct {
|
type ListSubmissionsResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
TotalCount int32 `protobuf:"varint,2,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
|
TotalCount int32 `protobuf:"varint,1,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"`
|
||||||
NextPageToken int32 `protobuf:"varint,3,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
|
NextPageToken int32 `protobuf:"varint,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
|
||||||
Submissions []*Submission `protobuf:"bytes,1,rep,name=submissions,proto3" json:"submissions,omitempty"`
|
Submissions []*Submission `protobuf:"bytes,3,rep,name=submissions,proto3" json:"submissions,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -549,14 +565,16 @@ const file_api_proto_submission_proto_rawDesc = "" +
|
|||||||
"\fsubmitted_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\vsubmittedAt\x129\n" +
|
"\fsubmitted_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\vsubmittedAt\x129\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"checked_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tcheckedAt\x12\x19\n" +
|
"checked_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tcheckedAt\x12\x19\n" +
|
||||||
"\bfile_url\x18\t \x01(\tR\afileUrl\"n\n" +
|
"\bfile_url\x18\t \x01(\tR\afileUrl\"\x87\x01\n" +
|
||||||
"\x11SubmitTaskRequest\x12%\n" +
|
"\x11SubmitTaskRequest\x12\x17\n" +
|
||||||
"\x0ecompetition_id\x18\x01 \x01(\tR\rcompetitionId\x12\x17\n" +
|
"\auser_id\x18\x01 \x01(\tR\x06userId\x12%\n" +
|
||||||
"\atask_id\x18\x02 \x01(\tR\x06taskId\x12\x19\n" +
|
"\x0ecompetition_id\x18\x02 \x01(\tR\rcompetitionId\x12\x17\n" +
|
||||||
"\bfile_url\x18\x03 \x01(\tR\afileUrl\"^\n" +
|
"\atask_id\x18\x03 \x01(\tR\x06taskId\x12\x19\n" +
|
||||||
"\x1cGetSubmissionsHistoryRequest\x12%\n" +
|
"\bfile_url\x18\x04 \x01(\tR\afileUrl\"w\n" +
|
||||||
"\x0ecompetition_id\x18\x01 \x01(\tR\rcompetitionId\x12\x17\n" +
|
"\x1cGetSubmissionsHistoryRequest\x12\x17\n" +
|
||||||
"\atask_id\x18\x02 \x01(\tR\x06taskId\"Y\n" +
|
"\auser_id\x18\x01 \x01(\tR\x06userId\x12%\n" +
|
||||||
|
"\x0ecompetition_id\x18\x02 \x01(\tR\rcompetitionId\x12\x17\n" +
|
||||||
|
"\atask_id\x18\x03 \x01(\tR\x06taskId\"Y\n" +
|
||||||
"\x1dGetSubmissionsHistoryResponse\x128\n" +
|
"\x1dGetSubmissionsHistoryResponse\x128\n" +
|
||||||
"\vsubmissions\x18\x01 \x03(\v2\x16.submission.SubmissionR\vsubmissions\";\n" +
|
"\vsubmissions\x18\x01 \x03(\v2\x16.submission.SubmissionR\vsubmissions\";\n" +
|
||||||
"\x14GetSubmissionRequest\x12#\n" +
|
"\x14GetSubmissionRequest\x12#\n" +
|
||||||
@@ -570,10 +588,10 @@ const file_api_proto_submission_proto_rawDesc = "" +
|
|||||||
"\auser_id\x18\x05 \x01(\tR\x06userId\x124\n" +
|
"\auser_id\x18\x05 \x01(\tR\x06userId\x124\n" +
|
||||||
"\x06status\x18\x06 \x01(\x0e2\x1c.submission.SubmissionStatusR\x06status\"\x9c\x01\n" +
|
"\x06status\x18\x06 \x01(\x0e2\x1c.submission.SubmissionStatusR\x06status\"\x9c\x01\n" +
|
||||||
"\x17ListSubmissionsResponse\x12\x1f\n" +
|
"\x17ListSubmissionsResponse\x12\x1f\n" +
|
||||||
"\vtotal_count\x18\x02 \x01(\x05R\n" +
|
"\vtotal_count\x18\x01 \x01(\x05R\n" +
|
||||||
"totalCount\x12&\n" +
|
"totalCount\x12&\n" +
|
||||||
"\x0fnext_page_token\x18\x03 \x01(\x05R\rnextPageToken\x128\n" +
|
"\x0fnext_page_token\x18\x02 \x01(\x05R\rnextPageToken\x128\n" +
|
||||||
"\vsubmissions\x18\x01 \x03(\v2\x16.submission.SubmissionR\vsubmissions*\xd7\x01\n" +
|
"\vsubmissions\x18\x03 \x03(\v2\x16.submission.SubmissionR\vsubmissions*\xd7\x01\n" +
|
||||||
"\x10SubmissionStatus\x12!\n" +
|
"\x10SubmissionStatus\x12!\n" +
|
||||||
"\x1dSUBMISSION_STATUS_UNSPECIFIED\x10\x00\x12\x1d\n" +
|
"\x1dSUBMISSION_STATUS_UNSPECIFIED\x10\x00\x12\x1d\n" +
|
||||||
"\x19SUBMISSION_STATUS_PENDING\x10\x01\x12$\n" +
|
"\x19SUBMISSION_STATUS_PENDING\x10\x01\x12$\n" +
|
||||||
|
|||||||
Reference in New Issue
Block a user