From 8a0e046576e4469d485ca3f11128d3419d330069 Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Wed, 6 Nov 2024 23:17:35 +0800 Subject: [PATCH 01/11] feat: implement rabbitmq msg queue --- apps/execution-service/go.mod | 1 + apps/execution-service/go.sum | 2 + apps/execution-service/handlers/submit.go | 58 ++++++------ apps/execution-service/main.go | 17 ++-- .../messagequeue/rabbitmq.go | 61 +++++++++++++ apps/execution-service/utils/log.go | 9 ++ apps/history-service/databases/history.go | 32 +++++++ apps/history-service/go.mod | 1 + apps/history-service/go.sum | 2 + apps/history-service/handlers/create.go | 19 +--- apps/history-service/main.go | 15 +-- apps/history-service/messagequeue/rabbitmq.go | 91 +++++++++++++++++++ apps/history-service/utils/log.go | 9 ++ 13 files changed, 259 insertions(+), 58 deletions(-) create mode 100644 apps/execution-service/messagequeue/rabbitmq.go create mode 100644 apps/execution-service/utils/log.go create mode 100644 apps/history-service/databases/history.go create mode 100644 apps/history-service/messagequeue/rabbitmq.go create mode 100644 apps/history-service/utils/log.go diff --git a/apps/execution-service/go.mod b/apps/execution-service/go.mod index 32a516cf23..aff484d2a7 100644 --- a/apps/execution-service/go.mod +++ b/apps/execution-service/go.mod @@ -31,6 +31,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/rabbitmq/amqp091-go v1.10.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect diff --git a/apps/execution-service/go.sum b/apps/execution-service/go.sum index 7d3c75e147..8a595690fa 100644 --- a/apps/execution-service/go.sum +++ b/apps/execution-service/go.sum @@ -85,6 +85,8 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/apps/execution-service/handlers/submit.go b/apps/execution-service/handlers/submit.go index 0bd06656a3..44cfa3d49e 100644 --- a/apps/execution-service/handlers/submit.go +++ b/apps/execution-service/handlers/submit.go @@ -1,16 +1,16 @@ package handlers import ( - "bytes" "encoding/json" "execution-service/constants" + "execution-service/messagequeue" "execution-service/models" "execution-service/utils" "fmt" + "net/http" + "github.com/go-chi/chi/v5" "google.golang.org/api/iterator" - "net/http" - "os" ) func (s *Service) ExecuteVisibleAndHiddenTestsAndSubmit(w http.ResponseWriter, r *http.Request) { @@ -84,33 +84,39 @@ func (s *Service) ExecuteVisibleAndHiddenTestsAndSubmit(w http.ResponseWriter, r return } - // get history-service url from os env - historyServiceUrl := os.Getenv("HISTORY_SERVICE_URL") - if historyServiceUrl == "" { - http.Error(w, "HISTORY_SERVICE_URL is not set", http.StatusInternalServerError) - return - } - - req, err := http.NewRequest(http.MethodPost, historyServiceUrl+"histories", - bytes.NewBuffer(jsonData)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) + err = messagequeue.PublishSubmissionMessage(jsonData) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + http.Error(w, fmt.Sprintf("Failed to save submission history: %v", err), http.StatusInternalServerError) return } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - http.Error(w, "Failed to save submission history", http.StatusInternalServerError) - } + // get history-service url from os env + // historyServiceUrl := os.Getenv("HISTORY_SERVICE_URL") + // if historyServiceUrl == "" { + // http.Error(w, "HISTORY_SERVICE_URL is not set", http.StatusInternalServerError) + // return + // } + + // req, err := http.NewRequest(http.MethodPost, historyServiceUrl+"histories", + // bytes.NewBuffer(jsonData)) + // if err != nil { + // http.Error(w, err.Error(), http.StatusInternalServerError) + // return + // } + + // req.Header.Set("Content-Type", "application/json") + + // client := &http.Client{} + // resp, err := client.Do(req) + // if err != nil { + // http.Error(w, err.Error(), http.StatusInternalServerError) + // return + // } + // defer resp.Body.Close() + + // if resp.StatusCode != http.StatusOK { + // http.Error(w, "Failed to save submission history", http.StatusInternalServerError) + // } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/apps/execution-service/main.go b/apps/execution-service/main.go index 706d0afcd6..f5f4bfe4c2 100644 --- a/apps/execution-service/main.go +++ b/apps/execution-service/main.go @@ -3,6 +3,8 @@ package main import ( "context" "execution-service/handlers" + "execution-service/messagequeue" + "execution-service/utils" "fmt" "log" "net/http" @@ -21,16 +23,15 @@ import ( func main() { // Load .env file err := godotenv.Load() - if err != nil { - log.Fatal("Error loading .env file") - } + utils.FailOnError(err, "Error loading .env file") + + amqpChannel := messagequeue.InitRabbitMQServer() + defer amqpChannel.Close() // Initialize Firestore client ctx := context.Background() client, err := initFirestore(ctx) - if err != nil { - log.Fatalf("Failed to initialize Firestore client: %v", err) - } + utils.FailOnError(err, "Failed to initialize Firestore client") defer client.Close() service := &handlers.Service{Client: client} @@ -107,7 +108,5 @@ func initRestServer(r *chi.Mux) { // Start the server log.Printf("Starting REST server on http://localhost:%s", port) err := http.ListenAndServe(fmt.Sprintf(":%s", port), r) - if err != nil { - log.Fatalf("Failed to start server: %v", err) - } + utils.FailOnError(err, "Failed to start REST server") } diff --git a/apps/execution-service/messagequeue/rabbitmq.go b/apps/execution-service/messagequeue/rabbitmq.go new file mode 100644 index 0000000000..0eaa731e7f --- /dev/null +++ b/apps/execution-service/messagequeue/rabbitmq.go @@ -0,0 +1,61 @@ +package messagequeue + +import ( + "execution-service/utils" + "fmt" + "log" + "os" + + amqp "github.com/rabbitmq/amqp091-go" +) + +const CODE_SUBMISSION_QUEUE_KEY = "code-submission" + +var ( + codeSubmissionQueue amqp.Queue + rabbitMQChannel *amqp.Channel +) + +func InitRabbitMQServer() *amqp.Channel { + // Connect to RabbitMQ server + rabbitMQURL := os.Getenv("RABBITMQ_URL") + conn, err := amqp.Dial(rabbitMQURL) + utils.FailOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + // Create a channel + ch, err := conn.Channel() + utils.FailOnError(err, "Failed to open a channel") + rabbitMQChannel = ch + + // Declare a queue + q, err := ch.QueueDeclare( + CODE_SUBMISSION_QUEUE_KEY, // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + utils.FailOnError(err, "Failed to declare a queue") + codeSubmissionQueue = q + + return ch +} + +func PublishSubmissionMessage(submission []byte) error { + err := rabbitMQChannel.Publish( + "", // exchange + codeSubmissionQueue.Name, // routing key + false, // mandatory + false, // immediate + amqp.Publishing{ + ContentType: "application/json", + Body: submission, + }) + if err != nil { + return fmt.Errorf("Failed to publish a message: %v", err) + } + log.Printf("RabbitMQ: [x] Sent %s", submission) + return nil +} diff --git a/apps/execution-service/utils/log.go b/apps/execution-service/utils/log.go new file mode 100644 index 0000000000..a77b2c29ca --- /dev/null +++ b/apps/execution-service/utils/log.go @@ -0,0 +1,9 @@ +package utils + +import "log" + +func FailOnError(err error, msg string) { + if err != nil { + log.Fatalf("%s: %s", msg, err) + } +} diff --git a/apps/history-service/databases/history.go b/apps/history-service/databases/history.go new file mode 100644 index 0000000000..e9b41e4b14 --- /dev/null +++ b/apps/history-service/databases/history.go @@ -0,0 +1,32 @@ +package databases + +import ( + "context" + "history-service/models" + + "cloud.google.com/go/firestore" +) + +func CreateHistory(client *firestore.Client, ctx context.Context, submissionHistory models.SubmissionHistory) (*firestore.DocumentRef, error) { + // Document reference ID in firestore mapped to the match ID in model + collection := client.Collection("collaboration-history") + + docRef, _, err := collection.Add(ctx, map[string]interface{}{ + "title": submissionHistory.Title, + "code": submissionHistory.Code, + "language": submissionHistory.Language, + "user": submissionHistory.User, + "matchedUser": submissionHistory.MatchedUser, + "matchedTopics": submissionHistory.MatchedTopics, + "questionDocRefId": submissionHistory.QuestionDocRefID, + "questionDifficulty": submissionHistory.QuestionDifficulty, + "questionTopics": submissionHistory.QuestionTopics, + "status": submissionHistory.Status, + "createdAt": firestore.ServerTimestamp, + "updatedAt": firestore.ServerTimestamp, + }) + if err != nil { + return nil, err + } + return docRef, nil +} diff --git a/apps/history-service/go.mod b/apps/history-service/go.mod index 37d6005bf1..2bdb4f0c8a 100644 --- a/apps/history-service/go.mod +++ b/apps/history-service/go.mod @@ -31,6 +31,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/rabbitmq/amqp091-go v1.10.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect diff --git a/apps/history-service/go.sum b/apps/history-service/go.sum index ce7234e8f6..fbc00bcfaa 100644 --- a/apps/history-service/go.sum +++ b/apps/history-service/go.sum @@ -85,6 +85,8 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/apps/history-service/handlers/create.go b/apps/history-service/handlers/create.go index 3dc635d0dd..e4868730b7 100644 --- a/apps/history-service/handlers/create.go +++ b/apps/history-service/handlers/create.go @@ -2,11 +2,11 @@ package handlers import ( "encoding/json" + "history-service/databases" "history-service/models" "history-service/utils" "net/http" - "cloud.google.com/go/firestore" "google.golang.org/api/iterator" ) @@ -22,22 +22,7 @@ func (s *Service) CreateHistory(w http.ResponseWriter, r *http.Request) { } // Document reference ID in firestore mapped to the match ID in model - collection := s.Client.Collection("collaboration-history") - - docRef, _, err := collection.Add(ctx, map[string]interface{}{ - "title": submissionHistory.Title, - "code": submissionHistory.Code, - "language": submissionHistory.Language, - "user": submissionHistory.User, - "matchedUser": submissionHistory.MatchedUser, - "matchedTopics": submissionHistory.MatchedTopics, - "questionDocRefId": submissionHistory.QuestionDocRefID, - "questionDifficulty": submissionHistory.QuestionDifficulty, - "questionTopics": submissionHistory.QuestionTopics, - "status": submissionHistory.Status, - "createdAt": firestore.ServerTimestamp, - "updatedAt": firestore.ServerTimestamp, - }) + docRef, err := databases.CreateHistory(s.Client, ctx, submissionHistory) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/apps/history-service/main.go b/apps/history-service/main.go index 751f2ed525..9d22656df1 100644 --- a/apps/history-service/main.go +++ b/apps/history-service/main.go @@ -3,7 +3,10 @@ package main import ( "context" "fmt" + "history-service/databases" "history-service/handlers" + "history-service/messagequeue" + "history-service/utils" "log" "net/http" "os" @@ -20,20 +23,20 @@ import ( func main() { err := godotenv.Load() - if err != nil { - log.Fatal("Error loading .env file") - } + utils.FailOnError(err, "Error loading .env file") // Initialize Firestore client ctx := context.Background() client, err := initFirestore(ctx) - if err != nil { - log.Fatalf("Failed to initialize Firestore client: %v", err) - } + utils.FailOnError(err, "Failed to initialize Firestore client") defer client.Close() service := &handlers.Service{Client: client} + amqpChannel := messagequeue.InitRabbitMQServer() + defer amqpChannel.Close() + messagequeue.ConsumeSubmissionMessages(client, databases.CreateHistory) + r := initChiRouter(service) initRestServer(r) } diff --git a/apps/history-service/messagequeue/rabbitmq.go b/apps/history-service/messagequeue/rabbitmq.go new file mode 100644 index 0000000000..9b42aeed84 --- /dev/null +++ b/apps/history-service/messagequeue/rabbitmq.go @@ -0,0 +1,91 @@ +package messagequeue + +import ( + "context" + "encoding/json" + "history-service/models" + "history-service/utils" + "log" + "os" + + "cloud.google.com/go/firestore" + amqp "github.com/rabbitmq/amqp091-go" +) + +const CODE_SUBMISSION_QUEUE_KEY = "code-submission" + +var ( + codeSubmissionQueue amqp.Queue + rabbitMQChannel *amqp.Channel +) + +func InitRabbitMQServer() *amqp.Channel { + // Connect to RabbitMQ server + rabbitMQURL := os.Getenv("RABBITMQ_URL") + conn, err := amqp.Dial(rabbitMQURL) + utils.FailOnError(err, "Failed to connect to RabbitMQ") + defer conn.Close() + + // Create a channel + ch, err := conn.Channel() + utils.FailOnError(err, "Failed to open a channel") + rabbitMQChannel = ch + defer ch.Close() + + // Declare a queue + q, err := ch.QueueDeclare( + "code-submission", // name + false, // durable + false, // delete when unused + false, // exclusive + false, // no-wait + nil, // arguments + ) + utils.FailOnError(err, "Failed to declare a queue") + codeSubmissionQueue = q + + return ch +} + +func ConsumeSubmissionMessages(client *firestore.Client, createSubmission func( + *firestore.Client, context.Context, models.SubmissionHistory) ( + *firestore.DocumentRef, error)) { + ctx := context.Background() + + // Consume messages from the queue + msgs, err := rabbitMQChannel.Consume( + codeSubmissionQueue.Name, // queue + "", // consumer + true, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, // args + ) + utils.FailOnError(err, "Failed to register a consumer") + + // Create a channel to block indefinitely + forever := make(chan bool) + + // Start a goroutine to handle incoming messages + go func() { + for d := range msgs { + log.Printf("RabbitMQ: Received a message: %v", d) + + // Parse request + var submissionHistory models.SubmissionHistory + if err := json.Unmarshal(d.Body, &submissionHistory); err != nil { + log.Printf("RabbitMQ: Error decoding JSON: %v", err) + continue + } + + _, err := createSubmission(client, ctx, submissionHistory) + if err != nil { + log.Printf("RabbitMQ: %v", err) + } + } + }() + + log.Printf("RabbitMQ: [*] Waiting for messages.") + <-forever +} diff --git a/apps/history-service/utils/log.go b/apps/history-service/utils/log.go new file mode 100644 index 0000000000..a77b2c29ca --- /dev/null +++ b/apps/history-service/utils/log.go @@ -0,0 +1,9 @@ +package utils + +import "log" + +func FailOnError(err error, msg string) { + if err != nil { + log.Fatalf("%s: %s", msg, err) + } +} From e48bc7ea91cf62bb9661613fefe0194b71ed3525 Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Wed, 6 Nov 2024 23:17:41 +0800 Subject: [PATCH 02/11] feat: update docker compose --- apps/docker-compose.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/docker-compose.yml b/apps/docker-compose.yml index a0565fe1dd..18a2242be8 100644 --- a/apps/docker-compose.yml +++ b/apps/docker-compose.yml @@ -68,7 +68,7 @@ services: - apps_network volumes: - ./history-service:/history-service - + signalling-service: build: context: ./signalling-service @@ -104,6 +104,19 @@ services: - 6379:6379 container_name: redis-container + rabbit-mq: + image: rabbitmq:3-management + networks: + - apps_network + ports: + - 5672:5672 # Port for RabbitMQ message broker + - 15672:15672 # Port for RabbitMQ Management UI + environment: + RABBITMQ_DEFAULT_USER: guest + RABBITMQ_DEFAULT_PASS: guest + volumes: + - rabbitmq_data:/var/lib/rabbitmq + python-sandbox: build: context: ./execution-service/execution/python @@ -114,3 +127,8 @@ services: networks: apps_network: + +volumes: + # Mounts a volume for RabbitMQ data persistence. + # This ensures that data is not lost when the container is restarted or removed. + rabbitmq_data: From 64a1badcfebdedcef17f31d6539c747b05481588 Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 00:33:50 +0800 Subject: [PATCH 03/11] fix: update docker-compose --- apps/docker-compose.yml | 6 ++- apps/execution-service/.env.example | 2 + apps/execution-service/main.go | 7 ++-- .../messagequeue/rabbitmq.go | 33 +++++++++++++---- apps/history-service/.env.example | 8 +++- apps/history-service/main.go | 5 ++- apps/history-service/messagequeue/rabbitmq.go | 37 ++++++++++++++----- 7 files changed, 73 insertions(+), 25 deletions(-) diff --git a/apps/docker-compose.yml b/apps/docker-compose.yml index 18a2242be8..12d02b5661 100644 --- a/apps/docker-compose.yml +++ b/apps/docker-compose.yml @@ -68,6 +68,8 @@ services: - apps_network volumes: - ./history-service:/history-service + depends_on: + - rabbitmq signalling-service: build: @@ -95,6 +97,8 @@ services: volumes: - ./execution-service:/execution-service - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - rabbitmq redis: image: redis:latest @@ -104,7 +108,7 @@ services: - 6379:6379 container_name: redis-container - rabbit-mq: + rabbitmq: image: rabbitmq:3-management networks: - apps_network diff --git a/apps/execution-service/.env.example b/apps/execution-service/.env.example index 6a1fb0bd86..4122078fbd 100644 --- a/apps/execution-service/.env.example +++ b/apps/execution-service/.env.example @@ -3,6 +3,8 @@ PORT=8083 # If you are NOT USING docker, use the below variables # HISTORY_SERVICE_URL=http://localhost:8082/ +# RABBITMQ_URL=amqp://guest:guest@localhost:5672/ # If you are USING docker, use the below variables HISTORY_SERVICE_URL=http://history-service:8082/ +RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672/ diff --git a/apps/execution-service/main.go b/apps/execution-service/main.go index f5f4bfe4c2..e2da2948be 100644 --- a/apps/execution-service/main.go +++ b/apps/execution-service/main.go @@ -25,9 +25,6 @@ func main() { err := godotenv.Load() utils.FailOnError(err, "Error loading .env file") - amqpChannel := messagequeue.InitRabbitMQServer() - defer amqpChannel.Close() - // Initialize Firestore client ctx := context.Background() client, err := initFirestore(ctx) @@ -36,6 +33,10 @@ func main() { service := &handlers.Service{Client: client} + amqpConnection, amqpChannel := messagequeue.InitRabbitMQServer() + defer amqpConnection.Close() + defer amqpChannel.Close() + r := initChiRouter(service) initRestServer(r) } diff --git a/apps/execution-service/messagequeue/rabbitmq.go b/apps/execution-service/messagequeue/rabbitmq.go index 0eaa731e7f..1a6aafcde7 100644 --- a/apps/execution-service/messagequeue/rabbitmq.go +++ b/apps/execution-service/messagequeue/rabbitmq.go @@ -5,23 +5,23 @@ import ( "fmt" "log" "os" + "time" amqp "github.com/rabbitmq/amqp091-go" ) -const CODE_SUBMISSION_QUEUE_KEY = "code-submission" +const ( + CODE_SUBMISSION_QUEUE_KEY = "code-submission" + NUM_RETRIES = 10 +) var ( codeSubmissionQueue amqp.Queue rabbitMQChannel *amqp.Channel ) -func InitRabbitMQServer() *amqp.Channel { - // Connect to RabbitMQ server - rabbitMQURL := os.Getenv("RABBITMQ_URL") - conn, err := amqp.Dial(rabbitMQURL) - utils.FailOnError(err, "Failed to connect to RabbitMQ") - defer conn.Close() +func InitRabbitMQServer() (*amqp.Connection, *amqp.Channel) { + conn := connectToRabbitMQ() // Create a channel ch, err := conn.Channel() @@ -40,7 +40,24 @@ func InitRabbitMQServer() *amqp.Channel { utils.FailOnError(err, "Failed to declare a queue") codeSubmissionQueue = q - return ch + return conn, ch +} + +func connectToRabbitMQ() *amqp.Connection { + var conn *amqp.Connection + var err error + rabbitMQURL := os.Getenv("RABBITMQ_URL") + for i := 0; i < NUM_RETRIES; i++ { // Retry up to 10 times + conn, err = amqp.Dial(rabbitMQURL) + if err == nil { + log.Println("Connected to RabbitMQ") + return conn + } + log.Printf("Failed to connect to RabbitMQ, retrying in 5 seconds... (Attempt %d/%d)", i+1, NUM_RETRIES) + time.Sleep(5 * time.Second) + } + utils.FailOnError(err, fmt.Sprintf("Failed to connect to RabbitMQ after %d attempts", NUM_RETRIES)) + return nil } func PublishSubmissionMessage(submission []byte) error { diff --git a/apps/history-service/.env.example b/apps/history-service/.env.example index b3bd4db2b4..0f3f18c101 100644 --- a/apps/history-service/.env.example +++ b/apps/history-service/.env.example @@ -1,2 +1,8 @@ FIREBASE_CREDENTIAL_PATH=cs3219-staging-codehisto-bb61c-firebase-adminsdk-egopb-95cfaf9b87.json -PORT=8082 \ No newline at end of file +PORT=8082 + +# If you are NOT USING docker, use the below variables +# RABBITMQ_URL=amqp://guest:guest@localhost:5672/ + +# If you are USING docker, use the below variables +RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672/ \ No newline at end of file diff --git a/apps/history-service/main.go b/apps/history-service/main.go index 9d22656df1..1b79073caf 100644 --- a/apps/history-service/main.go +++ b/apps/history-service/main.go @@ -33,9 +33,10 @@ func main() { service := &handlers.Service{Client: client} - amqpChannel := messagequeue.InitRabbitMQServer() + amqpConnection, amqpChannel := messagequeue.InitRabbitMQServer() + defer amqpConnection.Close() defer amqpChannel.Close() - messagequeue.ConsumeSubmissionMessages(client, databases.CreateHistory) + go messagequeue.ConsumeSubmissionMessages(client, databases.CreateHistory) r := initChiRouter(service) initRestServer(r) diff --git a/apps/history-service/messagequeue/rabbitmq.go b/apps/history-service/messagequeue/rabbitmq.go index 9b42aeed84..3b0e0293af 100644 --- a/apps/history-service/messagequeue/rabbitmq.go +++ b/apps/history-service/messagequeue/rabbitmq.go @@ -3,34 +3,34 @@ package messagequeue import ( "context" "encoding/json" + "fmt" "history-service/models" "history-service/utils" "log" "os" + "time" "cloud.google.com/go/firestore" amqp "github.com/rabbitmq/amqp091-go" ) -const CODE_SUBMISSION_QUEUE_KEY = "code-submission" +const ( + CODE_SUBMISSION_QUEUE_KEY = "code-submission" + NUM_RETRIES = 10 +) var ( codeSubmissionQueue amqp.Queue rabbitMQChannel *amqp.Channel ) -func InitRabbitMQServer() *amqp.Channel { - // Connect to RabbitMQ server - rabbitMQURL := os.Getenv("RABBITMQ_URL") - conn, err := amqp.Dial(rabbitMQURL) - utils.FailOnError(err, "Failed to connect to RabbitMQ") - defer conn.Close() +func InitRabbitMQServer() (*amqp.Connection, *amqp.Channel) { + conn := connectToRabbitMQ() // Create a channel ch, err := conn.Channel() utils.FailOnError(err, "Failed to open a channel") rabbitMQChannel = ch - defer ch.Close() // Declare a queue q, err := ch.QueueDeclare( @@ -44,7 +44,24 @@ func InitRabbitMQServer() *amqp.Channel { utils.FailOnError(err, "Failed to declare a queue") codeSubmissionQueue = q - return ch + return conn, ch +} + +func connectToRabbitMQ() *amqp.Connection { + var conn *amqp.Connection + var err error + rabbitMQURL := os.Getenv("RABBITMQ_URL") + for i := 0; i < NUM_RETRIES; i++ { // Retry up to 10 times + conn, err = amqp.Dial(rabbitMQURL) + if err == nil { + log.Println("Connected to RabbitMQ") + return conn + } + log.Printf("Failed to connect to RabbitMQ, retrying in 5 seconds... (Attempt %d/%d)", i+1, NUM_RETRIES) + time.Sleep(5 * time.Second) + } + utils.FailOnError(err, fmt.Sprintf("Failed to connect to RabbitMQ after %d attempts", NUM_RETRIES)) + return nil } func ConsumeSubmissionMessages(client *firestore.Client, createSubmission func( @@ -62,7 +79,7 @@ func ConsumeSubmissionMessages(client *firestore.Client, createSubmission func( false, // no-wait nil, // args ) - utils.FailOnError(err, "Failed to register a consumer") + utils.FailOnError(err, "RabbitMQ: Failed to register a consumer") // Create a channel to block indefinitely forever := make(chan bool) From fbd3a4de76d4f1254c6943c01dc40f14816d7fde Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 00:45:09 +0800 Subject: [PATCH 04/11] feat: update log msg --- apps/history-service/messagequeue/rabbitmq.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/history-service/messagequeue/rabbitmq.go b/apps/history-service/messagequeue/rabbitmq.go index 3b0e0293af..f945a866d0 100644 --- a/apps/history-service/messagequeue/rabbitmq.go +++ b/apps/history-service/messagequeue/rabbitmq.go @@ -87,7 +87,7 @@ func ConsumeSubmissionMessages(client *firestore.Client, createSubmission func( // Start a goroutine to handle incoming messages go func() { for d := range msgs { - log.Printf("RabbitMQ: Received a message: %v", d) + log.Printf("RabbitMQ: Received a message") // Parse request var submissionHistory models.SubmissionHistory From 8bd3d10c71bf007fb2723d5fcd81ef45e088e36a Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 00:52:29 +0800 Subject: [PATCH 05/11] feat: update readme --- apps/execution-service/README.md | 121 +++++++++++++++++++------------ apps/history-service/README.md | 25 +++++++ 2 files changed, 98 insertions(+), 48 deletions(-) diff --git a/apps/execution-service/README.md b/apps/execution-service/README.md index 2961ed152e..51322d38a0 100644 --- a/apps/execution-service/README.md +++ b/apps/execution-service/README.md @@ -74,10 +74,10 @@ The following json format will be returned: ```json [ - { - "input":"hello", - "expected":"olleh" - } + { + "input": "hello", + "expected": "olleh" + } ] ``` @@ -98,16 +98,16 @@ The following json format will be returned: ```json { - "visibleTestResults":[ + "visibleTestResults": [ { - "input":"hello", - "expected":"olleh", - "actual":"olleh", - "passed":true, - "error":"" + "input": "hello", + "expected": "olleh", + "actual": "olleh", + "passed": true, + "error": "" } ], - "customTestResults":null + "customTestResults": null } ``` @@ -127,29 +127,29 @@ The following json format will be returned: ```json { - "visibleTestResults":[ + "visibleTestResults": [ { - "input":"hello", - "expected":"olleh", - "actual":"olleh", - "passed":true, - "error":"" + "input": "hello", + "expected": "olleh", + "actual": "olleh", + "passed": true, + "error": "" } ], - "customTestResults":[ + "customTestResults": [ { - "input":"Hannah", - "expected":"hannaH", - "actual":"hannaH", - "passed":true, - "error":"" + "input": "Hannah", + "expected": "hannaH", + "actual": "hannaH", + "passed": true, + "error": "" }, { - "input":"abcdefg", - "expected":"gfedcba", - "actual":"gfedcba", - "passed":true, - "error":"" + "input": "abcdefg", + "expected": "gfedcba", + "actual": "gfedcba", + "passed": true, + "error": "" } ] } @@ -178,20 +178,20 @@ The following json format will be returned: ```json { - "visibleTestResults":[ + "visibleTestResults": [ { - "input":"hello", - "expected":"olleh", - "actual":"olleh", - "passed":true, - "error":"" + "input": "hello", + "expected": "olleh", + "actual": "olleh", + "passed": true, + "error": "" } ], - "hiddenTestResults":{ - "passed":2, - "total":2 + "hiddenTestResults": { + "passed": 2, + "total": 2 }, - "status":"Accepted" + "status": "Accepted" } ``` @@ -199,19 +199,44 @@ If compilation error exists or any of the tests (visible and hidden) fails, stat ```json { - "visibleTestResults":[ + "visibleTestResults": [ { - "input":"hello", - "expected":"olleh", - "actual":"", - "passed":false, - "error":"Command execution failed: Traceback (most recent call last):\n File \"/tmp/4149249165.py\", line 2, in \u003cmodule\u003e\n prit(name[::-1])\n ^^^^\nNameError: name 'prit' is not defined. Did you mean: 'print'?\n: %!w(*exec.ExitError=\u0026{0x4000364678 []})" + "input": "hello", + "expected": "olleh", + "actual": "", + "passed": false, + "error": "Command execution failed: Traceback (most recent call last):\n File \"/tmp/4149249165.py\", line 2, in \u003cmodule\u003e\n prit(name[::-1])\n ^^^^\nNameError: name 'prit' is not defined. Did you mean: 'print'?\n: %!w(*exec.ExitError=\u0026{0x4000364678 []})" } ], - "hiddenTestResults":{ - "passed":0, - "total":2 + "hiddenTestResults": { + "passed": 0, + "total": 2 }, - "status":"Attempted" + "status": "Attempted" } ``` + +## Setting up message queue with history-service + +A message queue is used to pass submission results asynchronously from the execution-service to the history-service. + +1. In order to do so, we can run the following command to set up a docker container for rabbitmq: + +```bash +docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management +``` + +2. Then we can run the execution-service: + +```bash +go run main.go +``` + +3. We can run the history-service by changing our directory and running the same command: + +```bash +cd ../history-service +go run main.go +``` + +To view more details on the RabbitMQ queue, we can go to `localhost:15672`. diff --git a/apps/history-service/README.md b/apps/history-service/README.md index 681c629bcd..13e416c0c3 100644 --- a/apps/history-service/README.md +++ b/apps/history-service/README.md @@ -73,3 +73,28 @@ The server will be available at http://localhost:8082. ```bash go run main.go ``` + +## Setting up message queue with history-service + +A message queue is used to pass submission results asynchronously from the execution-service to the history-service. + +1. In order to do so, we can run the following command to set up a docker container for rabbitmq: + +```bash +docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management +``` + +2. Then we can run the history-service: + +```bash +go run main.go +``` + +3. We can run the execution-service by changing our directory and running the same command: + +```bash +cd ../execution-service +go run main.go +``` + +To view more details on the RabbitMQ queue, we can go to `localhost:15672`. From b8e3004f8d21268f0f7c42eec4fe3deb83b53203 Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 00:54:53 +0800 Subject: [PATCH 06/11] docs: update readme --- apps/execution-service/README.md | 2 +- apps/history-service/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/execution-service/README.md b/apps/execution-service/README.md index 51322d38a0..434f3e6778 100644 --- a/apps/execution-service/README.md +++ b/apps/execution-service/README.md @@ -216,7 +216,7 @@ If compilation error exists or any of the tests (visible and hidden) fails, stat } ``` -## Setting up message queue with history-service +## Setting up message queue with RabbitMQ A message queue is used to pass submission results asynchronously from the execution-service to the history-service. diff --git a/apps/history-service/README.md b/apps/history-service/README.md index 13e416c0c3..b6dd62e07b 100644 --- a/apps/history-service/README.md +++ b/apps/history-service/README.md @@ -74,7 +74,7 @@ The server will be available at http://localhost:8082. go run main.go ``` -## Setting up message queue with history-service +## Setting up message queue with RabbitMQ A message queue is used to pass submission results asynchronously from the execution-service to the history-service. From bf0c096955050d55b544b487a70f617eb284def6 Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 01:07:32 +0800 Subject: [PATCH 07/11] docs: update readme --- apps/execution-service/README.md | 2 +- apps/history-service/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/execution-service/README.md b/apps/execution-service/README.md index 434f3e6778..f78028505b 100644 --- a/apps/execution-service/README.md +++ b/apps/execution-service/README.md @@ -220,7 +220,7 @@ If compilation error exists or any of the tests (visible and hidden) fails, stat A message queue is used to pass submission results asynchronously from the execution-service to the history-service. -1. In order to do so, we can run the following command to set up a docker container for rabbitmq: +1. In order to do so, we can run the following command to set up a docker container for RabbitMQ: ```bash docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management diff --git a/apps/history-service/README.md b/apps/history-service/README.md index b6dd62e07b..23de7f0e11 100644 --- a/apps/history-service/README.md +++ b/apps/history-service/README.md @@ -78,7 +78,7 @@ go run main.go A message queue is used to pass submission results asynchronously from the execution-service to the history-service. -1. In order to do so, we can run the following command to set up a docker container for rabbitmq: +1. In order to do so, we can run the following command to set up a docker container for RabbitMQ: ```bash docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management From 95a82dc161702934cf68a40368d3a74ab2235fe4 Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 01:10:12 +0800 Subject: [PATCH 08/11] ci: update docker compose test --- .github/workflows/test.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 90408360b4..706ccbed1b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - + - name: Set up .env env: QUESTION_FIREBASE_CREDENTIAL_PATH: ${{ vars.QUESTION_SERVICE_FIREBASE_CREDENTIAL_PATH }} @@ -30,7 +30,7 @@ jobs: echo "FIREBASE_CREDENTIAL_PATH=$QUESTION_FIREBASE_CREDENTIAL_PATH" >> .env echo "JWT_SECRET=$JWT_SECRET" >> .env echo "EXECUTION_SERVICE_URL=$EXECUTION_SERVICE_URL" >> .env - + - name: Set up credentials env: QUESTION_FIREBASE_JSON: ${{ secrets.QUESTION_SERVICE_FIREBASE_CREDENTIAL }} @@ -41,8 +41,8 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 - with: - go-version: '1.23.x' + with: + go-version: "1.23.x" - name: Install Go dependencies run: | @@ -51,7 +51,7 @@ jobs: - name: Install firebase tools run: curl -sL firebase.tools | bash - + - name: Run Go tests with Firebase emulator run: firebase emulators:exec --only firestore 'cd ./apps/question-service; go test -v ./tests' @@ -66,11 +66,11 @@ jobs: run: | cd ./apps/frontend cp .env.example .env - + - name: Set up Node.js uses: actions/setup-node@v2 with: - node-version: '22' + node-version: "22" - name: Install pnpm run: npm i -g pnpm @@ -117,6 +117,7 @@ jobs: EXECUTION_SERVICE_PORT: ${{ vars.EXECUTION_SERVICE_PORT }} MATCHING_SERVICE_TIMEOUT: ${{ vars.MATCHING_SERVICE_TIMEOUT }} REDIS_URL: ${{ vars.REDIS_URL }} + RABBITMQ_URL: ${{ vars.RABBITMQ_URL }} QUESTION_SERVICE_GRPC_URL: ${{ vars.QUESTION_SERVICE_GPRC_URL }} run: | cd ./apps/frontend @@ -147,11 +148,13 @@ jobs: cd ../history-service echo "FIREBASE_CREDENTIAL_PATH=$HISTORY_FIREBASE_CREDENTIAL_PATH" >> .env echo "PORT=$HISTORY_SERVICE_PORT" >> .env - + echo "RABBITMQ_URL=$RABBITMQ_URL" >> .env + cd ../execution-service echo "FIREBASE_CREDENTIAL_PATH=$EXECUTION_FIREBASE_CREDENTIAL_PATH" >> .env echo "PORT=$EXECUTION_SERVICE_PORT" >> .env echo "HISTORY_SERVICE_URL=$HISTORY_SERVICE_URL" >> .env + echo "RABBITMQ_URL=$RABBITMQ_URL" >> .env cd ../signalling-service echo "PORT=$SIGNALLING_SERVICE_PORT" >> .env @@ -170,7 +173,7 @@ jobs: cd ../history-service echo "$HISTORY_FIREBASE_JSON" > "./$HISTORY_FIREBASE_CREDENTIAL_PATH" - + cd ../execution-service echo "$EXECUTION_FIREBASE_JSON" > "./$EXECUTION_FIREBASE_CREDENTIAL_PATH" From 2b2bbd59a6c1be61563db2ee1e0e9ff7fc4b73cb Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 01:12:41 +0800 Subject: [PATCH 09/11] docs: update readme --- apps/execution-service/README.md | 2 +- apps/history-service/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/execution-service/README.md b/apps/execution-service/README.md index f78028505b..88a6f0be5e 100644 --- a/apps/execution-service/README.md +++ b/apps/execution-service/README.md @@ -239,4 +239,4 @@ cd ../history-service go run main.go ``` -To view more details on the RabbitMQ queue, we can go to `localhost:15672`. +To view more details on the RabbitMQ queue, we can go to `localhost:15672`, and login using `guest` as the username and password. diff --git a/apps/history-service/README.md b/apps/history-service/README.md index 23de7f0e11..1da55307dd 100644 --- a/apps/history-service/README.md +++ b/apps/history-service/README.md @@ -97,4 +97,4 @@ cd ../execution-service go run main.go ``` -To view more details on the RabbitMQ queue, we can go to `localhost:15672`. +To view more details on the RabbitMQ queue, we can go to `localhost:15672`, and login using `guest` as the username and password. From fac44307e888ab2a9c72699fa6a329a847f224a7 Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 01:20:36 +0800 Subject: [PATCH 10/11] docs: update readme --- apps/execution-service/README.md | 52 ++++++++++++++++---------------- apps/history-service/README.md | 48 ++++++++++++++--------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/apps/execution-service/README.md b/apps/execution-service/README.md index 88a6f0be5e..c53dc3ab9f 100644 --- a/apps/execution-service/README.md +++ b/apps/execution-service/README.md @@ -20,7 +20,32 @@ go run main.go The server will be available at http://localhost:8083. -## Running the Application via Docker +### Setting up message queue with RabbitMQ + +A message queue is used to pass submission results asynchronously from the execution-service to the history-service. + +1. In order to do so, we can run the following command to set up a docker container for RabbitMQ: + +```bash +docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management +``` + +2. Then we can run the execution-service: + +```bash +go run main.go +``` + +3. We can run the history-service by changing our directory and running the same command: + +```bash +cd ../history-service +go run main.go +``` + +To view more details on the RabbitMQ queue, we can go to `localhost:15672`, and login using `guest` as the username and password. + +### Running the Application via Docker To run the application via Docker, run the following command: @@ -215,28 +240,3 @@ If compilation error exists or any of the tests (visible and hidden) fails, stat "status": "Attempted" } ``` - -## Setting up message queue with RabbitMQ - -A message queue is used to pass submission results asynchronously from the execution-service to the history-service. - -1. In order to do so, we can run the following command to set up a docker container for RabbitMQ: - -```bash -docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management -``` - -2. Then we can run the execution-service: - -```bash -go run main.go -``` - -3. We can run the history-service by changing our directory and running the same command: - -```bash -cd ../history-service -go run main.go -``` - -To view more details on the RabbitMQ queue, we can go to `localhost:15672`, and login using `guest` as the username and password. diff --git a/apps/history-service/README.md b/apps/history-service/README.md index 1da55307dd..6fec9e5bbd 100644 --- a/apps/history-service/README.md +++ b/apps/history-service/README.md @@ -47,54 +47,54 @@ To start the server, run the following command: go run main.go ``` -The server will be available at http://localhost:8082. +### Setting up message queue with RabbitMQ -## Running the Application via Docker +A message queue is used to pass submission results asynchronously from the execution-service to the history-service. -To run the application via Docker, run the following command: +1. In order to do so, we can run the following command to set up a docker container for RabbitMQ: ```bash -docker build -t history-service . +docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management ``` +2. Then we can run the history-service: + ```bash -docker run -p 8082:8082 -d history-service +go run main.go ``` -The server will be available at http://localhost:8082. - -## API Endpoints - -- `POST /histories` -- `GET /histories/{docRefId}` -- `PUT /histories/{docRefId}` -- `DELETE /histories/{docRefId}` +3. We can run the execution-service by changing our directory and running the same command: ```bash +cd ../execution-service go run main.go ``` -## Setting up message queue with RabbitMQ +To view more details on the RabbitMQ queue, we can go to `localhost:15672`, and login using `guest` as the username and password. -A message queue is used to pass submission results asynchronously from the execution-service to the history-service. +The server will be available at http://localhost:8082. -1. In order to do so, we can run the following command to set up a docker container for RabbitMQ: +### Running the Application via Docker + +To run the application via Docker, run the following command: ```bash -docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management +docker build -t history-service . ``` -2. Then we can run the history-service: - ```bash -go run main.go +docker run -p 8082:8082 -d history-service ``` -3. We can run the execution-service by changing our directory and running the same command: +The server will be available at http://localhost:8082. + +## API Endpoints + +- `POST /histories` +- `GET /histories/{docRefId}` +- `PUT /histories/{docRefId}` +- `DELETE /histories/{docRefId}` ```bash -cd ../execution-service go run main.go ``` - -To view more details on the RabbitMQ queue, we can go to `localhost:15672`, and login using `guest` as the username and password. From d75215c120d6b03c06ac889be0e769fa4dc8122d Mon Sep 17 00:00:00 2001 From: tituschewxj Date: Thu, 7 Nov 2024 10:40:15 +0800 Subject: [PATCH 11/11] style(execution-service): :fire: remove commented code Revmoes the previous commented implementation of sending the submission to history service via REST API call. --- apps/execution-service/handlers/submit.go | 29 ----------------------- 1 file changed, 29 deletions(-) diff --git a/apps/execution-service/handlers/submit.go b/apps/execution-service/handlers/submit.go index 44cfa3d49e..1411ffcea7 100644 --- a/apps/execution-service/handlers/submit.go +++ b/apps/execution-service/handlers/submit.go @@ -61,7 +61,6 @@ func (s *Service) ExecuteVisibleAndHiddenTestsAndSubmit(w http.ResponseWriter, r } // Save the collaboration history via the history-service - // TODO: convert to message queue submissionReq := models.Submission{ Code: submission.Code, Language: submission.Language, @@ -90,34 +89,6 @@ func (s *Service) ExecuteVisibleAndHiddenTestsAndSubmit(w http.ResponseWriter, r return } - // get history-service url from os env - // historyServiceUrl := os.Getenv("HISTORY_SERVICE_URL") - // if historyServiceUrl == "" { - // http.Error(w, "HISTORY_SERVICE_URL is not set", http.StatusInternalServerError) - // return - // } - - // req, err := http.NewRequest(http.MethodPost, historyServiceUrl+"histories", - // bytes.NewBuffer(jsonData)) - // if err != nil { - // http.Error(w, err.Error(), http.StatusInternalServerError) - // return - // } - - // req.Header.Set("Content-Type", "application/json") - - // client := &http.Client{} - // resp, err := client.Do(req) - // if err != nil { - // http.Error(w, err.Error(), http.StatusInternalServerError) - // return - // } - // defer resp.Body.Close() - - // if resp.StatusCode != http.StatusOK { - // http.Error(w, "Failed to save submission history", http.StatusInternalServerError) - // } - w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(testResults)