From 92759509e386ca3cc4c16c68aa09cea7240454c1 Mon Sep 17 00:00:00 2001 From: Adrien Ha Date: Sun, 28 Jul 2024 21:35:16 +0200 Subject: [PATCH 01/42] [FIX]-CRUD-race-controller --- src/controllers/race/createRaceHandler.go | 35 ++++++++++------------- src/controllers/race/deleteRaceHandler.go | 5 ++-- src/controllers/race/updateRaceHandler.go | 33 ++++++++++++--------- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index 7562121..0ec83cc 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -10,39 +10,34 @@ import ( ) func CreateRaceHandler(c *gin.Context) { - var race models.Race + var createRaceValidator validators.CreateRaceValidator - if err := c.ShouldBindJSON(&race); err != nil { + if err := c.ShouldBindJSON(&createRaceValidator); err != nil { services.SetJsonBindingErrorResponse(c, err) - return } - raceValidator := validators.CreateRaceValidator{ - Duration: race.Duration, - ElapsedTime: race.ElapsedTime, - Laps: race.Laps, - RaceType: race.RaceType, - AverageSpeed: race.AverageSpeed, - TotalFaults: race.TotalFaults, - EffectiveDuration: race.EffectiveDuration, - UserID: race.UserID, - VehicleID: race.VehicleID, - } - - if err := raceValidator.Validate(); err != nil { + if err := createRaceValidator.Validate(); err != nil { services.SetValidationErrorResponse(c, err) - return } + race := models.Race{ + Duration: createRaceValidator.Duration, + ElapsedTime: createRaceValidator.ElapsedTime, + Laps: createRaceValidator.Laps, + RaceType: createRaceValidator.RaceType, + AverageSpeed: createRaceValidator.AverageSpeed, + TotalFaults: createRaceValidator.TotalFaults, + EffectiveDuration: createRaceValidator.EffectiveDuration, + UserID: createRaceValidator.UserID, + VehicleID: createRaceValidator.VehicleID, + } + db := services.GetConnection() if err := db.Create(&race).Error; err != nil { - // Log the detailed error message for debugging purposes fmt.Printf("Error creating Race: %v\n", err) - - // Set an appropriate error response services.SetInternalServerError(c, "Failed to create Race") return } diff --git a/src/controllers/race/deleteRaceHandler.go b/src/controllers/race/deleteRaceHandler.go index 9b98988..da281be 100644 --- a/src/controllers/race/deleteRaceHandler.go +++ b/src/controllers/race/deleteRaceHandler.go @@ -10,13 +10,13 @@ import ( ) func DeleteRaceHandler(c *gin.Context) { - raceId := c.Param("raceId") + raceID := c.Param("raceId") // Access the database connection db := services.GetConnection() var existingRace models.Race - if err := db.First(&existingRace, raceId).Error; err != nil { + if err := db.First(&existingRace, raceID).Error; err != nil { fmt.Println("Error retrieving race from the database:", err) services.SetNotFound(c, "Race not found") return @@ -30,5 +30,6 @@ func DeleteRaceHandler(c *gin.Context) { return } + fmt.Println("Race deleted successfully:", existingRace) services.SetNoContent(c) } diff --git a/src/controllers/race/updateRaceHandler.go b/src/controllers/race/updateRaceHandler.go index 3c1139d..eae0488 100644 --- a/src/controllers/race/updateRaceHandler.go +++ b/src/controllers/race/updateRaceHandler.go @@ -24,16 +24,11 @@ func UpdateRaceHandler(c *gin.Context) { fmt.Println("Existing Race:", existingRace) - raceValidator := validators.CreateRaceValidator{ - Duration: existingRace.Duration, - ElapsedTime: existingRace.ElapsedTime, - Laps: existingRace.Laps, - RaceType: existingRace.RaceType, - AverageSpeed: existingRace.AverageSpeed, - TotalFaults: existingRace.TotalFaults, - EffectiveDuration: existingRace.EffectiveDuration, - UserID: existingRace.UserID, - VehicleID: existingRace.VehicleID, + var raceValidator validators.CreateRaceValidator + + if err := c.ShouldBindJSON(&raceValidator); err != nil { + services.SetJsonBindingErrorResponse(c, err) + return } if err := raceValidator.Validate(); err != nil { @@ -41,9 +36,21 @@ func UpdateRaceHandler(c *gin.Context) { return } - existingRace.Update(raceValidator) - - db.Save(&existingRace) + existingRace.Duration = raceValidator.Duration + existingRace.ElapsedTime = raceValidator.ElapsedTime + existingRace.Laps = raceValidator.Laps + existingRace.RaceType = raceValidator.RaceType + existingRace.AverageSpeed = raceValidator.AverageSpeed + existingRace.TotalFaults = raceValidator.TotalFaults + existingRace.EffectiveDuration = raceValidator.EffectiveDuration + existingRace.UserID = raceValidator.UserID + existingRace.VehicleID = raceValidator.VehicleID + + if err := db.Save(&existingRace).Error; err != nil { + fmt.Printf("Error updating Race: %v\n", err) + services.SetInternalServerError(c, "Failed to update Race") + return + } services.SetCreated(c, "Race updated successfully", existingRace) } From 0ad71c11bf3270f57e8b4885ab3566b514b37df3 Mon Sep 17 00:00:00 2001 From: LFLeveneur <73178352+LFLeveneur@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:37:36 +0200 Subject: [PATCH 02/42] :construction: [TEST] Vehicle - setup test helpers --- src/tests/setup_test_helpers.go | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/tests/setup_test_helpers.go diff --git a/src/tests/setup_test_helpers.go b/src/tests/setup_test_helpers.go new file mode 100644 index 0000000..f4fe973 --- /dev/null +++ b/src/tests/setup_test_helpers.go @@ -0,0 +1,34 @@ +package tests + +import ( + "api/src/models" + + "gorm.io/gorm" +) + +func setupTestVehicle(db *gorm.DB) *models.Vehicle { + vehicle := &models.Vehicle{ + Name: "Test Vehicle", + BatteryLife: 100.0, + LineSensor1: true, + LineSensor2: false, + LineSensor3: true, + Camera: true, + SonarRange: 50.0, + WheelPower1: 90, + WheelPower2: 80, + WheelPower3: 70, + WheelPower4: 60, + LedColor: "red", + DisplayPanel: "LCD", + SpeakerStatus: true, + SoundPlaying: "test sound", + } + + err := db.Create(&vehicle).Error + if err != nil { + panic(err) + } + + return vehicle +} From baf2f76ac38ee1b0cb30745f911d01b7408da755 Mon Sep 17 00:00:00 2001 From: LFLeveneur <73178352+LFLeveneur@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:38:01 +0200 Subject: [PATCH 03/42] :construction: [TEST] Vehicle - test can create a new vehicle --- src/tests/vehicle/test_can_create_vehicle.go | 52 ++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/tests/vehicle/test_can_create_vehicle.go diff --git a/src/tests/vehicle/test_can_create_vehicle.go b/src/tests/vehicle/test_can_create_vehicle.go new file mode 100644 index 0000000..2d5bafc --- /dev/null +++ b/src/tests/vehicle/test_can_create_vehicle.go @@ -0,0 +1,52 @@ +package tests + +import ( + "api/src/models" + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "api/src/tests" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestCanCreateVehicle(t *testing.T) { + gin.SetMode(gin.TestMode) + + db := tests.GetTestDBConnection() + db.AutoMigrate(&models.Vehicle{}) + + vehicle := models.Vehicle{ + Name: "New Vehicle", + BatteryLife: 90.0, + LineSensor1: true, + LineSensor2: false, + LineSensor3: true, + Camera: true, + SonarRange: 40.0, + WheelPower1: 85, + WheelPower2: 75, + WheelPower3: 65, + WheelPower4: 55, + LedColor: "blue", + DisplayPanel: "OLED", + SpeakerStatus: true, + SoundPlaying: "new sound", + } + + body, _ := json.Marshal(vehicle) + recorder := httptest.NewRecorder() + router := tests.GetTestRouter() + request, _ := http.NewRequest(http.MethodPost, "/api/vehicles", bytes.NewBuffer(body)) + request.Header.Set("Content-Type", "application/json") + router.ServeHTTP(recorder, request) + + assert.Equal(t, http.StatusCreated, recorder.Code) + + var response map[string]interface{} + json.Unmarshal(recorder.Body.Bytes(), &response) + assert.Equal(t, "New Vehicle", response["data"].(map[string]interface{})["attributes"].(map[string]interface{})["vehicle_name"]) +} From 2a48756effcf749bef4ffcf006f87cb80f72a277 Mon Sep 17 00:00:00 2001 From: LFLeveneur <73178352+LFLeveneur@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:38:24 +0200 Subject: [PATCH 04/42] :construction: [TEST] Vehicle - test can delete a vehicle --- src/tests/vehicle/test_can_delete_vehicle.go | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/tests/vehicle/test_can_delete_vehicle.go diff --git a/src/tests/vehicle/test_can_delete_vehicle.go b/src/tests/vehicle/test_can_delete_vehicle.go new file mode 100644 index 0000000..cf1f276 --- /dev/null +++ b/src/tests/vehicle/test_can_delete_vehicle.go @@ -0,0 +1,32 @@ +package tests + +import ( + "api/src/models" + "net/http" + "net/http/httptest" + "strconv" + "testing" + "api/src/tests" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestCanDeleteVehicle(t *testing.T) { + gin.SetMode(gin.TestMode) + + db := tests.GetTestDBConnection() + db.AutoMigrate(&models.Vehicle{}) + vehicle := tests.setupTestVehicle(db) + + recorder := httptest.NewRecorder() + router := tests.GetTestRouter() + request, _ := http.NewRequest(http.MethodDelete, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) + router.ServeHTTP(recorder, request) + + assert.Equal(t, http.StatusOK, recorder.Code) + + var response map[string]interface{} + json.Unmarshal(recorder.Body.Bytes(), &response) + assert.Equal(t, "Vehicule deleted successfully", response["meta"].(map[string]interface{})["message"]) +} From 27e2da1b39c8e8d85e9696962f0205b417dc1f41 Mon Sep 17 00:00:00 2001 From: LFLeveneur <73178352+LFLeveneur@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:38:47 +0200 Subject: [PATCH 05/42] :construction: [TEST] Vehicle - test can get a vehicle --- src/tests/vehicle/test_can_get_vehicle.go | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/tests/vehicle/test_can_get_vehicle.go diff --git a/src/tests/vehicle/test_can_get_vehicle.go b/src/tests/vehicle/test_can_get_vehicle.go new file mode 100644 index 0000000..bbd44e1 --- /dev/null +++ b/src/tests/vehicle/test_can_get_vehicle.go @@ -0,0 +1,34 @@ +package tests + +import ( + "api/src/models" + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "strconv" + "testing" + "api/src/tests" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestCanGetVehicle(t *testing.T) { + gin.SetMode(gin.TestMode) + + db := tests.GetTestDBConnection() + db.AutoMigrate(&models.Vehicle{}) + vehicle := tests.setupTestVehicle(db) + + recorder := httptest.NewRecorder() + router := tests.GetTestRouter() + request, _ := http.NewRequest(http.MethodGet, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) + router.ServeHTTP(recorder, request) + + assert.Equal(t, http.StatusOK, recorder.Code) + + var response map[string]interface{} + json.Unmarshal(recorder.Body.Bytes(), &response) + assert.Equal(t, "Test Vehicle", response["data"].(map[string]interface{})["attributes"].(map[string]interface{})["vehicle_name"]) +} From a26ebe2b2c4dbe31a2b19e6a924da2583017ae6b Mon Sep 17 00:00:00 2001 From: LFLeveneur <73178352+LFLeveneur@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:39:46 +0200 Subject: [PATCH 06/42] :construction: [TEST] Vehicle - test can get a vehicles --- src/tests/vehicle/test_can_get_vehicles.go | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/tests/vehicle/test_can_get_vehicles.go diff --git a/src/tests/vehicle/test_can_get_vehicles.go b/src/tests/vehicle/test_can_get_vehicles.go new file mode 100644 index 0000000..83648af --- /dev/null +++ b/src/tests/vehicle/test_can_get_vehicles.go @@ -0,0 +1,32 @@ +package tests + +import ( + "api/src/models" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "api/src/tests" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestCanGetVehicles(t *testing.T) { + gin.SetMode(gin.TestMode) + + db := tests.GetTestDBConnection() + db.AutoMigrate(&models.Vehicle{}) + tests.setupTestVehicle(db) + + recorder := httptest.NewRecorder() + router := tests.GetTestRouter() + request, _ := http.NewRequest(http.MethodGet, "/api/vehicles", nil) + router.ServeHTTP(recorder, request) + + assert.Equal(t, http.StatusOK, recorder.Code) + + var response map[string]interface{} + json.Unmarshal(recorder.Body.Bytes(), &response) + assert.Greater(t, len(response["data"].([]interface{})), 0) +} From dbc751c117c34d8560153fe8172709cd5d9c2249 Mon Sep 17 00:00:00 2001 From: LFLeveneur <73178352+LFLeveneur@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:40:37 +0200 Subject: [PATCH 07/42] :construction: [TEST] Vehicle - test can update a vehicle --- src/tests/vehicle/test_can_update_vehicle.go | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/tests/vehicle/test_can_update_vehicle.go diff --git a/src/tests/vehicle/test_can_update_vehicle.go b/src/tests/vehicle/test_can_update_vehicle.go new file mode 100644 index 0000000..6f20a91 --- /dev/null +++ b/src/tests/vehicle/test_can_update_vehicle.go @@ -0,0 +1,40 @@ +package tests + +import ( + "api/src/models" + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "strconv" + "testing" + "api/src/tests" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestCanUpdateVehicle(t *testing.T) { + gin.SetMode(gin.TestMode) + + db := tests.GetTestDBConnection() + db.AutoMigrate(&models.Vehicle{}) + vehicle := tests.setupTestVehicle(db) + + updatedData := map[string]interface{}{ + "vehicle_name": "Updated Vehicle", + "battery_life": 95.0, + } + body, _ := json.Marshal(updatedData) + recorder := httptest.NewRecorder() + router := tests.GetTestRouter() + request, _ := http.NewRequest(http.MethodPatch, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), bytes.NewBuffer(body)) + request.Header.Set("Content-Type", "application/json") + router.ServeHTTP(recorder, request) + + assert.Equal(t, http.StatusOK, recorder.Code) + + var response map[string]interface{} + json.Unmarshal(recorder.Body.Bytes(), &response) + assert.Equal(t, "Updated Vehicle", response["data"].(map[string]interface{})["attributes"].(map[string]interface{})["vehicle_name"]) +} From f2cb6880810911be39e0fa39f0bc4d22d8d710f5 Mon Sep 17 00:00:00 2001 From: rijenth Date: Thu, 1 Aug 2024 21:19:12 +0200 Subject: [PATCH 08/42] fix: error in in vehicle test --- readme.md | 4 ++++ ...nnot_signup_if_email_is_not_provided_test.go | 1 + src/tests/setup_test_helpers.go | 2 +- ...te_vehicle.go => can_create_vehicle_test.go} | 17 +++++++---------- src/tests/vehicle/test_can_delete_vehicle.go | 5 +++-- src/tests/vehicle/test_can_get_vehicle.go | 5 ++--- src/tests/vehicle/test_can_get_vehicles.go | 4 ++-- src/tests/vehicle/test_can_update_vehicle.go | 4 ++-- 8 files changed, 22 insertions(+), 20 deletions(-) rename src/tests/vehicle/{test_can_create_vehicle.go => can_create_vehicle_test.go} (70%) diff --git a/readme.md b/readme.md index e50f7c1..6aa89ec 100644 --- a/readme.md +++ b/readme.md @@ -25,6 +25,10 @@ Log into the GO container ``` ## Testing +Run api login test without docker +```bash + go test ./src/tests/auth/login -v +``` Run api tests ```bash diff --git a/src/tests/auth/signup/cannot_signup_if_email_is_not_provided_test.go b/src/tests/auth/signup/cannot_signup_if_email_is_not_provided_test.go index 1e6d1d1..70b7bd3 100644 --- a/src/tests/auth/signup/cannot_signup_if_email_is_not_provided_test.go +++ b/src/tests/auth/signup/cannot_signup_if_email_is_not_provided_test.go @@ -21,6 +21,7 @@ func Test_cannot_signup_if_email_is_not_provided_test(t *testing.T) { user := map[string]string{ "password": "password", } + body, _ := json.Marshal(user) requestRecorder, _ := tests.PerformUnAuthenticatedRequest(http.MethodPost, "/api/signup", body) diff --git a/src/tests/setup_test_helpers.go b/src/tests/setup_test_helpers.go index f4fe973..798e333 100644 --- a/src/tests/setup_test_helpers.go +++ b/src/tests/setup_test_helpers.go @@ -6,7 +6,7 @@ import ( "gorm.io/gorm" ) -func setupTestVehicle(db *gorm.DB) *models.Vehicle { +func SetupTestVehicle(db *gorm.DB) *models.Vehicle { vehicle := &models.Vehicle{ Name: "Test Vehicle", BatteryLife: 100.0, diff --git a/src/tests/vehicle/test_can_create_vehicle.go b/src/tests/vehicle/can_create_vehicle_test.go similarity index 70% rename from src/tests/vehicle/test_can_create_vehicle.go rename to src/tests/vehicle/can_create_vehicle_test.go index 2d5bafc..3875354 100644 --- a/src/tests/vehicle/test_can_create_vehicle.go +++ b/src/tests/vehicle/can_create_vehicle_test.go @@ -2,12 +2,10 @@ package tests import ( "api/src/models" - "bytes" + "api/src/tests" "encoding/json" "net/http" - "net/http/httptest" "testing" - "api/src/tests" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -38,15 +36,14 @@ func TestCanCreateVehicle(t *testing.T) { } body, _ := json.Marshal(vehicle) - recorder := httptest.NewRecorder() - router := tests.GetTestRouter() - request, _ := http.NewRequest(http.MethodPost, "/api/vehicles", bytes.NewBuffer(body)) - request.Header.Set("Content-Type", "application/json") - router.ServeHTTP(recorder, request) - assert.Equal(t, http.StatusCreated, recorder.Code) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/vehicles", body) + + assert.Equal(t, http.StatusCreated, requestRecorder.Code) var response map[string]interface{} - json.Unmarshal(recorder.Body.Bytes(), &response) + + json.Unmarshal(requestRecorder.Body.Bytes(), &response) + assert.Equal(t, "New Vehicle", response["data"].(map[string]interface{})["attributes"].(map[string]interface{})["vehicle_name"]) } diff --git a/src/tests/vehicle/test_can_delete_vehicle.go b/src/tests/vehicle/test_can_delete_vehicle.go index cf1f276..b3898cd 100644 --- a/src/tests/vehicle/test_can_delete_vehicle.go +++ b/src/tests/vehicle/test_can_delete_vehicle.go @@ -2,11 +2,12 @@ package tests import ( "api/src/models" + "api/src/tests" + "encoding/json" "net/http" "net/http/httptest" "strconv" "testing" - "api/src/tests" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -17,7 +18,7 @@ func TestCanDeleteVehicle(t *testing.T) { db := tests.GetTestDBConnection() db.AutoMigrate(&models.Vehicle{}) - vehicle := tests.setupTestVehicle(db) + vehicle := tests.SetupTestVehicle(db) recorder := httptest.NewRecorder() router := tests.GetTestRouter() diff --git a/src/tests/vehicle/test_can_get_vehicle.go b/src/tests/vehicle/test_can_get_vehicle.go index bbd44e1..a33578d 100644 --- a/src/tests/vehicle/test_can_get_vehicle.go +++ b/src/tests/vehicle/test_can_get_vehicle.go @@ -2,13 +2,12 @@ package tests import ( "api/src/models" - "bytes" + "api/src/tests" "encoding/json" "net/http" "net/http/httptest" "strconv" "testing" - "api/src/tests" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -19,7 +18,7 @@ func TestCanGetVehicle(t *testing.T) { db := tests.GetTestDBConnection() db.AutoMigrate(&models.Vehicle{}) - vehicle := tests.setupTestVehicle(db) + vehicle := tests.SetupTestVehicle(db) recorder := httptest.NewRecorder() router := tests.GetTestRouter() diff --git a/src/tests/vehicle/test_can_get_vehicles.go b/src/tests/vehicle/test_can_get_vehicles.go index 83648af..da3bcfa 100644 --- a/src/tests/vehicle/test_can_get_vehicles.go +++ b/src/tests/vehicle/test_can_get_vehicles.go @@ -2,11 +2,11 @@ package tests import ( "api/src/models" + "api/src/tests" "encoding/json" "net/http" "net/http/httptest" "testing" - "api/src/tests" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -17,7 +17,7 @@ func TestCanGetVehicles(t *testing.T) { db := tests.GetTestDBConnection() db.AutoMigrate(&models.Vehicle{}) - tests.setupTestVehicle(db) + tests.SetupTestVehicle(db) recorder := httptest.NewRecorder() router := tests.GetTestRouter() diff --git a/src/tests/vehicle/test_can_update_vehicle.go b/src/tests/vehicle/test_can_update_vehicle.go index 6f20a91..f9a0049 100644 --- a/src/tests/vehicle/test_can_update_vehicle.go +++ b/src/tests/vehicle/test_can_update_vehicle.go @@ -2,13 +2,13 @@ package tests import ( "api/src/models" + "api/src/tests" "bytes" "encoding/json" "net/http" "net/http/httptest" "strconv" "testing" - "api/src/tests" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -19,7 +19,7 @@ func TestCanUpdateVehicle(t *testing.T) { db := tests.GetTestDBConnection() db.AutoMigrate(&models.Vehicle{}) - vehicle := tests.setupTestVehicle(db) + vehicle := tests.SetupTestVehicle(db) updatedData := map[string]interface{}{ "vehicle_name": "Updated Vehicle", From be5c6b8da1a1cb9684c2ae321ff1650cd9ceb9ed Mon Sep 17 00:00:00 2001 From: rijenth Date: Thu, 1 Aug 2024 21:36:32 +0200 Subject: [PATCH 09/42] refacto: use request performer in vehicle test --- ..._can_delete_vehicle.go => can_delete_vehicle_test.go} | 6 +----- .../{test_can_get_vehicle.go => can_get_vehicle_test.go} | 6 +----- ...test_can_get_vehicles.go => can_get_vehicles_test.go} | 6 +----- ..._can_update_vehicle.go => can_update_vehicle_test.go} | 9 ++------- 4 files changed, 5 insertions(+), 22 deletions(-) rename src/tests/vehicle/{test_can_delete_vehicle.go => can_delete_vehicle_test.go} (72%) rename src/tests/vehicle/{test_can_get_vehicle.go => can_get_vehicle_test.go} (73%) rename src/tests/vehicle/{test_can_get_vehicles.go => can_get_vehicles_test.go} (73%) rename src/tests/vehicle/{test_can_update_vehicle.go => can_update_vehicle_test.go} (71%) diff --git a/src/tests/vehicle/test_can_delete_vehicle.go b/src/tests/vehicle/can_delete_vehicle_test.go similarity index 72% rename from src/tests/vehicle/test_can_delete_vehicle.go rename to src/tests/vehicle/can_delete_vehicle_test.go index b3898cd..e21c8c2 100644 --- a/src/tests/vehicle/test_can_delete_vehicle.go +++ b/src/tests/vehicle/can_delete_vehicle_test.go @@ -5,7 +5,6 @@ import ( "api/src/tests" "encoding/json" "net/http" - "net/http/httptest" "strconv" "testing" @@ -20,10 +19,7 @@ func TestCanDeleteVehicle(t *testing.T) { db.AutoMigrate(&models.Vehicle{}) vehicle := tests.SetupTestVehicle(db) - recorder := httptest.NewRecorder() - router := tests.GetTestRouter() - request, _ := http.NewRequest(http.MethodDelete, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) - router.ServeHTTP(recorder, request) + recorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) assert.Equal(t, http.StatusOK, recorder.Code) diff --git a/src/tests/vehicle/test_can_get_vehicle.go b/src/tests/vehicle/can_get_vehicle_test.go similarity index 73% rename from src/tests/vehicle/test_can_get_vehicle.go rename to src/tests/vehicle/can_get_vehicle_test.go index a33578d..b3d8d37 100644 --- a/src/tests/vehicle/test_can_get_vehicle.go +++ b/src/tests/vehicle/can_get_vehicle_test.go @@ -5,7 +5,6 @@ import ( "api/src/tests" "encoding/json" "net/http" - "net/http/httptest" "strconv" "testing" @@ -20,10 +19,7 @@ func TestCanGetVehicle(t *testing.T) { db.AutoMigrate(&models.Vehicle{}) vehicle := tests.SetupTestVehicle(db) - recorder := httptest.NewRecorder() - router := tests.GetTestRouter() - request, _ := http.NewRequest(http.MethodGet, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) - router.ServeHTTP(recorder, request) + recorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) assert.Equal(t, http.StatusOK, recorder.Code) diff --git a/src/tests/vehicle/test_can_get_vehicles.go b/src/tests/vehicle/can_get_vehicles_test.go similarity index 73% rename from src/tests/vehicle/test_can_get_vehicles.go rename to src/tests/vehicle/can_get_vehicles_test.go index da3bcfa..4367579 100644 --- a/src/tests/vehicle/test_can_get_vehicles.go +++ b/src/tests/vehicle/can_get_vehicles_test.go @@ -5,7 +5,6 @@ import ( "api/src/tests" "encoding/json" "net/http" - "net/http/httptest" "testing" "github.com/gin-gonic/gin" @@ -19,10 +18,7 @@ func TestCanGetVehicles(t *testing.T) { db.AutoMigrate(&models.Vehicle{}) tests.SetupTestVehicle(db) - recorder := httptest.NewRecorder() - router := tests.GetTestRouter() - request, _ := http.NewRequest(http.MethodGet, "/api/vehicles", nil) - router.ServeHTTP(recorder, request) + recorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/vehicles/", nil) assert.Equal(t, http.StatusOK, recorder.Code) diff --git a/src/tests/vehicle/test_can_update_vehicle.go b/src/tests/vehicle/can_update_vehicle_test.go similarity index 71% rename from src/tests/vehicle/test_can_update_vehicle.go rename to src/tests/vehicle/can_update_vehicle_test.go index f9a0049..b4b03a5 100644 --- a/src/tests/vehicle/test_can_update_vehicle.go +++ b/src/tests/vehicle/can_update_vehicle_test.go @@ -3,10 +3,8 @@ package tests import ( "api/src/models" "api/src/tests" - "bytes" "encoding/json" "net/http" - "net/http/httptest" "strconv" "testing" @@ -26,11 +24,8 @@ func TestCanUpdateVehicle(t *testing.T) { "battery_life": 95.0, } body, _ := json.Marshal(updatedData) - recorder := httptest.NewRecorder() - router := tests.GetTestRouter() - request, _ := http.NewRequest(http.MethodPatch, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), bytes.NewBuffer(body)) - request.Header.Set("Content-Type", "application/json") - router.ServeHTTP(recorder, request) + + recorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), body) assert.Equal(t, http.StatusOK, recorder.Code) From 34d75c5ddf660172eeedbfcb4acf2c3bce074c12 Mon Sep 17 00:00:00 2001 From: Adrien Ha Date: Sun, 28 Jul 2024 23:23:14 +0200 Subject: [PATCH 10/42] [ADD]- All unitest race routes --- src/controllers/race/updateRaceHandler.go | 2 +- ...race_if_invalid_race_type_provided_test.go | 49 ++++++++++++++ ...race_if_invalid_race_type_provided_test.go | 65 ++++++++++++++++++ src/tests/race/create_race_test.go | 41 ++++++++++++ src/tests/race/delete_race_test.go | 62 +++++++++++++++++ src/tests/race/get_index_race_test.go | 37 ++++++++++ src/tests/race/get_single_race_test.go | 67 +++++++++++++++++++ src/tests/race/update_race_test.go | 59 ++++++++++++++++ 8 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go create mode 100644 src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go create mode 100644 src/tests/race/create_race_test.go create mode 100644 src/tests/race/delete_race_test.go create mode 100644 src/tests/race/get_index_race_test.go create mode 100644 src/tests/race/get_single_race_test.go create mode 100644 src/tests/race/update_race_test.go diff --git a/src/controllers/race/updateRaceHandler.go b/src/controllers/race/updateRaceHandler.go index eae0488..e1c130c 100644 --- a/src/controllers/race/updateRaceHandler.go +++ b/src/controllers/race/updateRaceHandler.go @@ -52,5 +52,5 @@ func UpdateRaceHandler(c *gin.Context) { return } - services.SetCreated(c, "Race updated successfully", existingRace) + services.SetOK(c, "Race updated successfully", existingRace) } diff --git a/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go b/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go new file mode 100644 index 0000000..1783ea4 --- /dev/null +++ b/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go @@ -0,0 +1,49 @@ +package race + +import ( + "api/src/models" + "api/src/tests" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func Test_cannot_create_race_if_invalid_race_type_provided(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + ID: 1, + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + body, _ := json.Marshal(map[string]interface{}{ + "duration": 120, + "elapsed_time": 110, + "laps": 5, + "race_type": "INVALID RACE TYPE", + "average_speed": 150, + "total_faults": 2, + "effective_duration": 118, + "user_id": 1, + "vehicle_id": vehicle.ID, + }) + + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/race/", body) + + responseBody := requestRecorder.Body.String() + fmt.Println("Response Body:", responseBody) + + assert.Equal(t, http.StatusUnprocessableEntity, requestRecorder.Code) + + expectedErrorMessage := "CreateRaceValidator.RaceType" + assert.Contains(t, responseBody, expectedErrorMessage) +} diff --git a/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go b/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go new file mode 100644 index 0000000..c307e25 --- /dev/null +++ b/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go @@ -0,0 +1,65 @@ +package race + +import ( + "api/src/models" + "api/src/tests" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func Test_cannot_update_race_if_invalid_race_type_provided(t *testing.T) { + + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + race := models.Race{ + ID: 1, + Duration: 100, + ElapsedTime: 90, + Laps: 3, + RaceType: "VS", + AverageSpeed: 120, + TotalFaults: 1, + EffectiveDuration: 85, + UserID: 1, + VehicleID: vehicle.ID, + } + databaseConnection.Create(&race) + + body, _ := json.Marshal(map[string]interface{}{ + "duration": 120, + "elapsed_time": 110, + "laps": 5, + "race_type": "INVALID RACE TYPE", + "average_speed": 150, + "total_faults": 2, + "effective_duration": 118, + "user_id": 1, + "vehicle_id": vehicle.ID, + }) + + requestURL := "/api/race/1" + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) + + responseBody := requestRecorder.Body.String() + fmt.Println("Response Body:", responseBody) + + assert.Equal(t, http.StatusUnprocessableEntity, requestRecorder.Code) + + expectedErrorMessage := "CreateRaceValidator.RaceType" + assert.Contains(t, responseBody, expectedErrorMessage) + +} diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go new file mode 100644 index 0000000..15dac96 --- /dev/null +++ b/src/tests/race/create_race_test.go @@ -0,0 +1,41 @@ +package race + +import ( + "api/src/models" + "api/src/tests" + "encoding/json" + "net/http" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func Test_create_race_successfully(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + body, _ := json.Marshal(map[string]interface{}{ + "duration": 120, + "elapsed_time": 110, + "laps": 5, + "race_type": "VS", + "average_speed": 150, + "total_faults": 2, + "effective_duration": 118, + "user_id": 1, + "vehicle_id": vehicle.ID, + }) + + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/race/", body) + + assert.Equal(t, http.StatusCreated, requestRecorder.Code) +} diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go new file mode 100644 index 0000000..1630941 --- /dev/null +++ b/src/tests/race/delete_race_test.go @@ -0,0 +1,62 @@ +package race + +import ( + "api/src/models" + "api/src/tests" + "fmt" + "net/http" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func Test_delete_race_successfully(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + race := models.Race{ + Duration: 100, + ElapsedTime: 90, + Laps: 3, + RaceType: "VS", + AverageSpeed: 120, + TotalFaults: 1, + EffectiveDuration: 85, + UserID: 1, + VehicleID: vehicle.ID, + } + databaseConnection.Create(&race) + + requestURL := fmt.Sprintf("/api/race/%d", race.ID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) + + assert.Equal(t, http.StatusNoContent, requestRecorder.Code) +} + +func Test_delete_race_not_found(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + nonExistentRaceID := 999 + requestURL := fmt.Sprintf("/api/race/%d", nonExistentRaceID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) + + assert.Equal(t, http.StatusNotFound, requestRecorder.Code) +} diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go new file mode 100644 index 0000000..1792aca --- /dev/null +++ b/src/tests/race/get_index_race_test.go @@ -0,0 +1,37 @@ +package race + +import ( + "api/src/models" + "api/src/tests" + "encoding/json" + "net/http" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +type ApiResponse struct { + Message string `json:"message"` + Data []models.Race `json:"data"` +} + +func Test_get_races_index(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) + + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/race/", nil) + + assert.Equal(t, http.StatusOK, requestRecorder.Code) + + var response ApiResponse + err := json.Unmarshal(requestRecorder.Body.Bytes(), &response) + assert.NoError(t, err) + + races := response.Data + assert.NotNil(t, races) + assert.True(t, len(races) >= 0) +} diff --git a/src/tests/race/get_single_race_test.go b/src/tests/race/get_single_race_test.go new file mode 100644 index 0000000..c524b43 --- /dev/null +++ b/src/tests/race/get_single_race_test.go @@ -0,0 +1,67 @@ +package race + +import ( + "api/src/models" + "api/src/tests" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func Test_get_single_race_successfully(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + race := models.Race{ + Duration: 100, + ElapsedTime: 90, + Laps: 3, + RaceType: "VS", + AverageSpeed: 120, + TotalFaults: 1, + EffectiveDuration: 85, + UserID: 1, + VehicleID: vehicle.ID, + } + databaseConnection.Create(&race) + + requestURL := fmt.Sprintf("/api/race/%d", race.ID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) + + assert.Equal(t, http.StatusOK, requestRecorder.Code) + + var response map[string]interface{} + err := json.Unmarshal(requestRecorder.Body.Bytes(), &response) + assert.NoError(t, err) + + data, ok := response["data"].(map[string]interface{}) + assert.True(t, ok) + assert.Equal(t, float64(race.ID), data["ID"]) +} + +func Test_get_single_race_not_found(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) + + invalidRaceID := 999999 + + requestURL := fmt.Sprintf("/api/race/%d", invalidRaceID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) + + assert.Equal(t, http.StatusNotFound, requestRecorder.Code) +} diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go new file mode 100644 index 0000000..e7cf281 --- /dev/null +++ b/src/tests/race/update_race_test.go @@ -0,0 +1,59 @@ +package race + +import ( + "api/src/models" + "api/src/tests" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func Test_update_race_successfully(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + race := models.Race{ + Duration: 100, + ElapsedTime: 90, + Laps: 3, + RaceType: "VS", + AverageSpeed: 120, + TotalFaults: 1, + EffectiveDuration: 85, + UserID: 1, + VehicleID: vehicle.ID, + } + databaseConnection.Create(&race) + + body, _ := json.Marshal(map[string]interface{}{ + "duration": 120, + "elapsed_time": 110, + "laps": 5, + "race_type": "TIME_TRIAL", + "average_speed": 150, + "total_faults": 2, + "effective_duration": 118, + "user_id": 1, + "vehicle_id": vehicle.ID, + }) + + requestURL := fmt.Sprintf("/api/race/%d", race.ID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) + + assert.Equal(t, http.StatusOK, requestRecorder.Code) + + databaseConnection.Unscoped().Delete(&vehicle) + databaseConnection.Unscoped().Delete(&race) +} From c201264771ec39372b78b46a83b78532292e7e37 Mon Sep 17 00:00:00 2001 From: rijenth Date: Thu, 25 Jul 2024 15:15:28 +0200 Subject: [PATCH 11/42] fix: several api arch error --- .../golang-api.postman_collection.json | 193 +++++++++++++++++- src/controllers/authController.go | 37 ++-- src/controllers/fool/deleteFoolHandler.go | 2 - src/controllers/race/deleteRaceHandler.go | 16 +- src/controllers/race/getSingleRaceHandler.go | 10 +- src/controllers/race/updateRaceHandler.go | 3 - src/models/userModel.go | 14 +- src/routes/protected/setupProtectedRoutes.go | 2 +- ...id_email_and_password_are_provided_test.go | 4 +- ...in_if_invalid_password_is_provided_test.go | 4 +- ...signup_if_password_is_not_provided_test.go | 3 +- ...race_if_invalid_race_type_provided_test.go | 4 +- ...race_if_invalid_race_type_provided_test.go | 4 +- src/tests/race/create_race_test.go | 2 +- src/tests/race/delete_race_test.go | 4 +- src/tests/race/get_index_race_test.go | 2 +- src/tests/race/get_single_race_test.go | 4 +- src/tests/race/update_race_test.go | 2 +- src/tests/setup_test_authenticated_user.go | 2 +- 19 files changed, 252 insertions(+), 60 deletions(-) diff --git a/documentation/golang-api.postman_collection.json b/documentation/golang-api.postman_collection.json index 0e0f9a9..fe8fb1e 100644 --- a/documentation/golang-api.postman_collection.json +++ b/documentation/golang-api.postman_collection.json @@ -1,10 +1,10 @@ { "info": { - "_postman_id": "8e9963c4-5424-4358-acd1-e98127500ef2", + "_postman_id": "0e01642e-03df-473f-9066-2df4319b8c75", "name": "golang-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "21427084", - "_collection_link": "https://hetic-project.postman.co/workspace/projet-restaurant-golang~aaccd814-8832-4e52-a0ee-6234c27c4a66/collection/21427084-8e9963c4-5424-4358-acd1-e98127500ef2?action=share&source=collection_link&creator=21427084" + "_collection_link": "https://hetic-project.postman.co/workspace/projet-restaurant-golang~aaccd814-8832-4e52-a0ee-6234c27c4a66/collection/21427084-0e01642e-03df-473f-9066-2df4319b8c75?action=share&source=collection_link&creator=21427084" }, "item": [ { @@ -189,6 +189,195 @@ } ] }, + { + "name": "Race", + "item": [ + { + "name": "Index", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/api/races", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "api", + "races" + ] + } + }, + "response": [] + }, + { + "name": "Get", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/api/races/:id", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "api", + "races", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "Post", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/api/races", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "api", + "races" + ] + } + }, + "response": [] + }, + { + "name": "Patch", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/api/races/:id", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "api", + "races", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "Delete", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{bearerToken}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "localhost:8000/api/races/:id", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "api", + "races", + ":id" + ], + "variable": [ + { + "key": "id", + "value": "1" + } + ] + } + }, + "response": [] + } + ] + }, { "name": "Hello World", "request": { diff --git a/src/controllers/authController.go b/src/controllers/authController.go index d51e580..8a55703 100644 --- a/src/controllers/authController.go +++ b/src/controllers/authController.go @@ -3,20 +3,22 @@ package controllers import ( "api/src/models" "api/src/services" + "errors" "net/http" "time" "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" ) var jwtKey = []byte("my_secret_key") type RegisterCredentials struct { - Username string `json:"username"` - Email string `json:"email"` - Password string `json:"password"` + Username *string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` } type LoginCredentials struct { @@ -41,14 +43,14 @@ func Signup(c *gin.Context) { var creds RegisterCredentials if err := c.BindJSON(&creds); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + services.SetJsonBindingErrorResponse(c, err) return } hashedPassword, err := services.HashPassword(creds.Password) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) + services.SetInternalServerError(c, "Internal server error while hashing password") return } @@ -57,30 +59,37 @@ func Signup(c *gin.Context) { result := services.GetConnection().Create(&user) if result.Error != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) + services.SetInternalServerError(c, result.Error.Error()) return } - c.JSON(http.StatusCreated, gin.H{"message": "User created"}) + services.SetCreated(c, "User created", user) } func Login(c *gin.Context) { var creds LoginCredentials if err := c.BindJSON(&creds); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + services.SetJsonBindingErrorResponse(c, err) return } var user models.User - if err := services.GetConnection().Where("email = ?", creds.Email).First(&user).Error; err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + query := services.GetConnection().Where("email = ?", creds.Email).Find(&user) + + if query.Error != nil { + if errors.Is(query.Error, gorm.ErrRecordNotFound) { + services.SetNotFound(c, "Invalid credentials") + return + } + + services.SetInternalServerError(c, "Internal server error") return } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(creds.Password)); err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + services.SetUnauthorized(c, "Invalid credentials") return } @@ -98,14 +107,14 @@ func Login(c *gin.Context) { tokenString, err := token.SignedString(jwtKey) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) + services.SetInternalServerError(c, "Internal server error") return } userInfo := UserInfo{ ID: user.ID, Email: user.Email, - Username: user.Username, + Username: *user.Username, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, } @@ -121,7 +130,7 @@ func Welcome(c *gin.Context) { claims, err := GetClaimsFromToken(c) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + services.SetUnauthorized(c, "Unauthorized") return } diff --git a/src/controllers/fool/deleteFoolHandler.go b/src/controllers/fool/deleteFoolHandler.go index a955950..ab82daa 100644 --- a/src/controllers/fool/deleteFoolHandler.go +++ b/src/controllers/fool/deleteFoolHandler.go @@ -17,7 +17,6 @@ func DeleteFoolHandler(c *gin.Context) { var existingFool models.Fool if err := db.First(&existingFool, foolId).Error; err != nil { - fmt.Println("Error retrieving fool from the database:", err) services.SetNotFound(c, "Fool not found") return } @@ -25,7 +24,6 @@ func DeleteFoolHandler(c *gin.Context) { fmt.Println("Existing fool to delete:", existingFool) if err := db.Delete(&existingFool).Error; err != nil { - fmt.Println("Error deleting fool from the database:", err) services.SetInternalServerError(c, "Failed to delete fool") return } diff --git a/src/controllers/race/deleteRaceHandler.go b/src/controllers/race/deleteRaceHandler.go index da281be..fc1e434 100644 --- a/src/controllers/race/deleteRaceHandler.go +++ b/src/controllers/race/deleteRaceHandler.go @@ -1,8 +1,6 @@ package handlers import ( - "fmt" - "api/src/models" "api/src/services" @@ -16,20 +14,20 @@ func DeleteRaceHandler(c *gin.Context) { db := services.GetConnection() var existingRace models.Race - if err := db.First(&existingRace, raceID).Error; err != nil { - fmt.Println("Error retrieving race from the database:", err) + + query := db.Where("id", raceID).Find(&existingRace) + + if query.RowsAffected == 0 { services.SetNotFound(c, "Race not found") return } - fmt.Println("Existing race to delete:", existingRace) + query = db.Where("id", raceID).Delete(&existingRace) - if err := db.Delete(&existingRace).Error; err != nil { - fmt.Println("Error deleting race from the database:", err) - services.SetInternalServerError(c, "Failed to delete race") + if query.Error != nil { + services.SetInternalServerError(c, "Internal server error") return } - fmt.Println("Race deleted successfully:", existingRace) services.SetNoContent(c) } diff --git a/src/controllers/race/getSingleRaceHandler.go b/src/controllers/race/getSingleRaceHandler.go index 9442eaf..2f70f77 100644 --- a/src/controllers/race/getSingleRaceHandler.go +++ b/src/controllers/race/getSingleRaceHandler.go @@ -14,12 +14,10 @@ func GetSingleRaceHandler(c *gin.Context) { raceId := c.Param("raceId") - if err := db.Preload("Vehicle").First(&race, raceId).Error; err != nil { - if err.Error() == "record not found" { - services.SetNotFound(c, "Race not found") - } else { - services.SetInternalServerError(c, "Failed to retrieve race") - } + query := db.Where("id", raceId).Find(&race) + + if query.RowsAffected == 0 { + services.SetNotFound(c, "Race not found") return } diff --git a/src/controllers/race/updateRaceHandler.go b/src/controllers/race/updateRaceHandler.go index e1c130c..cb6644c 100644 --- a/src/controllers/race/updateRaceHandler.go +++ b/src/controllers/race/updateRaceHandler.go @@ -17,13 +17,10 @@ func UpdateRaceHandler(c *gin.Context) { var existingRace models.Race if err := db.Preload("Vehicle").First(&existingRace, raceID).Error; err != nil { - fmt.Println("Error retrieving Race from the database:", err) services.SetNotFound(c, "Race not found") return } - fmt.Println("Existing Race:", existingRace) - var raceValidator validators.CreateRaceValidator if err := c.ShouldBindJSON(&raceValidator); err != nil { diff --git a/src/models/userModel.go b/src/models/userModel.go index 45b6d06..6fa076d 100644 --- a/src/models/userModel.go +++ b/src/models/userModel.go @@ -5,15 +5,16 @@ import ( ) type User struct { - ID uint `gorm:"primaryKey"` - Username string `gorm:"unique;not null" json:"username"` - Email string `gorm:"unique;not null" json:"email"` - Password string `gorm:"not null" json:"password"` + ID uint `gorm:"primaryKey"` + Username *string `gorm:"unique" json:"username"` + Email string `gorm:"unique;not null" json:"email"` + Password string `gorm:"not null" json:"password"` Model } func (u *User) Update(updateUser validators.UpdateUserValidator) { - u.Username = updateUser.Username + u.Username = &updateUser.Username + u.Email = updateUser.Email if updateUser.Password != "" { @@ -22,7 +23,8 @@ func (u *User) Update(updateUser validators.UpdateUserValidator) { } func (u *User) Create(CreateUser validators.RegisterUserValidator) { - u.Username = CreateUser.Username + u.Username = &CreateUser.Username + u.Email = CreateUser.Email u.Password = CreateUser.Password } diff --git a/src/routes/protected/setupProtectedRoutes.go b/src/routes/protected/setupProtectedRoutes.go index 7d4c6ab..31ce48c 100644 --- a/src/routes/protected/setupProtectedRoutes.go +++ b/src/routes/protected/setupProtectedRoutes.go @@ -34,7 +34,7 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { userGroup.DELETE("/:userId", user_controller.DeleteUserHandler) }*/ - raceGroup := routerGroup.Group("/race") + raceGroup := routerGroup.Group("/races") // RACE ROUTES { diff --git a/src/tests/auth/login/can_login_if_valid_email_and_password_are_provided_test.go b/src/tests/auth/login/can_login_if_valid_email_and_password_are_provided_test.go index f997d73..2cd9500 100644 --- a/src/tests/auth/login/can_login_if_valid_email_and_password_are_provided_test.go +++ b/src/tests/auth/login/can_login_if_valid_email_and_password_are_provided_test.go @@ -21,10 +21,12 @@ func Test_can_login_if_valid_email_and_password_are_provided(t *testing.T) { hashedPassword, _ := services.HashPassword("password") + username := "username" + user := models.User{ Email: "test@example.com", Password: string(hashedPassword), - Username: "username", + Username: &username, } databaseConnection.Create(&user) diff --git a/src/tests/auth/login/cannot_login_if_invalid_password_is_provided_test.go b/src/tests/auth/login/cannot_login_if_invalid_password_is_provided_test.go index 25c9ce2..43a9f31 100644 --- a/src/tests/auth/login/cannot_login_if_invalid_password_is_provided_test.go +++ b/src/tests/auth/login/cannot_login_if_invalid_password_is_provided_test.go @@ -21,10 +21,12 @@ func Test_cannot_login_if_invalid_password_is_provided(t *testing.T) { hashedPassword, _ := services.HashPassword("password") + username := "username" + user := models.User{ Email: "test@example.com", Password: string(hashedPassword), - Username: "username", + Username: &username, } databaseConnection.Create(&user) diff --git a/src/tests/auth/signup/cannot_signup_if_password_is_not_provided_test.go b/src/tests/auth/signup/cannot_signup_if_password_is_not_provided_test.go index f8b9154..4413ad5 100644 --- a/src/tests/auth/signup/cannot_signup_if_password_is_not_provided_test.go +++ b/src/tests/auth/signup/cannot_signup_if_password_is_not_provided_test.go @@ -19,8 +19,9 @@ func Test_cannot_signup_if_password_is_not_provided_test(t *testing.T) { databaseConnection.AutoMigrate(&models.User{}) user := map[string]string{ - "email": "test@test.com", + "email": "test123@test.com", } + body, _ := json.Marshal(user) requestRecorder, _ := tests.PerformUnAuthenticatedRequest(http.MethodPost, "/api/signup", body) diff --git a/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go b/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go index 1783ea4..a4030db 100644 --- a/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go +++ b/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go @@ -4,7 +4,6 @@ import ( "api/src/models" "api/src/tests" "encoding/json" - "fmt" "net/http" "testing" @@ -37,10 +36,9 @@ func Test_cannot_create_race_if_invalid_race_type_provided(t *testing.T) { "vehicle_id": vehicle.ID, }) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/race/", body) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/races/", body) responseBody := requestRecorder.Body.String() - fmt.Println("Response Body:", responseBody) assert.Equal(t, http.StatusUnprocessableEntity, requestRecorder.Code) diff --git a/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go b/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go index c307e25..cc6ecfe 100644 --- a/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go +++ b/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go @@ -4,7 +4,6 @@ import ( "api/src/models" "api/src/tests" "encoding/json" - "fmt" "net/http" "testing" @@ -51,11 +50,10 @@ func Test_cannot_update_race_if_invalid_race_type_provided(t *testing.T) { "vehicle_id": vehicle.ID, }) - requestURL := "/api/race/1" + requestURL := "/api/races/1" requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) responseBody := requestRecorder.Body.String() - fmt.Println("Response Body:", responseBody) assert.Equal(t, http.StatusUnprocessableEntity, requestRecorder.Code) diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go index 15dac96..d313a81 100644 --- a/src/tests/race/create_race_test.go +++ b/src/tests/race/create_race_test.go @@ -35,7 +35,7 @@ func Test_create_race_successfully(t *testing.T) { "vehicle_id": vehicle.ID, }) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/race/", body) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/races/", body) assert.Equal(t, http.StatusCreated, requestRecorder.Code) } diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go index 1630941..8b58283 100644 --- a/src/tests/race/delete_race_test.go +++ b/src/tests/race/delete_race_test.go @@ -36,7 +36,7 @@ func Test_delete_race_successfully(t *testing.T) { } databaseConnection.Create(&race) - requestURL := fmt.Sprintf("/api/race/%d", race.ID) + requestURL := fmt.Sprintf("/api/races/%d", race.ID) requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) assert.Equal(t, http.StatusNoContent, requestRecorder.Code) @@ -55,7 +55,7 @@ func Test_delete_race_not_found(t *testing.T) { databaseConnection.Create(&vehicle) nonExistentRaceID := 999 - requestURL := fmt.Sprintf("/api/race/%d", nonExistentRaceID) + requestURL := fmt.Sprintf("/api/races/%d", nonExistentRaceID) requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) assert.Equal(t, http.StatusNotFound, requestRecorder.Code) diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go index 1792aca..b20a899 100644 --- a/src/tests/race/get_index_race_test.go +++ b/src/tests/race/get_index_race_test.go @@ -23,7 +23,7 @@ func Test_get_races_index(t *testing.T) { databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/race/", nil) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/races/", nil) assert.Equal(t, http.StatusOK, requestRecorder.Code) diff --git a/src/tests/race/get_single_race_test.go b/src/tests/race/get_single_race_test.go index c524b43..2c1f0e9 100644 --- a/src/tests/race/get_single_race_test.go +++ b/src/tests/race/get_single_race_test.go @@ -37,7 +37,7 @@ func Test_get_single_race_successfully(t *testing.T) { } databaseConnection.Create(&race) - requestURL := fmt.Sprintf("/api/race/%d", race.ID) + requestURL := fmt.Sprintf("/api/races/%d", race.ID) requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) assert.Equal(t, http.StatusOK, requestRecorder.Code) @@ -60,7 +60,7 @@ func Test_get_single_race_not_found(t *testing.T) { invalidRaceID := 999999 - requestURL := fmt.Sprintf("/api/race/%d", invalidRaceID) + requestURL := fmt.Sprintf("/api/races/%d", invalidRaceID) requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) assert.Equal(t, http.StatusNotFound, requestRecorder.Code) diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index e7cf281..b1d6bea 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -49,7 +49,7 @@ func Test_update_race_successfully(t *testing.T) { "vehicle_id": vehicle.ID, }) - requestURL := fmt.Sprintf("/api/race/%d", race.ID) + requestURL := fmt.Sprintf("/api/races/%d", race.ID) requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) assert.Equal(t, http.StatusOK, requestRecorder.Code) diff --git a/src/tests/setup_test_authenticated_user.go b/src/tests/setup_test_authenticated_user.go index f852e60..1f9673e 100644 --- a/src/tests/setup_test_authenticated_user.go +++ b/src/tests/setup_test_authenticated_user.go @@ -32,7 +32,7 @@ func CreateTestUser(db *gorm.DB) *models.User { hashedPassword, _ := services.HashPassword(password) testUser = &models.User{ - Username: username, + Username: &username, Email: email, Password: string(hashedPassword), } From 784a2d8423dd1d5a7031d60dbd3bb9a4c49c3107 Mon Sep 17 00:00:00 2001 From: rijenth Date: Fri, 2 Aug 2024 19:52:39 +0200 Subject: [PATCH 12/42] hotfix: remove user password from user json --- src/models/userModel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/userModel.go b/src/models/userModel.go index 6fa076d..6ff52af 100644 --- a/src/models/userModel.go +++ b/src/models/userModel.go @@ -8,7 +8,7 @@ type User struct { ID uint `gorm:"primaryKey"` Username *string `gorm:"unique" json:"username"` Email string `gorm:"unique;not null" json:"email"` - Password string `gorm:"not null" json:"password"` + Password string `gorm:"not null" json:"-"` Model } From c7525e82564e8d278a5e842b182ad47835a64898 Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Sat, 10 Aug 2024 20:49:01 +0200 Subject: [PATCH 13/42] fix-error-in-auth-workflow (#19) * add: auth controller post validation * add: username and email address already exists verification --- src/controllers/authController.go | 40 ++++++++++++++++++++++++++++++- src/models/userModel.go | 2 +- src/services/email_validator.go | 9 +++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/services/email_validator.go diff --git a/src/controllers/authController.go b/src/controllers/authController.go index 8a55703..a220992 100644 --- a/src/controllers/authController.go +++ b/src/controllers/authController.go @@ -47,6 +47,32 @@ func Signup(c *gin.Context) { return } + if (creds.Email == "") || (creds.Password == "") { + services.SetUnprocessableEntity(c, "Email address and password are required") + + return + } + + if !services.EmailValidator(creds.Email) { + services.SetUnprocessableEntity(c, "User email address is invalid") + + return + } + + connection := services.GetConnection() + + if connection.Where("email = ?", creds.Email).First(&models.User{}).RowsAffected > 0 { + services.SetUnprocessableEntity(c, "A user with this email address already exists") + + return + } + + if creds.Username != nil && connection.Where("username = ?", *creds.Username).First(&models.User{}).RowsAffected > 0 { + services.SetUnprocessableEntity(c, "A user with this username already exists") + + return + } + hashedPassword, err := services.HashPassword(creds.Password) if err != nil { @@ -56,7 +82,7 @@ func Signup(c *gin.Context) { user := models.User{Email: creds.Email, Password: string(hashedPassword), Username: creds.Username} - result := services.GetConnection().Create(&user) + result := connection.Create(&user) if result.Error != nil { services.SetInternalServerError(c, result.Error.Error()) @@ -74,6 +100,18 @@ func Login(c *gin.Context) { return } + if (creds.Email == "") || (creds.Password == "") { + services.SetUnprocessableEntity(c, "Email address and password are required") + + return + } + + if !services.EmailValidator(creds.Email) { + services.SetUnprocessableEntity(c, "User email address is invalid") + + return + } + var user models.User query := services.GetConnection().Where("email = ?", creds.Email).Find(&user) diff --git a/src/models/userModel.go b/src/models/userModel.go index 6ff52af..42b390c 100644 --- a/src/models/userModel.go +++ b/src/models/userModel.go @@ -5,7 +5,7 @@ import ( ) type User struct { - ID uint `gorm:"primaryKey"` + ID uint `gorm:"primaryKey" json:"id"` Username *string `gorm:"unique" json:"username"` Email string `gorm:"unique;not null" json:"email"` Password string `gorm:"not null" json:"-"` diff --git a/src/services/email_validator.go b/src/services/email_validator.go new file mode 100644 index 0000000..7944fa8 --- /dev/null +++ b/src/services/email_validator.go @@ -0,0 +1,9 @@ +package services + +import "regexp" + +func EmailValidator(email string) bool { + re := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) + + return re.MatchString(email) +} From aa451389fcb4fda87cfa6e35297d6aedc578d2ca Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Tue, 20 Aug 2024 19:30:24 +0200 Subject: [PATCH 14/42] Add vehicle status table (#20) * add: new model VehicleState and child models * feat: can get and patch a vehicle state * feat: create a vehicule state when a user sign up * fix: test --- main.go | 19 +-- src/actions/insertSensorDataAction.go | 4 - src/controllers/authController.go | 54 ++++++++- .../getSingleVehiculeStateHandler.go | 31 +++++ .../updateVehicleStateHandler.go | 85 +++++++++++++ src/models/BuzzerVariable.go | 9 ++ src/models/SecondaryLedColor.go | 21 ++++ src/models/headAngle.go | 9 ++ src/models/primaryLedColor.go | 11 ++ src/models/vehicleModel.go | 113 +++++++++++------- src/models/vehicleState.go | 21 ++++ src/routes/protected/setupProtectedRoutes.go | 13 +- ...if_email_and_password_are_provided_test.go | 6 +- src/tests/vehicle/can_create_vehicle_test.go | 49 -------- .../createVehicleStateValidator.go | 20 ++++ .../updateVehicleStateValidator.go | 44 +++++++ 16 files changed, 398 insertions(+), 111 deletions(-) create mode 100644 src/controllers/vehicle_state/getSingleVehiculeStateHandler.go create mode 100644 src/controllers/vehicle_state/updateVehicleStateHandler.go create mode 100644 src/models/BuzzerVariable.go create mode 100644 src/models/SecondaryLedColor.go create mode 100644 src/models/headAngle.go create mode 100644 src/models/primaryLedColor.go create mode 100644 src/models/vehicleState.go delete mode 100644 src/tests/vehicle/can_create_vehicle_test.go create mode 100644 src/validators/vehicleState/createVehicleStateValidator.go create mode 100644 src/validators/vehicleState/updateVehicleStateValidator.go diff --git a/main.go b/main.go index fb4b07a..129aa6d 100644 --- a/main.go +++ b/main.go @@ -47,17 +47,22 @@ func closeDatabaseConnection(database *gorm.DB) { connection.Close() - err = database.AutoMigrate(&models.User{}, &models.SensorData{}, &models.Vehicle{}, &models.Fool{}, &models.Race{}) - - if err != nil { - log.Fatal("Error performing database migrations: ", err) - } - fmt.Println("Database migrations completed.") } func performMigrations(database *gorm.DB) { - err := database.AutoMigrate(&models.User{}, &models.SensorData{}, &models.Vehicle{}, &models.Fool{}, &models.Race{}) + err := database.AutoMigrate( + &models.User{}, + &models.SensorData{}, + &models.Vehicle{}, + &models.Fool{}, + &models.Race{}, + &models.VehicleState{}, + &models.PrimaryLedColor{}, + &models.SecondaryLedColor{}, + &models.BuzzerVariable{}, + &models.HeadAngle{}, + ) if err != nil { log.Fatal("Error performing database migrations: ", err) diff --git a/src/actions/insertSensorDataAction.go b/src/actions/insertSensorDataAction.go index 196c99f..0f3a37f 100644 --- a/src/actions/insertSensorDataAction.go +++ b/src/actions/insertSensorDataAction.go @@ -1,5 +1 @@ package actions - -func insert() { - -} \ No newline at end of file diff --git a/src/controllers/authController.go b/src/controllers/authController.go index a220992..d12445d 100644 --- a/src/controllers/authController.go +++ b/src/controllers/authController.go @@ -77,15 +77,59 @@ func Signup(c *gin.Context) { if err != nil { services.SetInternalServerError(c, "Internal server error while hashing password") + return } - user := models.User{Email: creds.Email, Password: string(hashedPassword), Username: creds.Username} + var user models.User + var errorOccuredInTransaction error + + connection.Transaction(func(transaction *gorm.DB) error { + + user = models.User{ + Username: creds.Username, + Email: creds.Email, + Password: string(hashedPassword), + } + + result := transaction.Create(&user) + + if result.Error != nil { + errorOccuredInTransaction = result.Error + + return errorOccuredInTransaction + } + + vehicles := []models.Vehicle{} + + transaction.Find(&vehicles) + + if len(vehicles) == 0 { + + errorOccuredInTransaction = errors.New( + "cannot register a user because no vehicles are available in the database", + ) + + return errorOccuredInTransaction + } + + for _, vehicle := range vehicles { + _, createVehiculeStateError := vehicle.InitVehicleState(&user, transaction) + + if createVehiculeStateError != nil { + errorOccuredInTransaction = errors.New( + createVehiculeStateError.Error(), + ) + + return createVehiculeStateError + } + } + return nil + }) - result := connection.Create(&user) + if errorOccuredInTransaction != nil { + services.SetInternalServerError(c, errorOccuredInTransaction.Error()) - if result.Error != nil { - services.SetInternalServerError(c, result.Error.Error()) return } @@ -127,7 +171,7 @@ func Login(c *gin.Context) { } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(creds.Password)); err != nil { - services.SetUnauthorized(c, "Invalid credentials") + services.SetUnprocessableEntity(c, "Invalid credentials") return } diff --git a/src/controllers/vehicle_state/getSingleVehiculeStateHandler.go b/src/controllers/vehicle_state/getSingleVehiculeStateHandler.go new file mode 100644 index 0000000..0861350 --- /dev/null +++ b/src/controllers/vehicle_state/getSingleVehiculeStateHandler.go @@ -0,0 +1,31 @@ +package vehicle_state + +import ( + "api/src/models" + "api/src/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm/clause" +) + +func GetASingleVehiculeStateHandler(c *gin.Context) { + vehiculeStateId := c.Param("id") + + if vehiculeStateId == "" || vehiculeStateId == "0" || vehiculeStateId == ":id" { + services.SetUnprocessableEntity(c, "Vehicle state id is required") + + return + } + + connection := services.GetConnection() + + var vehicleState models.VehicleState + + if connection.Where("id = ?", vehiculeStateId).Preload(clause.Associations).First(&vehicleState).RowsAffected == 0 { + services.SetUnprocessableEntity(c, "Vehicle state not found") + + return + } + + services.SetOK(c, "Vehicule state successfully retrieved", vehicleState) +} diff --git a/src/controllers/vehicle_state/updateVehicleStateHandler.go b/src/controllers/vehicle_state/updateVehicleStateHandler.go new file mode 100644 index 0000000..1445adb --- /dev/null +++ b/src/controllers/vehicle_state/updateVehicleStateHandler.go @@ -0,0 +1,85 @@ +package vehicle_state + +import ( + "api/src/models" + "api/src/services" + validators "api/src/validators/vehicleState" + + "github.com/gin-gonic/gin" + "gorm.io/gorm/clause" +) + +func UpdateVehicleStateHandler(c *gin.Context) { + vehiculeStateId := c.Param("id") + + if vehiculeStateId == "" || vehiculeStateId == "0" || vehiculeStateId == ":id" { + services.SetUnprocessableEntity(c, "Vehicle state not found") + return + } + + var UpdateVehicleStateValidator validators.UpdateVehicleStateValidator + + if err := c.ShouldBindJSON(&UpdateVehicleStateValidator); err != nil { + services.SetJsonBindingErrorResponse(c, err) + return + } + + if err := UpdateVehicleStateValidator.Validate(); err != nil { + services.SetValidationErrorResponse(c, err) + return + } + + connection := services.GetConnection() + + var vehicleState models.VehicleState + + if connection.Where("id = ?", vehiculeStateId).First(&vehicleState).RowsAffected == 0 { + services.SetUnprocessableEntity(c, "Vehicle state not found") + return + } + + connection.Model(models.VehicleState{}). + Where("id = ?", vehiculeStateId). + Updates(models.VehicleState{ + Face: UpdateVehicleStateValidator.Face, + LedAnimation: UpdateVehicleStateValidator.LedAnimation, + BuzzerAlarm: UpdateVehicleStateValidator.BuzzerAlarm, + VideoActivated: UpdateVehicleStateValidator.VideoActivated, + }) + + connection.Model(models.PrimaryLedColor{}). + Where("id = ?", vehicleState.PrimaryLedColorID). + Updates(models.PrimaryLedColor{ + LedIdentifier: UpdateVehicleStateValidator.PrimaryLedColor.LedIdentifier, + Red: UpdateVehicleStateValidator.PrimaryLedColor.Red, + Green: UpdateVehicleStateValidator.PrimaryLedColor.Green, + Blue: UpdateVehicleStateValidator.PrimaryLedColor.Blue, + }) + + connection.Model(models.SecondaryLedColor{}). + Where("id = ?", vehicleState.SecondaryLedColorID). + Updates(models.SecondaryLedColor{ + BinaryRepresentation: UpdateVehicleStateValidator.SecondaryLedColor.BinaryRepresentation, + Red: UpdateVehicleStateValidator.SecondaryLedColor.Red, + Green: UpdateVehicleStateValidator.SecondaryLedColor.Green, + Blue: UpdateVehicleStateValidator.SecondaryLedColor.Blue, + }) + + connection.Model(models.BuzzerVariable{}). + Where("id = ?", vehicleState.BuzzerVariableID). + Updates(models.BuzzerVariable{ + Activated: UpdateVehicleStateValidator.BuzzerVariable.Activated, + Frequency: UpdateVehicleStateValidator.BuzzerVariable.Frequency, + }) + + connection.Model(models.HeadAngle{}). + Where("id = ?", vehicleState.HeadAngleID). + Updates(models.HeadAngle{ + VerticalAngle: UpdateVehicleStateValidator.HeadAngle.VerticalAngle, + HorizontalAngle: UpdateVehicleStateValidator.HeadAngle.HorizontalAngle, + }) + + connection.Where("id = ?", vehiculeStateId).Preload(clause.Associations).First(&vehicleState) + + services.SetOK(c, "Vehicule state successfully updated", vehicleState) +} diff --git a/src/models/BuzzerVariable.go b/src/models/BuzzerVariable.go new file mode 100644 index 0000000..def3b76 --- /dev/null +++ b/src/models/BuzzerVariable.go @@ -0,0 +1,9 @@ +package models + +type BuzzerVariable struct { + ID uint `gorm:"primaryKey"` + Activated *uint8 `gorm:"not null" json:"activated"` + Frequency *uint16 `gorm:"not null" json:"frequency"` + VehicleStateID uint `gorm:"not null"` + Model +} diff --git a/src/models/SecondaryLedColor.go b/src/models/SecondaryLedColor.go new file mode 100644 index 0000000..793a929 --- /dev/null +++ b/src/models/SecondaryLedColor.go @@ -0,0 +1,21 @@ +package models + +import "gorm.io/gorm" + +type SecondaryLedColor struct { + ID uint `gorm:"primaryKey"` + BinaryRepresentation *int `gorm:"not null" json:"binary_representation"` + Red *uint8 `gorm:"not null" json:"red"` + Green *uint8 `gorm:"not null" json:"green"` + Blue *uint8 `gorm:"not null" json:"blue"` + VehicleStateID uint `gorm:"not null"` + Model +} + +func CreateSecondaryLedColor(db *gorm.DB, secondaryLedColor *SecondaryLedColor) (*SecondaryLedColor, error) { + if err := db.Create(secondaryLedColor).Error; err != nil { + return nil, err + } + + return secondaryLedColor, nil +} diff --git a/src/models/headAngle.go b/src/models/headAngle.go new file mode 100644 index 0000000..05d01f3 --- /dev/null +++ b/src/models/headAngle.go @@ -0,0 +1,9 @@ +package models + +type HeadAngle struct { + ID uint `gorm:"primaryKey"` + VerticalAngle *uint `gorm:"not null" json:"vertical_angle"` + HorizontalAngle *uint `gorm:"not null" json:"horizontal_angle"` + VehicleStateID uint `gorm:"not null"` + Model +} diff --git a/src/models/primaryLedColor.go b/src/models/primaryLedColor.go new file mode 100644 index 0000000..455a270 --- /dev/null +++ b/src/models/primaryLedColor.go @@ -0,0 +1,11 @@ +package models + +type PrimaryLedColor struct { + ID uint `gorm:"primaryKey"` + LedIdentifier *int `gorm:"not null" json:"led_identifier"` + Red *uint8 `gorm:"not null" json:"red"` + Green *uint8 `gorm:"not null" json:"green"` + Blue *uint8 `gorm:"not null" json:"blue"` + VehicleStateID uint `gorm:"not null"` + Model +} diff --git a/src/models/vehicleModel.go b/src/models/vehicleModel.go index 2fbeedb..34108a2 100644 --- a/src/models/vehicleModel.go +++ b/src/models/vehicleModel.go @@ -1,8 +1,6 @@ package models -import ( - validators "api/src/validators/vehicle" -) +import "gorm.io/gorm" type Vehicle struct { ID uint `gorm:"primaryKey"` @@ -24,46 +22,77 @@ type Vehicle struct { Model } -func (v *Vehicle) Create(createVehicle validators.CreateVehicleValidator) error { - if err := createVehicle.Validate(); err != nil { - return err +func (vehicle *Vehicle) InitVehicleState(user *User, db *gorm.DB) (VehicleState, error) { + defaultUint := uint(0) + defaultUint8 := uint8(0) + defaultUint16 := uint16(0) + + vehicleState := VehicleState{ + VehicleID: vehicle.ID, + Face: &defaultUint8, + LedAnimation: &defaultUint8, + BuzzerAlarm: &defaultUint8, + VideoActivated: &defaultUint8, + UserID: user.ID, + } + + if err := db.Create(&vehicleState).Error; err != nil { + return VehicleState{}, err + } + + primaryLedColor := PrimaryLedColor{ + LedIdentifier: new(int), + Red: &defaultUint8, + Green: &defaultUint8, + Blue: &defaultUint8, + VehicleStateID: vehicleState.ID, + } + + if err := db.Create(&primaryLedColor).Error; err != nil { + return VehicleState{}, err + } + + vehicleState.PrimaryLedColor = &primaryLedColor + + secondaryLedColor := SecondaryLedColor{ + BinaryRepresentation: new(int), + Red: &defaultUint8, + Green: &defaultUint8, + Blue: &defaultUint8, + VehicleStateID: vehicleState.ID, + } + + if err := db.Create(&secondaryLedColor).Error; err != nil { + return VehicleState{}, err + } + + vehicleState.SecondaryLedColor = &secondaryLedColor + + buzzerVariable := BuzzerVariable{ + Activated: &defaultUint8, + Frequency: &defaultUint16, + VehicleStateID: vehicleState.ID, + } + + if err := db.Create(&buzzerVariable).Error; err != nil { + return VehicleState{}, err } - v.Name = createVehicle.Name - v.BatteryLife = createVehicle.BatteryLife - v.LineSensor1 = createVehicle.LineSensor1 - v.LineSensor2 = createVehicle.LineSensor2 - v.LineSensor3 = createVehicle.LineSensor3 - v.Camera = createVehicle.Camera - v.SonarRange = createVehicle.SonarRange - v.WheelPower1 = createVehicle.WheelPower1 - v.WheelPower2 = createVehicle.WheelPower2 - v.WheelPower3 = createVehicle.WheelPower3 - v.WheelPower4 = createVehicle.WheelPower4 - v.LedColor = createVehicle.LedColor - v.DisplayPanel = createVehicle.DisplayPanel - v.SpeakerStatus = createVehicle.SpeakerStatus - v.SoundPlaying = createVehicle.SoundPlaying - return nil -} -func (v *Vehicle) Update(updateVehicle validators.CreateVehicleValidator) error { - if err := updateVehicle.Validate(); err != nil { - return err + vehicleState.BuzzerVariable = &buzzerVariable + + headAngle := HeadAngle{ + VerticalAngle: &defaultUint, + HorizontalAngle: &defaultUint, + VehicleStateID: vehicleState.ID, + } + + if err := db.Create(&headAngle).Error; err != nil { + return VehicleState{}, err } - v.Name = updateVehicle.Name - v.BatteryLife = updateVehicle.BatteryLife - v.LineSensor1 = updateVehicle.LineSensor1 - v.LineSensor2 = updateVehicle.LineSensor2 - v.LineSensor3 = updateVehicle.LineSensor3 - v.Camera = updateVehicle.Camera - v.SonarRange = updateVehicle.SonarRange - v.WheelPower1 = updateVehicle.WheelPower1 - v.WheelPower2 = updateVehicle.WheelPower2 - v.WheelPower3 = updateVehicle.WheelPower3 - v.WheelPower4 = updateVehicle.WheelPower4 - v.LedColor = updateVehicle.LedColor - v.DisplayPanel = updateVehicle.DisplayPanel - v.SpeakerStatus = updateVehicle.SpeakerStatus - v.SoundPlaying = updateVehicle.SoundPlaying - return nil + + vehicleState.HeadAngle = &headAngle + + db.Save(&vehicleState) + + return vehicleState, nil } diff --git a/src/models/vehicleState.go b/src/models/vehicleState.go new file mode 100644 index 0000000..9d5d429 --- /dev/null +++ b/src/models/vehicleState.go @@ -0,0 +1,21 @@ +package models + +type VehicleState struct { + ID uint `gorm:"primaryKey" json:"id"` + VehicleID uint `json:"-"` + //Vehicle Vehicle `gorm:"foreignKey:VehicleID"` + Face *uint8 `gorm:"not null" json:"face"` + LedAnimation *uint8 `gorm:"not null" json:"led_animation"` + BuzzerAlarm *uint8 `gorm:"not null" json:"buzzer_alarm"` + VideoActivated *uint8 `gorm:"not null" json:"video_activated"` + PrimaryLedColorID *uint `json:"-"` + PrimaryLedColor *PrimaryLedColor `gorm:"foreignKey:PrimaryLedColorID"` + SecondaryLedColorID *uint `json:"-"` + SecondaryLedColor *SecondaryLedColor `gorm:"foreignKey:SecondaryLedColorID"` + BuzzerVariableID *uint `json:"-"` + BuzzerVariable *BuzzerVariable `gorm:"foreignKey:BuzzerVariableID"` + HeadAngleID *uint `json:"-"` + HeadAngle *HeadAngle `gorm:"foreignKey:HeadAngleID"` + UserID uint `json:"-"` + Model +} diff --git a/src/routes/protected/setupProtectedRoutes.go b/src/routes/protected/setupProtectedRoutes.go index 31ce48c..8ff1569 100644 --- a/src/routes/protected/setupProtectedRoutes.go +++ b/src/routes/protected/setupProtectedRoutes.go @@ -3,6 +3,7 @@ package protected import ( controllers "api/src/controllers" race_controller "api/src/controllers/race" + "api/src/controllers/vehicle_state" //user_controller "api/src/controllers/user" "api/src/middleware" @@ -15,6 +16,7 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/protected", controllers.Welcome) + // VEHICLE ROUTES vehicleGroup := routerGroup.Group("/vehicles") { vehicleGroup.GET("/:id", controllers.GetVehicle) @@ -22,7 +24,6 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { vehicleGroup.POST("/", controllers.CreateVehicle) vehicleGroup.PATCH("/:id", controllers.UpdateVehicle) vehicleGroup.DELETE("/:id", controllers.DeleteVehicle) - } /*userGroup := routerGroup.Group("/user") @@ -34,9 +35,8 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { userGroup.DELETE("/:userId", user_controller.DeleteUserHandler) }*/ - raceGroup := routerGroup.Group("/races") - // RACE ROUTES + raceGroup := routerGroup.Group("/races") { raceGroup.GET("/", race_controller.GetAllRaceHandler) raceGroup.GET("/:raceId", race_controller.GetSingleRaceHandler) @@ -45,4 +45,11 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { raceGroup.DELETE("/:raceId", race_controller.DeleteRaceHandler) } + // VEHICLE STATE ROUTES + vehicleStateGroup := routerGroup.Group("/vehicle-states") + { + vehicleStateGroup.PATCH("/:id", vehicle_state.UpdateVehicleStateHandler) + vehicleStateGroup.GET("/:id", vehicle_state.GetASingleVehiculeStateHandler) + } + } diff --git a/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go b/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go index adec20d..fc52a8f 100644 --- a/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go +++ b/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go @@ -17,7 +17,11 @@ func Test_can_signup_if_email_and_password_are_provided_test(t *testing.T) { databaseConnection := tests.GetTestDBConnection() // Créer une requête de test et la table associée - databaseConnection.AutoMigrate(&models.User{}) + databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.VehicleState{}) + + databaseConnection.Create(&models.Vehicle{ + Name: "a vehicule is required to enable any user to signup", + }) user := map[string]string{ "email": "test@example.com", diff --git a/src/tests/vehicle/can_create_vehicle_test.go b/src/tests/vehicle/can_create_vehicle_test.go deleted file mode 100644 index 3875354..0000000 --- a/src/tests/vehicle/can_create_vehicle_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package tests - -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "net/http" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestCanCreateVehicle(t *testing.T) { - gin.SetMode(gin.TestMode) - - db := tests.GetTestDBConnection() - db.AutoMigrate(&models.Vehicle{}) - - vehicle := models.Vehicle{ - Name: "New Vehicle", - BatteryLife: 90.0, - LineSensor1: true, - LineSensor2: false, - LineSensor3: true, - Camera: true, - SonarRange: 40.0, - WheelPower1: 85, - WheelPower2: 75, - WheelPower3: 65, - WheelPower4: 55, - LedColor: "blue", - DisplayPanel: "OLED", - SpeakerStatus: true, - SoundPlaying: "new sound", - } - - body, _ := json.Marshal(vehicle) - - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/vehicles", body) - - assert.Equal(t, http.StatusCreated, requestRecorder.Code) - - var response map[string]interface{} - - json.Unmarshal(requestRecorder.Body.Bytes(), &response) - - assert.Equal(t, "New Vehicle", response["data"].(map[string]interface{})["attributes"].(map[string]interface{})["vehicle_name"]) -} diff --git a/src/validators/vehicleState/createVehicleStateValidator.go b/src/validators/vehicleState/createVehicleStateValidator.go new file mode 100644 index 0000000..03e524d --- /dev/null +++ b/src/validators/vehicleState/createVehicleStateValidator.go @@ -0,0 +1,20 @@ +package validators + +import ( + "github.com/go-playground/validator/v10" +) + +type CreateVehicleStateValidator struct { + VehicleID uint `json:"vehicle_id" validate:"required"` + Face uint8 `json:"face" validate:"required,gte=0,lte=10"` + LedAnimation uint8 `json:"led_animation" validate:"required,gte=0,lte=5"` + BuzzerAlarm uint8 `json:"buzzer_alarm" validate:"required,gte=0,lte=1"` + VideoActivated uint8 `json:"video_activated" validate:"required,gte=0,lte=1"` + UserID uint `json:"user_id" validate:"required"` +} + +func (c *CreateVehicleStateValidator) Validate() error { + validate := validator.New() + + return validate.Struct(c) +} diff --git a/src/validators/vehicleState/updateVehicleStateValidator.go b/src/validators/vehicleState/updateVehicleStateValidator.go new file mode 100644 index 0000000..58601b3 --- /dev/null +++ b/src/validators/vehicleState/updateVehicleStateValidator.go @@ -0,0 +1,44 @@ +package validators + +import "github.com/go-playground/validator/v10" + +type PrimaryLedColorValidator struct { + LedIdentifier *int `json:"led_identifier" validate:"required,gte=0"` + Red *uint8 `json:"red" validate:"required,min=0,max=255"` + Green *uint8 `json:"green" validate:"required,min=0,max=255"` + Blue *uint8 `json:"blue" validate:"required,min=0,max=255"` +} + +type SecondaryLedColorValidator struct { + BinaryRepresentation *int `json:"binary_representation" validate:"required,gte=0"` + Red *uint8 `json:"red" validate:"required,min=0,max=255"` + Green *uint8 `json:"green" validate:"required,min=0,max=255"` + Blue *uint8 `json:"blue" validate:"required,min=0,max=255"` +} + +type BuzzerVariableValidator struct { + Activated *uint8 `json:"activated" validate:"required,oneof=0 1"` + Frequency *uint16 `json:"frequency" validate:"required,min=0,max=10000"` +} + +type HeadAngleValidator struct { + VerticalAngle *uint `json:"vertical_angle" validate:"required,min=0,max=180"` + HorizontalAngle *uint `json:"horizontal_angle" validate:"required,min=0,max=180"` +} + +type UpdateVehicleStateValidator struct { + Face *uint8 `json:"face" validate:"required,gte=0"` + LedAnimation *uint8 `json:"led_animation" validate:"required,gte=0,lte=5"` + BuzzerAlarm *uint8 `json:"buzzer_alarm" validate:"required,oneof=0 1"` + VideoActivated *uint8 `json:"video_activated" validate:"required,oneof=0 1"` + PrimaryLedColor *PrimaryLedColorValidator `json:"PrimaryLedColor" validate:"required"` + SecondaryLedColor *SecondaryLedColorValidator `json:"SecondaryLedColor" validate:"required"` + BuzzerVariable *BuzzerVariableValidator `json:"BuzzerVariable" validate:"required"` + HeadAngle *HeadAngleValidator `json:"HeadAngle" validate:"required"` +} + +func (u *UpdateVehicleStateValidator) Validate() error { + validate := validator.New() + + return validate.Struct(u) +} From 1cb622dbf4fe996bad09b0c22f6fa501850c1763 Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Wed, 21 Aug 2024 11:24:26 +0200 Subject: [PATCH 15/42] Refacto vehicle table (#21) * refacto: vehicle model * add: get and index vehicle routes * fix: error create vehicle when there is already the same one in db --- .env.example | 4 + main.go | 40 ++++++ src/controllers/vehicle/getVehicleHandler.go | 31 +++++ .../vehicle/indexVehicleHandler.go | 19 +++ src/controllers/vehicleController.go | 124 ------------------ src/models/{vehicleModel.go => vehicle.go} | 20 +-- src/routes/protected/setupProtectedRoutes.go | 19 ++- src/tests/setup_test_helpers.go | 19 +-- src/tests/vehicle/can_delete_vehicle_test.go | 29 ---- src/tests/vehicle/can_get_vehicle_test.go | 29 ---- src/tests/vehicle/can_get_vehicles_test.go | 28 ---- src/tests/vehicle/can_update_vehicle_test.go | 35 ----- src/validators/vehicle/createVehicle.go | 28 ---- 13 files changed, 110 insertions(+), 315 deletions(-) create mode 100644 src/controllers/vehicle/getVehicleHandler.go create mode 100644 src/controllers/vehicle/indexVehicleHandler.go delete mode 100644 src/controllers/vehicleController.go rename src/models/{vehicleModel.go => vehicle.go} (72%) delete mode 100644 src/tests/vehicle/can_delete_vehicle_test.go delete mode 100644 src/tests/vehicle/can_get_vehicle_test.go delete mode 100644 src/tests/vehicle/can_get_vehicles_test.go delete mode 100644 src/tests/vehicle/can_update_vehicle_test.go delete mode 100644 src/validators/vehicle/createVehicle.go diff --git a/.env.example b/.env.example index bf52b32..51d6d47 100644 --- a/.env.example +++ b/.env.example @@ -6,3 +6,7 @@ DB_NAME= APP_FRONTEND_URL="http://localhost:5173" JWT_SECRET_KEY="example_key" + +VEHICLE_1_NAME="Freenove 1" +VEHICLE_1_IPADDRESS="0.0.0.0" +VEHICLE_1_IS_AVAILABLE=true diff --git a/main.go b/main.go index 129aa6d..1c55b73 100644 --- a/main.go +++ b/main.go @@ -6,8 +6,12 @@ import ( "api/src/services" "fmt" "log" + "os" + "strconv" + "strings" "github.com/gin-gonic/gin" + "github.com/joho/godotenv" "gorm.io/gorm" ) @@ -20,6 +24,8 @@ func main() { performMigrations(database) + initVehicleData(database) + initMQTT() startWebServer() @@ -71,6 +77,40 @@ func performMigrations(database *gorm.DB) { fmt.Println("Database migrations completed.") } +func initVehicleData(database *gorm.DB) { + err := godotenv.Load() + + if err != nil { + log.Fatal("Error loading .env file in initVehicleData: ", err) + } + + for i := 1; ; i++ { + nameKey := fmt.Sprintf("VEHICLE_%d_NAME", i) + ipAddressKey := fmt.Sprintf("VEHICLE_%d_IPADDRESS", i) + availableKey := fmt.Sprintf("VEHICLE_%d_IS_AVAILABLE", i) + + name := os.Getenv(nameKey) + ip := os.Getenv(ipAddressKey) + isAvailableStr := os.Getenv(availableKey) + + if name == "" || ip == "" || isAvailableStr == "" { + break + } + + isAvailable, err := strconv.ParseBool(strings.TrimSpace(isAvailableStr)) + + if err != nil { + log.Fatal("Error parsing isAvailable variable to boolean in initVehicleData: ", err) + } + + database.FirstOrCreate(&models.Vehicle{ + Name: name, + IpAdress: ip, + IsAvailable: isAvailable, + }) + } +} + func initMQTT() { fmt.Println("Starting mqtt connection...") diff --git a/src/controllers/vehicle/getVehicleHandler.go b/src/controllers/vehicle/getVehicleHandler.go new file mode 100644 index 0000000..902c045 --- /dev/null +++ b/src/controllers/vehicle/getVehicleHandler.go @@ -0,0 +1,31 @@ +package vehicle + +import ( + "api/src/models" + "api/src/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm/clause" +) + +func GetVehicleHandler(c *gin.Context) { + vehicleId := c.Param("id") + + if vehicleId == "" || vehicleId == "0" || vehicleId == ":id" { + services.SetUnprocessableEntity(c, "Vehicle id is required") + + return + } + + connection := services.GetConnection() + + var vehicle models.Vehicle + + if connection.Where("id = ?", vehicleId).Preload(clause.Associations).First(&vehicle).RowsAffected == 0 { + services.SetUnprocessableEntity(c, "Vehicle not found") + + return + } + + services.SetOK(c, "Vehicle successfully retrieved", vehicle) +} diff --git a/src/controllers/vehicle/indexVehicleHandler.go b/src/controllers/vehicle/indexVehicleHandler.go new file mode 100644 index 0000000..5225561 --- /dev/null +++ b/src/controllers/vehicle/indexVehicleHandler.go @@ -0,0 +1,19 @@ +package vehicle + +import ( + "api/src/models" + "api/src/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm/clause" +) + +func IndexVehicleHandler(c *gin.Context) { + connection := services.GetConnection() + + var vehicles []models.Vehicle + + connection.Preload(clause.Associations).Find(&vehicles) + + services.SetOK(c, "Vehicles successfully retrieved", vehicles) +} diff --git a/src/controllers/vehicleController.go b/src/controllers/vehicleController.go deleted file mode 100644 index f5f3338..0000000 --- a/src/controllers/vehicleController.go +++ /dev/null @@ -1,124 +0,0 @@ -package controllers - -import ( - "api/src/models" - "api/src/services" - "fmt" - "net/http" - "strconv" - - "github.com/gin-gonic/gin" -) - -// Helper function to transform vehicle model to API response format. -func transformVehicleToResponse(vehicle models.Vehicle) gin.H { - return gin.H{ - "data": gin.H{ - "type": "vehicule", - "id": strconv.Itoa(int(vehicle.ID)), - "attributes": vehicleToAttributes(vehicle), - }, - } -} - -// Helper function to map vehicle fields to response attributes. -func vehicleToAttributes(vehicle models.Vehicle) gin.H { - return gin.H{ - "battery_life": vehicle.BatteryLife, - "vehicle_name": vehicle.Name, - "line_sensors": []bool{vehicle.LineSensor1, vehicle.LineSensor2, vehicle.LineSensor3}, // Assume 3 line sensors - "camera": vehicle.Camera, - "sonar_range": vehicle.SonarRange, - "wheel_power": []int{vehicle.WheelPower1, vehicle.WheelPower2, vehicle.WheelPower3, vehicle.WheelPower4}, // 4 wheel powers - "led_color": vehicle.LedColor, - "display_panel": vehicle.DisplayPanel, - "speaker_status": vehicle.SpeakerStatus, - "sound_playing": vehicle.SoundPlaying, - } -} - -func GetVehicle(c *gin.Context) { - id := c.Param("id") - var vehicle models.Vehicle - result := services.GetConnection().First(&vehicle, id) - if result.Error != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Vehicle not found"}) - return - } - - c.JSON(http.StatusOK, transformVehicleToResponse(vehicle)) -} - -func GetVehicles(c *gin.Context) { - fmt.Println("GetVehicles") - var vehicles []models.Vehicle - result := services.GetConnection().Find(&vehicles) - if result.Error != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) - return - } - - var vehiclesResponse []gin.H - for _, vehicle := range vehicles { - vehiclesResponse = append(vehiclesResponse, transformVehicleToResponse(vehicle)) - } - - c.JSON(http.StatusOK, gin.H{"data": vehiclesResponse}) -} - -func CreateVehicle(c *gin.Context) { - var vehicle models.Vehicle - if err := c.BindJSON(&vehicle); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) - return - } - - result := services.GetConnection().Create(&vehicle) - if result.Error != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) - return - } - - c.JSON(http.StatusCreated, transformVehicleToResponse(vehicle)) -} - -func UpdateVehicle(c *gin.Context) { - id := c.Param("id") - var vehicle models.Vehicle - result := services.GetConnection().First(&vehicle, id) - if result.Error != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Vehicle not found"}) - return - } - - if err := c.BindJSON(&vehicle); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) - return - } - - result = services.GetConnection().Save(&vehicle) - if result.Error != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) - return - } - - c.JSON(http.StatusOK, transformVehicleToResponse(vehicle)) -} - -func DeleteVehicle(c *gin.Context) { - id := c.Param("id") - var vehicle models.Vehicle - result := services.GetConnection().First(&vehicle, id) - if result.Error != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "Vehicle not found"}) - return - } - - result = services.GetConnection().Delete(&vehicle) - if result.Error != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) - return - } - - c.JSON(http.StatusOK, gin.H{"meta": gin.H{"message": "Vehicule deleted successfully"}}) -} diff --git a/src/models/vehicleModel.go b/src/models/vehicle.go similarity index 72% rename from src/models/vehicleModel.go rename to src/models/vehicle.go index 34108a2..ba8f736 100644 --- a/src/models/vehicleModel.go +++ b/src/models/vehicle.go @@ -3,22 +3,10 @@ package models import "gorm.io/gorm" type Vehicle struct { - ID uint `gorm:"primaryKey"` - Name string `json:"vehicle_name"` - BatteryLife float64 `json:"battery_life"` - LineSensor1 bool `json:"line_sensor1"` - LineSensor2 bool `json:"line_sensor2"` - LineSensor3 bool `json:"line_sensor3"` - Camera bool `json:"camera"` - SonarRange float64 `json:"sonar_range"` - WheelPower1 int `json:"wheel_power1"` - WheelPower2 int `json:"wheel_power2"` - WheelPower3 int `json:"wheel_power3"` - WheelPower4 int `json:"wheel_power4"` - LedColor string `json:"led_color"` - DisplayPanel string `json:"display_panel"` - SpeakerStatus bool `json:"speaker_status"` - SoundPlaying string `json:"sound_playing"` + ID uint `gorm:"primaryKey"` + Name string `json:"vehicle_name"` + IpAdress string `json:"ip_adress"` + IsAvailable bool `json:"is_available"` Model } diff --git a/src/routes/protected/setupProtectedRoutes.go b/src/routes/protected/setupProtectedRoutes.go index 8ff1569..613e9f9 100644 --- a/src/routes/protected/setupProtectedRoutes.go +++ b/src/routes/protected/setupProtectedRoutes.go @@ -3,9 +3,9 @@ package protected import ( controllers "api/src/controllers" race_controller "api/src/controllers/race" + "api/src/controllers/vehicle" "api/src/controllers/vehicle_state" - //user_controller "api/src/controllers/user" "api/src/middleware" "github.com/gin-gonic/gin" @@ -16,16 +16,6 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/protected", controllers.Welcome) - // VEHICLE ROUTES - vehicleGroup := routerGroup.Group("/vehicles") - { - vehicleGroup.GET("/:id", controllers.GetVehicle) - vehicleGroup.GET("/", controllers.GetVehicles) - vehicleGroup.POST("/", controllers.CreateVehicle) - vehicleGroup.PATCH("/:id", controllers.UpdateVehicle) - vehicleGroup.DELETE("/:id", controllers.DeleteVehicle) - } - /*userGroup := routerGroup.Group("/user") // USER ROUTES { @@ -45,6 +35,13 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { raceGroup.DELETE("/:raceId", race_controller.DeleteRaceHandler) } + // VEHICLE ROUTES + vehicleGroup := routerGroup.Group("/vehicles") + { + vehicleGroup.GET("/", vehicle.IndexVehicleHandler) + vehicleGroup.GET("/:id", vehicle.GetVehicleHandler) + } + // VEHICLE STATE ROUTES vehicleStateGroup := routerGroup.Group("/vehicle-states") { diff --git a/src/tests/setup_test_helpers.go b/src/tests/setup_test_helpers.go index 798e333..dfe3f9e 100644 --- a/src/tests/setup_test_helpers.go +++ b/src/tests/setup_test_helpers.go @@ -8,24 +8,13 @@ import ( func SetupTestVehicle(db *gorm.DB) *models.Vehicle { vehicle := &models.Vehicle{ - Name: "Test Vehicle", - BatteryLife: 100.0, - LineSensor1: true, - LineSensor2: false, - LineSensor3: true, - Camera: true, - SonarRange: 50.0, - WheelPower1: 90, - WheelPower2: 80, - WheelPower3: 70, - WheelPower4: 60, - LedColor: "red", - DisplayPanel: "LCD", - SpeakerStatus: true, - SoundPlaying: "test sound", + Name: "Test Vehicle", + IpAdress: "1.1.1.1", + IsAvailable: true, } err := db.Create(&vehicle).Error + if err != nil { panic(err) } diff --git a/src/tests/vehicle/can_delete_vehicle_test.go b/src/tests/vehicle/can_delete_vehicle_test.go deleted file mode 100644 index e21c8c2..0000000 --- a/src/tests/vehicle/can_delete_vehicle_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package tests - -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "net/http" - "strconv" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestCanDeleteVehicle(t *testing.T) { - gin.SetMode(gin.TestMode) - - db := tests.GetTestDBConnection() - db.AutoMigrate(&models.Vehicle{}) - vehicle := tests.SetupTestVehicle(db) - - recorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) - - assert.Equal(t, http.StatusOK, recorder.Code) - - var response map[string]interface{} - json.Unmarshal(recorder.Body.Bytes(), &response) - assert.Equal(t, "Vehicule deleted successfully", response["meta"].(map[string]interface{})["message"]) -} diff --git a/src/tests/vehicle/can_get_vehicle_test.go b/src/tests/vehicle/can_get_vehicle_test.go deleted file mode 100644 index b3d8d37..0000000 --- a/src/tests/vehicle/can_get_vehicle_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package tests - -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "net/http" - "strconv" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestCanGetVehicle(t *testing.T) { - gin.SetMode(gin.TestMode) - - db := tests.GetTestDBConnection() - db.AutoMigrate(&models.Vehicle{}) - vehicle := tests.SetupTestVehicle(db) - - recorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), nil) - - assert.Equal(t, http.StatusOK, recorder.Code) - - var response map[string]interface{} - json.Unmarshal(recorder.Body.Bytes(), &response) - assert.Equal(t, "Test Vehicle", response["data"].(map[string]interface{})["attributes"].(map[string]interface{})["vehicle_name"]) -} diff --git a/src/tests/vehicle/can_get_vehicles_test.go b/src/tests/vehicle/can_get_vehicles_test.go deleted file mode 100644 index 4367579..0000000 --- a/src/tests/vehicle/can_get_vehicles_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package tests - -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "net/http" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestCanGetVehicles(t *testing.T) { - gin.SetMode(gin.TestMode) - - db := tests.GetTestDBConnection() - db.AutoMigrate(&models.Vehicle{}) - tests.SetupTestVehicle(db) - - recorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/vehicles/", nil) - - assert.Equal(t, http.StatusOK, recorder.Code) - - var response map[string]interface{} - json.Unmarshal(recorder.Body.Bytes(), &response) - assert.Greater(t, len(response["data"].([]interface{})), 0) -} diff --git a/src/tests/vehicle/can_update_vehicle_test.go b/src/tests/vehicle/can_update_vehicle_test.go deleted file mode 100644 index b4b03a5..0000000 --- a/src/tests/vehicle/can_update_vehicle_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package tests - -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "net/http" - "strconv" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestCanUpdateVehicle(t *testing.T) { - gin.SetMode(gin.TestMode) - - db := tests.GetTestDBConnection() - db.AutoMigrate(&models.Vehicle{}) - vehicle := tests.SetupTestVehicle(db) - - updatedData := map[string]interface{}{ - "vehicle_name": "Updated Vehicle", - "battery_life": 95.0, - } - body, _ := json.Marshal(updatedData) - - recorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, "/api/vehicles/"+strconv.Itoa(int(vehicle.ID)), body) - - assert.Equal(t, http.StatusOK, recorder.Code) - - var response map[string]interface{} - json.Unmarshal(recorder.Body.Bytes(), &response) - assert.Equal(t, "Updated Vehicle", response["data"].(map[string]interface{})["attributes"].(map[string]interface{})["vehicle_name"]) -} diff --git a/src/validators/vehicle/createVehicle.go b/src/validators/vehicle/createVehicle.go deleted file mode 100644 index bada778..0000000 --- a/src/validators/vehicle/createVehicle.go +++ /dev/null @@ -1,28 +0,0 @@ -package validators - -import ( - "github.com/go-playground/validator/v10" -) - -type CreateVehicleValidator struct { - Name string `json:"vehicle_name" validate:"required"` - BatteryLife float64 `json:"battery_life" validate:"required,gte=0"` - LineSensor1 bool `json:"line_sensor1"` - LineSensor2 bool `json:"line_sensor2"` - LineSensor3 bool `json:"line_sensor3"` - Camera bool `json:"camera"` - SonarRange float64 `json:"sonar_range" validate:"required,gte=0"` - WheelPower1 int `json:"wheel_power1"` - WheelPower2 int `json:"wheel_power2"` - WheelPower3 int `json:"wheel_power3"` - WheelPower4 int `json:"wheel_power4"` - LedColor string `json:"led_color"` - DisplayPanel string `json:"display_panel"` - SpeakerStatus bool `json:"speaker_status"` - SoundPlaying string `json:"sound_playing"` -} - -func (c *CreateVehicleValidator) Validate() error { - validate := validator.New() - return validate.Struct(c) -} From 76ee6177a83905bc7a134c4203685a8e83e566fd Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Wed, 21 Aug 2024 12:32:42 +0200 Subject: [PATCH 16/42] Refacto race model (#22) * refacto model race * update race controller * update route race controller * create race feature * - rename fields - add json formating to response race model - other minor changes * lint * remove: unused route --------- Co-authored-by: Fabrice Pivert --- src/controllers/race/createRaceHandler.go | 40 +++++-- src/controllers/race/deleteRaceHandler.go | 10 +- src/controllers/race/getAllRaceHandler.go | 2 +- .../race/getAllUsersRaceHandler.go | 34 ++++++ src/controllers/race/getSingleRaceHandler.go | 25 ---- src/controllers/race/updateRaceHandler.go | 28 ++--- src/models/race.go | 18 +++ src/models/raceModel.go | 53 --------- src/routes/protected/setupProtectedRoutes.go | 13 +-- src/tests/race/delete_race_test.go | 109 ++++++++---------- src/tests/race/get_single_race_test.go | 90 +++++++-------- src/tests/race/update_race_test.go | 102 ++++++++-------- src/validators/race/createRace.go | 40 +++++-- 13 files changed, 270 insertions(+), 294 deletions(-) create mode 100644 src/controllers/race/getAllUsersRaceHandler.go delete mode 100644 src/controllers/race/getSingleRaceHandler.go create mode 100644 src/models/race.go delete mode 100644 src/models/raceModel.go diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index 0ec83cc..3edb23d 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -5,42 +5,62 @@ import ( "api/src/services" validators "api/src/validators/race" "fmt" + "strconv" "github.com/gin-gonic/gin" ) func CreateRaceHandler(c *gin.Context) { - var createRaceValidator validators.CreateRaceValidator + // Récupération de l'ID utilisateur depuis les paramètres de l'URL + userIdStr := c.Param("userId") + if userIdStr == "" || userIdStr == "0" || userIdStr == ":userId" { + services.SetUnprocessableEntity(c, "User not found") + return + } + // Conversion du userId de string à uint + userId, err := strconv.ParseUint(userIdStr, 10, 32) + if err != nil { + services.SetUnprocessableEntity(c, "Invalid user ID") + return + } + + var createRaceValidator validators.CreateRaceValidator + + // Validation de la requête JSON if err := c.ShouldBindJSON(&createRaceValidator); err != nil { services.SetJsonBindingErrorResponse(c, err) return } + // Validation des données via le validateur if err := createRaceValidator.Validate(); err != nil { services.SetValidationErrorResponse(c, err) return } + // Création du modèle Race avec les données validées race := models.Race{ - Duration: createRaceValidator.Duration, - ElapsedTime: createRaceValidator.ElapsedTime, - Laps: createRaceValidator.Laps, - RaceType: createRaceValidator.RaceType, - AverageSpeed: createRaceValidator.AverageSpeed, - TotalFaults: createRaceValidator.TotalFaults, - EffectiveDuration: createRaceValidator.EffectiveDuration, - UserID: createRaceValidator.UserID, - VehicleID: createRaceValidator.VehicleID, + StartTime: createRaceValidator.StartTime, + EndTime: createRaceValidator.EndTime, + NumberOfCollisions: createRaceValidator.NumberOfCollisions, + DistanceTravelled: createRaceValidator.DistanceTravelled, + AverageSpeed: createRaceValidator.AverageSpeed, + OutOfParcours: createRaceValidator.OutOfParcours, + VehicleID: createRaceValidator.VehicleID, + UserID: uint(userId), // Conversion de uint64 à uint } + // Récupération de la connexion à la base de données db := services.GetConnection() + // Création de l'enregistrement dans la base de données if err := db.Create(&race).Error; err != nil { fmt.Printf("Error creating Race: %v\n", err) services.SetInternalServerError(c, "Failed to create Race") return } + // Réponse de succès avec l'objet Race créé services.SetCreated(c, "Race created successfully", race) } diff --git a/src/controllers/race/deleteRaceHandler.go b/src/controllers/race/deleteRaceHandler.go index fc1e434..b29df3f 100644 --- a/src/controllers/race/deleteRaceHandler.go +++ b/src/controllers/race/deleteRaceHandler.go @@ -15,16 +15,14 @@ func DeleteRaceHandler(c *gin.Context) { var existingRace models.Race - query := db.Where("id", raceID).Find(&existingRace) - - if query.RowsAffected == 0 { + // Recherche de la course par ID + if err := db.Where("id = ?", raceID).First(&existingRace).Error; err != nil { services.SetNotFound(c, "Race not found") return } - query = db.Where("id", raceID).Delete(&existingRace) - - if query.Error != nil { + // Suppression de la course + if err := db.Delete(&existingRace).Error; err != nil { services.SetInternalServerError(c, "Internal server error") return } diff --git a/src/controllers/race/getAllRaceHandler.go b/src/controllers/race/getAllRaceHandler.go index ae31c51..01716e4 100644 --- a/src/controllers/race/getAllRaceHandler.go +++ b/src/controllers/race/getAllRaceHandler.go @@ -12,7 +12,7 @@ func GetAllRaceHandler(c *gin.Context) { var races []models.Race - if err := db.Preload("Vehicle").Find(&races).Error; err != nil { + if err := db.Find(&races).Error; err != nil { services.SetInternalServerError(c, "Failed to retrieve races") return } diff --git a/src/controllers/race/getAllUsersRaceHandler.go b/src/controllers/race/getAllUsersRaceHandler.go new file mode 100644 index 0000000..8dde30b --- /dev/null +++ b/src/controllers/race/getAllUsersRaceHandler.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "api/src/models" + "api/src/services" + "strconv" + + "github.com/gin-gonic/gin" +) + +func GetAllUsersRaceHandler(c *gin.Context) { + db := services.GetConnection() + + var races []models.Race + + userIdStr := c.Param("userId") + + // Convertir userId de string à uint + userId, err := strconv.ParseUint(userIdStr, 10, 32) + if err != nil { + services.SetUnprocessableEntity(c, "Invalid user ID") + return + } + + // Requête pour trouver toutes les courses pour un utilisateur donné + query := db.Where("user_id = ?", uint(userId)).Find(&races) + + if query.RowsAffected == 0 { + services.SetNotFound(c, "No races found for this user") + return + } + + services.SetOK(c, "User races retrieved successfully", races) +} diff --git a/src/controllers/race/getSingleRaceHandler.go b/src/controllers/race/getSingleRaceHandler.go deleted file mode 100644 index 2f70f77..0000000 --- a/src/controllers/race/getSingleRaceHandler.go +++ /dev/null @@ -1,25 +0,0 @@ -package handlers - -import ( - "api/src/models" - "api/src/services" - - "github.com/gin-gonic/gin" -) - -func GetSingleRaceHandler(c *gin.Context) { - db := services.GetConnection() - - var race models.Race - - raceId := c.Param("raceId") - - query := db.Where("id", raceId).Find(&race) - - if query.RowsAffected == 0 { - services.SetNotFound(c, "Race not found") - return - } - - services.SetOK(c, "Race retrieved successfully", race) -} diff --git a/src/controllers/race/updateRaceHandler.go b/src/controllers/race/updateRaceHandler.go index cb6644c..04a0ff5 100644 --- a/src/controllers/race/updateRaceHandler.go +++ b/src/controllers/race/updateRaceHandler.go @@ -1,11 +1,10 @@ package handlers import ( - "fmt" - "api/src/models" "api/src/services" validators "api/src/validators/race" + "fmt" "github.com/gin-gonic/gin" ) @@ -15,14 +14,16 @@ func UpdateRaceHandler(c *gin.Context) { db := services.GetConnection() + // Récupère la course existante var existingRace models.Race - if err := db.Preload("Vehicle").First(&existingRace, raceID).Error; err != nil { + if err := db.First(&existingRace, raceID).Error; err != nil { services.SetNotFound(c, "Race not found") return } - var raceValidator validators.CreateRaceValidator + var raceValidator validators.UpdateRaceValidator + // Valide les données JSON if err := c.ShouldBindJSON(&raceValidator); err != nil { services.SetJsonBindingErrorResponse(c, err) return @@ -33,21 +34,22 @@ func UpdateRaceHandler(c *gin.Context) { return } - existingRace.Duration = raceValidator.Duration - existingRace.ElapsedTime = raceValidator.ElapsedTime - existingRace.Laps = raceValidator.Laps - existingRace.RaceType = raceValidator.RaceType - existingRace.AverageSpeed = raceValidator.AverageSpeed - existingRace.TotalFaults = raceValidator.TotalFaults - existingRace.EffectiveDuration = raceValidator.EffectiveDuration - existingRace.UserID = raceValidator.UserID - existingRace.VehicleID = raceValidator.VehicleID + // Mise à jour uniquement du champ End_time + if raceValidator.EndTime != nil && !raceValidator.EndTime.IsZero() { + if raceValidator.EndTime.Before(existingRace.StartTime) { + services.SetUnprocessableEntity(c, "EndTime cannot be before StartTime") + return + } + existingRace.EndTime = raceValidator.EndTime + } + // Sauvegarde les modifications dans la base de données if err := db.Save(&existingRace).Error; err != nil { fmt.Printf("Error updating Race: %v\n", err) services.SetInternalServerError(c, "Failed to update Race") return } + // Réponse de succès avec l'objet Race mis à jour services.SetOK(c, "Race updated successfully", existingRace) } diff --git a/src/models/race.go b/src/models/race.go new file mode 100644 index 0000000..29fe245 --- /dev/null +++ b/src/models/race.go @@ -0,0 +1,18 @@ +package models + +import ( + "time" +) + +type Race struct { + ID uint `gorm:"primaryKey"` + VehicleID uint `json:"vehicle_id"` + StartTime time.Time `json:"start_time"` + EndTime *time.Time `json:"end_time"` + NumberOfCollisions uint8 `json:"number_of_collisions"` + DistanceTravelled int `json:"distance_travelled"` + AverageSpeed int `json:"average_speed"` + OutOfParcours uint8 `json:"out_of_parcours"` + UserID uint `json:"user_id"` + Model +} diff --git a/src/models/raceModel.go b/src/models/raceModel.go deleted file mode 100644 index 558bf2c..0000000 --- a/src/models/raceModel.go +++ /dev/null @@ -1,53 +0,0 @@ -package models - -import ( - validators "api/src/validators/race" -) - -type Race struct { - ID uint `gorm:"primaryKey"` - Duration int - ElapsedTime int - Laps int - RaceType string - AverageSpeed int - TotalFaults int - EffectiveDuration int - UserID uint - VehicleID uint - Vehicle Vehicle `gorm:"foreignKey:VehicleID"` - Fool []Fool `gorm:"many2many:race_fool;" json:"fool"` - Model -} - -func (r *Race) Create(createRace validators.CreateRaceValidator) error { - if err := createRace.Validate(); err != nil { - return err - } - r.Duration = createRace.Duration - r.ElapsedTime = createRace.ElapsedTime - r.Laps = createRace.Laps - r.RaceType = createRace.RaceType - r.AverageSpeed = createRace.AverageSpeed - r.TotalFaults = createRace.TotalFaults - r.EffectiveDuration = createRace.EffectiveDuration - r.UserID = createRace.UserID - r.VehicleID = createRace.VehicleID - return nil -} - -func (r *Race) Update(updateRace validators.CreateRaceValidator) error { - if err := updateRace.Validate(); err != nil { - return err - } - r.Duration = updateRace.Duration - r.ElapsedTime = updateRace.ElapsedTime - r.Laps = updateRace.Laps - r.RaceType = updateRace.RaceType - r.AverageSpeed = updateRace.AverageSpeed - r.TotalFaults = updateRace.TotalFaults - r.EffectiveDuration = updateRace.EffectiveDuration - r.UserID = updateRace.UserID - r.VehicleID = updateRace.VehicleID - return nil -} diff --git a/src/routes/protected/setupProtectedRoutes.go b/src/routes/protected/setupProtectedRoutes.go index 613e9f9..915ff10 100644 --- a/src/routes/protected/setupProtectedRoutes.go +++ b/src/routes/protected/setupProtectedRoutes.go @@ -16,21 +16,18 @@ func SetupProtectedRoutes(routerGroup *gin.RouterGroup) { routerGroup.GET("/protected", controllers.Welcome) - /*userGroup := routerGroup.Group("/user") + userGroup := routerGroup.Group("/users") // USER ROUTES { - userGroup.GET("/:userId", user_controller.UserHandler) - userGroup.PUT("/:userId", user_controller.UpdateUserHandler) - userGroup.GET("/", user_controller.GetAllUserHandler) - userGroup.DELETE("/:userId", user_controller.DeleteUserHandler) - }*/ + userGroup.POST("/:userId/races", race_controller.CreateRaceHandler) + userGroup.GET("/:userId/races", race_controller.GetAllUsersRaceHandler) + + } // RACE ROUTES raceGroup := routerGroup.Group("/races") { raceGroup.GET("/", race_controller.GetAllRaceHandler) - raceGroup.GET("/:raceId", race_controller.GetSingleRaceHandler) - raceGroup.POST("/", race_controller.CreateRaceHandler) raceGroup.PATCH("/:raceId", race_controller.UpdateRaceHandler) raceGroup.DELETE("/:raceId", race_controller.DeleteRaceHandler) } diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go index 8b58283..9901a34 100644 --- a/src/tests/race/delete_race_test.go +++ b/src/tests/race/delete_race_test.go @@ -1,62 +1,51 @@ package race -import ( - "api/src/models" - "api/src/tests" - "fmt" - "net/http" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func Test_delete_race_successfully(t *testing.T) { - gin.SetMode(gin.TestMode) - - databaseConnection := tests.GetTestDBConnection() - - databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) - - vehicle := models.Vehicle{ - Name: "Toyota", - } - databaseConnection.Create(&vehicle) - - race := models.Race{ - Duration: 100, - ElapsedTime: 90, - Laps: 3, - RaceType: "VS", - AverageSpeed: 120, - TotalFaults: 1, - EffectiveDuration: 85, - UserID: 1, - VehicleID: vehicle.ID, - } - databaseConnection.Create(&race) - - requestURL := fmt.Sprintf("/api/races/%d", race.ID) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) - - assert.Equal(t, http.StatusNoContent, requestRecorder.Code) -} - -func Test_delete_race_not_found(t *testing.T) { - gin.SetMode(gin.TestMode) - - databaseConnection := tests.GetTestDBConnection() - - databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) - - vehicle := models.Vehicle{ - Name: "Toyota", - } - databaseConnection.Create(&vehicle) - - nonExistentRaceID := 999 - requestURL := fmt.Sprintf("/api/races/%d", nonExistentRaceID) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) - - assert.Equal(t, http.StatusNotFound, requestRecorder.Code) -} +// func Test_delete_race_successfully(t *testing.T) { +// gin.SetMode(gin.TestMode) + +// databaseConnection := tests.GetTestDBConnection() + +// databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + +// vehicle := models.Vehicle{ +// Name: "Toyota", +// } +// databaseConnection.Create(&vehicle) + +// race := models.Race{ +// Duration: 100, +// ElapsedTime: 90, +// Laps: 3, +// RaceType: "VS", +// AverageSpeed: 120, +// TotalFaults: 1, +// EffectiveDuration: 85, +// UserID: 1, +// VehicleID: vehicle.ID, +// } +// databaseConnection.Create(&race) + +// requestURL := fmt.Sprintf("/api/races/%d", race.ID) +// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) + +// assert.Equal(t, http.StatusNoContent, requestRecorder.Code) +// } + +// func Test_delete_race_not_found(t *testing.T) { +// gin.SetMode(gin.TestMode) + +// databaseConnection := tests.GetTestDBConnection() + +// databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + +// vehicle := models.Vehicle{ +// Name: "Toyota", +// } +// databaseConnection.Create(&vehicle) + +// nonExistentRaceID := 999 +// requestURL := fmt.Sprintf("/api/races/%d", nonExistentRaceID) +// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) + +// assert.Equal(t, http.StatusNotFound, requestRecorder.Code) +// } diff --git a/src/tests/race/get_single_race_test.go b/src/tests/race/get_single_race_test.go index 2c1f0e9..2cbceee 100644 --- a/src/tests/race/get_single_race_test.go +++ b/src/tests/race/get_single_race_test.go @@ -1,67 +1,55 @@ package race -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "fmt" - "net/http" - "testing" +// func Test_get_single_race_successfully(t *testing.T) { +// gin.SetMode(gin.TestMode) - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) +// databaseConnection := tests.GetTestDBConnection() -func Test_get_single_race_successfully(t *testing.T) { - gin.SetMode(gin.TestMode) +// databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) - databaseConnection := tests.GetTestDBConnection() +// vehicle := models.Vehicle{ +// Name: "Toyota", +// } +// databaseConnection.Create(&vehicle) - databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) +// race := models.Race{ +// Duration: 100, +// ElapsedTime: 90, +// Laps: 3, +// RaceType: "VS", +// AverageSpeed: 120, +// TotalFaults: 1, +// EffectiveDuration: 85, +// UserID: 1, +// VehicleID: vehicle.ID, +// } +// databaseConnection.Create(&race) - vehicle := models.Vehicle{ - Name: "Toyota", - } - databaseConnection.Create(&vehicle) +// requestURL := fmt.Sprintf("/api/races/%d", race.ID) +// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) - race := models.Race{ - Duration: 100, - ElapsedTime: 90, - Laps: 3, - RaceType: "VS", - AverageSpeed: 120, - TotalFaults: 1, - EffectiveDuration: 85, - UserID: 1, - VehicleID: vehicle.ID, - } - databaseConnection.Create(&race) +// assert.Equal(t, http.StatusOK, requestRecorder.Code) - requestURL := fmt.Sprintf("/api/races/%d", race.ID) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) +// var response map[string]interface{} +// err := json.Unmarshal(requestRecorder.Body.Bytes(), &response) +// assert.NoError(t, err) - assert.Equal(t, http.StatusOK, requestRecorder.Code) +// data, ok := response["data"].(map[string]interface{}) +// assert.True(t, ok) +// assert.Equal(t, float64(race.ID), data["ID"]) +// } - var response map[string]interface{} - err := json.Unmarshal(requestRecorder.Body.Bytes(), &response) - assert.NoError(t, err) +// func Test_get_single_race_not_found(t *testing.T) { +// gin.SetMode(gin.TestMode) - data, ok := response["data"].(map[string]interface{}) - assert.True(t, ok) - assert.Equal(t, float64(race.ID), data["ID"]) -} +// databaseConnection := tests.GetTestDBConnection() -func Test_get_single_race_not_found(t *testing.T) { - gin.SetMode(gin.TestMode) +// databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) - databaseConnection := tests.GetTestDBConnection() +// invalidRaceID := 999999 - databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) +// requestURL := fmt.Sprintf("/api/races/%d", invalidRaceID) +// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) - invalidRaceID := 999999 - - requestURL := fmt.Sprintf("/api/races/%d", invalidRaceID) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) - - assert.Equal(t, http.StatusNotFound, requestRecorder.Code) -} +// assert.Equal(t, http.StatusNotFound, requestRecorder.Code) +// } diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index b1d6bea..40c9c97 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -1,59 +1,47 @@ package race -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func Test_update_race_successfully(t *testing.T) { - gin.SetMode(gin.TestMode) - - databaseConnection := tests.GetTestDBConnection() - - databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) - - vehicle := models.Vehicle{ - Name: "Toyota", - } - databaseConnection.Create(&vehicle) - - race := models.Race{ - Duration: 100, - ElapsedTime: 90, - Laps: 3, - RaceType: "VS", - AverageSpeed: 120, - TotalFaults: 1, - EffectiveDuration: 85, - UserID: 1, - VehicleID: vehicle.ID, - } - databaseConnection.Create(&race) - - body, _ := json.Marshal(map[string]interface{}{ - "duration": 120, - "elapsed_time": 110, - "laps": 5, - "race_type": "TIME_TRIAL", - "average_speed": 150, - "total_faults": 2, - "effective_duration": 118, - "user_id": 1, - "vehicle_id": vehicle.ID, - }) - - requestURL := fmt.Sprintf("/api/races/%d", race.ID) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) - - assert.Equal(t, http.StatusOK, requestRecorder.Code) - - databaseConnection.Unscoped().Delete(&vehicle) - databaseConnection.Unscoped().Delete(&race) -} +// func Test_update_race_successfully(t *testing.T) { +// gin.SetMode(gin.TestMode) + +// databaseConnection := tests.GetTestDBConnection() + +// databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + +// vehicle := models.Vehicle{ +// Name: "Toyota", +// } +// databaseConnection.Create(&vehicle) + +// race := models.Race{ +// Duration: 100, +// ElapsedTime: 90, +// Laps: 3, +// RaceType: "VS", +// AverageSpeed: 120, +// TotalFaults: 1, +// EffectiveDuration: 85, +// UserID: 1, +// VehicleID: vehicle.ID, +// } +// databaseConnection.Create(&race) + +// body, _ := json.Marshal(map[string]interface{}{ +// "duration": 120, +// "elapsed_time": 110, +// "laps": 5, +// "race_type": "TIME_TRIAL", +// "average_speed": 150, +// "total_faults": 2, +// "effective_duration": 118, +// "user_id": 1, +// "vehicle_id": vehicle.ID, +// }) + +// requestURL := fmt.Sprintf("/api/races/%d", race.ID) +// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) + +// assert.Equal(t, http.StatusOK, requestRecorder.Code) + +// databaseConnection.Unscoped().Delete(&vehicle) +// databaseConnection.Unscoped().Delete(&race) +// } diff --git a/src/validators/race/createRace.go b/src/validators/race/createRace.go index 9bd17bb..179085e 100644 --- a/src/validators/race/createRace.go +++ b/src/validators/race/createRace.go @@ -1,20 +1,40 @@ package validators -import "github.com/go-playground/validator/v10" +import ( + "errors" + "time" + + "github.com/go-playground/validator/v10" +) type CreateRaceValidator struct { - Duration int `json:"duration" validate:"required,gte=0"` - ElapsedTime int `json:"elapsed_time" validate:"required,gte=0"` - Laps int `json:"laps" validate:"required,gte=0"` - RaceType string `json:"race_type" validate:"required,oneof=VS TIME_TRIAL"` - AverageSpeed int `json:"average_speed" validate:"required,gte=0"` - TotalFaults int `json:"total_faults" validate:"required,gte=0"` - EffectiveDuration int `json:"effective_duration" validate:"required,gte=0"` - UserID uint `json:"user_id" validate:"required"` - VehicleID uint `json:"vehicle_id" validate:"required"` + StartTime time.Time `json:"start_time" validate:"required"` + EndTime *time.Time `json:"end_time" validate:"omitempty,gtefield=StartTime"` + NumberOfCollisions uint8 `json:"number_of_collisions" validate:"required,gte=0"` + DistanceTravelled int `json:"distance_travelled" validate:"required,gte=0"` + AverageSpeed int `json:"average_speed" validate:"required,gte=0"` + OutOfParcours uint8 `json:"out_of_parcours" validate:"required,gte=0"` + VehicleID uint `json:"vehicle_id" validate:"required"` } func (c *CreateRaceValidator) Validate() error { validate := validator.New() return validate.Struct(c) } + +type UpdateRaceValidator struct { + StartTime time.Time `json:"start_time"` + EndTime *time.Time `json:"end_time" validate:"omitempty"` +} + +func (u *UpdateRaceValidator) Validate() error { + validate := validator.New() + + // Validation personnalisée: vérifier si EndTime n'est pas inférieur à StartTime + if u.EndTime != nil && u.EndTime.Before(u.StartTime) { + return errors.New("end_time cannot be before start_time") + } + + // Si la validation personnalisée passe, alors on fait la validation standard des tags + return validate.Struct(u) +} From d59e322a97e2eec3d99b2cfe9598f4d31ef672d5 Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Wed, 21 Aug 2024 15:37:47 +0200 Subject: [PATCH 17/42] add a race type for each races (#24) Co-authored-by: Fabrice Pivert --- src/controllers/race/createRaceHandler.go | 20 +++++----- src/models/race.go | 1 + src/validators/race/createRace.go | 40 ------------------- src/validators/race/createRacesValidator.go | 25 ++++++++++++ .../race/updateRaceStateValidator.go | 25 ++++++++++++ 5 files changed, 62 insertions(+), 49 deletions(-) delete mode 100644 src/validators/race/createRace.go create mode 100644 src/validators/race/createRacesValidator.go create mode 100644 src/validators/race/updateRaceStateValidator.go diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index 3edb23d..8822803 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -41,15 +41,17 @@ func CreateRaceHandler(c *gin.Context) { // Création du modèle Race avec les données validées race := models.Race{ - StartTime: createRaceValidator.StartTime, - EndTime: createRaceValidator.EndTime, - NumberOfCollisions: createRaceValidator.NumberOfCollisions, - DistanceTravelled: createRaceValidator.DistanceTravelled, - AverageSpeed: createRaceValidator.AverageSpeed, - OutOfParcours: createRaceValidator.OutOfParcours, - VehicleID: createRaceValidator.VehicleID, - UserID: uint(userId), // Conversion de uint64 à uint - } + StartTime: createRaceValidator.StartTime, + EndTime: createRaceValidator.EndTime, + NumberOfCollisions: *createRaceValidator.NumberOfCollisions, + DistanceTravelled: *createRaceValidator.DistanceTravelled, + AverageSpeed: *createRaceValidator.AverageSpeed, + OutOfParcours: *createRaceValidator.OutOfParcours, + RaceType: createRaceValidator.RaceType, + VehicleID: createRaceValidator.VehicleID, + UserID: uint(userId), // Conversion de uint64 à uint +} + // Récupération de la connexion à la base de données db := services.GetConnection() diff --git a/src/models/race.go b/src/models/race.go index 29fe245..116dfa9 100644 --- a/src/models/race.go +++ b/src/models/race.go @@ -13,6 +13,7 @@ type Race struct { DistanceTravelled int `json:"distance_travelled"` AverageSpeed int `json:"average_speed"` OutOfParcours uint8 `json:"out_of_parcours"` + RaceType string `json:"race_type"` UserID uint `json:"user_id"` Model } diff --git a/src/validators/race/createRace.go b/src/validators/race/createRace.go deleted file mode 100644 index 179085e..0000000 --- a/src/validators/race/createRace.go +++ /dev/null @@ -1,40 +0,0 @@ -package validators - -import ( - "errors" - "time" - - "github.com/go-playground/validator/v10" -) - -type CreateRaceValidator struct { - StartTime time.Time `json:"start_time" validate:"required"` - EndTime *time.Time `json:"end_time" validate:"omitempty,gtefield=StartTime"` - NumberOfCollisions uint8 `json:"number_of_collisions" validate:"required,gte=0"` - DistanceTravelled int `json:"distance_travelled" validate:"required,gte=0"` - AverageSpeed int `json:"average_speed" validate:"required,gte=0"` - OutOfParcours uint8 `json:"out_of_parcours" validate:"required,gte=0"` - VehicleID uint `json:"vehicle_id" validate:"required"` -} - -func (c *CreateRaceValidator) Validate() error { - validate := validator.New() - return validate.Struct(c) -} - -type UpdateRaceValidator struct { - StartTime time.Time `json:"start_time"` - EndTime *time.Time `json:"end_time" validate:"omitempty"` -} - -func (u *UpdateRaceValidator) Validate() error { - validate := validator.New() - - // Validation personnalisée: vérifier si EndTime n'est pas inférieur à StartTime - if u.EndTime != nil && u.EndTime.Before(u.StartTime) { - return errors.New("end_time cannot be before start_time") - } - - // Si la validation personnalisée passe, alors on fait la validation standard des tags - return validate.Struct(u) -} diff --git a/src/validators/race/createRacesValidator.go b/src/validators/race/createRacesValidator.go new file mode 100644 index 0000000..faf45d4 --- /dev/null +++ b/src/validators/race/createRacesValidator.go @@ -0,0 +1,25 @@ +package validators + +import ( + "time" + + "github.com/go-playground/validator/v10" +) + + +type CreateRaceValidator struct { + StartTime time.Time `json:"start_time" validate:"required"` + EndTime *time.Time `json:"end_time" validate:"omitempty,gtefield=StartTime"` + NumberOfCollisions *uint8 `json:"number_of_collisions" validate:"required,min=0,gte=0"` + DistanceTravelled *int `json:"distance_travelled" validate:"required,min=0,gte=0"` + AverageSpeed *int `json:"average_speed" validate:"required,min=0,gte=0"` + OutOfParcours *uint8 `json:"out_of_parcours" validate:"required,min=0,gte=0"` + RaceType string `json:"race_type" validate:"required,gte=0"` + VehicleID uint `json:"vehicle_id" validate:"required"` +} + +func (c *CreateRaceValidator) Validate() error { + validate := validator.New() + return validate.Struct(c) +} + diff --git a/src/validators/race/updateRaceStateValidator.go b/src/validators/race/updateRaceStateValidator.go new file mode 100644 index 0000000..da9adcd --- /dev/null +++ b/src/validators/race/updateRaceStateValidator.go @@ -0,0 +1,25 @@ +package validators + +import ( + "errors" + "time" + + "github.com/go-playground/validator/v10" +) + +type UpdateRaceValidator struct { + StartTime time.Time `json:"start_time"` + EndTime *time.Time `json:"end_time" validate:"omitempty"` +} + +func (u *UpdateRaceValidator) Validate() error { + validate := validator.New() + + // Validation personnalisée: vérifier si EndTime n'est pas inférieur à StartTime + if u.EndTime != nil && u.EndTime.Before(u.StartTime) { + return errors.New("end_time cannot be before start_time") + } + + // Si la validation personnalisée passe, alors on fait la validation standard des tags + return validate.Struct(u) +} \ No newline at end of file From 3f5649a19866e98970e355d4a2c309c2f49f178c Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Wed, 21 Aug 2024 16:36:08 +0200 Subject: [PATCH 18/42] :test_tube: Add test base on the race model (#25) Co-authored-by: gaoubak --- src/controllers/race/createRaceHandler.go | 27 +++-- ...race_if_invalid_race_type_provided_test.go | 47 -------- ...race_if_invalid_race_type_provided_test.go | 63 ----------- src/tests/race/create_race_test.go | 41 ++++--- src/tests/race/delete_race_test.go | 88 ++++++++------- src/tests/race/get_index_race_test.go | 42 ++++++- src/tests/race/get_single_race_test.go | 55 --------- src/tests/race/update_race_test.go | 104 ++++++++++-------- 8 files changed, 191 insertions(+), 276 deletions(-) delete mode 100644 src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go delete mode 100644 src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go delete mode 100644 src/tests/race/get_single_race_test.go diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index 8822803..c398445 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -11,13 +11,13 @@ import ( ) func CreateRaceHandler(c *gin.Context) { + fmt.Println("Start the creation of the race") // Récupération de l'ID utilisateur depuis les paramètres de l'URL userIdStr := c.Param("userId") if userIdStr == "" || userIdStr == "0" || userIdStr == ":userId" { services.SetUnprocessableEntity(c, "User not found") return } - // Conversion du userId de string à uint userId, err := strconv.ParseUint(userIdStr, 10, 32) if err != nil { @@ -26,7 +26,7 @@ func CreateRaceHandler(c *gin.Context) { } var createRaceValidator validators.CreateRaceValidator - + // Validation de la requête JSON if err := c.ShouldBindJSON(&createRaceValidator); err != nil { services.SetJsonBindingErrorResponse(c, err) @@ -41,17 +41,16 @@ func CreateRaceHandler(c *gin.Context) { // Création du modèle Race avec les données validées race := models.Race{ - StartTime: createRaceValidator.StartTime, - EndTime: createRaceValidator.EndTime, - NumberOfCollisions: *createRaceValidator.NumberOfCollisions, - DistanceTravelled: *createRaceValidator.DistanceTravelled, - AverageSpeed: *createRaceValidator.AverageSpeed, - OutOfParcours: *createRaceValidator.OutOfParcours, - RaceType: createRaceValidator.RaceType, - VehicleID: createRaceValidator.VehicleID, - UserID: uint(userId), // Conversion de uint64 à uint -} - + StartTime: createRaceValidator.StartTime, + EndTime: createRaceValidator.EndTime, + NumberOfCollisions: *createRaceValidator.NumberOfCollisions, + DistanceTravelled: *createRaceValidator.DistanceTravelled, + AverageSpeed: *createRaceValidator.AverageSpeed, + OutOfParcours: *createRaceValidator.OutOfParcours, + RaceType: createRaceValidator.RaceType, + VehicleID: createRaceValidator.VehicleID, + UserID: uint(userId), // Conversion de uint64 à uint + } // Récupération de la connexion à la base de données db := services.GetConnection() @@ -62,7 +61,7 @@ func CreateRaceHandler(c *gin.Context) { services.SetInternalServerError(c, "Failed to create Race") return } - + fmt.Println("Race created") // Réponse de succès avec l'objet Race créé services.SetCreated(c, "Race created successfully", race) } diff --git a/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go b/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go deleted file mode 100644 index a4030db..0000000 --- a/src/tests/race/cannot_create_race_if_invalid_race_type_provided_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package race - -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "net/http" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func Test_cannot_create_race_if_invalid_race_type_provided(t *testing.T) { - gin.SetMode(gin.TestMode) - - databaseConnection := tests.GetTestDBConnection() - - databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) - - vehicle := models.Vehicle{ - ID: 1, - Name: "Toyota", - } - databaseConnection.Create(&vehicle) - - body, _ := json.Marshal(map[string]interface{}{ - "duration": 120, - "elapsed_time": 110, - "laps": 5, - "race_type": "INVALID RACE TYPE", - "average_speed": 150, - "total_faults": 2, - "effective_duration": 118, - "user_id": 1, - "vehicle_id": vehicle.ID, - }) - - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/races/", body) - - responseBody := requestRecorder.Body.String() - - assert.Equal(t, http.StatusUnprocessableEntity, requestRecorder.Code) - - expectedErrorMessage := "CreateRaceValidator.RaceType" - assert.Contains(t, responseBody, expectedErrorMessage) -} diff --git a/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go b/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go deleted file mode 100644 index cc6ecfe..0000000 --- a/src/tests/race/cannot_update_race_if_invalid_race_type_provided_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package race - -import ( - "api/src/models" - "api/src/tests" - "encoding/json" - "net/http" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func Test_cannot_update_race_if_invalid_race_type_provided(t *testing.T) { - - gin.SetMode(gin.TestMode) - - databaseConnection := tests.GetTestDBConnection() - - databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) - - vehicle := models.Vehicle{ - Name: "Toyota", - } - databaseConnection.Create(&vehicle) - - race := models.Race{ - ID: 1, - Duration: 100, - ElapsedTime: 90, - Laps: 3, - RaceType: "VS", - AverageSpeed: 120, - TotalFaults: 1, - EffectiveDuration: 85, - UserID: 1, - VehicleID: vehicle.ID, - } - databaseConnection.Create(&race) - - body, _ := json.Marshal(map[string]interface{}{ - "duration": 120, - "elapsed_time": 110, - "laps": 5, - "race_type": "INVALID RACE TYPE", - "average_speed": 150, - "total_faults": 2, - "effective_duration": 118, - "user_id": 1, - "vehicle_id": vehicle.ID, - }) - - requestURL := "/api/races/1" - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) - - responseBody := requestRecorder.Body.String() - - assert.Equal(t, http.StatusUnprocessableEntity, requestRecorder.Code) - - expectedErrorMessage := "CreateRaceValidator.RaceType" - assert.Contains(t, responseBody, expectedErrorMessage) - -} diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go index d313a81..29cafbf 100644 --- a/src/tests/race/create_race_test.go +++ b/src/tests/race/create_race_test.go @@ -4,8 +4,10 @@ import ( "api/src/models" "api/src/tests" "encoding/json" + "fmt" "net/http" "testing" + "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -16,26 +18,39 @@ func Test_create_race_successfully(t *testing.T) { databaseConnection := tests.GetTestDBConnection() - databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) - + // Ensure the necessary migrations are run + databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) + // Create a mock user + user := models.User{ + Email: "testuser@example.com", + Password: "securepassword", + } + databaseConnection.Create(&user) + // Create a mock vehicle vehicle := models.Vehicle{ Name: "Toyota", } databaseConnection.Create(&vehicle) - body, _ := json.Marshal(map[string]interface{}{ - "duration": 120, - "elapsed_time": 110, - "laps": 5, - "race_type": "VS", - "average_speed": 150, - "total_faults": 2, - "effective_duration": 118, - "user_id": 1, - "vehicle_id": vehicle.ID, + // Define start and end times for the race + startTime := time.Now() + endTime := startTime.Add(time.Hour) + + // Prepare the JSON body for the POST request + createBody, _ := json.Marshal(map[string]interface{}{ + "start_time": startTime.Format(time.RFC3339), + "end_time": endTime.Format(time.RFC3339), + "number_of_collisions": 5, + "distance_travelled": 150, + "average_speed": 130, + "out_of_parcours": 1, + "user_id": user.ID, + "vehicle_id": vehicle.ID, }) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, "/api/races/", body) + // Perform the authenticated request + requestURL := fmt.Sprintf("/api/users/%d/races", user.ID) // Use /users/:userId/races + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPost, requestURL, createBody) assert.Equal(t, http.StatusCreated, requestRecorder.Code) } diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go index 9901a34..559e414 100644 --- a/src/tests/race/delete_race_test.go +++ b/src/tests/race/delete_race_test.go @@ -1,51 +1,65 @@ package race -// func Test_delete_race_successfully(t *testing.T) { -// gin.SetMode(gin.TestMode) +import ( + "api/src/models" + "api/src/tests" + "fmt" + "net/http" + "testing" + "time" -// databaseConnection := tests.GetTestDBConnection() + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) -// databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) +func Test_delete_race_successfully(t *testing.T) { + gin.SetMode(gin.TestMode) -// vehicle := models.Vehicle{ -// Name: "Toyota", -// } -// databaseConnection.Create(&vehicle) + databaseConnection := tests.GetTestDBConnection() -// race := models.Race{ -// Duration: 100, -// ElapsedTime: 90, -// Laps: 3, -// RaceType: "VS", -// AverageSpeed: 120, -// TotalFaults: 1, -// EffectiveDuration: 85, -// UserID: 1, -// VehicleID: vehicle.ID, -// } -// databaseConnection.Create(&race) + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) -// requestURL := fmt.Sprintf("/api/races/%d", race.ID) -// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) -// assert.Equal(t, http.StatusNoContent, requestRecorder.Code) -// } + startTime := time.Now() + endTime := startTime.Add(time.Hour) -// func Test_delete_race_not_found(t *testing.T) { -// gin.SetMode(gin.TestMode) + race := models.Race{ + VehicleID: vehicle.ID, + StartTime: startTime, + EndTime: &endTime, + NumberOfCollisions: 3, + DistanceTravelled: 100, + AverageSpeed: 120, + OutOfParcours: 0, + UserID: 1, + } + databaseConnection.Create(&race) -// databaseConnection := tests.GetTestDBConnection() + requestURL := fmt.Sprintf("/api/races/%d", race.ID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) -// databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + assert.Equal(t, http.StatusNoContent, requestRecorder.Code) +} -// vehicle := models.Vehicle{ -// Name: "Toyota", -// } -// databaseConnection.Create(&vehicle) +func Test_delete_race_not_found(t *testing.T) { + gin.SetMode(gin.TestMode) -// nonExistentRaceID := 999 -// requestURL := fmt.Sprintf("/api/races/%d", nonExistentRaceID) -// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) + databaseConnection := tests.GetTestDBConnection() -// assert.Equal(t, http.StatusNotFound, requestRecorder.Code) -// } + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + nonExistentRaceID := 999 + requestURL := fmt.Sprintf("/api/races/%d", nonExistentRaceID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodDelete, requestURL, nil) + + assert.Equal(t, http.StatusNotFound, requestRecorder.Code) +} diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go index b20a899..bc61426 100644 --- a/src/tests/race/get_index_race_test.go +++ b/src/tests/race/get_index_race_test.go @@ -4,8 +4,10 @@ import ( "api/src/models" "api/src/tests" "encoding/json" + "fmt" "net/http" "testing" + "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -21,17 +23,53 @@ func Test_get_races_index(t *testing.T) { databaseConnection := tests.GetTestDBConnection() + // Ensure the necessary migrations are run databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) - requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, "/api/races/", nil) + // Create a mock user + user := models.User{ + Email: "testuser2@example.com", + Password: "securepassword", + } + databaseConnection.Create(&user) + // Create a mock vehicle + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + // Create a mock race associated with the user + startTime := time.Now() + endTime := startTime.Add(time.Hour) + race := models.Race{ + VehicleID: vehicle.ID, + StartTime: startTime, + EndTime: &endTime, + NumberOfCollisions: 3, + DistanceTravelled: 100, + AverageSpeed: 120, + OutOfParcours: 0, + UserID: user.ID, + } + databaseConnection.Create(&race) + + // Construct the request URL + requestURL := fmt.Sprintf("/api/users/%d/races", user.ID) + + // Perform the authenticated request + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) + + // Assert the status code assert.Equal(t, http.StatusOK, requestRecorder.Code) + // Parse and check the response body var response ApiResponse err := json.Unmarshal(requestRecorder.Body.Bytes(), &response) assert.NoError(t, err) + // Validate the response data races := response.Data assert.NotNil(t, races) - assert.True(t, len(races) >= 0) + assert.True(t, len(races) > 0) } diff --git a/src/tests/race/get_single_race_test.go b/src/tests/race/get_single_race_test.go deleted file mode 100644 index 2cbceee..0000000 --- a/src/tests/race/get_single_race_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package race - -// func Test_get_single_race_successfully(t *testing.T) { -// gin.SetMode(gin.TestMode) - -// databaseConnection := tests.GetTestDBConnection() - -// databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) - -// vehicle := models.Vehicle{ -// Name: "Toyota", -// } -// databaseConnection.Create(&vehicle) - -// race := models.Race{ -// Duration: 100, -// ElapsedTime: 90, -// Laps: 3, -// RaceType: "VS", -// AverageSpeed: 120, -// TotalFaults: 1, -// EffectiveDuration: 85, -// UserID: 1, -// VehicleID: vehicle.ID, -// } -// databaseConnection.Create(&race) - -// requestURL := fmt.Sprintf("/api/races/%d", race.ID) -// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) - -// assert.Equal(t, http.StatusOK, requestRecorder.Code) - -// var response map[string]interface{} -// err := json.Unmarshal(requestRecorder.Body.Bytes(), &response) -// assert.NoError(t, err) - -// data, ok := response["data"].(map[string]interface{}) -// assert.True(t, ok) -// assert.Equal(t, float64(race.ID), data["ID"]) -// } - -// func Test_get_single_race_not_found(t *testing.T) { -// gin.SetMode(gin.TestMode) - -// databaseConnection := tests.GetTestDBConnection() - -// databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.Race{}) - -// invalidRaceID := 999999 - -// requestURL := fmt.Sprintf("/api/races/%d", invalidRaceID) -// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodGet, requestURL, nil) - -// assert.Equal(t, http.StatusNotFound, requestRecorder.Code) -// } diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index 40c9c97..44ec1f1 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -1,47 +1,61 @@ package race -// func Test_update_race_successfully(t *testing.T) { -// gin.SetMode(gin.TestMode) - -// databaseConnection := tests.GetTestDBConnection() - -// databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) - -// vehicle := models.Vehicle{ -// Name: "Toyota", -// } -// databaseConnection.Create(&vehicle) - -// race := models.Race{ -// Duration: 100, -// ElapsedTime: 90, -// Laps: 3, -// RaceType: "VS", -// AverageSpeed: 120, -// TotalFaults: 1, -// EffectiveDuration: 85, -// UserID: 1, -// VehicleID: vehicle.ID, -// } -// databaseConnection.Create(&race) - -// body, _ := json.Marshal(map[string]interface{}{ -// "duration": 120, -// "elapsed_time": 110, -// "laps": 5, -// "race_type": "TIME_TRIAL", -// "average_speed": 150, -// "total_faults": 2, -// "effective_duration": 118, -// "user_id": 1, -// "vehicle_id": vehicle.ID, -// }) - -// requestURL := fmt.Sprintf("/api/races/%d", race.ID) -// requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, body) - -// assert.Equal(t, http.StatusOK, requestRecorder.Code) - -// databaseConnection.Unscoped().Delete(&vehicle) -// databaseConnection.Unscoped().Delete(&race) -// } +import ( + "api/src/models" + "api/src/tests" + "encoding/json" + "fmt" + "net/http" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func Test_update_race_successfully(t *testing.T) { + gin.SetMode(gin.TestMode) + + databaseConnection := tests.GetTestDBConnection() + + databaseConnection.AutoMigrate(&models.Vehicle{}, &models.Race{}) + + vehicle := models.Vehicle{ + Name: "Toyota", + } + databaseConnection.Create(&vehicle) + + startTime := time.Now() + endTime := startTime.Add(time.Hour) + + race := models.Race{ + VehicleID: vehicle.ID, + StartTime: startTime, + EndTime: &endTime, + NumberOfCollisions: 3, + DistanceTravelled: 100, + AverageSpeed: 120, + OutOfParcours: 0, + UserID: 1, + } + databaseConnection.Create(&race) + + updateBody, _ := json.Marshal(map[string]interface{}{ + "start_time": startTime.Add(-time.Minute).Format(time.RFC3339), // Updated start time + "end_time": endTime.Add(time.Minute).Format(time.RFC3339), // Updated end time + "number_of_collisions": 5, + "distance_travelled": 150, + "average_speed": 130, + "out_of_parcours": 1, + "user_id": 2, // Assume user ID is updated + "vehicle_id": vehicle.ID, + }) + + requestURL := fmt.Sprintf("/api/races/%d", race.ID) + requestRecorder, _ := tests.PerformAuthenticatedRequest(http.MethodPatch, requestURL, updateBody) + + assert.Equal(t, http.StatusOK, requestRecorder.Code) + + databaseConnection.Unscoped().Delete(&vehicle) + databaseConnection.Unscoped().Delete(&race) +} From 084cdd9c1af3cbc2697d301aa7b6aa600e44d189 Mon Sep 17 00:00:00 2001 From: gaoubak Date: Wed, 21 Aug 2024 16:41:45 +0200 Subject: [PATCH 19/42] :ambulance: Fix test for race --- src/tests/race/create_race_test.go | 1 + src/tests/race/delete_race_test.go | 1 + src/tests/race/get_index_race_test.go | 1 + src/tests/race/update_race_test.go | 1 + 4 files changed, 4 insertions(+) diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go index 29cafbf..3b10fc6 100644 --- a/src/tests/race/create_race_test.go +++ b/src/tests/race/create_race_test.go @@ -46,6 +46,7 @@ func Test_create_race_successfully(t *testing.T) { "out_of_parcours": 1, "user_id": user.ID, "vehicle_id": vehicle.ID, + "race_type": "manual", }) // Perform the authenticated request diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go index 559e414..97fce59 100644 --- a/src/tests/race/delete_race_test.go +++ b/src/tests/race/delete_race_test.go @@ -36,6 +36,7 @@ func Test_delete_race_successfully(t *testing.T) { AverageSpeed: 120, OutOfParcours: 0, UserID: 1, + RaceType: "manual", } databaseConnection.Create(&race) diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go index bc61426..62e7ee2 100644 --- a/src/tests/race/get_index_race_test.go +++ b/src/tests/race/get_index_race_test.go @@ -51,6 +51,7 @@ func Test_get_races_index(t *testing.T) { AverageSpeed: 120, OutOfParcours: 0, UserID: user.ID, + RaceType: "manual", } databaseConnection.Create(&race) diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index 44ec1f1..ba6e78f 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -49,6 +49,7 @@ func Test_update_race_successfully(t *testing.T) { "out_of_parcours": 1, "user_id": 2, // Assume user ID is updated "vehicle_id": vehicle.ID, + "race_type": "manual", }) requestURL := fmt.Sprintf("/api/races/%d", race.ID) From e65257ed2c0fe9faefd4b291adb96cd5f6e119a9 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Wed, 21 Aug 2024 11:47:14 +0200 Subject: [PATCH 20/42] wip: implementing seeder --- .env.example | 1 + main.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/.env.example b/.env.example index 51d6d47..833c6a6 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ DB_HOST= DB_PORT= DB_NAME= +ALLOW_DATABASE_SEEDING=false APP_FRONTEND_URL="http://localhost:5173" JWT_SECRET_KEY="example_key" diff --git a/main.go b/main.go index 1c55b73..1a4e824 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,8 @@ func main() { initVehicleData(database) + seedDatabase(database) + initMQTT() startWebServer() @@ -111,6 +113,42 @@ func initVehicleData(database *gorm.DB) { } } +func seedDatabase(database *gorm.DB) { + err := godotenv.Load() + + if err != nil { + log.Fatal("Error loading .env file in seedDatabase: ", err) + } + + allowDatabaseSeeding := os.Getenv("ALLOW_DATABASE_SEEDING") + + if allowDatabaseSeeding != "true" { + return + } + + var usernames = []string{"David", "Goliath", "Pierre"} + var emails = []string{"l-david@test.com", "a-goliath@test.com", "q-pierre@test.com"} + hashedPassword, _ := services.HashPassword("password") + + for i := 0; i < len(usernames); i++ { + database.FirstOrCreate(&models.User{}, models.User{ + Username: &usernames[i], + Email: emails[i], + Password: string(hashedPassword), + }) + } + + vehicle := database.First(&models.Vehicle{}) + + if vehicle.RowsAffected == 0 { + database.FirstOrCreate(&models.Vehicle{}, models.Vehicle{ + Name: "Seed Vehicle", + IpAdress: "0.0.0.0", + IsAvailable: false, + }) + } +} + func initMQTT() { fmt.Println("Starting mqtt connection...") From 8c52ff49158e83e1644bb90e31d21e9d3b13ed25 Mon Sep 17 00:00:00 2001 From: gaoubak Date: Wed, 21 Aug 2024 17:09:41 +0200 Subject: [PATCH 21/42] :seedling: Update seeder to add 3 race for each user in the seeder --- main.go | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 1a4e824..26b07e5 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/gin-gonic/gin" "github.com/joho/godotenv" @@ -121,7 +122,6 @@ func seedDatabase(database *gorm.DB) { } allowDatabaseSeeding := os.Getenv("ALLOW_DATABASE_SEEDING") - if allowDatabaseSeeding != "true" { return } @@ -130,6 +130,7 @@ func seedDatabase(database *gorm.DB) { var emails = []string{"l-david@test.com", "a-goliath@test.com", "q-pierre@test.com"} hashedPassword, _ := services.HashPassword("password") + // Seed users for i := 0; i < len(usernames); i++ { database.FirstOrCreate(&models.User{}, models.User{ Username: &usernames[i], @@ -138,19 +139,40 @@ func seedDatabase(database *gorm.DB) { }) } - vehicle := database.First(&models.Vehicle{}) - - if vehicle.RowsAffected == 0 { - database.FirstOrCreate(&models.Vehicle{}, models.Vehicle{ - Name: "Seed Vehicle", - IpAdress: "0.0.0.0", - IsAvailable: false, - }) + // Seed a vehicle if not already present + var vehicle models.Vehicle + database.FirstOrCreate(&vehicle, models.Vehicle{ + Name: "Seed Vehicle", + IpAdress: "0.0.0.0", + IsAvailable: false, + }) + + // Fetch all users + var users []models.User + database.Find(&users) + + // Seed races for each user + for _, user := range users { + for i := 0; i < 3; i++ { + startTime := time.Now().Add(time.Duration(i) * time.Hour) + endTime := startTime.Add(1 * time.Hour) + race := models.Race{ + VehicleID: vehicle.ID, + StartTime: startTime, + EndTime: &endTime, + NumberOfCollisions: uint8(i + 1), + DistanceTravelled: 100 + (i * 10), + AverageSpeed: 120 + (i * 5), + OutOfParcours: uint8(i % 2), + UserID: user.ID, + } + database.Create(&race) + } } } func initMQTT() { - fmt.Println("Starting mqtt connection...") + fmt.Println("Starting MQTT connection...") client := services.InitMQTTClient("tcp://mosquitto:1883") From 58fbd59f268780e1d029d3fc39cfed269ffaf347 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Thu, 22 Aug 2024 10:42:46 +0200 Subject: [PATCH 22/42] add: vehicle state for each vehicle in seeding process --- main.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 26b07e5..4bdf531 100644 --- a/main.go +++ b/main.go @@ -130,7 +130,6 @@ func seedDatabase(database *gorm.DB) { var emails = []string{"l-david@test.com", "a-goliath@test.com", "q-pierre@test.com"} hashedPassword, _ := services.HashPassword("password") - // Seed users for i := 0; i < len(usernames); i++ { database.FirstOrCreate(&models.User{}, models.User{ Username: &usernames[i], @@ -139,7 +138,6 @@ func seedDatabase(database *gorm.DB) { }) } - // Seed a vehicle if not already present var vehicle models.Vehicle database.FirstOrCreate(&vehicle, models.Vehicle{ Name: "Seed Vehicle", @@ -147,12 +145,32 @@ func seedDatabase(database *gorm.DB) { IsAvailable: false, }) - // Fetch all users var users []models.User + var vehicles []models.Vehicle + var races []models.Race + var userAlreadyHaveRaces bool database.Find(&users) + database.Find(&vehicles) - // Seed races for each user for _, user := range users { + var userAlreadyHaveVehicleStateForThisVehicle bool + + for _, vehicle := range vehicles { + userAlreadyHaveVehicleStateForThisVehicle = database.Where("user_id = ? AND vehicle_id = ?", user.ID, vehicle.ID). + Find(&models.VehicleState{}). + RowsAffected > 0 + + if !userAlreadyHaveVehicleStateForThisVehicle { + vehicle.InitVehicleState(&user, database) + } + } + + userAlreadyHaveRaces = database.Where("user_id = ?", user.ID).Find(&races).RowsAffected >= 3 + + if userAlreadyHaveRaces { + continue + } + for i := 0; i < 3; i++ { startTime := time.Now().Add(time.Duration(i) * time.Hour) endTime := startTime.Add(1 * time.Hour) From efbe5ada214b85313355245741b15dce5396d8d6 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Thu, 22 Aug 2024 10:49:44 +0200 Subject: [PATCH 23/42] fix: enhance MySQL healthcheck for quicker service startup --- docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9233231..9f9f7e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,9 +34,10 @@ services: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: database healthcheck: - test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + test: ["CMD", "mysql", "-uroot", "-proot", "-e", "SELECT 1"] + interval: 5s timeout: 2s - retries: 10 + retries: 3 image: mysql:latest ports: - "3306:3306" From 1fbfcca3fc50b85ef47b5b4be39d68443e11cea3 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Thu, 22 Aug 2024 14:33:49 +0200 Subject: [PATCH 24/42] fix: route index user race --- src/controllers/race/getAllUsersRaceHandler.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/controllers/race/getAllUsersRaceHandler.go b/src/controllers/race/getAllUsersRaceHandler.go index 8dde30b..07412cb 100644 --- a/src/controllers/race/getAllUsersRaceHandler.go +++ b/src/controllers/race/getAllUsersRaceHandler.go @@ -22,13 +22,7 @@ func GetAllUsersRaceHandler(c *gin.Context) { return } - // Requête pour trouver toutes les courses pour un utilisateur donné - query := db.Where("user_id = ?", uint(userId)).Find(&races) - - if query.RowsAffected == 0 { - services.SetNotFound(c, "No races found for this user") - return - } + db.Where("user_id = ?", uint(userId)).Find(&races) services.SetOK(c, "User races retrieved successfully", races) } From b53465e57d66c03c59df988dde2be8ef84f31f74 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Thu, 22 Aug 2024 15:33:06 +0200 Subject: [PATCH 25/42] hotfix: cors --- src/routes/routes.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/routes.go b/src/routes/routes.go index de481d0..7827e37 100644 --- a/src/routes/routes.go +++ b/src/routes/routes.go @@ -12,12 +12,13 @@ import ( func SetupCors(router *gin.Engine) *gin.Engine { allowedOrigins := []string{ os.Getenv("APP_FRONTEND_URL"), + "http://127.0.0.1:5173", } corsConfig := cors.Config{ AllowOrigins: allowedOrigins, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowHeaders: []string{"Origin", "Content-Type", "Accept"}, + AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, } From d7a1936234ec389a296e42e9f96d85cfd3cf4250 Mon Sep 17 00:00:00 2001 From: gaoubak Date: Thu, 22 Aug 2024 16:03:56 +0200 Subject: [PATCH 26/42] :sparkles: Add name and race status for race --- main.go | 5 ++++ src/models/race.go | 4 ++- src/models/raceStatus.go | 22 +++++++++++++++ src/tests/race/create_race_test.go | 2 ++ src/tests/race/get_index_race_test.go | 1 + src/tests/race/update_race_test.go | 1 + src/validators/race/createRacesValidator.go | 28 ++++++++++++++----- .../race/updateRaceStateValidator.go | 27 ++++++++++++++---- 8 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 src/models/raceStatus.go diff --git a/main.go b/main.go index 4bdf531..24364a1 100644 --- a/main.go +++ b/main.go @@ -171,6 +171,8 @@ func seedDatabase(database *gorm.DB) { continue } + raceNames := []string{"Morning Sprint", "Afternoon Challenge", "Evening Marathon"} + for i := 0; i < 3; i++ { startTime := time.Now().Add(time.Duration(i) * time.Hour) endTime := startTime.Add(1 * time.Hour) @@ -183,6 +185,9 @@ func seedDatabase(database *gorm.DB) { AverageSpeed: 120 + (i * 5), OutOfParcours: uint8(i % 2), UserID: user.ID, + RaceType: "manual", + RaceStatus: "Completed", + Name: raceNames[i], } database.Create(&race) } diff --git a/src/models/race.go b/src/models/race.go index 116dfa9..b703be8 100644 --- a/src/models/race.go +++ b/src/models/race.go @@ -6,6 +6,7 @@ import ( type Race struct { ID uint `gorm:"primaryKey"` + Name string `json:"name"` VehicleID uint `json:"vehicle_id"` StartTime time.Time `json:"start_time"` EndTime *time.Time `json:"end_time"` @@ -13,7 +14,8 @@ type Race struct { DistanceTravelled int `json:"distance_travelled"` AverageSpeed int `json:"average_speed"` OutOfParcours uint8 `json:"out_of_parcours"` - RaceType string `json:"race_type"` + RaceType string `json:"race_type"` + RaceStatus RaceStatus `json:"race_status" validate:"required"` UserID uint `json:"user_id"` Model } diff --git a/src/models/raceStatus.go b/src/models/raceStatus.go new file mode 100644 index 0000000..4a450c9 --- /dev/null +++ b/src/models/raceStatus.go @@ -0,0 +1,22 @@ +package models + +import ( + "errors" +) + +type RaceStatus string + +const ( + RaceStatusNotStarted RaceStatus = "Not Started" + RaceStatusInProgress RaceStatus = "In Progress" + RaceStatusCompleted RaceStatus = "Completed" +) + +// Validates the race status value +func (rs RaceStatus) IsValid() error { + switch rs { + case RaceStatusNotStarted, RaceStatusInProgress, RaceStatusCompleted: + return nil + } + return errors.New("invalid race status") +} diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go index 3b10fc6..c536908 100644 --- a/src/tests/race/create_race_test.go +++ b/src/tests/race/create_race_test.go @@ -38,6 +38,7 @@ func Test_create_race_successfully(t *testing.T) { // Prepare the JSON body for the POST request createBody, _ := json.Marshal(map[string]interface{}{ + "name": "testuser", "start_time": startTime.Format(time.RFC3339), "end_time": endTime.Format(time.RFC3339), "number_of_collisions": 5, @@ -47,6 +48,7 @@ func Test_create_race_successfully(t *testing.T) { "user_id": user.ID, "vehicle_id": vehicle.ID, "race_type": "manual", + "race_status": "Not Started", }) // Perform the authenticated request diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go index 62e7ee2..6307d85 100644 --- a/src/tests/race/get_index_race_test.go +++ b/src/tests/race/get_index_race_test.go @@ -52,6 +52,7 @@ func Test_get_races_index(t *testing.T) { OutOfParcours: 0, UserID: user.ID, RaceType: "manual", + RaceStatus: "Not Started", } databaseConnection.Create(&race) diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index ba6e78f..6697440 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -50,6 +50,7 @@ func Test_update_race_successfully(t *testing.T) { "user_id": 2, // Assume user ID is updated "vehicle_id": vehicle.ID, "race_type": "manual", + "race_status": "Not Started", }) requestURL := fmt.Sprintf("/api/races/%d", race.ID) diff --git a/src/validators/race/createRacesValidator.go b/src/validators/race/createRacesValidator.go index faf45d4..fead306 100644 --- a/src/validators/race/createRacesValidator.go +++ b/src/validators/race/createRacesValidator.go @@ -6,20 +6,34 @@ import ( "github.com/go-playground/validator/v10" ) - type CreateRaceValidator struct { + Name string `json:"name" validate:"required"` StartTime time.Time `json:"start_time" validate:"required"` EndTime *time.Time `json:"end_time" validate:"omitempty,gtefield=StartTime"` - NumberOfCollisions *uint8 `json:"number_of_collisions" validate:"required,min=0,gte=0"` - DistanceTravelled *int `json:"distance_travelled" validate:"required,min=0,gte=0"` - AverageSpeed *int `json:"average_speed" validate:"required,min=0,gte=0"` - OutOfParcours *uint8 `json:"out_of_parcours" validate:"required,min=0,gte=0"` - RaceType string `json:"race_type" validate:"required,gte=0"` + NumberOfCollisions *uint8 `json:"number_of_collisions" validate:"required,min=0,gte=0"` + DistanceTravelled *int `json:"distance_travelled" validate:"required,min=0,gte=0"` + AverageSpeed *int `json:"average_speed" validate:"required,min=0,gte=0"` + OutOfParcours *uint8 `json:"out_of_parcours" validate:"required,min=0,gte=0"` + RaceType string `json:"race_type" validate:"required"` + RaceStatus string `json:"race_status" validate:"required,race_status_valid"` VehicleID uint `json:"vehicle_id" validate:"required"` } +func ValidateRaceStatus(fl validator.FieldLevel) bool { + status := fl.Field().String() + validStatuses := []string{"Not Started", "In Progress", "Completed"} + for _, validStatus := range validStatuses { + if status == validStatus { + return true + } + } + return false +} + func (c *CreateRaceValidator) Validate() error { validate := validator.New() + + validate.RegisterValidation("race_status_valid", ValidateRaceStatus) + return validate.Struct(c) } - diff --git a/src/validators/race/updateRaceStateValidator.go b/src/validators/race/updateRaceStateValidator.go index da9adcd..6b74d66 100644 --- a/src/validators/race/updateRaceStateValidator.go +++ b/src/validators/race/updateRaceStateValidator.go @@ -8,18 +8,35 @@ import ( ) type UpdateRaceValidator struct { - StartTime time.Time `json:"start_time"` - EndTime *time.Time `json:"end_time" validate:"omitempty"` + StartTime time.Time `json:"start_time"` + EndTime *time.Time `json:"end_time" validate:"omitempty"` + RaceStatus string `json:"race_status" validate:"required,raceStatus"` +} + +// Custom validation function for RaceStatus enum +func raceStatusValidator(fl validator.FieldLevel) bool { + status := fl.Field().String() + validStatuses := []string{"Not Started", "In Progress", "Completed"} + + for _, validStatus := range validStatuses { + if status == validStatus { + return true + } + } + return false } func (u *UpdateRaceValidator) Validate() error { validate := validator.New() - // Validation personnalisée: vérifier si EndTime n'est pas inférieur à StartTime + // Register the custom validator for RaceStatus + validate.RegisterValidation("raceStatus", raceStatusValidator) + + // Custom validation: check if EndTime is not before StartTime if u.EndTime != nil && u.EndTime.Before(u.StartTime) { return errors.New("end_time cannot be before start_time") } - // Si la validation personnalisée passe, alors on fait la validation standard des tags + // Standard validation based on struct tags return validate.Struct(u) -} \ No newline at end of file +} From 3ed8b86d0f7c2629651bf060d8107bf7fe7ca75a Mon Sep 17 00:00:00 2001 From: Rijenth Date: Thu, 22 Aug 2024 17:13:48 +0200 Subject: [PATCH 27/42] fix: severals errors --- main.go | 4 +- src/controllers/race/createRaceHandler.go | 7 ++-- src/controllers/race/updateRaceHandler.go | 8 ++++ src/models/race.go | 4 +- src/models/raceStatus.go | 22 ---------- src/tests/race/create_race_test.go | 4 +- src/tests/race/delete_race_test.go | 2 +- src/tests/race/get_index_race_test.go | 4 +- src/tests/race/update_race_test.go | 13 ++---- src/validators/race/createRacesValidator.go | 17 +------- .../race/updateRaceStateValidator.go | 42 ------------------- src/validators/race/updateRaceValidator.go | 25 +++++++++++ 12 files changed, 51 insertions(+), 101 deletions(-) delete mode 100644 src/models/raceStatus.go delete mode 100644 src/validators/race/updateRaceStateValidator.go create mode 100644 src/validators/race/updateRaceValidator.go diff --git a/main.go b/main.go index 24364a1..4f7b50a 100644 --- a/main.go +++ b/main.go @@ -185,8 +185,8 @@ func seedDatabase(database *gorm.DB) { AverageSpeed: 120 + (i * 5), OutOfParcours: uint8(i % 2), UserID: user.ID, - RaceType: "manual", - RaceStatus: "Completed", + Type: "manual", + Status: "completed", Name: raceNames[i], } database.Create(&race) diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index c398445..8ae4a82 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -11,7 +11,6 @@ import ( ) func CreateRaceHandler(c *gin.Context) { - fmt.Println("Start the creation of the race") // Récupération de l'ID utilisateur depuis les paramètres de l'URL userIdStr := c.Param("userId") if userIdStr == "" || userIdStr == "0" || userIdStr == ":userId" { @@ -41,13 +40,15 @@ func CreateRaceHandler(c *gin.Context) { // Création du modèle Race avec les données validées race := models.Race{ + Name: createRaceValidator.Name, StartTime: createRaceValidator.StartTime, EndTime: createRaceValidator.EndTime, NumberOfCollisions: *createRaceValidator.NumberOfCollisions, DistanceTravelled: *createRaceValidator.DistanceTravelled, AverageSpeed: *createRaceValidator.AverageSpeed, OutOfParcours: *createRaceValidator.OutOfParcours, - RaceType: createRaceValidator.RaceType, + Status: createRaceValidator.Status, + Type: createRaceValidator.Type, VehicleID: createRaceValidator.VehicleID, UserID: uint(userId), // Conversion de uint64 à uint } @@ -61,7 +62,7 @@ func CreateRaceHandler(c *gin.Context) { services.SetInternalServerError(c, "Failed to create Race") return } - fmt.Println("Race created") + // Réponse de succès avec l'objet Race créé services.SetCreated(c, "Race created successfully", race) } diff --git a/src/controllers/race/updateRaceHandler.go b/src/controllers/race/updateRaceHandler.go index 04a0ff5..746a9bd 100644 --- a/src/controllers/race/updateRaceHandler.go +++ b/src/controllers/race/updateRaceHandler.go @@ -43,6 +43,14 @@ func UpdateRaceHandler(c *gin.Context) { existingRace.EndTime = raceValidator.EndTime } + if raceValidator.Name != "" { + existingRace.Name = raceValidator.Name + } + + if raceValidator.Status != "" { + existingRace.Status = raceValidator.Status + } + // Sauvegarde les modifications dans la base de données if err := db.Save(&existingRace).Error; err != nil { fmt.Printf("Error updating Race: %v\n", err) diff --git a/src/models/race.go b/src/models/race.go index b703be8..74ba004 100644 --- a/src/models/race.go +++ b/src/models/race.go @@ -14,8 +14,8 @@ type Race struct { DistanceTravelled int `json:"distance_travelled"` AverageSpeed int `json:"average_speed"` OutOfParcours uint8 `json:"out_of_parcours"` - RaceType string `json:"race_type"` - RaceStatus RaceStatus `json:"race_status" validate:"required"` + Status string `json:"status"` + Type string `json:"type"` UserID uint `json:"user_id"` Model } diff --git a/src/models/raceStatus.go b/src/models/raceStatus.go deleted file mode 100644 index 4a450c9..0000000 --- a/src/models/raceStatus.go +++ /dev/null @@ -1,22 +0,0 @@ -package models - -import ( - "errors" -) - -type RaceStatus string - -const ( - RaceStatusNotStarted RaceStatus = "Not Started" - RaceStatusInProgress RaceStatus = "In Progress" - RaceStatusCompleted RaceStatus = "Completed" -) - -// Validates the race status value -func (rs RaceStatus) IsValid() error { - switch rs { - case RaceStatusNotStarted, RaceStatusInProgress, RaceStatusCompleted: - return nil - } - return errors.New("invalid race status") -} diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go index c536908..fe30fb3 100644 --- a/src/tests/race/create_race_test.go +++ b/src/tests/race/create_race_test.go @@ -47,8 +47,8 @@ func Test_create_race_successfully(t *testing.T) { "out_of_parcours": 1, "user_id": user.ID, "vehicle_id": vehicle.ID, - "race_type": "manual", - "race_status": "Not Started", + "type": "manual", + "status": "not_started", }) // Perform the authenticated request diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go index 97fce59..3718784 100644 --- a/src/tests/race/delete_race_test.go +++ b/src/tests/race/delete_race_test.go @@ -36,7 +36,7 @@ func Test_delete_race_successfully(t *testing.T) { AverageSpeed: 120, OutOfParcours: 0, UserID: 1, - RaceType: "manual", + Type: "manual", } databaseConnection.Create(&race) diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go index 6307d85..855b5ef 100644 --- a/src/tests/race/get_index_race_test.go +++ b/src/tests/race/get_index_race_test.go @@ -51,8 +51,8 @@ func Test_get_races_index(t *testing.T) { AverageSpeed: 120, OutOfParcours: 0, UserID: user.ID, - RaceType: "manual", - RaceStatus: "Not Started", + Type: "manual", + Status: "Not Started", } databaseConnection.Create(&race) diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index 6697440..cbc5f05 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -41,16 +41,9 @@ func Test_update_race_successfully(t *testing.T) { databaseConnection.Create(&race) updateBody, _ := json.Marshal(map[string]interface{}{ - "start_time": startTime.Add(-time.Minute).Format(time.RFC3339), // Updated start time - "end_time": endTime.Add(time.Minute).Format(time.RFC3339), // Updated end time - "number_of_collisions": 5, - "distance_travelled": 150, - "average_speed": 130, - "out_of_parcours": 1, - "user_id": 2, // Assume user ID is updated - "vehicle_id": vehicle.ID, - "race_type": "manual", - "race_status": "Not Started", + "start_time": startTime.Add(-time.Minute).Format(time.RFC3339), // Updated start time + "end_time": endTime.Add(time.Minute).Format(time.RFC3339), // Updated end time + "status": "not_started", }) requestURL := fmt.Sprintf("/api/races/%d", race.ID) diff --git a/src/validators/race/createRacesValidator.go b/src/validators/race/createRacesValidator.go index fead306..ee52657 100644 --- a/src/validators/race/createRacesValidator.go +++ b/src/validators/race/createRacesValidator.go @@ -14,26 +14,13 @@ type CreateRaceValidator struct { DistanceTravelled *int `json:"distance_travelled" validate:"required,min=0,gte=0"` AverageSpeed *int `json:"average_speed" validate:"required,min=0,gte=0"` OutOfParcours *uint8 `json:"out_of_parcours" validate:"required,min=0,gte=0"` - RaceType string `json:"race_type" validate:"required"` - RaceStatus string `json:"race_status" validate:"required,race_status_valid"` + Status string `json:"status" validate:"required,oneof='not_started' 'in_progress' 'completed'"` + Type string `json:"type" validate:"required,oneof='manual' 'auto'"` VehicleID uint `json:"vehicle_id" validate:"required"` } -func ValidateRaceStatus(fl validator.FieldLevel) bool { - status := fl.Field().String() - validStatuses := []string{"Not Started", "In Progress", "Completed"} - for _, validStatus := range validStatuses { - if status == validStatus { - return true - } - } - return false -} - func (c *CreateRaceValidator) Validate() error { validate := validator.New() - validate.RegisterValidation("race_status_valid", ValidateRaceStatus) - return validate.Struct(c) } diff --git a/src/validators/race/updateRaceStateValidator.go b/src/validators/race/updateRaceStateValidator.go deleted file mode 100644 index 6b74d66..0000000 --- a/src/validators/race/updateRaceStateValidator.go +++ /dev/null @@ -1,42 +0,0 @@ -package validators - -import ( - "errors" - "time" - - "github.com/go-playground/validator/v10" -) - -type UpdateRaceValidator struct { - StartTime time.Time `json:"start_time"` - EndTime *time.Time `json:"end_time" validate:"omitempty"` - RaceStatus string `json:"race_status" validate:"required,raceStatus"` -} - -// Custom validation function for RaceStatus enum -func raceStatusValidator(fl validator.FieldLevel) bool { - status := fl.Field().String() - validStatuses := []string{"Not Started", "In Progress", "Completed"} - - for _, validStatus := range validStatuses { - if status == validStatus { - return true - } - } - return false -} - -func (u *UpdateRaceValidator) Validate() error { - validate := validator.New() - - // Register the custom validator for RaceStatus - validate.RegisterValidation("raceStatus", raceStatusValidator) - - // Custom validation: check if EndTime is not before StartTime - if u.EndTime != nil && u.EndTime.Before(u.StartTime) { - return errors.New("end_time cannot be before start_time") - } - - // Standard validation based on struct tags - return validate.Struct(u) -} diff --git a/src/validators/race/updateRaceValidator.go b/src/validators/race/updateRaceValidator.go new file mode 100644 index 0000000..c74b376 --- /dev/null +++ b/src/validators/race/updateRaceValidator.go @@ -0,0 +1,25 @@ +package validators + +import ( + "errors" + "time" + + "github.com/go-playground/validator/v10" +) + +type UpdateRaceValidator struct { + EndTime *time.Time `json:"end_time" validate:"omitempty"` + Name string `json:"name"` + StartTime time.Time `json:"start_time"` + Status string `json:"status" validate:"oneof='not_started' 'in_progress' 'completed'"` +} + +func (u *UpdateRaceValidator) Validate() error { + validate := validator.New() + + if u.EndTime != nil && u.EndTime.Before(u.StartTime) { + return errors.New("end_time cannot be before start_time") + } + + return validate.Struct(u) +} From 8a91c2977e4f09205566e9f2838598263d107055 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Thu, 22 Aug 2024 16:37:02 +0200 Subject: [PATCH 28/42] add: unauthenticated server sent event route --- .../updateVehicleStateHandler.go | 20 ++++++++ src/models/vehicleState.go | 46 +++++++++++++++++ src/routes/public/setupPublicRoutes.go | 4 ++ src/services/server_side_event.go | 50 +++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 src/services/server_side_event.go diff --git a/src/controllers/vehicle_state/updateVehicleStateHandler.go b/src/controllers/vehicle_state/updateVehicleStateHandler.go index 1445adb..1580966 100644 --- a/src/controllers/vehicle_state/updateVehicleStateHandler.go +++ b/src/controllers/vehicle_state/updateVehicleStateHandler.go @@ -81,5 +81,25 @@ func UpdateVehicleStateHandler(c *gin.Context) { connection.Where("id = ?", vehiculeStateId).Preload(clause.Associations).First(&vehicleState) + err := broadcastUpdatedVehicleState(vehicleState) + + if err != nil { + services.SetInternalServerError(c, "Error while broadcasting vehicle state update") + + return + } + services.SetOK(c, "Vehicule state successfully updated", vehicleState) } + +func broadcastUpdatedVehicleState(vehicleState models.VehicleState) error { + json, err := vehicleState.ToJson() + + if err != nil { + return err + } + + go services.BroadcastMessage(json) + + return nil +} diff --git a/src/models/vehicleState.go b/src/models/vehicleState.go index 9d5d429..b98f3a0 100644 --- a/src/models/vehicleState.go +++ b/src/models/vehicleState.go @@ -1,5 +1,7 @@ package models +import "encoding/json" + type VehicleState struct { ID uint `gorm:"primaryKey" json:"id"` VehicleID uint `json:"-"` @@ -19,3 +21,47 @@ type VehicleState struct { UserID uint `json:"-"` Model } + +func (vehicleState *VehicleState) ToJson() (string, error) { + type vehicleStateJson struct { + Type string `json:"type"` + ID uint `json:"id"` + Attributes interface{} `json:"attributes"` + } + + type attributes struct { + Face *uint8 `json:"face"` + LedAnimation *uint8 `json:"led_animation"` + BuzzerAlarm *uint8 `json:"buzzer_alarm"` + VideoActivated *uint8 `json:"video_activated"` + PrimaryLedColor interface{} `json:"primary_led_color,omitempty"` + SecondaryLedColor interface{} `json:"secondary_led_color,omitempty"` + BuzzerVariable interface{} `json:"buzzer_variable,omitempty"` + HeadAngle interface{} `json:"head_angle,omitempty"` + } + + attr := attributes{ + Face: vehicleState.Face, + LedAnimation: vehicleState.LedAnimation, + BuzzerAlarm: vehicleState.BuzzerAlarm, + VideoActivated: vehicleState.VideoActivated, + PrimaryLedColor: vehicleState.PrimaryLedColor, + SecondaryLedColor: vehicleState.SecondaryLedColor, + BuzzerVariable: vehicleState.BuzzerVariable, + HeadAngle: vehicleState.HeadAngle, + } + + vsJson := vehicleStateJson{ + Type: "vehicle_state", + ID: vehicleState.ID, + Attributes: attr, + } + + jsonData, err := json.Marshal(vsJson) + + if err != nil { + return "", err + } + + return string(jsonData), nil +} diff --git a/src/routes/public/setupPublicRoutes.go b/src/routes/public/setupPublicRoutes.go index a7f75d2..7922bc9 100644 --- a/src/routes/public/setupPublicRoutes.go +++ b/src/routes/public/setupPublicRoutes.go @@ -2,6 +2,7 @@ package public import ( "api/src/controllers" + "api/src/services" "github.com/gin-gonic/gin" ) @@ -16,4 +17,7 @@ func SetupPublicRoutes(routerGroup *gin.RouterGroup) { "message": "Hello World!", }) }) + + //todo: passer cette route en authenticated + routerGroup.GET("/sse", services.SSEHandler) } diff --git a/src/services/server_side_event.go b/src/services/server_side_event.go new file mode 100644 index 0000000..d01d01e --- /dev/null +++ b/src/services/server_side_event.go @@ -0,0 +1,50 @@ +package services + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +var clients []chan string + +func SSEHandler(c *gin.Context) { + flusher, ok := c.Writer.(http.Flusher) + if !ok { + http.Error(c.Writer, "Streaming unsupported!", http.StatusInternalServerError) + return + } + + messageChan := make(chan string) + clients = append(clients, messageChan) + + c.Writer.Header().Set("Content-Type", "text/event-stream") + c.Writer.Header().Set("Cache-Control", "no-cache") + c.Writer.Header().Set("Connection", "keep-alive") + + defer func() { + for i, client := range clients { + if client == messageChan { + clients = append(clients[:i], clients[i+1:]...) + break + } + } + }() + + for { + select { + case msg := <-messageChan: + fmt.Fprintf(c.Writer, "data: %s\n\n", msg) + flusher.Flush() + case <-c.Request.Context().Done(): + return + } + } +} + +func BroadcastMessage(message string) { + for _, client := range clients { + client <- message + } +} From 88919752839d8ee4e5b1b5c2ba946dec50b5abce Mon Sep 17 00:00:00 2001 From: gaoubak Date: Fri, 23 Aug 2024 12:25:20 +0200 Subject: [PATCH 29/42] :bug: Fix model race add fk for user and vehicle and add change link to that --- src/controllers/fool/deleteFoolHandler.go | 32 ------------------- src/controllers/race/createRaceHandler.go | 4 ++- src/controllers/race/getAllRaceHandler.go | 2 +- .../race/getAllUsersRaceHandler.go | 2 +- src/controllers/race/updateRaceHandler.go | 2 +- src/models/model.go | 4 +-- src/models/race.go | 6 ++-- src/models/vehicle.go | 2 +- src/validators/race/updateRaceValidator.go | 13 ++------ 9 files changed, 16 insertions(+), 51 deletions(-) delete mode 100644 src/controllers/fool/deleteFoolHandler.go diff --git a/src/controllers/fool/deleteFoolHandler.go b/src/controllers/fool/deleteFoolHandler.go deleted file mode 100644 index ab82daa..0000000 --- a/src/controllers/fool/deleteFoolHandler.go +++ /dev/null @@ -1,32 +0,0 @@ -package handlers - -import ( - "fmt" - - "api/src/models" - "api/src/services" - - "github.com/gin-gonic/gin" -) - -func DeleteFoolHandler(c *gin.Context) { - foolId := c.Param("foolId") - - // Access the database connection - db := services.GetConnection() - - var existingFool models.Fool - if err := db.First(&existingFool, foolId).Error; err != nil { - services.SetNotFound(c, "Fool not found") - return - } - - fmt.Println("Existing fool to delete:", existingFool) - - if err := db.Delete(&existingFool).Error; err != nil { - services.SetInternalServerError(c, "Failed to delete fool") - return - } - - services.SetNoContent(c) -} diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index 8ae4a82..81a049b 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -50,7 +50,7 @@ func CreateRaceHandler(c *gin.Context) { Status: createRaceValidator.Status, Type: createRaceValidator.Type, VehicleID: createRaceValidator.VehicleID, - UserID: uint(userId), // Conversion de uint64 à uint + UserID: uint(userId), } // Récupération de la connexion à la base de données @@ -63,6 +63,8 @@ func CreateRaceHandler(c *gin.Context) { return } + db.Preload("Vehicle").Find(&race) + // Réponse de succès avec l'objet Race créé services.SetCreated(c, "Race created successfully", race) } diff --git a/src/controllers/race/getAllRaceHandler.go b/src/controllers/race/getAllRaceHandler.go index 01716e4..ae31c51 100644 --- a/src/controllers/race/getAllRaceHandler.go +++ b/src/controllers/race/getAllRaceHandler.go @@ -12,7 +12,7 @@ func GetAllRaceHandler(c *gin.Context) { var races []models.Race - if err := db.Find(&races).Error; err != nil { + if err := db.Preload("Vehicle").Find(&races).Error; err != nil { services.SetInternalServerError(c, "Failed to retrieve races") return } diff --git a/src/controllers/race/getAllUsersRaceHandler.go b/src/controllers/race/getAllUsersRaceHandler.go index 07412cb..b164197 100644 --- a/src/controllers/race/getAllUsersRaceHandler.go +++ b/src/controllers/race/getAllUsersRaceHandler.go @@ -22,7 +22,7 @@ func GetAllUsersRaceHandler(c *gin.Context) { return } - db.Where("user_id = ?", uint(userId)).Find(&races) + db.Where("user_id = ?", uint(userId)).Preload("Vehicle").Find(&races) services.SetOK(c, "User races retrieved successfully", races) } diff --git a/src/controllers/race/updateRaceHandler.go b/src/controllers/race/updateRaceHandler.go index 746a9bd..965e029 100644 --- a/src/controllers/race/updateRaceHandler.go +++ b/src/controllers/race/updateRaceHandler.go @@ -52,7 +52,7 @@ func UpdateRaceHandler(c *gin.Context) { } // Sauvegarde les modifications dans la base de données - if err := db.Save(&existingRace).Error; err != nil { + if err := db.Preload("Vehicle").Save(&existingRace).Error; err != nil { fmt.Printf("Error updating Race: %v\n", err) services.SetInternalServerError(c, "Failed to update Race") return diff --git a/src/models/model.go b/src/models/model.go index 4ec7c76..2b64f71 100644 --- a/src/models/model.go +++ b/src/models/model.go @@ -7,7 +7,7 @@ import ( ) type Model struct { - CreatedAt time.Time - UpdatedAt time.Time + CreatedAt time.Time `gorm:"type:datetime"` + UpdatedAt time.Time `gorm:"type:datetime"` DeletedAt gorm.DeletedAt `gorm:"index"` } diff --git a/src/models/race.go b/src/models/race.go index 74ba004..928eea3 100644 --- a/src/models/race.go +++ b/src/models/race.go @@ -8,8 +8,9 @@ type Race struct { ID uint `gorm:"primaryKey"` Name string `json:"name"` VehicleID uint `json:"vehicle_id"` - StartTime time.Time `json:"start_time"` - EndTime *time.Time `json:"end_time"` + Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` + StartTime time.Time `gorm:"type:datetime" json:"start_time"` + EndTime *time.Time `gorm:"type:datetime" json:"end_time"` NumberOfCollisions uint8 `json:"number_of_collisions"` DistanceTravelled int `json:"distance_travelled"` AverageSpeed int `json:"average_speed"` @@ -17,5 +18,6 @@ type Race struct { Status string `json:"status"` Type string `json:"type"` UserID uint `json:"user_id"` + User User `gorm:"foreignKey:UserID" json:"-"` Model } diff --git a/src/models/vehicle.go b/src/models/vehicle.go index ba8f736..6cab04d 100644 --- a/src/models/vehicle.go +++ b/src/models/vehicle.go @@ -4,7 +4,7 @@ import "gorm.io/gorm" type Vehicle struct { ID uint `gorm:"primaryKey"` - Name string `json:"vehicle_name"` + Name string `json:"name"` IpAdress string `json:"ip_adress"` IsAvailable bool `json:"is_available"` Model diff --git a/src/validators/race/updateRaceValidator.go b/src/validators/race/updateRaceValidator.go index c74b376..66e5a4f 100644 --- a/src/validators/race/updateRaceValidator.go +++ b/src/validators/race/updateRaceValidator.go @@ -1,25 +1,18 @@ package validators import ( - "errors" "time" "github.com/go-playground/validator/v10" ) type UpdateRaceValidator struct { - EndTime *time.Time `json:"end_time" validate:"omitempty"` - Name string `json:"name"` - StartTime time.Time `json:"start_time"` - Status string `json:"status" validate:"oneof='not_started' 'in_progress' 'completed'"` + EndTime *time.Time `json:"end_time" validate:"omitempty"` + Name string `json:"name"` + Status string `json:"status" validate:"oneof='not_started' 'in_progress' 'completed'"` } func (u *UpdateRaceValidator) Validate() error { validate := validator.New() - - if u.EndTime != nil && u.EndTime.Before(u.StartTime) { - return errors.New("end_time cannot be before start_time") - } - return validate.Struct(u) } From bf0ad0d5efeffa836a87498bffec3f521bf67f4d Mon Sep 17 00:00:00 2001 From: gaoubak Date: Fri, 23 Aug 2024 12:44:16 +0200 Subject: [PATCH 30/42] :wastebasket: Delete model , validator and any ref to sensorData and Fool --- main.go | 2 -- src/models/foolModel.go | 27 --------------- src/models/sensorDataModel.go | 33 ------------------- src/services/mqttService.go | 16 +++------ src/validators/fool/createFool.go | 14 -------- src/validators/sensorData/createSensorData.go | 16 --------- 6 files changed, 4 insertions(+), 104 deletions(-) delete mode 100644 src/models/foolModel.go delete mode 100644 src/models/sensorDataModel.go delete mode 100644 src/validators/fool/createFool.go delete mode 100644 src/validators/sensorData/createSensorData.go diff --git a/main.go b/main.go index 4f7b50a..15c00dd 100644 --- a/main.go +++ b/main.go @@ -62,9 +62,7 @@ func closeDatabaseConnection(database *gorm.DB) { func performMigrations(database *gorm.DB) { err := database.AutoMigrate( &models.User{}, - &models.SensorData{}, &models.Vehicle{}, - &models.Fool{}, &models.Race{}, &models.VehicleState{}, &models.PrimaryLedColor{}, diff --git a/src/models/foolModel.go b/src/models/foolModel.go deleted file mode 100644 index c7ced5b..0000000 --- a/src/models/foolModel.go +++ /dev/null @@ -1,27 +0,0 @@ -package models - -import ( - validators "api/src/validators/fool" -) - -type Fool struct { - ID uint `gorm:"primaryKey"` - Name string `json:"name"` - Model -} - -func (f *Fool) Create(createFool validators.CreateFoolValidator) error { - if err := createFool.Validate(); err != nil { - return err - } - f.Name = createFool.Name - return nil -} - -func (f *Fool) Update(updateFool validators.CreateFoolValidator) error { - if err := updateFool.Validate(); err != nil { - return err - } - f.Name = updateFool.Name - return nil -} diff --git a/src/models/sensorDataModel.go b/src/models/sensorDataModel.go deleted file mode 100644 index 9e04508..0000000 --- a/src/models/sensorDataModel.go +++ /dev/null @@ -1,33 +0,0 @@ -package models - -import ( - validators "api/src/validators/sensorData" -) - -type SensorData struct { - ID uint `gorm:"primaryKey"` - Light float64 - Sonar float64 - Track float64 - Model -} - -func (s *SensorData) Create(createSensorData validators.CreateSensorDataValidator) error { - if err := createSensorData.Validate(); err != nil { - return err - } - s.Light = createSensorData.Light - s.Sonar = createSensorData.Sonar - s.Track = createSensorData.Track - return nil -} - -func (s *SensorData) Update(updateSensorData validators.CreateSensorDataValidator) error { - if err := updateSensorData.Validate(); err != nil { - return err - } - s.Light = updateSensorData.Light - s.Sonar = updateSensorData.Sonar - s.Track = updateSensorData.Track - return nil -} diff --git a/src/services/mqttService.go b/src/services/mqttService.go index 78d51ab..ea6f718 100644 --- a/src/services/mqttService.go +++ b/src/services/mqttService.go @@ -1,7 +1,6 @@ package services import ( - "api/src/models" "fmt" "strconv" @@ -14,30 +13,23 @@ func messagePubHandler(client mqtt.Client, msg mqtt.Message) { fmt.Println(payload) fmt.Println(msg.Topic()) - value, err := strconv.ParseFloat(payload, 64) + _, err := strconv.ParseFloat(payload, 64) if err != nil { fmt.Println("Error converting payload to float:", err) return } - var sensorData models.SensorData - switch msg.Topic() { case "esp32/track": - sensorData.Track = value + fmt.Println("esp32/track") case "esp32/sonar": - sensorData.Sonar = value + fmt.Println("esp32/sonar") case "esp32/light": - sensorData.Light = value + fmt.Println("esp32/light") default: fmt.Println("Invalid topic") return } - - result := GetConnection().Create(&sensorData) - if result.Error != nil { - fmt.Println("Error inserting data into database:", result.Error) - } } func connectHandler(client mqtt.Client) { diff --git a/src/validators/fool/createFool.go b/src/validators/fool/createFool.go deleted file mode 100644 index dfb927c..0000000 --- a/src/validators/fool/createFool.go +++ /dev/null @@ -1,14 +0,0 @@ -package validators - -import ( - "github.com/go-playground/validator/v10" -) - -type CreateFoolValidator struct { - Name string `json:"name" validate:"required"` -} - -func (c *CreateFoolValidator) Validate() error { - validate := validator.New() - return validate.Struct(c) -} diff --git a/src/validators/sensorData/createSensorData.go b/src/validators/sensorData/createSensorData.go deleted file mode 100644 index f1cd06a..0000000 --- a/src/validators/sensorData/createSensorData.go +++ /dev/null @@ -1,16 +0,0 @@ -package validators - -import ( - "github.com/go-playground/validator/v10" -) - -type CreateSensorDataValidator struct { - Light float64 `json:"light" validate:"required,gte=0"` - Sonar float64 `json:"sonar" validate:"required,gte=0"` - Track float64 `json:"track" validate:"required,gte=0"` -} - -func (c *CreateSensorDataValidator) Validate() error { - validate := validator.New() - return validate.Struct(c) -} From 501f226dcf9d21c722b326ba59ab285406acc500 Mon Sep 17 00:00:00 2001 From: gaoubak Date: Fri, 23 Aug 2024 12:50:46 +0200 Subject: [PATCH 31/42] :sparkles: New model Vehicle Battery --- src/models/VehicleBatteryModel.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/models/VehicleBatteryModel.go diff --git a/src/models/VehicleBatteryModel.go b/src/models/VehicleBatteryModel.go new file mode 100644 index 0000000..884b3db --- /dev/null +++ b/src/models/VehicleBatteryModel.go @@ -0,0 +1,9 @@ +package models + +type VehicleBatteryModel struct { + ID uint `gorm:"primaryKey"` + VehicleID uint `gorm:"not null" json:"vehicle_id"` + Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` + BatteryValue int `gorm:"not null" json:"battery_value"` + Model +} From 94723fc1c68156f1911e95dbd4d18b3bd65c26c1 Mon Sep 17 00:00:00 2001 From: gaoubak Date: Fri, 23 Aug 2024 14:46:12 +0200 Subject: [PATCH 32/42] :sparkles: Add the model in the main go --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index 15c00dd..7c27181 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,7 @@ func performMigrations(database *gorm.DB) { &models.SecondaryLedColor{}, &models.BuzzerVariable{}, &models.HeadAngle{}, + &models.VehicleBatteryModel{}, ) if err != nil { From 7babac2cc3dd12ac15d2c627819379e6c9b48ae3 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Fri, 23 Aug 2024 15:00:04 +0200 Subject: [PATCH 33/42] rename: battery model --- main.go | 2 +- src/models/VehicleBattery.go | 9 +++++++++ src/models/VehicleBatteryModel.go | 9 --------- 3 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 src/models/VehicleBattery.go delete mode 100644 src/models/VehicleBatteryModel.go diff --git a/main.go b/main.go index 7c27181..8f9fd89 100644 --- a/main.go +++ b/main.go @@ -69,7 +69,7 @@ func performMigrations(database *gorm.DB) { &models.SecondaryLedColor{}, &models.BuzzerVariable{}, &models.HeadAngle{}, - &models.VehicleBatteryModel{}, + &models.VehicleBattery{}, ) if err != nil { diff --git a/src/models/VehicleBattery.go b/src/models/VehicleBattery.go new file mode 100644 index 0000000..0346c85 --- /dev/null +++ b/src/models/VehicleBattery.go @@ -0,0 +1,9 @@ +package models + +type VehicleBattery struct { + ID uint `gorm:"primaryKey"` + VehicleID uint `gorm:"not null" json:"vehicle_id"` + Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` + Value int `gorm:"not null" json:"value"` + Model +} diff --git a/src/models/VehicleBatteryModel.go b/src/models/VehicleBatteryModel.go deleted file mode 100644 index 884b3db..0000000 --- a/src/models/VehicleBatteryModel.go +++ /dev/null @@ -1,9 +0,0 @@ -package models - -type VehicleBatteryModel struct { - ID uint `gorm:"primaryKey"` - VehicleID uint `gorm:"not null" json:"vehicle_id"` - Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` - BatteryValue int `gorm:"not null" json:"battery_value"` - Model -} From d4c75ec732288dc285ae3aed4456be872482d667 Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Sun, 25 Aug 2024 13:00:40 +0200 Subject: [PATCH 34/42] Fix post race datetime error (#33) * feat: add custom time attribute * implement: custom time attribute * fix: race test failling due to wrong date format in body --- main.go | 12 ++++-- src/controllers/authController.go | 11 ++--- src/controllers/race/createRaceHandler.go | 12 +++++- src/controllers/race/updateRaceHandler.go | 20 ++++++--- src/models/attributes/customTime.go | 48 +++++++++++++++++++++ src/models/model.go | 9 ++-- src/models/race.go | 30 ++++++------- src/tests/race/create_race_test.go | 9 ++-- src/tests/race/delete_race_test.go | 6 +-- src/tests/race/get_index_race_test.go | 8 ++-- src/tests/race/update_race_test.go | 16 ++++--- src/validators/race/createRacesValidator.go | 22 +++++----- src/validators/race/updateRaceValidator.go | 8 ++-- 13 files changed, 145 insertions(+), 66 deletions(-) create mode 100644 src/models/attributes/customTime.go diff --git a/main.go b/main.go index 8f9fd89..4c8b009 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "api/src/models" + "api/src/models/attributes" "api/src/routes" "api/src/services" "fmt" @@ -173,12 +174,17 @@ func seedDatabase(database *gorm.DB) { raceNames := []string{"Morning Sprint", "Afternoon Challenge", "Evening Marathon"} for i := 0; i < 3; i++ { - startTime := time.Now().Add(time.Duration(i) * time.Hour) - endTime := startTime.Add(1 * time.Hour) + var startTime attributes.CustomTime + startTime.Time = time.Now().Add(time.Duration(i) * time.Hour) + + endTime := &attributes.CustomTime{} + + endTime.Time = startTime.Time.Add(time.Duration(i) * time.Minute) + race := models.Race{ VehicleID: vehicle.ID, StartTime: startTime, - EndTime: &endTime, + EndTime: endTime, NumberOfCollisions: uint8(i + 1), DistanceTravelled: 100 + (i * 10), AverageSpeed: 120 + (i * 5), diff --git a/src/controllers/authController.go b/src/controllers/authController.go index d12445d..bfb622b 100644 --- a/src/controllers/authController.go +++ b/src/controllers/authController.go @@ -2,6 +2,7 @@ package controllers import ( "api/src/models" + "api/src/models/attributes" "api/src/services" "errors" "net/http" @@ -32,11 +33,11 @@ type Claims struct { } type UserInfo struct { - ID uint `json:"id"` - Email string `json:"email"` - Username string `json:"username"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `json:"id"` + Email string `json:"email"` + Username string `json:"username"` + CreatedAt attributes.CustomTime `json:"created_at"` + UpdatedAt attributes.CustomTime `json:"updated_at"` } func Signup(c *gin.Context) { diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index 81a049b..214a420 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -2,6 +2,7 @@ package handlers import ( "api/src/models" + "api/src/models/attributes" "api/src/services" validators "api/src/validators/race" "fmt" @@ -38,11 +39,18 @@ func CreateRaceHandler(c *gin.Context) { return } + startTime := createRaceValidator.StartTime + var endTime *attributes.CustomTime + + if createRaceValidator.EndTime != nil { + endTime = createRaceValidator.EndTime + } + // Création du modèle Race avec les données validées race := models.Race{ Name: createRaceValidator.Name, - StartTime: createRaceValidator.StartTime, - EndTime: createRaceValidator.EndTime, + StartTime: startTime, + EndTime: endTime, NumberOfCollisions: *createRaceValidator.NumberOfCollisions, DistanceTravelled: *createRaceValidator.DistanceTravelled, AverageSpeed: *createRaceValidator.AverageSpeed, diff --git a/src/controllers/race/updateRaceHandler.go b/src/controllers/race/updateRaceHandler.go index 965e029..92b21fe 100644 --- a/src/controllers/race/updateRaceHandler.go +++ b/src/controllers/race/updateRaceHandler.go @@ -16,8 +16,14 @@ func UpdateRaceHandler(c *gin.Context) { // Récupère la course existante var existingRace models.Race + if err := db.First(&existingRace, raceID).Error; err != nil { - services.SetNotFound(c, "Race not found") + if err.Error() == "record not found" { + services.SetNotFound(c, err.Error()) + return + } + + services.SetInternalServerError(c, err.Error()) return } @@ -36,10 +42,12 @@ func UpdateRaceHandler(c *gin.Context) { // Mise à jour uniquement du champ End_time if raceValidator.EndTime != nil && !raceValidator.EndTime.IsZero() { - if raceValidator.EndTime.Before(existingRace.StartTime) { + if raceValidator.EndTime.Before(existingRace.StartTime.Time) { services.SetUnprocessableEntity(c, "EndTime cannot be before StartTime") + return } + existingRace.EndTime = raceValidator.EndTime } @@ -51,13 +59,13 @@ func UpdateRaceHandler(c *gin.Context) { existingRace.Status = raceValidator.Status } - // Sauvegarde les modifications dans la base de données - if err := db.Preload("Vehicle").Save(&existingRace).Error; err != nil { + if err := db.Save(&existingRace).Error; err != nil { fmt.Printf("Error updating Race: %v\n", err) - services.SetInternalServerError(c, "Failed to update Race") + services.SetInternalServerError(c, "Failed to update Race : "+err.Error()) return } - // Réponse de succès avec l'objet Race mis à jour + db.Preload("Vehicle").First(&existingRace, raceID) + services.SetOK(c, "Race updated successfully", existingRace) } diff --git a/src/models/attributes/customTime.go b/src/models/attributes/customTime.go new file mode 100644 index 0000000..9c97306 --- /dev/null +++ b/src/models/attributes/customTime.go @@ -0,0 +1,48 @@ +package attributes + +import ( + "database/sql/driver" + "fmt" + "time" +) + +type CustomTime struct { + time.Time +} + +const customTimeFormat = "2006-01-02 15:04:05" + +func (ct CustomTime) MarshalJSON() ([]byte, error) { + formatted := fmt.Sprintf("\"%s\"", ct.Format(customTimeFormat)) + return []byte(formatted), nil +} + +func (ct *CustomTime) UnmarshalJSON(b []byte) error { + str := string(b) + str = str[1 : len(str)-1] + t, err := time.Parse(customTimeFormat, str) + if err != nil { + return fmt.Errorf("invalid time format: %v", err) + } + ct.Time = t + return nil +} + +// Scan implémente l'interface sql.Scanner, permet de lire une valeur SQL et de la convertir en CustomTime +func (ct *CustomTime) Scan(value interface{}) error { + if value == nil { + *ct = CustomTime{Time: time.Time{}} + return nil + } + t, ok := value.(time.Time) + if !ok { + return fmt.Errorf("cannot scan type %T into CustomTime: %v", value, value) + } + *ct = CustomTime{Time: t} + return nil +} + +// Value implémente l'interface driver.Valuer, permet de convertir CustomTime en un type supporté par SQL +func (ct CustomTime) Value() (driver.Value, error) { + return ct.Time, nil +} diff --git a/src/models/model.go b/src/models/model.go index 2b64f71..241eb6b 100644 --- a/src/models/model.go +++ b/src/models/model.go @@ -1,13 +1,14 @@ package models import ( - "time" + "api/src/models/attributes" "gorm.io/gorm" ) type Model struct { - CreatedAt time.Time `gorm:"type:datetime"` - UpdatedAt time.Time `gorm:"type:datetime"` - DeletedAt gorm.DeletedAt `gorm:"index"` + // json format YYYY-MM-DD HH:MM:SS + CreatedAt attributes.CustomTime `gorm:"type:datetime" json:"created_at"` + UpdatedAt attributes.CustomTime `gorm:"type:datetime" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"type:datetime;index" json:"-"` } diff --git a/src/models/race.go b/src/models/race.go index 928eea3..be8068a 100644 --- a/src/models/race.go +++ b/src/models/race.go @@ -1,23 +1,23 @@ package models import ( - "time" + "api/src/models/attributes" ) type Race struct { - ID uint `gorm:"primaryKey"` - Name string `json:"name"` - VehicleID uint `json:"vehicle_id"` - Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` - StartTime time.Time `gorm:"type:datetime" json:"start_time"` - EndTime *time.Time `gorm:"type:datetime" json:"end_time"` - NumberOfCollisions uint8 `json:"number_of_collisions"` - DistanceTravelled int `json:"distance_travelled"` - AverageSpeed int `json:"average_speed"` - OutOfParcours uint8 `json:"out_of_parcours"` - Status string `json:"status"` - Type string `json:"type"` - UserID uint `json:"user_id"` - User User `gorm:"foreignKey:UserID" json:"-"` + ID uint `gorm:"primaryKey"` + Name string `json:"name"` + VehicleID uint `json:"vehicle_id"` + Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` + StartTime attributes.CustomTime `gorm:"type:datetime" json:"start_time"` + EndTime *attributes.CustomTime `gorm:"type:datetime" json:"end_time"` + NumberOfCollisions uint8 `json:"number_of_collisions"` + DistanceTravelled int `json:"distance_travelled"` + AverageSpeed int `json:"average_speed"` + OutOfParcours uint8 `json:"out_of_parcours"` + Status string `json:"status"` + Type string `json:"type"` + UserID uint `json:"user_id"` + User User `gorm:"foreignKey:UserID" json:"-"` Model } diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go index fe30fb3..fcbd7d5 100644 --- a/src/tests/race/create_race_test.go +++ b/src/tests/race/create_race_test.go @@ -2,6 +2,7 @@ package race import ( "api/src/models" + "api/src/models/attributes" "api/src/tests" "encoding/json" "fmt" @@ -33,14 +34,14 @@ func Test_create_race_successfully(t *testing.T) { databaseConnection.Create(&vehicle) // Define start and end times for the race - startTime := time.Now() - endTime := startTime.Add(time.Hour) + startTime := attributes.CustomTime{Time: time.Now()} + endTime := &attributes.CustomTime{Time: startTime.Add(time.Minute)} // Prepare the JSON body for the POST request createBody, _ := json.Marshal(map[string]interface{}{ "name": "testuser", - "start_time": startTime.Format(time.RFC3339), - "end_time": endTime.Format(time.RFC3339), + "start_time": startTime, + "end_time": endTime, "number_of_collisions": 5, "distance_travelled": 150, "average_speed": 130, diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go index 3718784..49f4f93 100644 --- a/src/tests/race/delete_race_test.go +++ b/src/tests/race/delete_race_test.go @@ -2,6 +2,7 @@ package race import ( "api/src/models" + "api/src/models/attributes" "api/src/tests" "fmt" "net/http" @@ -24,13 +25,12 @@ func Test_delete_race_successfully(t *testing.T) { } databaseConnection.Create(&vehicle) - startTime := time.Now() - endTime := startTime.Add(time.Hour) + var startTime attributes.CustomTime + startTime.Time = time.Now() race := models.Race{ VehicleID: vehicle.ID, StartTime: startTime, - EndTime: &endTime, NumberOfCollisions: 3, DistanceTravelled: 100, AverageSpeed: 120, diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go index 855b5ef..e9e8e1a 100644 --- a/src/tests/race/get_index_race_test.go +++ b/src/tests/race/get_index_race_test.go @@ -2,6 +2,7 @@ package race import ( "api/src/models" + "api/src/models/attributes" "api/src/tests" "encoding/json" "fmt" @@ -40,12 +41,13 @@ func Test_get_races_index(t *testing.T) { databaseConnection.Create(&vehicle) // Create a mock race associated with the user - startTime := time.Now() - endTime := startTime.Add(time.Hour) + var startTime attributes.CustomTime + startTime.Time = time.Now() + race := models.Race{ VehicleID: vehicle.ID, StartTime: startTime, - EndTime: &endTime, + EndTime: nil, NumberOfCollisions: 3, DistanceTravelled: 100, AverageSpeed: 120, diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index cbc5f05..6961764 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -2,6 +2,7 @@ package race import ( "api/src/models" + "api/src/models/attributes" "api/src/tests" "encoding/json" "fmt" @@ -25,13 +26,13 @@ func Test_update_race_successfully(t *testing.T) { } databaseConnection.Create(&vehicle) - startTime := time.Now() - endTime := startTime.Add(time.Hour) + var startTime attributes.CustomTime + startTime.Time = time.Now() race := models.Race{ VehicleID: vehicle.ID, StartTime: startTime, - EndTime: &endTime, + EndTime: nil, NumberOfCollisions: 3, DistanceTravelled: 100, AverageSpeed: 120, @@ -40,10 +41,13 @@ func Test_update_race_successfully(t *testing.T) { } databaseConnection.Create(&race) + endTime := &attributes.CustomTime{ + Time: startTime.Add(time.Minute), + } + updateBody, _ := json.Marshal(map[string]interface{}{ - "start_time": startTime.Add(-time.Minute).Format(time.RFC3339), // Updated start time - "end_time": endTime.Add(time.Minute).Format(time.RFC3339), // Updated end time - "status": "not_started", + "end_time": endTime, + "status": "completed", }) requestURL := fmt.Sprintf("/api/races/%d", race.ID) diff --git a/src/validators/race/createRacesValidator.go b/src/validators/race/createRacesValidator.go index ee52657..2b55076 100644 --- a/src/validators/race/createRacesValidator.go +++ b/src/validators/race/createRacesValidator.go @@ -1,22 +1,22 @@ package validators import ( - "time" + "api/src/models/attributes" "github.com/go-playground/validator/v10" ) type CreateRaceValidator struct { - Name string `json:"name" validate:"required"` - StartTime time.Time `json:"start_time" validate:"required"` - EndTime *time.Time `json:"end_time" validate:"omitempty,gtefield=StartTime"` - NumberOfCollisions *uint8 `json:"number_of_collisions" validate:"required,min=0,gte=0"` - DistanceTravelled *int `json:"distance_travelled" validate:"required,min=0,gte=0"` - AverageSpeed *int `json:"average_speed" validate:"required,min=0,gte=0"` - OutOfParcours *uint8 `json:"out_of_parcours" validate:"required,min=0,gte=0"` - Status string `json:"status" validate:"required,oneof='not_started' 'in_progress' 'completed'"` - Type string `json:"type" validate:"required,oneof='manual' 'auto'"` - VehicleID uint `json:"vehicle_id" validate:"required"` + Name string `json:"name" validate:"required"` + StartTime attributes.CustomTime `json:"start_time" validate:"required"` + EndTime *attributes.CustomTime `json:"end_time" validate:"omitempty,gtefield=StartTime"` + NumberOfCollisions *uint8 `json:"number_of_collisions" validate:"required,min=0,gte=0"` + DistanceTravelled *int `json:"distance_travelled" validate:"required,min=0,gte=0"` + AverageSpeed *int `json:"average_speed" validate:"required,min=0,gte=0"` + OutOfParcours *uint8 `json:"out_of_parcours" validate:"required,min=0,gte=0"` + Status string `json:"status" validate:"required,oneof='not_started' 'in_progress' 'completed'"` + Type string `json:"type" validate:"required,oneof='manual' 'auto'"` + VehicleID uint `json:"vehicle_id" validate:"required"` } func (c *CreateRaceValidator) Validate() error { diff --git a/src/validators/race/updateRaceValidator.go b/src/validators/race/updateRaceValidator.go index 66e5a4f..38fe80a 100644 --- a/src/validators/race/updateRaceValidator.go +++ b/src/validators/race/updateRaceValidator.go @@ -1,15 +1,15 @@ package validators import ( - "time" + "api/src/models/attributes" "github.com/go-playground/validator/v10" ) type UpdateRaceValidator struct { - EndTime *time.Time `json:"end_time" validate:"omitempty"` - Name string `json:"name"` - Status string `json:"status" validate:"oneof='not_started' 'in_progress' 'completed'"` + EndTime *attributes.CustomTime `json:"end_time"` + Name string `json:"name"` + Status string `json:"status" validate:"oneof='not_started' 'in_progress' 'completed' ''"` } func (u *UpdateRaceValidator) Validate() error { From 702b833e482b6dd86f207871c0096ed6d755bda3 Mon Sep 17 00:00:00 2001 From: A_Rijenth Date: Tue, 27 Aug 2024 19:54:24 +0200 Subject: [PATCH 35/42] Handle mqtt message to update race (#32) * rename: column in race model * feat: handle race model update from mqtt message * fix: database test not called because of wrong naming * minor fix * update: documentation * add: vehicle can end race through mqtt --- .../golang-api.postman_collection.json | 185 ++++++++---------- main.go | 24 +-- src/controllers/race/createRaceHandler.go | 36 ++-- src/models/race.go | 28 +-- src/services/mqttService.go | 42 ++-- src/services/mqtt_handler.go | 70 +++++++ ...g_database_is_working_as_expected_test.go} | 0 src/tests/race/create_race_test.go | 22 +-- src/tests/race/delete_race_test.go | 16 +- src/tests/race/get_index_race_test.go | 20 +- src/tests/race/update_race_test.go | 16 +- src/validators/race/createRacesValidator.go | 17 +- 12 files changed, 267 insertions(+), 209 deletions(-) create mode 100644 src/services/mqtt_handler.go rename src/tests/database/{testing_database_is_working_as_expected.go => testing_database_is_working_as_expected_test.go} (100%) diff --git a/documentation/golang-api.postman_collection.json b/documentation/golang-api.postman_collection.json index fe8fb1e..539f067 100644 --- a/documentation/golang-api.postman_collection.json +++ b/documentation/golang-api.postman_collection.json @@ -74,9 +74,14 @@ } }, "response": [] - }, + } + ] + }, + { + "name": "Race", + "item": [ { - "name": "Post", + "name": "Index", "request": { "auth": { "type": "bearer", @@ -88,33 +93,24 @@ } ] }, - "method": "POST", + "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "localhost:8000/api/vehicles", + "raw": "localhost:8000/api/races", "host": [ "localhost" ], "port": "8000", "path": [ "api", - "vehicles" + "races" ] } }, "response": [] }, { - "name": "Patch", + "name": "Index user races", "request": { "auth": { "type": "bearer", @@ -126,33 +122,32 @@ } ] }, - "method": "PATCH", + "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "{\n \n}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "localhost:8000/api/vehicles", + "raw": "localhost:8000/api/users/:userId/races", "host": [ "localhost" ], "port": "8000", "path": [ "api", - "vehicles" + "users", + ":userId", + "races" + ], + "variable": [ + { + "key": "userId", + "value": "4" + } ] } }, "response": [] }, { - "name": "Delete", + "name": "Post", "request": { "auth": { "type": "bearer", @@ -164,36 +159,41 @@ } ] }, - "method": "DELETE", + "method": "POST", "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"start_time\": \"2024-08-25 19:07:00\",\n \"type\": \"manual\",\n \"name\": \"ma course\",\n \"status\": \"not_started\",\n \"average_speed\": 0.13,\n \"vehicle_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "localhost:8000/api/vehicles/:id", + "raw": "localhost:8000/api/users/:userId/races", "host": [ "localhost" ], "port": "8000", "path": [ "api", - "vehicles", - ":id" + "users", + ":userId", + "races" ], "variable": [ { - "key": "id", - "value": "" + "key": "userId", + "value": "1" } ] } }, "response": [] - } - ] - }, - { - "name": "Race", - "item": [ + }, { - "name": "Index", + "name": "Patch", "request": { "auth": { "type": "bearer", @@ -205,39 +205,19 @@ } ] }, - "method": "GET", + "method": "PATCH", "header": [], - "url": { - "raw": "localhost:8000/api/races", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "api", - "races" - ] - } - }, - "response": [] - }, - { - "name": "Get", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{bearerToken}}", - "type": "string" + "body": { + "mode": "raw", + "raw": "{\n \"end_time\": \"2024-08-25 19:09:00\",\n \"status\": \"completed\"\n}", + "options": { + "raw": { + "language": "json" } - ] + } }, - "method": "GET", - "header": [], "url": { - "raw": "localhost:8000/api/races/:id", + "raw": "localhost:8000/api/races/:raceId", "host": [ "localhost" ], @@ -245,12 +225,12 @@ "path": [ "api", "races", - ":id" + ":raceId" ], "variable": [ { - "key": "id", - "value": "1" + "key": "raceId", + "value": "15" } ] } @@ -258,7 +238,7 @@ "response": [] }, { - "name": "Post", + "name": "Delete", "request": { "auth": { "type": "bearer", @@ -270,33 +250,30 @@ } ] }, - "method": "POST", + "method": "DELETE", "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "localhost:8000/api/races", + "raw": "localhost:8000/api/races/5", "host": [ "localhost" ], "port": "8000", "path": [ "api", - "races" + "races", + "5" ] } }, "response": [] - }, + } + ] + }, + { + "name": "Vehicle state", + "item": [ { - "name": "Patch", + "name": "get a vehicle state", "request": { "auth": { "type": "bearer", @@ -308,26 +285,17 @@ } ] }, - "method": "PATCH", + "method": "GET", "header": [], - "body": { - "mode": "raw", - "raw": "{\n \n}", - "options": { - "raw": { - "language": "json" - } - } - }, "url": { - "raw": "localhost:8000/api/races/:id", + "raw": "localhost:8000/api/vehicle-states/:id", "host": [ "localhost" ], "port": "8000", "path": [ "api", - "races", + "vehicle-states", ":id" ], "variable": [ @@ -341,7 +309,7 @@ "response": [] }, { - "name": "Delete", + "name": "patch a vehicle state", "request": { "auth": { "type": "bearer", @@ -353,17 +321,26 @@ } ] }, - "method": "DELETE", + "method": "PATCH", "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"face\": 0,\n \"led_animation\": 0,\n \"buzzer_alarm\": 0,\n \"video_activated\": 0,\n \"PrimaryLedColor\": {\n \"led_identifier\": 0,\n \"red\": 0,\n \"green\": 0,\n \"blue\": 0\n },\n \"SecondaryLedColor\": {\n \"binary_representation\": 0,\n \"red\": 0,\n \"green\": 0,\n \"blue\": 0\n },\n \"BuzzerVariable\": {\n \"activated\": 0,\n \"frequency\": 0\n },\n \"HeadAngle\": {\n \"vertical_angle\": 0,\n \"horizontal_angle\": 0\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { - "raw": "localhost:8000/api/races/:id", + "raw": "localhost:8000/api/vehicle-states/:id", "host": [ "localhost" ], "port": "8000", "path": [ "api", - "races", + "vehicle-states", ":id" ], "variable": [ diff --git a/main.go b/main.go index 4c8b009..20795c6 100644 --- a/main.go +++ b/main.go @@ -149,7 +149,7 @@ func seedDatabase(database *gorm.DB) { var vehicles []models.Vehicle var races []models.Race var userAlreadyHaveRaces bool - database.Find(&users) + database.Where("username IN ?", usernames).Find(&users) database.Find(&vehicles) for _, user := range users { @@ -182,17 +182,17 @@ func seedDatabase(database *gorm.DB) { endTime.Time = startTime.Time.Add(time.Duration(i) * time.Minute) race := models.Race{ - VehicleID: vehicle.ID, - StartTime: startTime, - EndTime: endTime, - NumberOfCollisions: uint8(i + 1), - DistanceTravelled: 100 + (i * 10), - AverageSpeed: 120 + (i * 5), - OutOfParcours: uint8(i % 2), - UserID: user.ID, - Type: "manual", - Status: "completed", - Name: raceNames[i], + VehicleID: vehicle.ID, + StartTime: startTime, + EndTime: endTime, + CollisionDuration: 0, + DistanceCovered: 100 + (i * 10), + AverageSpeed: 10 + float64(i), + OutOfParcours: 0, + UserID: user.ID, + Type: "manual", + Status: "completed", + Name: raceNames[i], } database.Create(&race) } diff --git a/src/controllers/race/createRaceHandler.go b/src/controllers/race/createRaceHandler.go index 214a420..b0bcc07 100644 --- a/src/controllers/race/createRaceHandler.go +++ b/src/controllers/race/createRaceHandler.go @@ -5,7 +5,6 @@ import ( "api/src/models/attributes" "api/src/services" validators "api/src/validators/race" - "fmt" "strconv" "github.com/gin-gonic/gin" @@ -39,6 +38,13 @@ func CreateRaceHandler(c *gin.Context) { return } + db := services.GetConnection() + + if db.Where("id = ?", userId).Find(&models.User{}).RowsAffected == 0 { + services.SetUnprocessableEntity(c, "User not found") + return + } + startTime := createRaceValidator.StartTime var endTime *attributes.CustomTime @@ -48,26 +54,22 @@ func CreateRaceHandler(c *gin.Context) { // Création du modèle Race avec les données validées race := models.Race{ - Name: createRaceValidator.Name, - StartTime: startTime, - EndTime: endTime, - NumberOfCollisions: *createRaceValidator.NumberOfCollisions, - DistanceTravelled: *createRaceValidator.DistanceTravelled, - AverageSpeed: *createRaceValidator.AverageSpeed, - OutOfParcours: *createRaceValidator.OutOfParcours, - Status: createRaceValidator.Status, - Type: createRaceValidator.Type, - VehicleID: createRaceValidator.VehicleID, - UserID: uint(userId), + Name: createRaceValidator.Name, + StartTime: startTime, + EndTime: endTime, + CollisionDuration: 0, + DistanceCovered: 0, + AverageSpeed: 0, + OutOfParcours: 0, + Status: createRaceValidator.Status, + Type: createRaceValidator.Type, + VehicleID: createRaceValidator.VehicleID, + UserID: uint(userId), } - // Récupération de la connexion à la base de données - db := services.GetConnection() - // Création de l'enregistrement dans la base de données if err := db.Create(&race).Error; err != nil { - fmt.Printf("Error creating Race: %v\n", err) - services.SetInternalServerError(c, "Failed to create Race") + services.SetInternalServerError(c, err.Error()) return } diff --git a/src/models/race.go b/src/models/race.go index be8068a..9737999 100644 --- a/src/models/race.go +++ b/src/models/race.go @@ -5,19 +5,19 @@ import ( ) type Race struct { - ID uint `gorm:"primaryKey"` - Name string `json:"name"` - VehicleID uint `json:"vehicle_id"` - Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` - StartTime attributes.CustomTime `gorm:"type:datetime" json:"start_time"` - EndTime *attributes.CustomTime `gorm:"type:datetime" json:"end_time"` - NumberOfCollisions uint8 `json:"number_of_collisions"` - DistanceTravelled int `json:"distance_travelled"` - AverageSpeed int `json:"average_speed"` - OutOfParcours uint8 `json:"out_of_parcours"` - Status string `json:"status"` - Type string `json:"type"` - UserID uint `json:"user_id"` - User User `gorm:"foreignKey:UserID" json:"-"` + ID uint `gorm:"primaryKey"` + Name string `json:"name"` + VehicleID uint `json:"vehicle_id"` + Vehicle Vehicle `gorm:"foreignKey:VehicleID" json:"vehicle"` + StartTime attributes.CustomTime `gorm:"type:datetime; not null" json:"start_time"` + EndTime *attributes.CustomTime `gorm:"type:datetime" json:"end_time"` + CollisionDuration int `gorm:"not null" json:"collision_duration"` + DistanceCovered int `gorm:"not null" json:"distance_covered"` + AverageSpeed float64 `gorm:"not null" json:"average_speed"` + OutOfParcours int `gorm:"not null" json:"out_of_parcours"` + Status string `gorm:"not null" json:"status"` + Type string `gorm:"not null" json:"type"` + UserID uint `json:"user_id"` + User User `gorm:"foreignKey:UserID" json:"-"` Model } diff --git a/src/services/mqttService.go b/src/services/mqttService.go index ea6f718..8eaf9b4 100644 --- a/src/services/mqttService.go +++ b/src/services/mqttService.go @@ -2,32 +2,44 @@ package services import ( "fmt" - "strconv" + "strings" mqtt "github.com/eclipse/paho.mqtt.golang" ) func messagePubHandler(client mqtt.Client, msg mqtt.Message) { - fmt.Println("Received message:") + topic := msg.Topic() payload := string(msg.Payload()) - fmt.Println(payload) - fmt.Println(msg.Topic()) - _, err := strconv.ParseFloat(payload, 64) - if err != nil { - fmt.Println("Error converting payload to float:", err) + messageParts := strings.Split(topic, "/") + + if messageParts[0] != "esp32" { + return + } + + if len(messageParts) < 4 { + fmt.Println("Invalid message received. Must have at least 4 parts.") return } - switch msg.Topic() { - case "esp32/track": - fmt.Println("esp32/track") - case "esp32/sonar": - fmt.Println("esp32/sonar") - case "esp32/light": - fmt.Println("esp32/light") + mqttHandler := MQTTHandler{} + + model := messageParts[1] + id := messageParts[2] + column := messageParts[3] + + // fmt.Println("############### MESSAGE RECEIVED ###############") + // fmt.Println(msg.Topic()) + // fmt.Println(payload) + // fmt.Println("models: ", model) + // fmt.Println("id: ", id) + // fmt.Println("column: ", column) + // fmt.Println("############# END #################") + + switch model { + case "races": + mqttHandler.HandleMQTTRaceData(id, column, payload) default: - fmt.Println("Invalid topic") return } } diff --git a/src/services/mqtt_handler.go b/src/services/mqtt_handler.go new file mode 100644 index 0000000..b823336 --- /dev/null +++ b/src/services/mqtt_handler.go @@ -0,0 +1,70 @@ +package services + +import ( + "api/src/models" + "fmt" + "strconv" + "strings" +) + +type MQTTHandler struct{} + +func (h MQTTHandler) HandleMQTTRaceData(id string, column string, payload string) { + connection := GetConnection() + + var race models.Race + + if connection.Where("id = ?", id).First(&race).RowsAffected == 0 { + return + } + + var columnToUpdate string = column + var valueToUpdate interface{} + payload = strings.TrimSpace(payload) + + switch column { + case "distance_covered", "out_of_parcours", "collision_duration": + // we are always expecting a float from the ESP32 because of the way we are sending the data + payloadToFloat, err := strconv.ParseFloat(payload, 64) + + if err != nil { + fmt.Printf("Error while converting payload to float for %s: %v\n", column, err) + return + } + + valueToUpdate = int(payloadToFloat) + + case "average_speed": + // we are always expecting a float from the ESP32 because of the way we are sending the data + payloadToFloat, err := strconv.ParseFloat(payload, 64) + + if err != nil { + fmt.Printf("Error while converting payload to float for %s: %v\n", column, err) + return + } + + valueToUpdate = payloadToFloat + + case "status": + valueToUpdate = payload + + default: + return + } + + if race.Status == "completed" { + fmt.Printf("['mqtt_handler] an update was requested for a completed race: %d - ignoring\n", race.ID) + return + } + + if race.Status == "not_started" { + connection.Model(&race).Update("status", "in_progress") + } + + if columnToUpdate == "status" { + connection.Model(&race).Update("status", valueToUpdate) + return + } + + connection.Model(&race).Update(columnToUpdate, valueToUpdate) +} diff --git a/src/tests/database/testing_database_is_working_as_expected.go b/src/tests/database/testing_database_is_working_as_expected_test.go similarity index 100% rename from src/tests/database/testing_database_is_working_as_expected.go rename to src/tests/database/testing_database_is_working_as_expected_test.go diff --git a/src/tests/race/create_race_test.go b/src/tests/race/create_race_test.go index fcbd7d5..4ec3e26 100644 --- a/src/tests/race/create_race_test.go +++ b/src/tests/race/create_race_test.go @@ -39,17 +39,17 @@ func Test_create_race_successfully(t *testing.T) { // Prepare the JSON body for the POST request createBody, _ := json.Marshal(map[string]interface{}{ - "name": "testuser", - "start_time": startTime, - "end_time": endTime, - "number_of_collisions": 5, - "distance_travelled": 150, - "average_speed": 130, - "out_of_parcours": 1, - "user_id": user.ID, - "vehicle_id": vehicle.ID, - "type": "manual", - "status": "not_started", + "name": "testuser", + "start_time": startTime, + "end_time": endTime, + "collision_duration": 5, + "distance_covered": 150, + "average_speed": 130, + "out_of_parcours": 1, + "user_id": user.ID, + "vehicle_id": vehicle.ID, + "type": "manual", + "status": "not_started", }) // Perform the authenticated request diff --git a/src/tests/race/delete_race_test.go b/src/tests/race/delete_race_test.go index 49f4f93..53e509d 100644 --- a/src/tests/race/delete_race_test.go +++ b/src/tests/race/delete_race_test.go @@ -29,14 +29,14 @@ func Test_delete_race_successfully(t *testing.T) { startTime.Time = time.Now() race := models.Race{ - VehicleID: vehicle.ID, - StartTime: startTime, - NumberOfCollisions: 3, - DistanceTravelled: 100, - AverageSpeed: 120, - OutOfParcours: 0, - UserID: 1, - Type: "manual", + VehicleID: vehicle.ID, + StartTime: startTime, + CollisionDuration: 3, + DistanceCovered: 100, + AverageSpeed: 10, + OutOfParcours: 0, + UserID: 1, + Type: "manual", } databaseConnection.Create(&race) diff --git a/src/tests/race/get_index_race_test.go b/src/tests/race/get_index_race_test.go index e9e8e1a..5bae393 100644 --- a/src/tests/race/get_index_race_test.go +++ b/src/tests/race/get_index_race_test.go @@ -45,16 +45,16 @@ func Test_get_races_index(t *testing.T) { startTime.Time = time.Now() race := models.Race{ - VehicleID: vehicle.ID, - StartTime: startTime, - EndTime: nil, - NumberOfCollisions: 3, - DistanceTravelled: 100, - AverageSpeed: 120, - OutOfParcours: 0, - UserID: user.ID, - Type: "manual", - Status: "Not Started", + VehicleID: vehicle.ID, + StartTime: startTime, + EndTime: nil, + CollisionDuration: 3, + DistanceCovered: 100, + AverageSpeed: 10, + OutOfParcours: 0, + UserID: user.ID, + Type: "manual", + Status: "Not Started", } databaseConnection.Create(&race) diff --git a/src/tests/race/update_race_test.go b/src/tests/race/update_race_test.go index 6961764..e437c62 100644 --- a/src/tests/race/update_race_test.go +++ b/src/tests/race/update_race_test.go @@ -30,14 +30,14 @@ func Test_update_race_successfully(t *testing.T) { startTime.Time = time.Now() race := models.Race{ - VehicleID: vehicle.ID, - StartTime: startTime, - EndTime: nil, - NumberOfCollisions: 3, - DistanceTravelled: 100, - AverageSpeed: 120, - OutOfParcours: 0, - UserID: 1, + VehicleID: vehicle.ID, + StartTime: startTime, + EndTime: nil, + CollisionDuration: 3, + DistanceCovered: 100, + AverageSpeed: 10, + OutOfParcours: 0, + UserID: 1, } databaseConnection.Create(&race) diff --git a/src/validators/race/createRacesValidator.go b/src/validators/race/createRacesValidator.go index 2b55076..78ff775 100644 --- a/src/validators/race/createRacesValidator.go +++ b/src/validators/race/createRacesValidator.go @@ -7,16 +7,13 @@ import ( ) type CreateRaceValidator struct { - Name string `json:"name" validate:"required"` - StartTime attributes.CustomTime `json:"start_time" validate:"required"` - EndTime *attributes.CustomTime `json:"end_time" validate:"omitempty,gtefield=StartTime"` - NumberOfCollisions *uint8 `json:"number_of_collisions" validate:"required,min=0,gte=0"` - DistanceTravelled *int `json:"distance_travelled" validate:"required,min=0,gte=0"` - AverageSpeed *int `json:"average_speed" validate:"required,min=0,gte=0"` - OutOfParcours *uint8 `json:"out_of_parcours" validate:"required,min=0,gte=0"` - Status string `json:"status" validate:"required,oneof='not_started' 'in_progress' 'completed'"` - Type string `json:"type" validate:"required,oneof='manual' 'auto'"` - VehicleID uint `json:"vehicle_id" validate:"required"` + Name string `json:"name" validate:"required"` + StartTime attributes.CustomTime `json:"start_time" validate:"required"` + EndTime *attributes.CustomTime `json:"end_time" validate:"omitempty,gtefield=StartTime"` + Status string `json:"status" validate:"required,oneof='not_started' 'in_progress' 'completed'"` + Type string `json:"type" validate:"required,oneof='manual' 'auto'"` + VehicleID uint `json:"vehicle_id" validate:"required"` + AverageSpeed float64 `json:"average_speed" validate:"required"` } func (c *CreateRaceValidator) Validate() error { From 196a6d2f045c27edbd6b698cc9b85b076c2a1ab3 Mon Sep 17 00:00:00 2001 From: Rijenth Date: Tue, 27 Aug 2024 20:02:42 +0200 Subject: [PATCH 36/42] hotfix: race seeding not working properly --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 20795c6..3ea60fb 100644 --- a/main.go +++ b/main.go @@ -187,7 +187,7 @@ func seedDatabase(database *gorm.DB) { EndTime: endTime, CollisionDuration: 0, DistanceCovered: 100 + (i * 10), - AverageSpeed: 10 + float64(i), + AverageSpeed: float64(i), OutOfParcours: 0, UserID: user.ID, Type: "manual", From 1e6691572c56b9355bd8714a2ee6f48a3575737d Mon Sep 17 00:00:00 2001 From: Rijenth Date: Tue, 27 Aug 2024 20:34:05 +0200 Subject: [PATCH 37/42] hotfix: remove average_speed from create race validator --- src/validators/race/createRacesValidator.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/validators/race/createRacesValidator.go b/src/validators/race/createRacesValidator.go index 78ff775..8de6498 100644 --- a/src/validators/race/createRacesValidator.go +++ b/src/validators/race/createRacesValidator.go @@ -7,13 +7,12 @@ import ( ) type CreateRaceValidator struct { - Name string `json:"name" validate:"required"` - StartTime attributes.CustomTime `json:"start_time" validate:"required"` - EndTime *attributes.CustomTime `json:"end_time" validate:"omitempty,gtefield=StartTime"` - Status string `json:"status" validate:"required,oneof='not_started' 'in_progress' 'completed'"` - Type string `json:"type" validate:"required,oneof='manual' 'auto'"` - VehicleID uint `json:"vehicle_id" validate:"required"` - AverageSpeed float64 `json:"average_speed" validate:"required"` + Name string `json:"name" validate:"required"` + StartTime attributes.CustomTime `json:"start_time" validate:"required"` + EndTime *attributes.CustomTime `json:"end_time" validate:"omitempty,gtefield=StartTime"` + Status string `json:"status" validate:"required,oneof='not_started' 'in_progress' 'completed'"` + Type string `json:"type" validate:"required,oneof='manual' 'auto'"` + VehicleID uint `json:"vehicle_id" validate:"required"` } func (c *CreateRaceValidator) Validate() error { From cb641c113e6f4fe07df3dee99ad1ad85b0f5965c Mon Sep 17 00:00:00 2001 From: Rijenth Date: Tue, 27 Aug 2024 20:48:21 +0200 Subject: [PATCH 38/42] hotfix: race end_time not filled when receiving race ended message --- src/services/mqtt_handler.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/services/mqtt_handler.go b/src/services/mqtt_handler.go index b823336..ebd6347 100644 --- a/src/services/mqtt_handler.go +++ b/src/services/mqtt_handler.go @@ -2,9 +2,11 @@ package services import ( "api/src/models" + "api/src/models/attributes" "fmt" "strconv" "strings" + "time" ) type MQTTHandler struct{} @@ -63,6 +65,16 @@ func (h MQTTHandler) HandleMQTTRaceData(id string, column string, payload string if columnToUpdate == "status" { connection.Model(&race).Update("status", valueToUpdate) + + if valueToUpdate == "completed" { + + race.EndTime = &attributes.CustomTime{ + Time: time.Now(), + } + + connection.Model(&race).Update("end_time", race.EndTime) + } + return } From 940da400e2b0291b060055f52b825c025835cdc9 Mon Sep 17 00:00:00 2001 From: jubs_kan3ki Date: Sat, 31 Aug 2024 14:20:07 +0200 Subject: [PATCH 39/42] feat(auth): enhance API with login rememberMe and welcome email on register --- .env.example | 4 ++ Dockerfile | 23 ++++++++++ docker-compose.yml | 51 ++++++++++++++++----- go.mod | 1 + go.sum | 4 ++ main.go | 12 ++++- src/config/config.go | 31 +++++++++++++ src/controllers/authController.go | 30 ++++-------- src/middleware/mailMiddleware.go | 52 +++++++++++++++++++++ src/routes/public/setupPublicRoutes.go | 8 ++-- src/routes/routes.go | 5 +- src/services/sendEmailService.go | 63 ++++++++++++++++++++++++++ src/templates/welcome_template.html | 15 ++++++ 13 files changed, 260 insertions(+), 39 deletions(-) create mode 100644 Dockerfile create mode 100644 src/config/config.go create mode 100644 src/middleware/mailMiddleware.go create mode 100644 src/services/sendEmailService.go create mode 100644 src/templates/welcome_template.html diff --git a/.env.example b/.env.example index 833c6a6..c969e47 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,10 @@ DB_HOST= DB_PORT= DB_NAME= +SMTP_HOST= +SMTP_USER= +SMTP_PASS= + ALLOW_DATABASE_SEEDING=false APP_FRONTEND_URL="http://localhost:5173" JWT_SECRET_KEY="example_key" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d235417 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM golang:1.21.6 as builder + +WORKDIR /app + +COPY go.mod go.sum ./ + +RUN go mod download + +COPY . . + +RUN go build -o main . + +FROM golang:1.21.6 + +WORKDIR /app + +COPY --from=builder /app/main . + +COPY --from=builder /app/src ./src + +EXPOSE 8000 + +CMD ["./main"] diff --git a/docker-compose.yml b/docker-compose.yml index 9f9f7e8..a8bd9aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,23 @@ -services: +version: '3.9' + +services: api: - build: + build: context: . dockerfile: Dockerfile_dev container_name: go environment: - Port: 8000 + - PORT=8000 + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DB_NAME=${DB_NAME} + - DB_USER=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + - SMTP_HOST=${SMTP_HOST} + - SMTP_USER=${SMTP_USER} + - SMTP_PASS=${SMTP_PASS} + - EMAIL_FROM=${SMTP_USER} + - TEMPLATE_PATH=/app/src/templates ports: - "8000:8000" volumes: @@ -16,29 +28,36 @@ services: depends_on: mysql: condition: service_healthy + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:8000/health" ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s mosquitto: image: eclipse-mosquitto + container_name: mosquitto ports: - - "1883:1883" - - "9001:9001" + - "1883:1883" + - "9001:9001" volumes: - - ./mosquitto.conf:/mosquitto/config/mosquitto.conf + - ./mosquitto.conf:/mosquitto/config/mosquitto.conf restart: always networks: - goland-network mysql: container_name: mysql + image: mysql:latest environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: database healthcheck: - test: ["CMD", "mysql", "-uroot", "-proot", "-e", "SELECT 1"] + test: [ "CMD", "mysql", "-uroot", "-proot", "-e", "SELECT 1" ] interval: 5s timeout: 2s retries: 3 - image: mysql:latest ports: - "3306:3306" restart: always @@ -49,17 +68,27 @@ services: adminer: container_name: adminer + image: adminer depends_on: - mysql - image: adminer ports: - - 8080:8080 + - "8080:8080" restart: always networks: - goland-network + email_service: + image: bytemark/smtp + container_name: smtp + environment: + - SMTP_USER=${SMTP_USER} + - SMTP_PASS=${SMTP_PASS} + ports: + - "1025:25" + restart: always + networks: + - goland-network networks: goland-network: driver: bridge - \ No newline at end of file diff --git a/go.mod b/go.mod index 0ec1f4a..5a9a5db 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.25.0 + gopkg.in/mail.v2 v2.3.1 gorm.io/driver/mysql v1.5.7 gorm.io/driver/sqlite v1.5.6 gorm.io/gorm v1.25.11 diff --git a/go.sum b/go.sum index 557ed21..11e8a84 100644 --- a/go.sum +++ b/go.sum @@ -110,9 +110,13 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= 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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 3ea60fb..c4d191f 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "api/src/config" "api/src/models" "api/src/models/attributes" "api/src/routes" @@ -210,13 +211,20 @@ func initMQTT() { } func startWebServer() { + cfg, err := config.LoadConfig() + if err != nil { + log.Fatalf("Failed to load configuration: %v", err) + } + router := gin.New() router.Use(gin.Logger()) router.Use(gin.Recovery()) - router = routes.SetupRouter(router) + router = routes.SetupRouter(router, cfg) - router.Run(":8000") + if err := router.Run(":8000"); err != nil { + log.Fatalf("Failed to start server: %v", err) + } fmt.Println("Server started on port 8000.") } diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 0000000..59f3d03 --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,31 @@ +package config + +import ( + "os" +) + +type Config struct { + DbHost string + DbPort string + DbName string + DbUser string + DbPassword string + SmtpHost string + SmtpUser string + SmtpPass string + EmailFrom string +} + +func LoadConfig() (*Config, error) { + return &Config{ + DbHost: os.Getenv("DB_HOST"), + DbPort: os.Getenv("DB_PORT"), + DbName: os.Getenv("DB_NAME"), + DbUser: os.Getenv("DB_USERNAME"), + DbPassword: os.Getenv("DB_PASSWORD"), + SmtpHost: os.Getenv("SMTP_HOST"), + SmtpUser: os.Getenv("SMTP_USER"), + SmtpPass: os.Getenv("SMTP_PASS"), + EmailFrom: os.Getenv("SMTP_USER"), + }, nil +} diff --git a/src/controllers/authController.go b/src/controllers/authController.go index bfb622b..45a29c0 100644 --- a/src/controllers/authController.go +++ b/src/controllers/authController.go @@ -50,13 +50,11 @@ func Signup(c *gin.Context) { if (creds.Email == "") || (creds.Password == "") { services.SetUnprocessableEntity(c, "Email address and password are required") - return } if !services.EmailValidator(creds.Email) { services.SetUnprocessableEntity(c, "User email address is invalid") - return } @@ -64,21 +62,17 @@ func Signup(c *gin.Context) { if connection.Where("email = ?", creds.Email).First(&models.User{}).RowsAffected > 0 { services.SetUnprocessableEntity(c, "A user with this email address already exists") - return } if creds.Username != nil && connection.Where("username = ?", *creds.Username).First(&models.User{}).RowsAffected > 0 { services.SetUnprocessableEntity(c, "A user with this username already exists") - return } hashedPassword, err := services.HashPassword(creds.Password) - if err != nil { services.SetInternalServerError(c, "Internal server error while hashing password") - return } @@ -86,7 +80,6 @@ func Signup(c *gin.Context) { var errorOccuredInTransaction error connection.Transaction(func(transaction *gorm.DB) error { - user = models.User{ Username: creds.Username, Email: creds.Email, @@ -94,34 +87,27 @@ func Signup(c *gin.Context) { } result := transaction.Create(&user) - if result.Error != nil { errorOccuredInTransaction = result.Error - return errorOccuredInTransaction } vehicles := []models.Vehicle{} - transaction.Find(&vehicles) if len(vehicles) == 0 { - errorOccuredInTransaction = errors.New( "cannot register a user because no vehicles are available in the database", ) - return errorOccuredInTransaction } for _, vehicle := range vehicles { _, createVehiculeStateError := vehicle.InitVehicleState(&user, transaction) - if createVehiculeStateError != nil { errorOccuredInTransaction = errors.New( createVehiculeStateError.Error(), ) - return createVehiculeStateError } } @@ -130,10 +116,14 @@ func Signup(c *gin.Context) { if errorOccuredInTransaction != nil { services.SetInternalServerError(c, errorOccuredInTransaction.Error()) - return } + c.Set("user_email", user.Email) + if user.Username != nil { + c.Set("username", *user.Username) + } + services.SetCreated(c, "User created", user) } @@ -145,20 +135,19 @@ func Login(c *gin.Context) { return } + rememberMe := c.DefaultPostForm("remember_me", "false") == "true" + if (creds.Email == "") || (creds.Password == "") { services.SetUnprocessableEntity(c, "Email address and password are required") - return } if !services.EmailValidator(creds.Email) { services.SetUnprocessableEntity(c, "User email address is invalid") - return } var user models.User - query := services.GetConnection().Where("email = ?", creds.Email).Find(&user) if query.Error != nil { @@ -166,7 +155,6 @@ func Login(c *gin.Context) { services.SetNotFound(c, "Invalid credentials") return } - services.SetInternalServerError(c, "Internal server error") return } @@ -177,6 +165,9 @@ func Login(c *gin.Context) { } expirationTime := time.Now().Add(3 * time.Hour) + if rememberMe { + expirationTime = time.Now().Add(24 * time.Hour) + } claims := &Claims{ Email: creds.Email, @@ -188,7 +179,6 @@ func Login(c *gin.Context) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtKey) - if err != nil { services.SetInternalServerError(c, "Internal server error") return diff --git a/src/middleware/mailMiddleware.go b/src/middleware/mailMiddleware.go new file mode 100644 index 0000000..39b8121 --- /dev/null +++ b/src/middleware/mailMiddleware.go @@ -0,0 +1,52 @@ +// middleware/send_email.go +package middleware + +import ( + "api/src/config" + "api/src/services" + "log" + + "github.com/gin-gonic/gin" +) + +func SendWelcomeEmailMiddleware(cfg *config.Config) gin.HandlerFunc { + return func(c *gin.Context) { + c.Next() + + if c.Writer.Status() == 201 { + email, emailExists := c.Get("user_email") + username, usernameExists := c.Get("username") + + if !emailExists { + log.Println("user_email not found in context") + return + } + + if !usernameExists { + log.Println("username not found in context") + return + } + + log.Printf("Preparing to send welcome email to: %s", email) + + emailRequest := services.EmailRequest{ + To: email.(string), + Subject: "Welcome to TechRace!", + Template: "welcome_template.html", + Data: map[string]interface{}{ + "Username": username.(string), + }, + } + + err := services.SendEmail(emailRequest, cfg) + if err != nil { + log.Printf("Failed to send welcome email: %v", err) + c.JSON(500, gin.H{"error": "Failed to send welcome email"}) + } else { + log.Println("Welcome email sent successfully") + } + } else { + log.Printf("Response status is not 201: %d", c.Writer.Status()) + } + } +} diff --git a/src/routes/public/setupPublicRoutes.go b/src/routes/public/setupPublicRoutes.go index 7922bc9..13b8e53 100644 --- a/src/routes/public/setupPublicRoutes.go +++ b/src/routes/public/setupPublicRoutes.go @@ -1,15 +1,16 @@ package public import ( + "api/src/config" "api/src/controllers" + "api/src/middleware" "api/src/services" "github.com/gin-gonic/gin" ) -func SetupPublicRoutes(routerGroup *gin.RouterGroup) { - routerGroup.POST("/signup", controllers.Signup) - +func SetupPublicRoutes(routerGroup *gin.RouterGroup, cfg *config.Config) { + routerGroup.POST("/signup", controllers.Signup, middleware.SendWelcomeEmailMiddleware(cfg)) routerGroup.POST("/login", controllers.Login) routerGroup.GET("/hello", func(c *gin.Context) { @@ -18,6 +19,5 @@ func SetupPublicRoutes(routerGroup *gin.RouterGroup) { }) }) - //todo: passer cette route en authenticated routerGroup.GET("/sse", services.SSEHandler) } diff --git a/src/routes/routes.go b/src/routes/routes.go index 7827e37..dbecda8 100644 --- a/src/routes/routes.go +++ b/src/routes/routes.go @@ -1,6 +1,7 @@ package routes import ( + "api/src/config" "api/src/routes/protected" "api/src/routes/public" "os" @@ -28,12 +29,12 @@ func SetupCors(router *gin.Engine) *gin.Engine { return router } -func SetupRouter(router *gin.Engine) *gin.Engine { +func SetupRouter(router *gin.Engine, cfg *config.Config) *gin.Engine { router = SetupCors(router) apiGroup := router.Group("/api") - public.SetupPublicRoutes(apiGroup) + public.SetupPublicRoutes(apiGroup, cfg) protected.SetupProtectedRoutes(apiGroup) diff --git a/src/services/sendEmailService.go b/src/services/sendEmailService.go new file mode 100644 index 0000000..f9b75bd --- /dev/null +++ b/src/services/sendEmailService.go @@ -0,0 +1,63 @@ +// services/send_email.go +package services + +import ( + "bytes" + "html/template" + "log" + "os" + "path/filepath" + + "api/src/config" + + "gopkg.in/mail.v2" +) + +type EmailRequest struct { + To string + Subject string + Template string + Data map[string]interface{} +} + +func SendEmail(request EmailRequest, cfg *config.Config) error { + templatePath := os.Getenv("TEMPLATE_PATH") + fullPath := filepath.Join(templatePath, request.Template) + + log.Printf("Loading email template from: %s", fullPath) + + t, err := template.ParseFiles(fullPath) + if err != nil { + log.Printf("Error loading template: %v", err) + return err + } + + var buf bytes.Buffer + err = t.Execute(&buf, request.Data) + if err != nil { + log.Printf("Error executing template: %v", err) + return err + } + + log.Printf("Sending email to: %s with subject: %s", request.To, request.Subject) + + m := mail.NewMessage() + m.SetHeader("From", cfg.EmailFrom) + m.SetHeader("To", request.To) + m.SetHeader("Subject", request.Subject) + m.SetBody("text/html", buf.String()) + + d := mail.NewDialer(cfg.SmtpHost, 587, cfg.SmtpUser, cfg.SmtpPass) + + // Log SMTP configuration avant l'envoi + log.Printf("SMTP Host: %s, SMTP User: %s", cfg.SmtpHost, cfg.SmtpUser) + + if err := d.DialAndSend(m); err != nil { + log.Printf("Error sending email: %v", err) + return err + } + + log.Println("Email sent successfully") + + return nil +} diff --git a/src/templates/welcome_template.html b/src/templates/welcome_template.html new file mode 100644 index 0000000..a17f3be --- /dev/null +++ b/src/templates/welcome_template.html @@ -0,0 +1,15 @@ + + + + + Bienvenue sur TechRace ! + + +

Bienvenue sur TechRace, {{.Username}}!

+

Nous sommes ravis de vous accueillir au sein de notre communauté TechRace, où vous pouvez explorer le monde passionnant des voitures télécommandées.

+

Préparez-vous à découvrir des courses palpitantes, des véhicules innovants, et une communauté de passionnés tout aussi enthousiastes que vous.

+

Si vous avez des questions ou avez besoin d'assistance, notre équipe est là pour vous aider. N'hésitez pas à nous contacter à tout moment !

+

Merci de nous avoir rejoints et amusez-vous bien,

+

L'équipe TechRace

+ + From c428da3652704c11761f2e64c3a7a2ed1101d06f Mon Sep 17 00:00:00 2001 From: jubs_kan3ki Date: Sat, 31 Aug 2024 14:35:41 +0200 Subject: [PATCH 40/42] fix(tests): correct SetupPublicRoutes argument error in setup_test_router.go --- src/middleware/mailMiddleware.go | 1 - src/tests/setup_test_router.go | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/middleware/mailMiddleware.go b/src/middleware/mailMiddleware.go index 39b8121..ce31757 100644 --- a/src/middleware/mailMiddleware.go +++ b/src/middleware/mailMiddleware.go @@ -1,4 +1,3 @@ -// middleware/send_email.go package middleware import ( diff --git a/src/tests/setup_test_router.go b/src/tests/setup_test_router.go index bd86d20..5c054a1 100644 --- a/src/tests/setup_test_router.go +++ b/src/tests/setup_test_router.go @@ -1,8 +1,10 @@ package tests import ( + "api/src/config" "api/src/routes/protected" "api/src/routes/public" + "log" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" @@ -23,7 +25,12 @@ func setupTestRouter(router *gin.Engine) *gin.Engine { apiGroup := router.Group("/api") - public.SetupPublicRoutes(apiGroup) + cfg, err := config.LoadConfig() + if err != nil { + log.Fatalf("Failed to load configuration: %v", err) + } + + public.SetupPublicRoutes(apiGroup, cfg) protected.SetupProtectedRoutes(apiGroup) From 8dae58b18e8b40474bf96064977e3f99a091b274 Mon Sep 17 00:00:00 2001 From: jubs_kan3ki Date: Sat, 31 Aug 2024 14:44:52 +0200 Subject: [PATCH 41/42] fix(tests): resolve database and API response issues in auth signup tests --- ...if_email_and_password_are_provided_test.go | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go b/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go index fc52a8f..45049df 100644 --- a/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go +++ b/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go @@ -4,6 +4,7 @@ import ( "api/src/models" "api/src/tests" "encoding/json" + "log" "net/http" "testing" @@ -14,37 +15,58 @@ import ( func Test_can_signup_if_email_and_password_are_provided_test(t *testing.T) { gin.SetMode(gin.TestMode) + // Initialisez la connexion à la base de données de test databaseConnection := tests.GetTestDBConnection() - // Créer une requête de test et la table associée - databaseConnection.AutoMigrate(&models.User{}, &models.Vehicle{}, &models.VehicleState{}) + // Effectuez les migrations pour toutes les tables nécessaires + err := databaseConnection.AutoMigrate( + &models.User{}, + &models.Vehicle{}, + &models.VehicleState{}, + &models.PrimaryLedColor{}, // Ajoutez toutes les tables nécessaires ici + &models.SecondaryLedColor{}, + &models.BuzzerVariable{}, + &models.HeadAngle{}, + &models.VehicleBattery{}, + ) + if err != nil { + log.Fatalf("Failed to migrate database: %v", err) + } + // Préparez les données nécessaires pour le test databaseConnection.Create(&models.Vehicle{ - Name: "a vehicule is required to enable any user to signup", + Name: "A vehicle is required to enable any user to signup", }) + // Données utilisateur pour le test user := map[string]string{ "email": "test@example.com", "password": "password", } body, _ := json.Marshal(user) - // On effecture une requête unauthenticated + // Effectuez une requête POST non authentifiée pour le signup requestRecorder, _ := tests.PerformUnAuthenticatedRequest(http.MethodPost, "/api/signup", body) - // Vérifier le statut de la réponse - assert.Equal(t, http.StatusCreated, requestRecorder.Code) + // Vérifiez le statut de la réponse + assert.Equal(t, http.StatusCreated, requestRecorder.Code, "Expected status code 201, got %d", requestRecorder.Code) - // Vérifier le contenu de la réponse - var response map[string]string - json.Unmarshal(requestRecorder.Body.Bytes(), &response) - assert.Equal(t, "User created", response["message"]) + // Vérifiez le contenu de la réponse + var response map[string]interface{} + err = json.Unmarshal(requestRecorder.Body.Bytes(), &response) + if err != nil { + t.Fatalf("Failed to unmarshal response body: %v", err) + } + assert.Equal(t, "User created", response["message"], "Expected message 'User created', got '%s'", response["message"]) - // Vérifier que l'utilisateur a été créé dans la base de données + // Vérifiez que l'utilisateur a été créé dans la base de données var createdUser models.User - databaseConnection.Where("email = ?", user["email"]).First(&createdUser) - assert.Equal(t, user["email"], createdUser.Email) + result := databaseConnection.Where("email = ?", user["email"]).First(&createdUser) + if result.Error != nil { + t.Fatalf("Expected user with email %s to be created, but got error: %v", user["email"], result.Error) + } + assert.Equal(t, user["email"], createdUser.Email, "Expected email to be '%s', got '%s'", user["email"], createdUser.Email) - // On retire l'utilisateur de la base de données + // Nettoyez la base de données en supprimant l'utilisateur créé databaseConnection.Unscoped().Delete(&createdUser) } From 2179f3dfafb4441c163c2c950bf4888fb65dbe12 Mon Sep 17 00:00:00 2001 From: jubs_kan3ki Date: Sat, 31 Aug 2024 14:50:17 +0200 Subject: [PATCH 42/42] fix(tests): define SecondaryLedColor in signup tests to fix build error --- .../can_signup_if_email_and_password_are_provided_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go b/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go index 45049df..bf74e12 100644 --- a/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go +++ b/src/tests/auth/signup/can_signup_if_email_and_password_are_provided_test.go @@ -23,7 +23,7 @@ func Test_can_signup_if_email_and_password_are_provided_test(t *testing.T) { &models.User{}, &models.Vehicle{}, &models.VehicleState{}, - &models.PrimaryLedColor{}, // Ajoutez toutes les tables nécessaires ici + &models.PrimaryLedColor{}, &models.SecondaryLedColor{}, &models.BuzzerVariable{}, &models.HeadAngle{},