Skip to content

Commit af8d002

Browse files
committed
submission route and judge0 integration completed
1 parent 31a03df commit af8d002

File tree

6 files changed

+314
-9
lines changed

6 files changed

+314
-9
lines changed

cmd/portal-api/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,19 @@ import (
2121
"github.com/krishkumar84/bdcoe-golang-portal/pkg/middleware"
2222
"github.com/krishkumar84/bdcoe-golang-portal/pkg/storage/mongodb"
2323
// "github.com/krishkumar84/bdcoe-golang-portal/pkg/http/handler/users"
24+
"github.com/krishkumar84/bdcoe-golang-portal/pkg/judge0"
25+
"github.com/krishkumar84/bdcoe-golang-portal/pkg/http/handler/submission"
2426
)
2527

2628
func main() {
2729

2830
// load config
2931

3032
cfg := config.MustLoad()
31-
33+
judgeClient := judge0.NewClient(
34+
"judge0-portal.nip.io",
35+
"",
36+
)
3237

3338
//database
3439
//storage
@@ -84,6 +89,7 @@ router.Handle("GET /api/admin/test",
8489
router.HandleFunc("DELETE /api/contest/{contestId}/question/{questionId}", contest.DeleteQuestionFromContestById(storage))
8590
router.HandleFunc("POST /api/question/{id}/testcase", question.AddTestCaseToQuestion(storage))
8691
router.HandleFunc("DELETE /api/question/{questionId}/testcase/{testCaseId}", question.DeleteTestCaseFromQuestionById(storage))
92+
router.HandleFunc("POST /api/submissions", submission.CreateSubmission(storage, judgeClient))
8793

8894
//start server
8995

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package submission
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"time"
7+
8+
"github.com/krishkumar84/bdcoe-golang-portal/pkg/judge0"
9+
"github.com/krishkumar84/bdcoe-golang-portal/pkg/storage"
10+
"github.com/krishkumar84/bdcoe-golang-portal/pkg/types"
11+
"github.com/krishkumar84/bdcoe-golang-portal/pkg/utils/response"
12+
"go.mongodb.org/mongo-driver/bson/primitive"
13+
)
14+
15+
func CreateSubmission(storage storage.Storage, judgeClient *judge0.Client) http.HandlerFunc {
16+
return func(w http.ResponseWriter, r *http.Request) {
17+
// Parse submission request
18+
var submissionReq struct {
19+
QuestionID string `json:"question_id"`
20+
ContestID string `json:"contest_id"`
21+
Code string `json:"code"`
22+
LanguageID string `json:"language_id"`
23+
}
24+
25+
if err := json.NewDecoder(r.Body).Decode(&submissionReq); err != nil {
26+
response.WriteJson(w, http.StatusBadRequest, response.GeneralError(err))
27+
return
28+
}
29+
30+
question, err := storage.GetQuestionById(submissionReq.QuestionID)
31+
if err != nil {
32+
response.WriteJson(w, http.StatusNotFound, response.GeneralError(err))
33+
return
34+
}
35+
36+
questionID, _ := primitive.ObjectIDFromHex(submissionReq.QuestionID)
37+
contestID, _ := primitive.ObjectIDFromHex(submissionReq.ContestID)
38+
39+
userID := primitive.NewObjectID()
40+
41+
submission := types.Submission{
42+
ID: primitive.NewObjectID(),
43+
UserID: userID,
44+
QuestionID: questionID,
45+
ContestID: contestID,
46+
Code: submissionReq.Code,
47+
LanguageID: submissionReq.LanguageID,
48+
Status: types.StatusPending,
49+
Score: 0,
50+
SubmittedAt: time.Now(),
51+
}
52+
53+
// Create Judge0 submission request
54+
judgeReq := judge0.SubmissionRequest{
55+
SourceCode: submissionReq.Code,
56+
LanguageID: submissionReq.LanguageID,
57+
TimeLimit: float64(question[0]["cpu_time_limit"].(int)) / 1000.0,
58+
MemoryLimit: question[0]["memory_limit"].(int),
59+
}
60+
61+
testCases := question[0]["test_cases"].([]interface{})
62+
63+
totalScore := 0
64+
passedTests := 0
65+
66+
for _, tc := range testCases {
67+
testCase := tc.(map[string]interface{})
68+
69+
// Update judge request with test case input/output
70+
judgeReq.Stdin = testCase["input"].(string)
71+
judgeReq.ExpectedOutput = testCase["expected_output"].(string)
72+
73+
token, err := judgeClient.SubmitCode(judgeReq)
74+
if err != nil {
75+
response.WriteJson(w, http.StatusInternalServerError, response.GeneralError(err))
76+
return
77+
}
78+
79+
var status *judge0.SubmissionStatus
80+
for i := 0; i < 10; i++ {
81+
status, err = judgeClient.GetSubmissionStatus(token)
82+
if err != nil {
83+
time.Sleep(time.Second)
84+
continue
85+
}
86+
if status.Status.ID != 1 && status.Status.ID != 2 { // Not In Queue or Processing
87+
break
88+
}
89+
time.Sleep(time.Second)
90+
}
91+
92+
if status.Status.ID == 3 {
93+
passedTests++
94+
}
95+
}
96+
97+
if len(testCases) > 0 {
98+
totalScore = (passedTests * 100) / len(testCases)
99+
}
100+
101+
finalStatus := types.StatusAccepted
102+
if totalScore < 100 {
103+
finalStatus = types.StatusWrongAnswer
104+
}
105+
106+
submission.Status = finalStatus
107+
submission.Score = totalScore
108+
109+
submissionID, err := storage.CreateSubmission(submission)
110+
if err != nil {
111+
response.WriteJson(w, http.StatusInternalServerError, response.GeneralError(err))
112+
return
113+
}
114+
115+
response.WriteJson(w, http.StatusCreated, map[string]interface{}{
116+
"submission_id": submissionID,
117+
"status": finalStatus,
118+
"score": totalScore,
119+
})
120+
}
121+
}

pkg/judge0/client.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package judge0
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
)
10+
11+
type Client struct {
12+
baseURL string
13+
apiKey string
14+
httpClient *http.Client
15+
}
16+
17+
type SubmissionRequest struct {
18+
SourceCode string `json:"source_code"`
19+
LanguageID string `json:"language_id"`
20+
Stdin string `json:"stdin"`
21+
ExpectedOutput string `json:"expected_output"`
22+
TimeLimit float64 `json:"time_limit"`
23+
MemoryLimit int `json:"memory_limit"`
24+
}
25+
26+
type SubmissionResponse struct {
27+
Token string `json:"token"`
28+
}
29+
30+
type SubmissionStatus struct {
31+
Status Status `json:"status"`
32+
Stdout string `json:"stdout"`
33+
Time string `json:"time"`
34+
Memory int `json:"memory"`
35+
stderr string `json:"stderr"`
36+
Message string `json:"message"`
37+
ExitCode int `json:"exit_code"`
38+
}
39+
40+
type Status struct {
41+
ID int `json:"id"`
42+
Description string `json:"description"`
43+
}
44+
45+
func NewClient(baseURL, apiKey string) *Client {
46+
return &Client{
47+
baseURL: baseURL,
48+
apiKey: apiKey,
49+
httpClient: &http.Client{
50+
Timeout: time.Second * 10,
51+
},
52+
}
53+
}
54+
55+
func (c *Client) SubmitCode(req SubmissionRequest) (string, error) {
56+
url := fmt.Sprintf("%s/submissions?base64_encoded=false", c.baseURL)
57+
58+
body, err := json.Marshal(req)
59+
if err != nil {
60+
return "", fmt.Errorf("error marshaling request: %v", err)
61+
}
62+
63+
request, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
64+
if err != nil {
65+
return "", fmt.Errorf("error creating request: %v", err)
66+
}
67+
68+
69+
response, err := c.httpClient.Do(request)
70+
if err != nil {
71+
return "", fmt.Errorf("error making request: %v", err)
72+
}
73+
defer response.Body.Close()
74+
75+
if response.StatusCode != http.StatusCreated {
76+
return "", fmt.Errorf("unexpected status code: %d", response.StatusCode)
77+
}
78+
79+
var result SubmissionResponse
80+
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
81+
return "", fmt.Errorf("error decoding response: %v", err)
82+
}
83+
84+
return result.Token, nil
85+
}
86+
87+
func (c *Client) GetSubmissionStatus(token string) (*SubmissionStatus, error) {
88+
url := fmt.Sprintf("%s/submissions/%s?base64_encoded=false", c.baseURL, token)
89+
90+
request, err := http.NewRequest("GET", url, nil)
91+
if err != nil {
92+
return nil, fmt.Errorf("error creating request: %v", err)
93+
}
94+
95+
response, err := c.httpClient.Do(request)
96+
if err != nil {
97+
return nil, fmt.Errorf("error making request: %v", err)
98+
}
99+
defer response.Body.Close()
100+
101+
if response.StatusCode != http.StatusOK {
102+
return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode)
103+
}
104+
105+
var status SubmissionStatus
106+
if err := json.NewDecoder(response.Body).Decode(&status); err != nil {
107+
return nil, fmt.Errorf("error decoding response: %v", err)
108+
}
109+
110+
return &status, nil
111+
}

