final project structure

This commit is contained in:
2025-04-15 22:36:22 +02:00
parent d6b3e88b5f
commit 284a40fac2
65 changed files with 668 additions and 447 deletions

View File

@@ -0,0 +1,28 @@
package asset
import (
"wagfarm-api/asset/fertilizer"
"wagfarm-api/asset/land"
"wagfarm-api/asset/machine"
"wagfarm-api/asset/pesticide"
"wagfarm-api/asset/plant"
"wagfarm-api/asset/product"
"wagfarm-api/asset/seed"
"github.com/gin-gonic/gin"
)
const Models []interface{} {
}
func RegisterRoutes(rg *gin.RouterGroup) {
group := rg.Group("/asset")
fertilizer.RegisterRoutes(group)
land.RegisterRoutes(group)
machine.RegisterRoutes(group)
pesticide.RegisterRoutes(group)
plant.RegisterRoutes(group)
product.RegisterRoutes(group)
seed.RegisterRoutes(group)
}

View File

@@ -0,0 +1,30 @@
package base
type Asset struct {
ID uint `gorm:"primaryKey`
Name string `gorm:"not null"`
Notes string
}
func (a Asset) GetID() uint { return a.ID }
type Resource struct {
Asset
UnitID uint `gorm:"not null"`
Unit unit.Unit `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Cost decimal.Decimal `gorm:"not null; type:decimal(10,2);"` // in euro/unit
}
type BoughtResource struct {
Resource
BuyDate Datetime.Time `gorm:"type:date;default:CURRENT_DATE"`
BoughtFromID uint `gorm:"not null"`
BoughtFrom Seller `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type SoldResource struct {
Resource
SaleDate Datetime.Time `gorm:"type:date;default:CURRENT_DATE"`
SoldToID uint `gorm:"not null"`
SoldTo Customer `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

View File

@@ -0,0 +1,20 @@
package fertilizer
import "wagfarm-api/asset/base"
type Fertilizer struct {
base.BoughtResource
FertilizerTypeID uint `gorm:"not null"`
FertilizerType FertilizerType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
// Association to the ingredients. This represents the composition.
Ingredients []FertilizerIngredient `gorm:"not null; many2many:fertilizer_ingredient"`
}
type FertilizerIngredient struct {
FertilizerAssetID uint `gorm:"primaryKey"` // foreign key to the fertilizer asset
IngredientID uint `gorm:"primaryKey"` // foreign key to the ingredient
Amount float64 `gorm:"not null"` // amount of the ingredient
// Preload the ingredient details if needed.
Ingredient Ingredient `gorm:"not null"`
}

View File

@@ -0,0 +1,11 @@
package fertilizer
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/fertilizer"), generic.DefaultCRUDController[Fertilizer]())
}

View File

@@ -0,0 +1,11 @@
package land
import "wagfarm-api/asset/base"
type Land struct {
base.Asset
FID string `gorm:"not null"`
IsWaterProtectionArea bool `gorm:"not null"`
IsRedArea bool `gorm:"not null"`
Area float64 `gorm:"not null"`
}

View File

@@ -0,0 +1,11 @@
package land
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/land"), generic.DefaultCRUDController[Land]())
}

View File

@@ -0,0 +1,9 @@
package machine
import (
"wagfarm-api/asset/base"
)
type Machine struct {
base.Resource
}

View File

@@ -0,0 +1,11 @@
package machine
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/machine"), generic.DefaultCRUDController[Machine]())
}

View File

@@ -0,0 +1,9 @@
package pesticide
import "wagfarm-api/asset/base"
type Pesticide struct {
base.BoughtResource
PesticideTypeID uint `gorm:"not null"`
PesticideType PesticideType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

View File

@@ -0,0 +1,11 @@
package pesticide
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/pesticide"), generic.DefaultCRUDController[Pesticide]())
}

View File

@@ -0,0 +1,18 @@
package plant
import (
"wagfarm-api/asset/base"
"wagfarm-api/asset/land"
)
type Plant struct {
base.Asset
LandId uint `gorm:"not null"`
Land land.Land `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
CropID uint `gorm:"not null"`
Crop crop.Crop `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
CoverCropID uint `gorm:"not null"`
CoverCrop crop.Crop `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Year uint `gorm:"not null"`
AreaPercentage float64 `gorm:"not null"`
}

View File

@@ -0,0 +1,11 @@
package plant
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/plant"), generic.DefaultCRUDController[Plant]())
}

