Skip to content

Commit

Permalink
feature(domain): Developed Exercise Solving Functionality
Browse files Browse the repository at this point in the history
Separated exercise domain layer from course layer, separated models, added routes to initialization, removed `noctx` linter due to unnecessary warnings. Added variables to .env.template

Referred issue: #4
  • Loading branch information
FranCalveyra committed Feb 5, 2025
1 parent 02b633d commit 0e8a478
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 78 deletions.
6 changes: 5 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ AUTH0_AUDIENCE=<CUSTOM_API_IDENTIFIER>

# These two are different from the previous ones, from these ones we're getting the Management API JWT
AUTH0_TEST_CLIENT_ID=<AUTH0_TEST_APP_CLIENT_ID>
AUTH0_TEST_CLIENT_SECRET=<AUTH0_TEST_APP_CLIENT_SECRET>
AUTH0_TEST_CLIENT_SECRET=<AUTH0_TEST_APP_CLIENT_SECRET>

#Port for the code runner container
RUNNER_PORT=<RUNNER_PORT>
RUNNER_URL=<RUNNER_URL>
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ linters:
- nestif # reports deeply nested if statements
- nilerr # finds the code that returns nil even if it checks that the error is not nil
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
- noctx # finds sending http request without context.Context
# - noctx # finds sending http request without context.Context
- nolintlint # reports ill-formed or insufficient nolint directives
- nonamedreturns # reports all named returns
- nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL
Expand Down
10 changes: 9 additions & 1 deletion config/routerConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ import (
"gorm.io/gorm"
"net/http"
"rpl-service/controllers/course"
"rpl-service/controllers/exercise"
"rpl-service/models"
"rpl-service/platform/middleware"
)

