diff --git a/.env b/.env index efc9e7d..0a24f8b 100644 --- a/.env +++ b/.env @@ -1,7 +1,7 @@ # .env -DB_HOST=mariadb -DB_ROOT_PASSWORD=iWO8ME9hlOCTml +DB_HOST=db DB_USER=api-user DB_PASSWORD=Pu38tkYv3W9k6S DB_DATABASE=wagfarm -DB_PORT=3306 \ No newline at end of file +DB_PORT=5432 +GIN_MODE=debug \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 05ccf5c..8752859 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,32 +1,34 @@ services: db: - image: mariadb + image: postgres:16 container_name: wagfarm-db + restart: always environment: - MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} - MYSQL_USER: ${DB_USER} - MYSQL_PASSWORD: ${DB_PASSWORD} - MYSQL_DATABASE: ${DB_DATABASE} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_DATABASE} volumes: - - ./data/mysql:/var/lib/mysql - # - ./postgres-init:/docker-entrypoint-initdb.d # SQL scripts + - pgdata:/var/lib/postgresql/data networks: - wagfarm-net healthcheck: - test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] - interval: 5s + test: ["CMD-SHELL", "pg_isready", "-U", "${DB_USER}"] + interval: 10s timeout: 5s retries: 5 + start_period: 5s api: build: ./wagfarm-api container_name: wagfarm-api + restart: always environment: - DB_HOST=${DB_HOST} - DB_PORT=${DB_PORT} - DB_USER=${DB_USER} - DB_PASSWORD=${DB_PASSWORD} - DB_NAME=${DB_DATABASE} + - GIN_MODE=${GIN_MODE} ports: - "8089:8080" depends_on: @@ -34,6 +36,15 @@ services: condition: service_healthy networks: - wagfarm-net + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] # or use the health endpoint of your API + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s networks: - wagfarm-net: \ No newline at end of file + wagfarm-net: + +volumes: + pgdata: \ No newline at end of file diff --git a/mariadb-init/01-init.sql b/mariadb-init/01-init.sql deleted file mode 100644 index 4d906f0..0000000 --- a/mariadb-init/01-init.sql +++ /dev/null @@ -1,42 +0,0 @@ -CREATE TABLE asset_type ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(100) NOT NULL UNIQUE -); - -CREATE TABLE asset ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - asset_type_id INT NOT NULL, - FOREIGN KEY (asset_type_id) REFERENCES asset_type(id) -); - -CREATE TABLE log_type ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(100) NOT NULL UNIQUE -); - -CREATE TABLE log ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - log_type_id INT NOT NULL, - date DATE DEFAULT CURRENT_DATE, - FOREIGN KEY (log_type_id) REFERENCES log_type(id) -); - --- Junction table for equipment assets -CREATE TABLE log_equipment_asset ( - log_id INT NOT NULL, - asset_id INT NOT NULL, - PRIMARY KEY (log_id, asset_id), - FOREIGN KEY (log_id) REFERENCES log(id) ON DELETE CASCADE, - FOREIGN KEY (asset_id) REFERENCES asset(id) ON DELETE CASCADE -); - --- Junction table for substance assets -CREATE TABLE log_substance_asset ( - log_id INT NOT NULL, - asset_id INT NOT NULL, - PRIMARY KEY (log_id, asset_id), - FOREIGN KEY (log_id) REFERENCES log(id) ON DELETE CASCADE, - FOREIGN KEY (asset_id) REFERENCES asset(id) ON DELETE CASCADE -); diff --git a/wagfarm-api/Dockerfile b/wagfarm-api/Dockerfile index d84a297..7119699 100644 --- a/wagfarm-api/Dockerfile +++ b/wagfarm-api/Dockerfile @@ -1,32 +1,26 @@ -# Use official Golang image -FROM golang:1.20-alpine as builder +# Stage 1: Build the Go binary +FROM golang:1.21-alpine AS builder -# Set the Current Working Directory inside the container WORKDIR /app -# Copy go mod and sum files +# Copy go.mod and go.sum first COPY go.mod go.sum ./ - -# Download all dependencies. Dependencies will be cached if the go.mod and go.sum are not changed RUN go mod download -# Copy the source code into the container -COPY src src +# Copy the rest of the code +COPY . . -# Build the Go app -RUN go build -o main src +# Build the Go binary +RUN go build -o wagfarm-api main.go -# Start a new stage from a smaller image -FROM alpine:latest +# Stage 2: Minimal runtime image +FROM alpine:latest -# Install required libraries -RUN apk --no-cache add ca-certificates +WORKDIR /root/ -# Copy the pre-built binary file from the builder stage -COPY --from=builder /app/main . +# Copy the binary from the builder +COPY --from=builder /app/wagfarm-api . -# Expose the API port -EXPOSE 8089 - -# Run the Go app -CMD ["./main"] +# Run the binary +CMD ["./wagfarm-api"] +#CMD ["tail", "-f", "/dev/null"] \ No newline at end of file diff --git a/wagfarm-api/assets/fertilizer/model.go b/wagfarm-api/assets/fertilizer/model.go index 18b33e9..0fd6082 100644 --- a/wagfarm-api/assets/fertilizer/model.go +++ b/wagfarm-api/assets/fertilizer/model.go @@ -12,16 +12,13 @@ type Fertilizer struct { FertilizerTypeID uint `gorm:"not null"` FertilizerType 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"` + Ingredients []ingredient.Ingredient `gorm:"not null; many2many:fertilizer_ingredient"` } type FertilizerIngredient struct { - FertilizerAssetID uint `gorm:"primaryKey"` // foreign key to the fertilizer assets - 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.Ingredient `gorm:"not null"` + FertilizerID uint `gorm:"primaryKey"` // foreign key to the fertilizer assets + IngredientID uint `gorm:"primaryKey"` // foreign key to the ingredient + Amount float64 `gorm:"not null"` // amount of the ingredient } func RegisterModels() { diff --git a/wagfarm-api/database/db.go b/wagfarm-api/database/db.go index d48b405..bf374ff 100644 --- a/wagfarm-api/database/db.go +++ b/wagfarm-api/database/db.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "gorm.io/driver/mysql" + "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -21,11 +21,11 @@ func Connect() error { dbName := os.Getenv("DB_NAME") dsn := fmt.Sprintf( - "%s:%s@tcp(%s:%s)/%s?parseTime=true", - dbUser, dbPassword, dbHost, dbPort, dbName, + "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + dbHost, dbPort, dbUser, dbPassword, dbName, ) - db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { return fmt.Errorf("failed to connect to database: %w", err) } diff --git a/wagfarm-api/go.mod b/wagfarm-api/go.mod index 3fed03e..e539864 100644 --- a/wagfarm-api/go.mod +++ b/wagfarm-api/go.mod @@ -4,9 +4,9 @@ go 1.20 require ( github.com/gin-gonic/gin v1.10.0 - gorm.io/driver/mysql v1.5.7 - gorm.io/gorm v1.25.12 github.com/shopspring/decimal v1.4.0 + gorm.io/driver/postgres v1.5.11 + gorm.io/gorm v1.25.12 ) require ( @@ -21,6 +21,10 @@ require ( github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -35,6 +39,7 @@ require ( golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect diff --git a/wagfarm-api/go.sum b/wagfarm-api/go.sum index 391309e..dd803ec 100644 --- a/wagfarm-api/go.sum +++ b/wagfarm-api/go.sum @@ -25,6 +25,14 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -71,6 +79,8 @@ golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= @@ -86,6 +96,8 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.5.0/go.mod h1:FFla/fJuCvyTi7rJQd27qlNX2v3L6deTR1GgTjSOLPo= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= diff --git a/wagfarm-api/logs/base/model.go b/wagfarm-api/logs/base/model.go index 839d307..86093ba 100644 --- a/wagfarm-api/logs/base/model.go +++ b/wagfarm-api/logs/base/model.go @@ -11,15 +11,15 @@ import ( ) 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:"not null"` - Duration float64 `gorm:"not null"` // in hours - VisibiltyID uint `gorm:"not null"` - Visibility visibility.Visibility `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"` - Cost decimal.Decimal `gorm:"not null;type:decimal(10,2);"` // in euro + ID uint `gorm:"primaryKey"` + Name string `gorm:"not null"` + Notes string `gorm:"not null"` + LogType string `gorm:"not null"` + Date time.Time `gorm:"not null"` + Duration float64 `gorm:"not null"` // in hours + VisibilityID uint `gorm:"not null"` + Visibility visibility.Visibility `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;not null"` + Cost decimal.Decimal `gorm:"not null;type:decimal(10,2);"` // in euro } func (l BaseLog) GetID() uint { return l.ID } diff --git a/wagfarm-api/main.go b/wagfarm-api/main.go index e89b53f..3f16b30 100644 --- a/wagfarm-api/main.go +++ b/wagfarm-api/main.go @@ -11,23 +11,6 @@ import ( "github.com/gin-gonic/gin" ) -func initLogging() { - // Ensure the logs directory exists - if err := os.MkdirAll("logs", 0755); err != nil { - log.Fatalf("Failed to create log directory: %v", err) - } - - // Open the log file - f, err := os.OpenFile("logs/api.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - log.Fatalf("Failed to open log file: %v", err) - } - - // Set log output - log.SetOutput(f) - gin.DefaultWriter = f // capture Gin logs too -} - func RegisterModels() { assets.RegisterModels() logs.RegisterModels() @@ -39,11 +22,14 @@ func RegisterRoutes(r *gin.Engine) { assets.RegisterRoutes(group) logs.RegisterRoutes(group) taxonomy.RegisterRoutes(group) + + r.GET("/health", func(c *gin.Context) { + c.Status(200) + }) } func main() { - initLogging() - + log.SetOutput(os.Stdout) log.Println("Starting API server...") // connect to database