View File

@@ -0,0 +1,9 @@
package product
import "wagfarm-api/asset/base"
type Product struct {
base.SoldResource
ProductTypeID uint `gorm:"not null"`
ProductType ProductType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

View File

@@ -0,0 +1,11 @@
package product
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/product"), generic.DefaultCRUDController[Product]())
}

View File

@@ -0,0 +1,11 @@
package seed
import (
"wagfarm-api/asset/base"
)
type Seed struct {
base.BoughtResource
SeedTypeID uint `gorm:"not null"`
SeedType SeedType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

View File

@@ -0,0 +1,11 @@
package seed
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/seed"), generic.DefaultCRUDController[Seed]())
}

View File

@@ -1,142 +0,0 @@
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"})
}

View File

@@ -8,6 +8,7 @@ import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
var DB *gorm.DB
@@ -37,7 +38,7 @@ func Connect() error {
func Migrate() error {
var err error
err = DB.AutoMigrate(slices.Concat(
models.LogModels, models.AssetModels, models.TaxonomyModels))
log.Models, asset.Models, taxonomy.Models))
if err != nil {
return fmt.Errorf("auto migration failed: %w", err)
}
@@ -49,3 +50,7 @@ func Migrate() error {
return nil
}
func Preload() *gorm.DB {
return DB.Preload(clause.Associations)
}

View File