pkg/storage/mongodb/mongodb.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,4 +630,57 @@ func (m *MongoDB) DeleteTestCaseFromQuestionById(questionId string, testCaseId s
630630
}
631631

632632
return nil
633+
}
634+
635+
func (m *MongoDB) CreateSubmission(submission types.Submission) (string, error) {
636+
collection := m.db.Collection("submissions")
637+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
638+
defer cancel()
639+
640+
result, err := collection.InsertOne(ctx, submission)
641+
if err != nil {
642+
return "", err
643+
}
644+
645+
return result.InsertedID.(primitive.ObjectID).Hex(), nil
646+
}
647+
648+
func (m *MongoDB) GetSubmissionById(id string) (*types.Submission, error) {
649+
objectId, err := primitive.ObjectIDFromHex(id)
650+
if err != nil {
651+
return nil, fmt.Errorf("invalid submission id format")
652+
}
653+
654+
collection := m.db.Collection("submissions")
655+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
656+
defer cancel()
657+
658+
var submission types.Submission
659+
err = collection.FindOne(ctx, bson.M{"_id": objectId}).Decode(&submission)
660+
if err != nil {
661+
return nil, err
662+
}
663+
664+
return &submission, nil
665+
}
666+
667+
func (m *MongoDB) UpdateSubmissionStatus(id string, status string, score int) error {
668+
objectId, err := primitive.ObjectIDFromHex(id)
669+
if err != nil {
670+
return fmt.Errorf("invalid submission id format")
671+
}
672+
673+
collection := m.db.Collection("submissions")
674+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
675+
defer cancel()
676+
677+
update := bson.M{
678+
"$set": bson.M{
679+
"status": status,
680+
"score": score,
681+
},
682+
}
683+
684+
_, err = collection.UpdateOne(ctx, bson.M{"_id": objectId}, update)
685+
return err
633686
}

