Skip to content

Commit

Permalink
feat: Real time submission status (#138)
Browse files Browse the repository at this point in the history
* chore: Remove unnecessary environment variable

Production mode can be enabled by the `GIN_MODE` environment variable

* chore: Add new micro-services to docker compose file

* feat(submissions): Create SSE implementation to send real time updates to students

* feat: Attach gateway instance queue to real time updates exchange

* feat(submissions): Listen for messages in the gateway instance queue

* docs(): Update insomnia collection

Add new SSE endpoint
  • Loading branch information
PedroChaparro authored Jan 6, 2024
1 parent ab9af40 commit 4e879f3
Show file tree
Hide file tree
Showing 15 changed files with 524 additions and 77 deletions.
30 changes: 26 additions & 4 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ services:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: codelabs
ports:
- 5432:5432
- "127.0.0.1:5432:5432"
volumes:
- ./volumes/postgres:/var/lib/postgresql/data

Expand All @@ -23,8 +23,8 @@ services:
RABBITMQ_DEFAULT_USER: rabbitmq
RABBITMQ_DEFAULT_PASS: rabbitmq
ports:
- 5672:5672
- 15672:15672
- "127.0.0.1:5672:5672"
- "127.0.0.1:15672:15672"
volumes:
- ./volumes/rabbitmq:/var/lib/rabbitmq

Expand All @@ -33,9 +33,31 @@ services:
image: ghcr.io/upb-code-labs/static-files-microservice:latest
container_name: codelabs_static_files
restart: on-failure
environment:
- GIN_MODE=release
ports:
- "127.0.0.1:8081:8080"

codelabs_tests_runner:
image: ghcr.io/upb-code-labs/tests-microservice:latest
container_name: codelabs_tests_runner
restart: on-failure
environment:
- STATIC_FILES_MICROSERVICE_ADDRESS=http://codelabs_static_files:8080
- RABBIT_MQ_CONNECTION_STRING=amqp://rabbitmq:rabbitmq@codelabs_rabbitmq:5672/
depends_on:
- codelabs_static_files
- codelabs_rabbitmq

codelabs_submissions_status_updater:
image: ghcr.io/upb-code-labs/submissions-status-updater-microservice:latest
container_name: codelabs_submissions_status_updater
restart: on-failure
environment:
- DB_CONNECTION_STRING=postgres://postgres:postgres@codelabs_postgres_db:5432/codelabs?sslmode=disable
- RABBIT_MQ_CONNECTION_STRING=amqp://rabbitmq:rabbitmq@codelabs_rabbitmq:5672/
depends_on:
- codelabs_rabbitmq
- codelabs_postgres_db

# Utils
Expand All @@ -47,4 +69,4 @@ services:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: postgres
ports:
- 5050:80
- 5050:80
128 changes: 77 additions & 51 deletions docs/insomnia/collection.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,63 @@
{
"_type": "export",
"__export_format": 4,
"__export_date": "2024-01-03T17:36:17.054Z",
"__export_date": "2024-01-06T19:10:47.325Z",
"__export_source": "insomnia.desktop.app:v8.5.1",
"resources": [
{
"_id": "req_f1e349bedd1f42d488d77ab00214efd4",
"parentId": "fld_5ae17b12ab184b19a3040ad2c722b563",
"modified": 1704303336221,
"_id": "req_c79a83f97f4943e2ba7f948709a942d7",
"parentId": "fld_37fd927c541f48ec8d0e26e2c7e32d99",
"modified": 1704566711830,
"created": 1704564805581,
"url": "{{ _.BASE_URL }}/submissions/f8db912d-c9b3-47bb-9b27-dd4c3ff7d902/status",
"name": "realtime-submission-status",
"description": "",
"method": "GET",
"body": {},
"parameters": [],
"headers": [
{ "name": "User-Agent", "value": "insomnia/8.5.1" },
{ "name": "Accept", "value": "text/event-stream" }
],
"authentication": {},
"metaSortKey": -1704564805581,
"isPrivate": false,
"settingStoreCookies": true,
"settingSendCookies": true,
"settingDisableRenderRequestBody": false,
"settingEncodeUrl": true,
"settingRebuildPath": true,
"settingFollowRedirects": "global",
"_type": "request"
},
{
"_id": "fld_37fd927c541f48ec8d0e26e2c7e32d99",
"parentId": "wrk_ee7b69c875f047a186149cf3aebde840",
"modified": 1704303261718,
"created": 1704303261718,
"name": "submissions",
"description": "",
"environment": {},
"environmentPropertyOrder": null,
"metaSortKey": -1704303261718,
"_type": "request_group"
},
{
"_id": "wrk_ee7b69c875f047a186149cf3aebde840",
"parentId": null,
"modified": 1704552663523,
"created": 1704552663523,
"name": "UPB Codelabs Multipart Form Request",
"description": "",
"scope": "collection",
"_type": "workspace"
},
{
"_id": "req_d507f22248c74f8f95a82d5de0173ef3",
"parentId": "fld_37fd927c541f48ec8d0e26e2c7e32d99",
"modified": 1704568127989,
"created": 1704303265321,
"url": "{{ _.BASE_URL }}/submissions/a843c319-0a9a-489e-bb02-683afe39c856",
"url": "{{ _.BASE_URL }}/submissions/f8db912d-c9b3-47bb-9b27-dd4c3ff7d902",
"name": "submit-to-test-block",
"description": "",
"method": "POST",
Expand All @@ -22,7 +70,7 @@
"value": "",
"description": "",
"type": "file",
"fileName": "/home/pacq/IdeaProjects/java/submission.zip"
"fileName": "/home/pacq/IdeaProjects/java/submission-failing-tests.zip"
}
]
},
Expand All @@ -43,30 +91,8 @@
"_type": "request"
},
{
"_id": "fld_5ae17b12ab184b19a3040ad2c722b563",
"parentId": "wrk_015f06b0727b4d81af80c31f9fc4f68f",
"modified": 1704303261718,
"created": 1704303261718,
"name": "submissions",
"description": "",
"environment": {},
"environmentPropertyOrder": null,
"metaSortKey": -1704303261718,
"_type": "request_group"
},
{
"_id": "wrk_015f06b0727b4d81af80c31f9fc4f68f",
"parentId": null,
"modified": 1703867428111,
"created": 1703867428111,
"name": "UPB Codelabs Multipart Form Request",
"description": "",
"scope": "collection",
"_type": "workspace"
},
{
"_id": "req_415eff49d69146239cb94e4840c73722",
"parentId": "fld_0f115af031f64a478dc8707d5aba1048",
"_id": "req_3f020f4783ec4522804310d0c476015a",
"parentId": "fld_a345116ed98f40f582ec6e2c95172aaa",
"modified": 1704303362728,
"created": 1703898807668,
"url": "{{ _.BASE_URL }}/blocks/test_blocks/a843c319-0a9a-489e-bb02-683afe39c856",
Expand Down Expand Up @@ -116,8 +142,8 @@
"_type": "request"
},
{
"_id": "fld_0f115af031f64a478dc8707d5aba1048",
"parentId": "wrk_015f06b0727b4d81af80c31f9fc4f68f",
"_id": "fld_a345116ed98f40f582ec6e2c95172aaa",
"parentId": "wrk_ee7b69c875f047a186149cf3aebde840",
"modified": 1703867530994,
"created": 1703867530994,
"name": "laboratories",
Expand All @@ -128,11 +154,11 @@
"_type": "request_group"
},
{
"_id": "req_1f5b243091ed48d19b56b9e9f1b8b60a",
"parentId": "fld_0f115af031f64a478dc8707d5aba1048",
"modified": 1704303345351,
"_id": "req_8140a0acb17647b3828403192520779a",
"parentId": "fld_a345116ed98f40f582ec6e2c95172aaa",
"modified": 1704552735587,
"created": 1703867533491,
"url": "{{ _.BASE_URL }}/laboratories/test_blocks/be92a311-7679-4313-8b00-483b8b41f869",
"url": "{{ _.BASE_URL }}/laboratories/test_blocks/db05325d-cf62-44ee-a266-aff0b5e96604",
"name": "create-test-block",
"description": "",
"method": "POST",
Expand All @@ -142,7 +168,7 @@
{
"id": "pair_a735916e2a8a49779538d59b17038494",
"name": "language_uuid",
"value": "0ab75ff2-ac23-464e-8d04-057ace064e79",
"value": "2e434e15-dcc3-47c5-90eb-69ae0482d8bc",
"description": ""
},
{
Expand Down Expand Up @@ -178,8 +204,8 @@
"_type": "request"
},
{
"_id": "req_58fd7eb9e2364bbb91112abd8f714b72",
"parentId": "fld_e99fc9329e1e4e9ea4aad5ac37ff965c",
"_id": "req_a76e8e18d2914eed81b2fd48797e1805",
"parentId": "fld_3f8b79f9a7be49a89b03b1dc66dd2399",
"modified": 1704303253094,
"created": 1704303229169,
"url": "{{ _.BASE_URL }}/session/login",
Expand Down Expand Up @@ -207,8 +233,8 @@
"_type": "request"
},
{
"_id": "fld_e99fc9329e1e4e9ea4aad5ac37ff965c",
"parentId": "wrk_015f06b0727b4d81af80c31f9fc4f68f",
"_id": "fld_3f8b79f9a7be49a89b03b1dc66dd2399",
"parentId": "wrk_ee7b69c875f047a186149cf3aebde840",
"modified": 1703867444303,
"created": 1703867444303,
"name": "session",
Expand All @@ -219,8 +245,8 @@
"_type": "request_group"
},
{
"_id": "req_81c24080612643dea636b43cba6a5c72",
"parentId": "fld_e99fc9329e1e4e9ea4aad5ac37ff965c",
"_id": "req_db92faeeb4094ccfa7845aead3c20913",
"parentId": "fld_3f8b79f9a7be49a89b03b1dc66dd2399",
"modified": 1704219709662,
"created": 1703867446159,
"url": "{{ _.BASE_URL }}/session/login",
Expand Down Expand Up @@ -248,8 +274,8 @@
"_type": "request"
},
{
"_id": "env_5128b6531490f5b58316ee3db66aec4f90c368ff",
"parentId": "wrk_015f06b0727b4d81af80c31f9fc4f68f",
"_id": "env_bcfa61886b884785b09fba922f57589d",
"parentId": "wrk_ee7b69c875f047a186149cf3aebde840",
"modified": 1703867471567,
"created": 1703867428113,
"name": "Base Environment",
Expand All @@ -261,23 +287,23 @@
"_type": "environment"
},
{
"_id": "jar_5128b6531490f5b58316ee3db66aec4f90c368ff",
"parentId": "wrk_015f06b0727b4d81af80c31f9fc4f68f",
"modified": 1704219710135,
"_id": "jar_153b3f2d60024a729f2c325761bc6cfc",
"parentId": "wrk_ee7b69c875f047a186149cf3aebde840",
"modified": 1704564666118,
"created": 1703867428114,
"name": "Default Jar",
"cookies": [
{
"key": "session",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiMWEzMTc1NmEtMGU2YS00M2U1LTk0OWMtNDNjYTQyMGE0ODk1Iiwicm9sZSI6InRlYWNoZXIiLCJpc3MiOiJjb2RlbGFicyIsImV4cCI6MTcwNDI0MTMxMCwibmJmIjoxNzA0MjE5NzEwLCJpYXQiOjE3MDQyMTk3MTB9.w7TP5LqkKsFOPPAcrmlRsOIgZZWpwghV-JNPrmGGDKY",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYjU5NzA0Y2EtMzYyNS00MDJhLTg0MzEtYjE1NGExODhkODdjIiwicm9sZSI6InN0dWRlbnQiLCJpc3MiOiJjb2RlbGFicyIsImV4cCI6MTcwNDU4NjI2NiwibmJmIjoxNzA0NTY0NjY2LCJpYXQiOjE3MDQ1NjQ2NjZ9.-uEV60Wqs5PM_lsMI-MINe8vQHfx3YMavXvNu0Y-dmg",
"maxAge": 21600,
"domain": "127.0.0.1",
"path": "/",
"httpOnly": true,
"hostOnly": true,
"creation": "2023-12-29T16:32:00.273Z",
"lastAccessed": "2024-01-02T18:21:50.134Z",
"id": "83917d37-e9ed-4d68-b48f-9fe70e2b0139"
"lastAccessed": "2024-01-06T18:11:06.117Z",
"id": "bc747985-a9ec-4e7e-b836-4c8d3a5c7a3f"
}
],
"_type": "cookie_jar"
Expand Down
5 changes: 0 additions & 5 deletions src/config/infrastructure/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ func InstanceHttpServer() (r *gin.Engine) {
engine := gin.Default()
engine.Use(sharedInfrastructure.ErrorHandlerMiddleware())

isInProductionEnvironment := sharedInfrastructure.GetEnvironment().Environment == "production"
if isInProductionEnvironment {
gin.SetMode(gin.ReleaseMode)
}

// Configure CORS rules
corsConfig := cors.DefaultConfig()
corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
Expand Down
9 changes: 9 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
config "github.com/UPB-Code-Labs/main-api/src/config/infrastructure"
shared "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure"
submissionsImplementations "github.com/UPB-Code-Labs/main-api/src/submissions/infrastructure/implementations"
)

func main() {
Expand All @@ -18,6 +19,14 @@ func main() {
shared.ConnectToRabbitMQ()
defer shared.CloseRabbitMQConnection()

// Start listening for messages in the submissions real time updates queue
submissionsRealTimeUpdatesQueueMgr := submissionsImplementations.GetSubmissionsRealTimeUpdatesQueueMgrInstance()
go submissionsRealTimeUpdatesQueueMgr.ListenForUpdates()

// Start listening for SSE connections
realTimeSubmissionsUpdatesSender := submissionsImplementations.GetSubmissionsRealTimeUpdatesSenderInstance()
go realTimeSubmissionsUpdatesSender.Listen()

// Start HTTP server
router := config.InstanceHttpServer()
router.Run(":8080")
Expand Down
3 changes: 0 additions & 3 deletions src/shared/infrastructure/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import (
)

type EnvironmentSpec struct {
// Execution environment
Environment string `split_words:"true" default:"development"`

// Connection strings
DbConnectionString string `split_words:"true" default:"postgres://postgres:postgres@localhost:5432/codelabs?sslmode=disable"`
RabbitMQConnectionString string `split_words:"true" default:"amqp://rabbitmq:rabbitmq@localhost:5672/"`
Expand Down
12 changes: 12 additions & 0 deletions src/shared/infrastructure/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,15 @@ func WithAuthorizationMiddleware(role []string) gin.HandlerFunc {
c.Next()
}
}

func WithServerSentEventsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Set headers to allow server sent events
c.Writer.Header().Set("Content-Type", "text/event-stream")
c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("Transfer-Encoding", "chunked")

c.Next()
}
}
32 changes: 32 additions & 0 deletions src/submissions/application/use_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,35 @@ func (useCases *SubmissionUseCases) submitWorkToQueue(submissionUUID string) err

return nil
}

func (useCases *SubmissionUseCases) GetSubmissionStatus(studentUUID, testBlockUUID string) (*dtos.SubmissionStatusUpdateDTO, error) {
// Check if the student could submit to the given test block
canSubmit, err := useCases.CanStudentSubmitToTestBlock(studentUUID, testBlockUUID)
if err != nil {
return nil, err
}

if !canSubmit {
return nil, errors.StudentCannotSubmitToTestBlock{}
}

// Get the submission
submission, err := useCases.SubmissionsRepository.GetStudentSubmission(studentUUID, testBlockUUID)
if err != nil {
return nil, err
}

if submission == nil {
return nil, errors.StudentSubmissionNotFound{}
}

// Get the submission status
dto := dtos.SubmissionStatusUpdateDTO{
SubmissionUUID: submission.UUID,
SubmissionStatus: submission.Status,
TestsPassed: submission.Passing,
TestsOutput: submission.Stdout,
}

return &dto, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type SubmissionsRepository interface {
SaveSubmission(dto *dtos.CreateSubmissionDTO) (submissionUUID string, err error)
ResetSubmissionStatus(submissionUUID string) (err error)

GetSubmission(dto *dtos.GetSubmissionDTO) (submissions *entities.Submission, err error)
GetStudentSubmission(studentUUID string, testBlockUUID string) (submission *entities.Submission, err error)
GetSubmissionWorkMetadata(submissionUUID string) (submissionWorkMetadata *entities.SubmissionWork, err error)

Expand Down
7 changes: 7 additions & 0 deletions src/submissions/domain/dtos/submissions_dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ type GetSubmissionDTO struct {
StudentUUID string
TestBlockUUID string
}

type SubmissionStatusUpdateDTO struct {
SubmissionUUID string `json:"submission_uuid"`
SubmissionStatus string `json:"submission_status"`
TestsPassed bool `json:"tests_passed"`
TestsOutput string `json:"tests_output"`
}
Loading

0 comments on commit 4e879f3

Please sign in to comment.