func InitializeRoutes(router *gin.Engine, db *gorm.DB) {
for _, endpoint := range course.Endpoints {
initializeRoutes(router, db, course.Endpoints)
initializeRoutes(router, db, exercise.Endpoints)
}

// Private methods

func initializeRoutes(router *gin.Engine, db *gorm.DB, endpoints []models.Endpoint) {
for _, endpoint := range endpoints {
mapToGinRoute(router, endpoint, db)
}
}
Expand Down
2 changes: 1 addition & 1 deletion controllers/course/courseController.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func Exists(w http.ResponseWriter, r *http.Request, db *gorm.DB) {
}

func Create(w http.ResponseWriter, r *http.Request, db *gorm.DB) {
// Should Create a new course
// Should create a new course
var body models.Course
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
Expand Down
68 changes: 68 additions & 0 deletions controllers/exercise/exerciseController.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package exercise

import (
"encoding/json"
"github.com/google/uuid"
"gorm.io/gorm"
"io"
"net/http"
"rpl-service/models"
"rpl-service/services/exercises"
)

/**
{
"exerciseID": "...",
"code" : "..."
}
*/

func SolveExercise(w http.ResponseWriter, r *http.Request, db *gorm.DB) {
// With exerciseId, I should search for all its tests, run one by one, and then send the result of each one as JSONs
exerciseID, err := uuid.Parse(r.PathValue("exerciseId"))
if err != nil {
http.Error(w, "Invalid exercise ID format", http.StatusBadRequest)
return
}

var exercise models.Exercise
db.Model(models.Exercise{}).Where(&exercise, "ID = ?", exerciseID)

// TODO: make a more valid check, exercise.ID is currently a uint, don't know why
// TODO 2: we may need to remove the gorm.Model and manually declare each UUID as primary key
if exercise.Name == "" {
http.Error(w, "No exercise with that ID", http.StatusNotFound)
return
}
var response models.SolveExerciseResponse
body, readErr := io.ReadAll(r.Body)
if readErr != nil {
http.Error(w, "Invalid body format", http.StatusBadRequest)
return
}
if respErr := json.Unmarshal(body, &response); respErr != nil {
http.Error(w, "Invalid body format", http.StatusBadRequest)
return
}
results, exerciseError := exercises.SolveExercise(exerciseID, db, response.ExerciseCode)
if exerciseError != nil {
http.Error(w, "Error while executing tests", http.StatusInternalServerError)
return
}
byteResults, err := json.Marshal(results) //nolint:musttag // No need
if err != nil {
return
}

_, writeErr := w.Write(byteResults)
if writeErr != nil {
return
}
}
func CreateExercise(_ http.ResponseWriter, _ *http.Request, _ *gorm.DB) {
// TODO
}

func FindExercise(_ http.ResponseWriter, _ *http.Request, _ *gorm.DB) {
// TODO
}
34 changes: 34 additions & 0 deletions controllers/exercise/exerciseRouter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package exercise

import (
"rpl-service/models"
)

const BaseURL = "/exercise" // May change

var SolveExerciseEndpoint = models.Endpoint{
Method: models.POST,
Path: BaseURL + "/{exerciseId}",
HandlerFunction: SolveExercise,
IsProtected: true,
}

var CreateExerciseEndpoint = models.Endpoint{
Method: models.POST,
Path: BaseURL,
HandlerFunction: CreateExercise,
IsProtected: true,
}

var GetExerciseEndpoint = models.Endpoint{
Method: models.GET,
Path: BaseURL + "/{exerciseId}",
HandlerFunction: FindExercise,
IsProtected: true,
}

var Endpoints = []models.Endpoint{
SolveExerciseEndpoint,
CreateExerciseEndpoint,
GetExerciseEndpoint,
}
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,13 @@ services:
volumes:
- db_data:/var/lib/postgresql/data

runner:
container_name: code-runner
image: "gchr.io/willytonkas/code-runner:latest"
depends_on:
- app
ports:
- ${RUNNER_PORT}:${RUNNER_PORT}

volumes:
db_data:
33 changes: 33 additions & 0 deletions mappers/testMapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package mappers

import (
"bytes"
"encoding/json"
"io"
"net/http"
"rpl-service/models"
)

func SolveExerciseRequestBody(exerciseCode, testScript string) io.Reader {
return bytes.NewReader([]byte(`{"script":"` + exerciseCode + "\", \"tests\":" + testScript + `"`))
}

func SolveExerciseResponseToResult(response *http.Response, exerciseName, testName string) models.ExerciseResult {
var exerciseResponse struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
ReturnCode int `json:"returncode"`
}
err := json.NewDecoder(response.Body).Decode(&exerciseResponse)
if err != nil {
return models.ExerciseResult{}
}

return models.ExerciseResult{
ExerciseName: exerciseName,
TestName: testName,
TestPassed: exerciseResponse.ReturnCode == 0,
Stdout: exerciseResponse.Stdout,
Stderr: exerciseResponse.Stderr,
}
}
35 changes: 0 additions & 35 deletions models/courseModel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package models

import (
"github.com/google/uuid"
"github.com/lib/pq"
"gorm.io/gorm"
)

Expand All @@ -12,37 +11,3 @@ type Course struct {
Name string `json:"name"`
Description string `json:"description"`
}

type Exercise struct {
gorm.Model
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()" json:"ID"`
Name string `json:"name"`
Description string `json:"description"`
BaseCode string `json:"base-code"`
Points int `json:"points"`
UnitNumber int `json:"unit_number"`
TestIDs pq.StringArray `json:"testIDs" gorm:"type:text[]"`
}

type Test struct {
gorm.Model
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()" json:"ID"`
Name string `json:"name"`
Input pq.StringArray `json:"input" gorm:"type:text[]"`
Output pq.StringArray `json:"output" gorm:"type:text[]"`
}

type TestDTO struct {
Name string `json:"name"`
Input []string `json:"input"`
Output []string `json:"output"`
}

type ExerciseDTO struct {
Name string `json:"name"`
Description string `json:"description"`
BaseCode string `json:"base-code"`
Points int `json:"points"`
UnitNumber int `json:"unit_number"`
TestData []TestDTO `json:"test-data"`
}
52 changes: 52 additions & 0 deletions models/exerciseModel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package models

import (
"github.com/google/uuid"
"github.com/lib/pq"
"gorm.io/gorm"
)

type SolveExerciseResponse struct {
ExerciseCode string `json:"code"`
}

type ExerciseResult struct {
ExerciseName string
TestName string
TestPassed bool
Stdout string
Stderr string
}
type Exercise struct {
gorm.Model
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()" json:"ID"`
Name string `json:"name"`
Description string `json:"description"`
BaseCode string `json:"base-code"`
Points int `json:"points"`
UnitNumber int `json:"unit_number"`
TestIDs pq.StringArray `json:"testIDs" gorm:"type:text[]"`
}

type Test struct {
gorm.Model
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()" json:"ID"`
Name string `json:"name"`
TestScript string `json:"testScript"`
}

// TestDTO Should remove DTOs?
type TestDTO struct {
Name string `json:"name"`
TestScript string `json:"testScript"`
}

// ExerciseDTO Should remove DTOs?
type ExerciseDTO struct {
Name string `json:"name"`
Description string `json:"description"`
BaseCode string `json:"base-code"`
Points int `json:"points"`
UnitNumber int `json:"unit_number"`
TestData []TestDTO `json:"test-data"`
}
Loading

0 comments on commit 0e8a478

Please sign in to comment.