@@ -1,24 +1,46 @@
package logs
package generic
import (
"net/http"
"strconv"
"time"
"wagfarm-api/database"
"wagfarm-api/models"
"github.com/gin-gonic/gin"
)
type SeedingLogController struct {
GenericLogController
type RController struct {
GetAll func(*gin.Context)
GetOne func(*gin.Context)
}
func DefaultRController[T Model]() RController {
return RController{
GetAll: GetAll[T],
GetOne: GetOne[T],
}
}
type CRUDController struct {
RController
Create func(*gin.Context)
Update func(*gin.Context)
Delete func(*gin.Context)
}
func DefaultCRUDController[T Model]() CRUDController {
return CRUDController{
RController: DefaultRController[T](),
Create: Create[T],
Update: Update[T],
Delete: Delete[T],
}
}
// 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 {
func GetAll[T Model](c *gin.Context) {
var logs []T
if err := database.Preload().Find(&logs).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error fetching logs: " + err.Error()})
return
}
@@ -26,7 +48,7 @@ func (l *SeedingLogController) GetAll(c *gin.Context) {
}
// GetLog handles GET /logs/:id and returns a single log record.
func (l *SeedingLogController) GetOne(c *gin.Context) {
func GetOne[T Model](c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
@@ -34,48 +56,33 @@ func (l *SeedingLogController) GetOne(c *gin.Context) {
return
}
var logEntry models.Log
if err := database.DB.Preload("Visibility").First(&logEntry, id).Error; err != nil {
var log T
if err := database.Preload().First(&log, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"})
return
}
c.JSON(http.StatusOK, logEntry)
c.JSON(http.StatusOK, log)
}
// 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 {
func Create[T Model](c *gin.Context) {
var log T
if err := c.ShouldBindJSON(&log); 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 {
if err := database.DB.Create(&log).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)
database.Preload().First(&log, log.GetID())
c.JSON(http.StatusCreated, log)
}
// UpdateLog handles PUT /logs/:id and updates an existing log record.
func (l *SeedingLogController) Update(c *gin.Context) {
func Update[T Model](c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
@@ -83,38 +90,29 @@ func (l *SeedingLogController) Update(c *gin.Context) {
return
}
var existing models.Log
var existing T
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 {
var log T
if err := c.ShouldBindJSON(&log); 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 {
if err := database.DB.Model(&existing).Updates(log).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update log: " + err.Error()})
return
}
database.DB.Preload("Visibility").First(&existing, id)
database.Preload().First(&existing, log.GetID())
c.JSON(http.StatusOK, existing)
}
// DeleteLog handles DELETE /logs/:id and deletes a log record.
func (l *SeedingLogController) Delete(c *gin.Context) {
func Delete[T Model](c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
@@ -122,13 +120,13 @@ func (l *SeedingLogController) Delete(c *gin.Context) {
return
}
var logEntry models.Log
if err := database.DB.First(&logEntry, id).Error; err != nil {
var log T
if err := database.DB.First(&log, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Log not found"})
return
}
if err := database.DB.Delete(&logEntry).Error; err != nil {
if err := database.DB.Delete(&log).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete log: " + err.Error()})
return
}

View File

@@ -0,0 +1,5 @@
package generic
type Model interface {
GetID() uint
}

View File

@@ -0,0 +1,16 @@
package generic
import "github.com/gin-gonic/gin"
func RegisterCRUDRoutes(rg *gin.RouterGroup, l CRUDController) {
rg.GET("/", l.GetAll)
rg.GET("/:id", l.GetOne)
rg.POST("/", l.Create)
rg.PUT("/:id", l.Update)
rg.DELETE("/:id", l.Delete)
}
func RegisterRRoutes(rg *gin.RouterGroup, l RController) {
rg.GET("/", l.GetAll)
rg.GET("/:id", l.GetOne)
}

View File

@@ -0,0 +1,31 @@
package base
import "time"
type BaseLog struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
Notes string `gorm:"not null"`
LogType string `gorm:"not null"`
Date time.Time `gorm:"default:CURRENT_TIME"`
Duration float64 `gorm:"not null"` // in hours
VisibiltyID uint `gorm:"not null"`
Visibility taxonomy.Visibility `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}
func (l BaseLog) GetID() uint { return l.ID }
type ExtendedLog struct {
ID uint `gorm:"primaryKey"`
LogID uint `gorm:"not null"`
BaseLog BaseLog `gorm:"foreignKey:LogID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}
func (l ExtendedLog) GetID() uint { return l.ID }
type FieldWorkLog struct {
ExtendedLog
WorkedAreaPercentage float64 `gorm:"not null"`
MachineID uint `gorm:"not null"`
Machine asset.Machine `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}

View File

@@ -0,0 +1,11 @@
package base
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg, generic.DefaultCRUDController[BaseLog]())
}

View File

@@ -0,0 +1,12 @@
package cropprotection
import "wagfarm-api/log/base"
type CropProtectionLog struct {
base.FieldWorkLog
PesticideID uint `gorm:"not null"`
Pesticide taxonomy.Pesticide `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units of the pesticide
PerformedByID uint `gorm:"not null"`
PerformedBy taxonomy.Worker `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}

View File

@@ -0,0 +1,11 @@
package cropprotection
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/cropprotection"), generic.DefaultCRUDController[CropProtectionLog]())
}

View File

@@ -0,0 +1,12 @@
package fertilizing
import (
"wagfarm-api/log/base"
)
type FertilizingLog struct {
base.ExtendedLog
FertilizerID uint `gorm:"not null"`
Fertilizer asset.Fertilizer `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units of the fertilizer
}

View File

@@ -0,0 +1,11 @@
package fertilizing
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/fertilizer"), generic.DefaultCRUDController[FertilizingLog]())
}

View File

@@ -0,0 +1,9 @@
package harvest
import base "wagfarm-api/log/seeding"
type HarvestLog struct {
base.FieldWorkLog
Product asset.Product `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units of the product
}

View File

@@ -0,0 +1,11 @@
package harvest
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/harvest"), generic.DefaultCRUDController[HarvestLog]())
}

View File

@@ -0,0 +1,14 @@
package seeding
import "wagfarm-api/log/base"
type SeedingLog struct {
base.FieldWorkLog
SeedID uint `gorm:"not null"`
Seed asset.Seed `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
TechniqueID uint `gorm:"not null"`
Technique taxonomy.SeedingTechnique `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units/ha
ConditionsID uint `gorm:"not null"`
Conditions taxonomy.SeedingCondition `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}

View File

@@ -0,0 +1,11 @@
package seeding
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/seeding"), generic.DefaultCRUDController[SeedingLog]())
}

View File

@@ -4,7 +4,6 @@ import (
"log"
"os"
"wagfarm-api/database"
"wagfarm-api/routes"
"github.com/gin-gonic/gin"
)
@@ -26,6 +25,10 @@ func initLogging() {
gin.DefaultWriter = f // capture Gin logs too
}
func RegisterRoutes(r *gin.Engine) {
}
func main() {
initLogging()
@@ -44,7 +47,7 @@ func main() {
log.Println("Database migrated successfully!")
r := gin.Default()
routes.RegisterRoutes(r)
RegisterRoutes(r)
if err := r.Run(":8080"); err != nil {
log.Fatalf("Failed to run server: %v", err)

View File

@@ -1,98 +0,0 @@
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"`
Notes string
}
type Land struct {
_Asset
FID string `gorm:"not null"`
IsWaterProtectionArea bool `gorm:"not null"`
IsRedArea bool `gorm:"not null"`
Area float64 `gorm:"not null"`
}
type Plant struct {
_Asset
LandId uint `gorm:"not null"`
Land Land `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
CropID uint `gorm:"not null"`
Crop Crop `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
CoverCropID uint `gorm:"not null"`
CoverCrop Crop `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Year uint `gorm:"not null"`
AreaPercentage float64 `gorm:"not null"`
}
type _Resource struct {
_Asset
UnitID uint `gorm:"not null"`
Unit Unit `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Cost decimal.Decimal `gorm:"not null; type:decimal(10,2);"` // in euro/unit
}
type Machine struct {
_Resource
}
type _BoughtResource struct {
_Resource
BuyDate Datetime.Time `gorm:"type:date;default:CURRENT_DATE"`
BoughtFromID uint `gorm:"not null"`
BoughtFrom Seller `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type _SoldResource struct {
_Resource
SaleDate Datetime.Time `gorm:"type:date;default:CURRENT_DATE"`
SoldToID uint `gorm:"not null"`
SoldTo Customer `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type Seed struct {
_BoughtResource
SeedTypeID uint `gorm:"not null"`
SeedType SeedType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type Fertilizer struct {
_Asset
_BoughtResource
FertilizerTypeID uint `gorm:"not null"`
FertilizerType FertilizerType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
// Association to the ingredients. This represents the composition.
Ingredients []FertilizerIngredient `gorm:"not null; many2many:fertilizer_ingredient"`
}
type FertilizerIngredient struct {
FertilizerAssetID uint `gorm:"primaryKey"` // foreign key to the fertilizer asset
IngredientID uint `gorm:"primaryKey"` // foreign key to the ingredient
Amount float64 `gorm:"not null"` // amount of the ingredient
// Preload the ingredient details if needed.
Ingredient Ingredient `gorm:"not null"`
}
type Pesticide struct {
_BoughtResource
PesticideTypeID uint `gorm:"not null"`
PesticideType PesticideType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type Product struct {
_SoldResource
ProductTypeID uint `gorm:"not null"`
ProductType ProductType `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

View File

@@ -1,67 +0,0 @@
package models
var LogModels = []interface{}{
&Log{},
&SeedingLog{},
&FertilizingLog{},
&CropProtectionLog{},
&HarvestLog{},
}
type Log struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
Notes string `gorm:"not null"`
LogType string `gorm:"not null"`
Date Datetime.Time `gorm:"type:date;default:CURRENT_DATE"not null`
Duration float64 `gorm:"not null"` // in hours
VisibiltyID uint `gorm:"not null"`
Visibility Visibility `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}
type _LogMetadata struct {
ID uint `gorm:"primaryKey"`
LogID uint `gorm:"not null"`
Log Log `gorm:"foreignKey:LogID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}
type _FieldWorkLog struct {
_LogMetadata
WorkedAreaPercentage float64 `gorm:"not null"`
MachineID uint `gorm:"not null"`
Machine Machine `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}
type SeedingLog struct {
_FieldWorkLog
SeedID uint `gorm:"not null"`
Seed Seed `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
TechniqueID uint `gorm:"not null"`
Technique SeedingTechnique `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units/ha
ConditionsID uint `gorm:"not null"`
Conditions SeedingCondition `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}
type FertilizingLog struct {
_FieldWorkLog
FertilizerID uint `gorm:"not null"`
Fertilizer Fertilizer `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units of the fertilizer
}
type CropProtectionLog struct {
_FieldWorkLog
PesticideID uint `gorm:"not null"`
Pesticide Pesticide `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units of the pesticide
PerformedByID uint `gorm:"not null"`
PerformedBy Worker `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
}
type HarvestLog struct {
_FieldWorkLog
ProductID uint `gorm:"not null"`
Product Product `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"`
Amount float64 `gorm:"not null"` // in units of the product
}

View File

@@ -1,44 +0,0 @@
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"
}
type Unit struct{ _Taxonomy }
type Visibility struct{ _Taxonomy }
type SeedingTechnique struct{ _Taxonomy }
type SeedingCondition struct{ _Taxonomy }
type Worker struct{ _Taxonomy }
type Crop struct{ _Taxonomy }
type Seller struct{ _Taxonomy }
type Customer struct{ _Taxonomy }
type SeedType struct {
_Taxonomy
CropID uint `gorm:"not null"`
Crop Crop `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
type Ingredient struct {
_Taxonomy
UnitID uint `gorm:"not null"`
Unit Unit `gorm:"not null"`
}
type FertilizerType struct{ _Taxonomy }
type PesticideType struct{ _Taxonomy }
type ProductType struct{ _Taxonomy }

View File

@@ -1,27 +0,0 @@
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)
}
}

View File

@@ -1,13 +0,0 @@
package routes
import (
"github.com/gin-gonic/gin"
)
func RegisterRoutes(r *gin.Engine) {
// Use API versioning if desired, e.g., v1
v1 := r.Group("/v1")
{
RegisterLogRoutes(v1)
}
}

View File

@@ -0,0 +1,8 @@
package base
type Taxonomy struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;index" json:"name"` // e.g., "kg", "lb"
}
func (t Taxonomy) GetID() uint { return t.ID }

View File

@@ -0,0 +1,5 @@
package crop
import "wagfarm-api/taxonomy/base"
type Crop struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package crop
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/crop"), generic.DefaultCRUDController[Crop]())
}

View File

@@ -0,0 +1,5 @@
package customer
import "wagfarm-api/taxonomy/base"
type Customer struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package customer
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/customer"), generic.DefaultCRUDController[Customer]())
}

View File

@@ -0,0 +1,5 @@
package fertilizertype
import "wagfarm-api/taxonomy/base"
type FertilizerType struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package fertilizertype
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/fertilizertype"), generic.DefaultCRUDController[FertilizerType]())
}

View File

@@ -0,0 +1,12 @@
package ingredient
import (
"wagfarm-api/taxonomy/base"
"wagfarm-api/taxonomy/unit"
)
type Ingredient struct {
base.Taxonomy
UnitID uint `gorm:"not null"`
Unit unit.Unit `gorm:"not null"`
}

View File

@@ -0,0 +1,11 @@
package ingredient
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/ingredient"), generic.DefaultCRUDController[Ingredient]())
}

View File

@@ -0,0 +1,5 @@
package pesticidetype
import "wagfarm-api/taxonomy/base"
type PesticideType struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package pesticidetype
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/pesticidetype"), generic.DefaultCRUDController[PesticideType]())
}

View File

@@ -0,0 +1,5 @@
package producttype
import "wagfarm-api/taxonomy/base"
type ProductType struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package producttype
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/producttype"), generic.DefaultCRUDController[ProductType]())
}

View File

@@ -0,0 +1,5 @@
package seedingcondition
import "wagfarm-api/taxonomy/base"
type SeedingCondition struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package seedingcondition
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/seedingcondition"), generic.DefaultCRUDController[SeedingCondition]())
}

View File

@@ -0,0 +1,5 @@
package seedingtechnique
import "wagfarm-api/taxonomy/base"
type SeedingTechnique struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package seedingtechnique
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/seedingtechnique"), generic.DefaultCRUDController[SeedingTechnique]())
}

View File

@@ -0,0 +1,12 @@
package seedtype
import (
"wagfarm-api/taxonomy/base"
"wagfarm-api/taxonomy/crop"
)
type SeedType struct {
base.Taxonomy
CropID uint `gorm:"not null"`
Crop crop.Crop `gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}

View File

@@ -0,0 +1,11 @@
package seedtype
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/seedtype"), generic.DefaultCRUDController[SeedType]())
}

View File

@@ -0,0 +1,5 @@
package seller
import "wagfarm-api/taxonomy/base"
type Seller struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package seller
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/seller"), generic.DefaultCRUDController[Seller]())
}

View File

@@ -0,0 +1,5 @@
package unit
import "wagfarm-api/taxonomy/base"
type Unit struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package unit
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/unit"), generic.DefaultCRUDController[Unit]())
}

View File

@@ -0,0 +1,5 @@
package visibility
import "wagfarm-api/taxonomy/base"
type Visibility struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package visibility
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/visibility"), generic.DefaultCRUDController[Visibility]())
}

View File

@@ -0,0 +1,5 @@
package worker
import "wagfarm-api/taxonomy/base"
type Worker struct{ base.Taxonomy }

View File

@@ -0,0 +1,11 @@
package worker
import (
"wagfarm-api/generic"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(rg *gin.RouterGroup) {
generic.RegisterCRUDRoutes(rg.Group("/worker"), generic.DefaultCRUDController[Worker]())
}