From b4288b2abb56cd251fb60ed9e855b47bda110236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borna=20Rajkovi=C4=87?= Date: Mon, 1 Apr 2024 11:09:54 +0200 Subject: [PATCH] Upgraded to go1.22 + replaced log with slog --- README.md | 19 ++++++++++++-- api/api.go | 5 ++-- db.go | 12 ++++++++- db/database.go | 6 ++--- go.mod | 4 +-- go.sum | 2 ++ main.go | 56 +++++++++++++++++++++++++++++++++++------- makefile | 20 +++------------ migration/migration.go | 16 ++++++------ 9 files changed, 97 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 9b6b560..3016296 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Simple api used for tracking holidays -To checkout application open [https://holiday.bbr-dev.info](https://holiday.bbr-dev.info) +To check out application, open [https://holiday.bbr-dev.info](https://holiday.bbr-dev.info) ## Endpoints @@ -68,7 +68,7 @@ By default, responses are returned as a json array },...] } ``` -eg. +e.g. ``` { "holidays": [{ @@ -99,4 +99,19 @@ CSV Response ``` id,date,name,description,is_state_holiday,is_religious_holiday 74a2a769-abf2-45d4-bdc4-442bbcc89138,2023-12-25,Christmas,TBD,true,true +``` + +### Development + +To start server few environment variables need to be set up. This can be done by creating `.env` file with following content + +```bash +PSQL_HOST=localhost +PSQL_PORT=5432 +PSQL_USER=holiday +PSQL_PASSWORD=holidayPassword +PSQL_DB=holiday + +PROFILE=dev,basic-auth +AUTH_KEY=holiday:holidayPassword ``` \ No newline at end of file diff --git a/api/api.go b/api/api.go index 60d0d91..57f3807 100644 --- a/api/api.go +++ b/api/api.go @@ -1,11 +1,10 @@ package api import ( - "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" "holiday-api/domain/holiday" - "log" + "log/slog" "net/http" "strconv" "time" @@ -339,7 +338,7 @@ func render(c *gin.Context, status int, response any, contentType *string) { } func Abort(c *gin.Context, err error, statusCode int, message string) { - log.Output(1, fmt.Sprintf("error | %s | err: %v", message, errorMessage(err))) + slog.Error(message, slog.String("err", errorMessage(err))) c.AbortWithError(statusCode, err) } diff --git a/db.go b/db.go index 6c7fae9..55ed2e7 100644 --- a/db.go +++ b/db.go @@ -16,12 +16,22 @@ func envMustExist(env string) string { } } +func envOrDefault(env string, defaultValue string) string { + if value, exists := os.LookupEnv(env); exists { + return value + } else { + return defaultValue + } +} + func connectToDb() (*sqlx.DB, error) { host := envMustExist("PSQL_HOST") port := envMustExist("PSQL_PORT") user := envMustExist("PSQL_USER") password := envMustExist("PSQL_PASSWORD") dbname := envMustExist("PSQL_DB") + sslMode := envOrDefault("PSQL_SSLMODE", "disable") + schema := envOrDefault("PSQL_SCHEMA", "public") - return db.ConnectToDbNamed(host, port, user, password, dbname) + return db.ConnectToDbNamed(host, port, user, password, dbname, sslMode, schema) } diff --git a/db/database.go b/db/database.go index 6a57dc0..39aaaba 100644 --- a/db/database.go +++ b/db/database.go @@ -12,9 +12,9 @@ var DevMigrations embed.FS //go:embed prod/*.sql var ProdMigrations embed.FS -func ConnectToDbNamed(host string, port string, user string, password string, dbname string) (*sqlx.DB, error) { - psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", - host, port, user, password, dbname) +func ConnectToDbNamed(host string, port string, user string, password string, dbname string, sslMode string, schema string) (*sqlx.DB, error) { + psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s search_path=%s", + host, port, user, password, dbname, sslMode, schema) db, err := sqlx.Open("postgres", psqlInfo) if err != nil { diff --git a/go.mod b/go.mod index 1a7c2a8..a081916 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module holiday-api -go 1.19 +go 1.22 require ( github.com/gin-gonic/gin v1.9.1 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.6.0 github.com/jmoiron/sqlx v1.3.5 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 diff --git a/go.sum b/go.sum index aec158a..d219e58 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= diff --git a/main.go b/main.go index 49012ba..3b336a8 100644 --- a/main.go +++ b/main.go @@ -1,36 +1,51 @@ package main import ( + "github.com/gin-gonic/gin" "github.com/joho/godotenv" _ "github.com/lib/pq" "holiday-api/api" "holiday-api/db" "holiday-api/migration" "io/fs" - "log" + "log/slog" "net/http" "os" + "runtime/debug" "strings" ) func init() { godotenv.Load() - log.SetPrefix("") - log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + if !hasProfile("dev") { + gin.SetMode(gin.ReleaseMode) + } + if value := os.Getenv("LOG_FORMAT"); value == "json" { + slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) + } } func main() { - db, err := connectToDb() + commit, time := buildInfo() + slog.Info("build info", slog.String("commit", commit), slog.String("time", time)) + + client, err := connectToDb() if err != nil { - log.Fatalf("couldn't connect to db: %v", err) + slog.Error("couldn't connect to client", slog.String("err", err.Error())) + os.Exit(1) } - if err := migration.InitializeMigrations(db, migrationFolder()); err != nil { - log.Fatalf("couldn't execute migrations: %v", err) + if err := migration.InitializeMigrations(client, migrationFolder()); err != nil { + slog.Error("couldn't finish migration", slog.String("err", err.Error())) + os.Exit(1) } - server := api.SetupServer(db) + server := api.SetupServer(client) - log.Fatal(http.ListenAndServe(":5281", server)) + port := ":" + getOrDefault("SERVER_PORT", "5281") + slog.Info("app is ready", slog.String("port", port)) + if err := http.ListenAndServe(port, server); err != nil { + slog.Error("Couldn't start server!\n", slog.Any("err", err.Error())) + } } func hasProfile(value string) bool { @@ -49,3 +64,26 @@ func migrationFolder() fs.FS { } return db.ProdMigrations } + +func buildInfo() (string, string) { + revision := "" + time := "" + + if info, ok := debug.ReadBuildInfo(); ok { + for _, setting := range info.Settings { + if setting.Key == "vcs.revision" { + revision = setting.Value + } else if setting.Key == "vcs.time" { + time = setting.Value + } + } + } + return revision, time +} + +func getOrDefault(env string, defaultValue string) string { + if value, present := os.LookupEnv(env); present { + return value + } + return defaultValue +} diff --git a/makefile b/makefile index f90dbdb..5f81b66 100644 --- a/makefile +++ b/makefile @@ -1,19 +1,11 @@ # scripts for building app -# requires go 1.19+ and git installed +# requires go 1.22+ and git installed -VERSION := 1.0.0 - -serve: - go run ./... - -setup: - go get +VERSION := $(shell git describe --tags --always) docker-dev: - docker image build -t registry.bbr-dev.info/holiday-api/backend:$(VERSION)-dev . - docker tag registry.bbr-dev.info/holiday-api/backend:$(VERSION)-dev registry.bbr-dev.info/holiday-api/backend:latest-dev - docker image push registry.bbr-dev.info/holiday-api/backend:$(VERSION)-dev - docker image push registry.bbr-dev.info/holiday-api/backend:latest-dev + docker image build -t registry.bbr-dev.info/holiday-api/backend/dev:latest . + docker image push registry.bbr-dev.info/holiday-api/backend/dev:latest docker-prod: @@ -22,10 +14,6 @@ docker-prod: docker image push registry.bbr-dev.info/holiday-api/backend:$(VERSION) docker image push registry.bbr-dev.info/holiday-api/backend:latest -release: - git tag $(VERSION) - git push origin $(VERSION) - test: go test ./... diff --git a/migration/migration.go b/migration/migration.go index 64f48de..57696c9 100644 --- a/migration/migration.go +++ b/migration/migration.go @@ -4,10 +4,11 @@ import ( "context" "crypto/sha256" "encoding/base64" + "errors" "fmt" "github.com/jmoiron/sqlx" "io/fs" - "log" + "log/slog" "sort" "strings" "time" @@ -92,7 +93,8 @@ func validateMigrations(db *sqlx.DB, migrations map[string]Migration, migrationF } func executeMigration(db *sqlx.DB, name string, script string) error { - log.Printf("[INFO] script='%s' | migrations - executing", name) + logger := slog.Default().With(slog.String("script", name)) + logger.Info("migrations - executing") tx := db.MustBeginTx(context.Background(), nil) var err error = nil if _, e := tx.Exec(script); e != nil { @@ -102,10 +104,10 @@ func executeMigration(db *sqlx.DB, name string, script string) error { err = e } if err != nil { - log.Printf("[ERROR] script='%s' | migrations - failed executing", name) + logger.Error("migrations - failed executing", slog.String("err", err.Error())) tx.Rollback() } else { - log.Printf("[INFO] script='%s' | migrations - succesfully executed", name) + logger.Info("migrations - successfully executed") tx.Commit() } return err @@ -119,9 +121,9 @@ func validateMigration(name string, migration Migration, script string) error { calculatedHash := hash(script) if calculatedHash != migration.Hash { - err := fmt.Sprintf("migrations - mismatch in hash for %s (expected '%s', calculated '%s')", name, migration.Hash, calculatedHash) - log.Printf("[ERROR] script='%s' err='%s' | migrations - failed executing", script, err) - return fmt.Errorf("migrations - mismatch in hashes for %s", name) + err := errors.New(fmt.Sprintf("migrations - mismatch in hash for %s (expected '%s', calculated '%s')", name, migration.Hash, calculatedHash)) + slog.Error("migrations - failed validation", slog.String("script", name), slog.String("err", err.Error())) + return err } return nil }