backend: add car
This commit is contained in:
118
go-backend/internal/controllers/car_controller.go
Normal file
118
go-backend/internal/controllers/car_controller.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"GoMembership/internal/constants"
|
||||
"GoMembership/internal/models"
|
||||
"GoMembership/internal/services"
|
||||
"GoMembership/internal/utils"
|
||||
"GoMembership/pkg/errors"
|
||||
"GoMembership/pkg/logger"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CarController struct {
|
||||
S services.CarServiceInterface
|
||||
UserService services.UserServiceInterface
|
||||
}
|
||||
|
||||
func (cr *CarController) Create(c *gin.Context) {
|
||||
requestUser, err := cr.UserService.FromContext(c)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error extracting user from context in Create car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||
return
|
||||
}
|
||||
if !requestUser.HasPrivilege(constants.Priviliges.Create) {
|
||||
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to create a car. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Create), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||
return
|
||||
}
|
||||
var newCar models.Car
|
||||
if err := c.ShouldBindJSON(&newCar); err != nil {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
}
|
||||
car, err := cr.S.Create(&newCar)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error creating car", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, car)
|
||||
}
|
||||
|
||||
func (cr *CarController) Update(c *gin.Context) {
|
||||
requestUser, err := cr.UserService.FromContext(c)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error extracting user from context in Update car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||
return
|
||||
}
|
||||
if !requestUser.HasPrivilege(constants.Priviliges.Update) {
|
||||
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to update a car. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Update), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||
return
|
||||
}
|
||||
var car models.Car
|
||||
if err := c.ShouldBindJSON(&car); err != nil {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
}
|
||||
logger.Error.Printf("updating car: %v", car)
|
||||
updatedCar, err := cr.S.Update(&car)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error updating car", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, updatedCar)
|
||||
}
|
||||
|
||||
func (cr *CarController) GetAll(c *gin.Context) {
|
||||
requestUser, err := cr.UserService.FromContext(c)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error extracting user from context in GetAll car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||
return
|
||||
}
|
||||
|
||||
if !requestUser.HasPrivilege(constants.Priviliges.View) {
|
||||
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to access car data. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Delete), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
cars, err := cr.S.GetAll()
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error getting cars", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"cars": cars,
|
||||
})
|
||||
}
|
||||
|
||||
func (cr *CarController) Delete(c *gin.Context) {
|
||||
type input struct {
|
||||
Car struct {
|
||||
ID uint `json:"id" binding:"required,numeric"`
|
||||
} `json:"car"`
|
||||
}
|
||||
var deleteData input
|
||||
requestUser, err := cr.UserService.FromContext(c)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error extracting user from context in Delete car handler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||
return
|
||||
}
|
||||
|
||||
if !requestUser.HasPrivilege(constants.Priviliges.Delete) {
|
||||
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to delete a car. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.Delete), http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&deleteData); err != nil {
|
||||
utils.HandleValidationError(c, err)
|
||||
return
|
||||
}
|
||||
err = cr.S.Delete(&deleteData.Car.ID)
|
||||
if err != nil {
|
||||
utils.RespondWithError(c, err, "Error deleting car", http.StatusInternalServerError, errors.Responses.Fields.Car, errors.Responses.Keys.InternalServerError)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, "Car deleted")
|
||||
}
|
||||
@@ -29,6 +29,10 @@ func Open(dbPath string, adminMail string) (*gorm.DB, error) {
|
||||
&models.Verification{},
|
||||
&models.Licence{},
|
||||
&models.Category{},
|
||||
&models.Insurance{},
|
||||
&models.Car{},
|
||||
&models.Location{},
|
||||
&models.Damage{},
|
||||
&models.BankAccount{}); err != nil {
|
||||
logger.Error.Fatalf("Couldn't create database: %v", err)
|
||||
return nil, err
|
||||
|
||||
13
go-backend/internal/models/Insurance.go
Normal file
13
go-backend/internal/models/Insurance.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Insurance struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
OwnerID uint `gorm:"not null" json:"owner_id" binding:"numeric"`
|
||||
Company string `json:"company" binding:"safe_content"`
|
||||
Reference string `json:"reference" binding:"safe_content"`
|
||||
Notes string `json:"notes" binding:"safe_content"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
}
|
||||
133
go-backend/internal/models/car.go
Normal file
133
go-backend/internal/models/car.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"GoMembership/pkg/errors"
|
||||
"GoMembership/pkg/logger"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Car struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Status uint `json:"status"`
|
||||
Name string `json:"name"`
|
||||
Brand string `gorm:"not null" json:"brand"`
|
||||
Model string `gorm:"not null" json:"model"`
|
||||
Color string `gorm:"not null" json:"color"`
|
||||
LicencePlate string `gorm:"not null,unique" json:"licence_plate"`
|
||||
Price float32 `json:"price"`
|
||||
Rate float32 `json:"rate"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
Location Location `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"location"`
|
||||
LocationID uint
|
||||
Damages *[]Damage `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"damages"`
|
||||
Insurances *[]Insurance `gorm:"foreignkey:OwnerID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Latitude float32 `json:"latitude"`
|
||||
Longitude float32 `json:"longitude"`
|
||||
}
|
||||
|
||||
type Damage struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CarID uint `json:"car_id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
Opponent *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"opponent"`
|
||||
OpponentID uint
|
||||
Insurance *Insurance `gorm:"foreignkey:OwnerID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"insurance"`
|
||||
InsuranceID uint
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
func (c *Car) Create(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Create the base User record (omit associations to handle them separately)
|
||||
if err := tx.Create(c).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace associated Categories (assumes Categories already exist)
|
||||
if c.Insurances != nil {
|
||||
if err := tx.Model(c).Association("Insurances").Replace(c.Insurances); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logger.Info.Printf("car created: %#v", c)
|
||||
// Preload all associations to return the fully populated User
|
||||
return tx.
|
||||
Preload("Insurances").
|
||||
First(c, c.ID).Error // Refresh the user object with all associations
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Car) Update(db *gorm.DB) error {
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
// Check if the user exists in the database
|
||||
var existingCar Car
|
||||
|
||||
logger.Info.Printf("updating car: %#v", c)
|
||||
if err := tx.
|
||||
Preload("Insurances").
|
||||
First(&existingCar, c.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(c)
|
||||
if result.Error != nil {
|
||||
logger.Error.Printf("car update error: %#v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.ErrNoRowsAffected
|
||||
}
|
||||
|
||||
if c.Insurances != nil {
|
||||
if err := tx.Save(*c.Insurances).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.
|
||||
Preload("Insurances").
|
||||
First(&c, c.ID).Error
|
||||
}
|
||||
|
||||
func (c *Car) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&c).Error
|
||||
}
|
||||
|
||||
func GetAllCars(db *gorm.DB) ([]Car, error) {
|
||||
var cars []Car
|
||||
if err := db.Find(&cars).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cars, nil
|
||||
}
|
||||
|
||||
func (c *Car) FromID(db *gorm.DB, id uint) error {
|
||||
var car Car
|
||||
if err := db.Preload("Insurances").First(&car, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
*c = car
|
||||
return nil
|
||||
}
|
||||
69
go-backend/internal/repositories/car_repository.go
Normal file
69
go-backend/internal/repositories/car_repository.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"GoMembership/internal/database"
|
||||
"GoMembership/internal/models"
|
||||
"GoMembership/pkg/errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CarRepository interface defines the CRUD operations
|
||||
type CarRepositoryInterface interface {
|
||||
Create(car *models.Car) (*models.Car, error)
|
||||
GetByID(id uint) (*models.Car, error)
|
||||
GetAll() ([]models.Car, error)
|
||||
Update(car *models.Car) (*models.Car, error)
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
type CarRepository struct{}
|
||||
|
||||
// Create a new car
|
||||
func (r *CarRepository) Create(car *models.Car) (*models.Car, error) {
|
||||
if err := database.DB.Create(car).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return car, nil
|
||||
}
|
||||
|
||||
// GetByID fetches a car by its ID
|
||||
func (r *CarRepository) GetByID(id uint) (*models.Car, error) {
|
||||
var car models.Car
|
||||
if err := database.DB.Where("id = ?", id).First(&car).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.ErrNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &car, nil
|
||||
}
|
||||
|
||||
// GetAll retrieves all cars
|
||||
func (r *CarRepository) GetAll() ([]models.Car, error) {
|
||||
var cars []models.Car
|
||||
if err := database.DB.Find(&cars).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cars, nil
|
||||
}
|
||||
|
||||
// Update an existing car
|
||||
func (r *CarRepository) Update(car *models.Car) (*models.Car, error) {
|
||||
if err := database.DB.Save(car).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return car, nil
|
||||
}
|
||||
|
||||
// Delete a car (soft delete)
|
||||
func (r *CarRepository) Delete(id uint) error {
|
||||
result := database.DB.Delete(&models.Car{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController, contactController *controllers.ContactController, licenceController *controllers.LicenceController) {
|
||||
func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController, contactController *controllers.ContactController, licenceController *controllers.LicenceController, carController *controllers.CarController) {
|
||||
router.GET("/api/users/verify/:id", userController.VerifyMailHandler)
|
||||
router.POST("/api/users/register", userController.RegisterUser)
|
||||
router.POST("/api/users/contact", contactController.RelayContactRequest)
|
||||
@@ -19,6 +19,10 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
|
||||
userRouter := router.Group("/api/auth")
|
||||
userRouter.Use(middlewares.AuthMiddleware())
|
||||
{
|
||||
userRouter.GET("/cars", carController.GetAll)
|
||||
userRouter.PUT("/cars", carController.Update)
|
||||
userRouter.POST("/cars", carController.Create)
|
||||
userRouter.DELETE("/cars", carController.Delete)
|
||||
userRouter.GET("/users/current", userController.CurrentUserHandler)
|
||||
userRouter.POST("/logout", userController.LogoutHandler)
|
||||
userRouter.PUT("/users", userController.UpdateHandler)
|
||||
|
||||
@@ -48,6 +48,8 @@ func Run(db *gorm.DB) {
|
||||
membershipController := &controllers.MembershipController{Service: membershipService, UserService: userService}
|
||||
licenceController := &controllers.LicenceController{Service: licenceService}
|
||||
contactController := &controllers.ContactController{EmailService: emailService}
|
||||
carService := &services.CarService{DB: db}
|
||||
carController := &controllers.CarController{S: carService, UserService: userService}
|
||||
|
||||
router := gin.Default()
|
||||
// gin.SetMode(gin.ReleaseMode)
|
||||
@@ -63,7 +65,7 @@ func Run(db *gorm.DB) {
|
||||
limiter := middlewares.NewIPRateLimiter(config.Security.Ratelimits.Limit, config.Security.Ratelimits.Burst)
|
||||
router.Use(middlewares.RateLimitMiddleware(limiter))
|
||||
|
||||
routes.RegisterRoutes(router, userController, membershipController, contactController, licenceController)
|
||||
routes.RegisterRoutes(router, userController, membershipController, contactController, licenceController, carController)
|
||||
validation.SetupValidators(db)
|
||||
|
||||
logger.Info.Println("Starting server on :8080")
|
||||
|
||||
67
go-backend/internal/services/car_service.go
Normal file
67
go-backend/internal/services/car_service.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"GoMembership/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CarServiceInterface interface {
|
||||
Create(car *models.Car) (*models.Car, error)
|
||||
Update(car *models.Car) (*models.Car, error)
|
||||
Delete(carID *uint) error
|
||||
FromID(id uint) (*models.Car, error)
|
||||
GetAll() (*[]models.Car, error)
|
||||
}
|
||||
|
||||
type CarService struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
// Create a new car
|
||||
func (s *CarService) Create(car *models.Car) (*models.Car, error) {
|
||||
|
||||
err := car.Create(s.DB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return car, nil
|
||||
}
|
||||
|
||||
// Update an existing car
|
||||
func (s *CarService) Update(car *models.Car) (*models.Car, error) {
|
||||
err := car.Update(s.DB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return car, nil
|
||||
}
|
||||
|
||||
// Delete a car (soft delete)
|
||||
func (s *CarService) Delete(carID *uint) error {
|
||||
var car models.Car
|
||||
err := car.FromID(s.DB, *carID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return car.Delete(s.DB)
|
||||
}
|
||||
|
||||
// GetByID fetches a car by its ID
|
||||
func (s *CarService) FromID(id uint) (*models.Car, error) {
|
||||
car := &models.Car{}
|
||||
err := car.FromID(s.DB, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return car, nil
|
||||
}
|
||||
|
||||
// GetAll retrieves all cars
|
||||
func (s *CarService) GetAll() (*[]models.Car, error) {
|
||||
var cars []models.Car
|
||||
if err := s.DB.Find(&cars).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cars, nil
|
||||
}
|
||||
Reference in New Issue
Block a user