diff --git a/wagfarm-api/controllers/logs/logs.go b/wagfarm-api/controllers/logs/logs.go new file mode 100644 index 0000000..5bc4817 --- /dev/null +++ b/wagfarm-api/controllers/logs/logs.go @@ -0,0 +1,142 @@ +package logs + +import ( + "net/http" + "strconv" + "time" + "wagfarm-api/database" + "wagfarm-api/models" + + "github.com/gin-gonic/gin" +) + +type LogController interface { + GetAll(c *gin.Context) + GetOne(c *gin.Context) + Create(c *gin.Context) + Update(c *gin.Context) + Delete(c *gin.Context) +} + +type GenericLogController struct{} + +// GetLogs handles GET /logs and returns all log records. +func (l *GenericLogController) GetAll(c *gin.Context) { + var logs []models.Log + // Preload the Visibility association + if err := database.DB.Preload("Visibility").Find(&logs).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Error fetching logs: " + err.Error()}) + return + } + c.JSON(http.StatusOK, logs) +} + +// GetLog handles GET /logs/:id and returns a single log record. +func (l *GenericLogController) GetOne(c *gin.Context) { + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid log ID"}) + return + } + + var logEntry models.Log + if err := database.DB.Preload("Visibility").First(&logEntry, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"}) + return + } + c.JSON(http.StatusOK, logEntry) +} + +// CreateLog handles POST /logs and creates a new log record. +func (l *GenericLogController) Create(c *gin.Context) { + var input models.Log + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // For safety, you might want to assign defaults (for example, date) if not provided. + if input.Date.IsZero() { + input.Date = time.Now() + } + + newLog := models.Log{ + Name: input.Name, + Notes: input.Notes, + LogType: input.LogType, + Date: input.Date, + Duration: input.Duration, + VisibiltyID: input.VisibiltyID, + } + + if err := database.DB.Create(&newLog).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create log: " + err.Error()}) + return + } + + // Optionally preload visibility before returning. + database.DB.Preload("Visibility").First(&newLog, newLog.ID) + c.JSON(http.StatusCreated, newLog) +} + +// UpdateLog handles PUT /logs/:id and updates an existing log record. +func (l *GenericLogController) Update(c *gin.Context) { + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid log ID"}) + return + } + + var existing models.Log + if err := database.DB.First(&existing, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"}) + return + } + + var input models.Log + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updated := models.Log{ + Name: input.Name, + Notes: input.Notes, + LogType: input.LogType, + Date: input.Date, + Duration: input.Duration, + VisibiltyID: input.VisibiltyID, + } + + if err := database.DB.Model(&existing).Updates(updated).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update log: " + err.Error()}) + return + } + + database.DB.Preload("Visibility").First(&existing, id) + c.JSON(http.StatusOK, existing) +} + +// DeleteLog handles DELETE /logs/:id and deletes a log record. +func (l *GenericLogController) Delete(c *gin.Context) { + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid log ID"}) + return + } + + var logEntry models.Log + if err := database.DB.First(&logEntry, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"}) + return + } + + if err := database.DB.Delete(&logEntry).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete log: " + err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Log deleted successfully"}) +} diff --git a/wagfarm-api/controllers/logs/seedinglog.go b/wagfarm-api/controllers/logs/seedinglog.go new file mode 100644 index 0000000..d1574e3 --- /dev/null +++ b/wagfarm-api/controllers/logs/seedinglog.go @@ -0,0 +1,136 @@ +package logs + +import ( + "net/http" + "strconv" + "time" + "wagfarm-api/database" + "wagfarm-api/models" + + "github.com/gin-gonic/gin" +) + +type SeedingLogController struct { + GenericLogController +} + +// GetLogs handles GET /logs and returns all log records. +func (l *SeedingLogController) GetAll(c *gin.Context) { + var logs []models.SeedingLog + // Preload the Visibility association + if err := database.DB.Preload("Log").Find(&logs).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Error fetching logs: " + err.Error()}) + return + } + c.JSON(http.StatusOK, logs) +} + +// GetLog handles GET /logs/:id and returns a single log record. +func (l *SeedingLogController) GetOne(c *gin.Context) { + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid log ID"}) + return + } + + var logEntry models.Log + if err := database.DB.Preload("Visibility").First(&logEntry, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"}) + return + } + c.JSON(http.StatusOK, logEntry) +} + +// CreateLog handles POST /logs and creates a new log record. +func (l *SeedingLogController) Create(c *gin.Context) { + var input models.Log + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // For safety, you might want to assign defaults (for example, date) if not provided. + if input.Date.IsZero() { + input.Date = time.Now() + } + + newLog := models.Log{ + Name: input.Name, + Notes: input.Notes, + LogType: input.LogType, + Date: input.Date, + Duration: input.Duration, + VisibiltyID: input.VisibiltyID, + } + + if err := database.DB.Create(&newLog).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create log: " + err.Error()}) + return + } + + // Optionally preload visibility before returning. + database.DB.Preload("Visibility").First(&newLog, newLog.ID) + c.JSON(http.StatusCreated, newLog) +} + +// UpdateLog handles PUT /logs/:id and updates an existing log record. +func (l *SeedingLogController) Update(c *gin.Context) { + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid log ID"}) + return + } + + var existing models.Log + if err := database.DB.First(&existing, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"}) + return + } + + var input models.Log + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updated := models.Log{ + Name: input.Name, + Notes: input.Notes, + LogType: input.LogType, + Date: input.Date, + Duration: input.Duration, + VisibiltyID: input.VisibiltyID, + } + + if err := database.DB.Model(&existing).Updates(updated).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update log: " + err.Error()}) + return + } + + database.DB.Preload("Visibility").First(&existing, id) + c.JSON(http.StatusOK, existing) +} + +// DeleteLog handles DELETE /logs/:id and deletes a log record. +func (l *SeedingLogController) Delete(c *gin.Context) { + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid log ID"}) + return + } + + var logEntry models.Log + if err := database.DB.First(&logEntry, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"}) + return + } + + if err := database.DB.Delete(&logEntry).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete log: " + err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Log deleted successfully"}) +} diff --git a/wagfarm-api/database/db.go b/wagfarm-api/database/db.go index b5e50d7..dc47c61 100644 --- a/wagfarm-api/database/db.go +++ b/wagfarm-api/database/db.go @@ -2,8 +2,9 @@ package database import ( "fmt" - "log" "os" + "slices" + "wagfarm-api/models" "gorm.io/driver/mysql" "gorm.io/gorm" @@ -11,7 +12,9 @@ import ( var DB *gorm.DB -func Connect() { +func Connect() error { + var err error + var db *gorm.DB dbUser := os.Getenv("DB_USER") dbPassword := os.Getenv("DB_PASSWORD") dbHost := os.Getenv("DB_HOST") @@ -23,10 +26,26 @@ func Connect() { dbUser, dbPassword, dbHost, dbPort, dbName, ) - db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { - log.Fatalf("Failed to connect to database: %v", err) + return fmt.Errorf("failed to connect to database: %w", err) } DB = db + return nil +} +func Migrate() error { + var err error + err = DB.AutoMigrate(slices.Concat( + models.LogModels, models.AssetModels, models.TaxonomyModels)) + if err != nil { + return fmt.Errorf("auto migration failed: %w", err) + } + + err = DB.SetupJoinTable(&models.Fertilizer{}, "Ingredients", &models.FertilizerIngredient{}) + if err != nil { + return fmt.Errorf("failed to setup join table for FertilizerAsset and Ingredients: %w", err) + } + + return nil } diff --git a/wagfarm-api/main.go b/wagfarm-api/main.go index ccbd7ca..665cb11 100644 --- a/wagfarm-api/main.go +++ b/wagfarm-api/main.go @@ -4,7 +4,6 @@ import ( "log" "os" "wagfarm-api/database" - "wagfarm-api/models" "wagfarm-api/routes" "github.com/gin-gonic/gin" @@ -27,31 +26,25 @@ func initLogging() { gin.DefaultWriter = f // capture Gin logs too } -func dbMigration() { - database.DB.AutoMigrate( - &models.AssetType{}, - &models.Asset{}, - &models.LogType{}, - &models.Log{}, - ) - - db.Create(&models.AssetType{Name: "machine"}) - db.Create(&models.AssetType{Name: "field"}) - db.Create(&models.AssetType{Name: "animal"}) - db.Create(&models.AssetType{Name: "substance"}) - - err := db.SetupJoinTable(&models.Fertilizer{}, "Ingredients", &models.FertilizerIngredient{}) -} - func main() { initLogging() log.Println("Starting API server...") - database.Connect() + // connect to database + if err := database.Connect(); err != nil { + log.Fatalf("Database connection error: %v", err) + } + + // Run migrations. + if err := database.Migrate(); err != nil { + log.Fatalf("Migration failed: %v", err) + } + + log.Println("Database migrated successfully!") r := gin.Default() - routes.SetupRoutes(r) + routes.RegisterRoutes(r) if err := r.Run(":8080"); err != nil { log.Fatalf("Failed to run server: %v", err) diff --git a/wagfarm-api/models/asset_models.go b/wagfarm-api/models/assets.go similarity index 95% rename from wagfarm-api/models/asset_models.go rename to wagfarm-api/models/assets.go index 93f7e72..c81dca5 100644 --- a/wagfarm-api/models/asset_models.go +++ b/wagfarm-api/models/assets.go @@ -1,5 +1,16 @@ package models +var AssetModels = []interface{}{ + &Land{}, + &Plant{}, + &Machine{}, + &Seed{}, + &Fertilizer{}, + &FertilizerIngredient{}, + &Pesticide{}, + &Product{}, +} + type _Asset struct { ID uint `gorm:"primaryKey` Name string `gorm:"not null"` diff --git a/wagfarm-api/models/log_models.go b/wagfarm-api/models/logs.go similarity index 95% rename from wagfarm-api/models/log_models.go rename to wagfarm-api/models/logs.go index 3089630..9bb0d69 100644 --- a/wagfarm-api/models/log_models.go +++ b/wagfarm-api/models/logs.go @@ -1,5 +1,13 @@ package models +var LogModels = []interface{}{ + &Log{}, + &SeedingLog{}, + &FertilizingLog{}, + &CropProtectionLog{}, + &HarvestLog{}, +} + type Log struct { ID uint `gorm:"primaryKey"` Name string `gorm:"not null"` diff --git a/wagfarm-api/models/taxonomy_models.go b/wagfarm-api/models/taxonomy.go similarity index 76% rename from wagfarm-api/models/taxonomy_models.go rename to wagfarm-api/models/taxonomy.go index c88657e..9f4cf3c 100644 --- a/wagfarm-api/models/taxonomy_models.go +++ b/wagfarm-api/models/taxonomy.go @@ -1,5 +1,21 @@ package models +var TaxonomyModels = []interface{}{ + &Unit{}, + &Visibility{}, + &SeedingTechnique{}, + &SeedingCondition{}, + &Worker{}, + &Crop{}, + &Seller{}, + &Customer{}, + &SeedType{}, + &Ingredient{}, + &FertilizerType{}, + &PesticideType{}, + &ProductType{}, +} + type _Taxonomy struct { ID uint `gorm:"primaryKey" json:"id"` Name string `gorm:"not null;index" json:"name"` // e.g., "kg", "lb" diff --git a/wagfarm-api/routes/asset/asset_routes.go b/wagfarm-api/routes/asset/asset_routes.go deleted file mode 100644 index 4f946de..0000000 --- a/wagfarm-api/routes/asset/asset_routes.go +++ /dev/null @@ -1,35 +0,0 @@ -package asset - -import ( - "go-api/database" - "go-api/models" - "net/http" - - "github.com/gin-gonic/gin" -) - -func RegisterRoutes(router *gin.Engine) { - db := database.DB - - router.GET("/asset-types", func(c *gin.Context) { - var types []models.AssetType - db.Find(&types) - c.JSON(http.StatusOK, types) - }) - - router.POST("/asset-types", func(c *gin.Context) { - var assetType models.AssetType - if err := c.ShouldBindJSON(&assetType); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - db.Create(&assetType) - c.JSON(http.StatusCreated, assetType) - }) - - router.GET("/assets", func(c *gin.Context) { - var assets []models.Asset - db.Preload("AssetType").Find(&assets) - c.JSON(http.StatusOK, assets) - }) -} diff --git a/wagfarm-api/routes/log/harvest.go b/wagfarm-api/routes/log/harvest.go deleted file mode 100644 index 77da0fa..0000000 --- a/wagfarm-api/routes/log/harvest.go +++ /dev/null @@ -1,30 +0,0 @@ -package log - -import ( - "wagfarm-api/database" - - "github.com/gin-gonic/gin" -) - -/* used columns: -name, date, plant, equipment -*/ - -func RegisterHarvestRoutes(router *gin.Engine) { - db := database.DB - - router.GET("/harvest", func(c *gin.Context) { - var harvests []models.Log - db.Find(&harvests) - c.JSON(http.StatusOK, types) - }) - - router.POST("/harvest", func(c *gin.Context) { - }) - router.PUT("/harvest", func(c *gin.Context) { - - }) - router.DELELTE("/harvest", func(c *gin.Context) { - - }) -} diff --git a/wagfarm-api/routes/log/log_routes.go b/wagfarm-api/routes/log/log_routes.go deleted file mode 100644 index 7f4a1a0..0000000 --- a/wagfarm-api/routes/log/log_routes.go +++ /dev/null @@ -1,38 +0,0 @@ -package log - -import ( - "net/http" - "wagfarm-api/database" - "wagfarm-api/models" - - "github.com/gin-gonic/gin" -) - -func RegisterRoutes(router *gin.Engine) { - db := database.DB - group := router.Group("/log") - - RegisterHarvestRoutes(group) - - group.GET("/type", func(c *gin.Context) { - var types []models.LogType - db.Find(&types) - c.JSON(http.StatusOK, types) - }) - - group.POST("/type", func(c *gin.Context) { - var logType models.LogType - if err := c.ShouldBindJSON(&logType); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - db.Create(&logType) - c.JSON(http.StatusCreated, logType) - }) - - group.GET("/", func(c *gin.Context) { - var logs []models.Log - db.Preload("LogType").Preload("Equipment").Preload("Substance").Find(&logs) - c.JSON(http.StatusOK, logs) - }) -} diff --git a/wagfarm-api/routes/logs.go b/wagfarm-api/routes/logs.go new file mode 100644 index 0000000..57b4ce0 --- /dev/null +++ b/wagfarm-api/routes/logs.go @@ -0,0 +1,27 @@ +package routes + +import ( + "wagfarm-api/controllers/logs" + + "github.com/gin-gonic/gin" +) + +func RegisterLogRoutes(rg *gin.RouterGroup) { + group := rg.Group("/log") + { + RegisterLog(group, &logs.GenericLogController{}) + RegisterLog(group.Group("/seeding"), &logs.SeedingLogController{}) + } +} +} + +func RegisterLog(rg *gin.RouterGroup controller logs.LogController) { + group := rg.Group(routePrefix) + { + group.GET("/", controller.GetAll) + group.GET("/:id", controller.GetOne) + group.POST("/", controller.Create) + group.PUT("/:id", controller.Update) + group.DELETE("/:id", controller.Delete) + } +} diff --git a/wagfarm-api/routes/routes.go b/wagfarm-api/routes/routes.go index 94e1b97..57b7a54 100644 --- a/wagfarm-api/routes/routes.go +++ b/wagfarm-api/routes/routes.go @@ -1,13 +1,13 @@ package routes import ( - "wagfarm-api/routes/asset" - "wagfarm-api/routes/log" - "github.com/gin-gonic/gin" ) -func SetupRoutes(router *gin.Engine) { - log.RegisterRoutes(router) - asset.RegisterRoutes(router) +func RegisterRoutes(r *gin.Engine) { + // Use API versioning if desired, e.g., v1 + v1 := r.Group("/v1") + { + RegisterLogRoutes(v1) + } }