pkg/storage/storage.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ type Storage interface {
2626
AddTestCaseToQuestion(questionId string, testCase types.TestCase) (string, error)
2727
DeleteTestCaseFromQuestionById(questionId string, testCaseId string) error
2828
EditTestCaseById(testCaseId string, testCase types.TestCase) error
29+
CreateSubmission(submission types.Submission) (string, error)
30+
GetSubmissionById(id string) (*types.Submission, error)
31+
UpdateSubmissionStatus(id string, status string, score int) error
2932
}

pkg/types/models.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,27 @@ type TestCase struct {
6262
}
6363

6464
type Submission struct {
65-
ID string `bson:"_id,omitempty" json:"submission_id"`
66-
UserID primitive.ObjectID `bson:"user_id" json:"user_id"`
67-
QuestionID primitive.ObjectID `bson:"question_id" json:"question_id"`
68-
ContestID primitive.ObjectID `bson:"contest_id" json:"contest_id"`
69-
Code string `bson:"code" json:"code"`
70-
LanguageId string `bson:"language_id" json:"language_id"`
71-
Status string `bson:"status" json:"status"`
72-
CreatedAt time.Time `bson:"created_at" json:"created_at"`
65+
ID primitive.ObjectID `bson:"_id,omitempty" json:"id"`
66+
UserID primitive.ObjectID `bson:"user_id" json:"user_id" validate:"required"`
67+
QuestionID primitive.ObjectID `bson:"question_id" json:"question_id" validate:"required"`
68+
ContestID primitive.ObjectID `bson:"contest_id" json:"contest_id" validate:"required"`
69+
Code string `bson:"code" json:"code" validate:"required"`
70+
LanguageID string `bson:"language_id" json:"language_id" validate:"required"`
71+
Status string `bson:"status" json:"status"`
72+
Score int `bson:"score" json:"score"`
73+
SubmittedAt time.Time `bson:"submitted_at" json:"submitted_at"`
7374
}
7475

76+
// Add submission status constants
77+
const (
78+
StatusPending = "pending"
79+
StatusAccepted = "accepted"
80+
StatusWrongAnswer = "wrong_answer"
81+
StatusError = "error"
82+
StatusTimeLimitExceeded = "time_limit_exceeded"
83+
StatusCompileError = "compile_error"
84+
)
85+
7586
type Leaderboard struct {
7687
ID string `bson:"_id,omitempty" json:"leaderboard_id"`
7788
UserID primitive.ObjectID `bson:"user_id" json:"user_id"`

0 commit comments

Comments
 (0)