diff --git a/.env.example b/.env.example index e7973e9..a5fce78 100644 --- a/.env.example +++ b/.env.example @@ -1,32 +1,17 @@ -ENV_DIRECTORY= -ENV_MODE= +ENV_MODE=testing / development / production -LOG_LEVEL= +LOG_LEVEL=debug / info / warn / error -APP_NAME= -APP_VERSION= +TIMEZONE=Europe/Madrid -TIMEZONE= - -PASETO_ASYMMETRIC_KEY= -PASETO_DURATION= +PASETO_ASYMMETRIC_KEY=some_key +PASETO_DURATION=168h SMTP_HOST= SMTP_PORT= SMTP_USER= SMTP_PASS= -DATABASE_DRIVER_NAME= -DATABASE_DATA_SOURCE= -DATABASE_MIGRATE= - -# if you want override the datasource key, you can use this format _OVERRIDE_KEY -# AUTH_DATA_SOURCE=something_data_source -# example i: OVERRIDE_AUTH_DATA_SOURCE will get the value something_data_source -# example ii: -# app := app.New(app.Config{ -# Name: appName, -# Version: version, -# EnvDirectory: envDirectory, -# DatabaseDataSource: "OVERRIDE_AUTH_DATA_SOURCE", -# }) +DATABASE_ONE_DRIVER_NAME=pgx / mysql / pg +DATABASE_ONE_DATA_SOURCE=datasource +DATABASE_ONE_MIGRATE=boolean diff --git a/app/app.go b/app.go similarity index 65% rename from app/app.go rename to app.go index abdaf9d..bc68eaf 100644 --- a/app/app.go +++ b/app.go @@ -1,4 +1,4 @@ -package app +package goblocks import ( "bufio" @@ -14,6 +14,7 @@ import ( "aidanwoods.dev/go-paseto" + "github.com/alexedwards/scs/v2" "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/iofs" @@ -82,6 +83,12 @@ const ( type LogLevel slog.Level +type DatabaseConfig struct { + DriverName string + DataSource string + Migrate bool +} + type Config struct { // default "" Name string @@ -89,9 +96,6 @@ type Config struct { // default "" Version string - // default ".env" - EnvDirectory string - // default "development" EnvMode Environment @@ -104,30 +108,24 @@ type Config struct { // default nil Paseto *Paseto - // default "" - SMTPHost string - - // default "" - SMTPPort string - - // default "" - SMTPUser string - - // default "" - SMTPPass string - - // default "" - DatabaseDriverName string - - // default "" - DatabaseDataSource string + // default map[string]DatabaseConfig{} + Databases map[string]DatabaseConfig // default false - DatabaseMigrate bool + CreateSession bool + + // default false + CreateMailer bool + + // default false + CreateTemplates bool } type App struct { - config Config + config Config + Session *scs.SessionManager + Mailer Mailer + //Templates *Templates } type Paseto struct { @@ -138,58 +136,34 @@ type Paseto struct { func New(config ...Config) *App { cfg := Config{ - Name: "", - Version: "", - EnvDirectory: ".env", - EnvMode: EnvironmentDevelopment, - LogLevel: slog.LevelDebug, - Timezone: "UTC", - Paseto: nil, - SMTPHost: "", - SMTPPort: "", - SMTPUser: "", - SMTPPass: "", - DatabaseDriverName: "pgx", - DatabaseDataSource: "", - DatabaseMigrate: false, + Name: "", + Version: "", + EnvMode: EnvironmentDevelopment, + LogLevel: slog.LevelDebug, + Timezone: "UTC", + Paseto: nil, + Databases: make(map[string]DatabaseConfig), + CreateSession: false, + CreateMailer: false, + CreateTemplates: false, } if len(config) > 0 { cfg = config[0] - - if cfg.EnvDirectory == "" { - cfg.EnvDirectory = ".env" - } - if cfg.EnvMode == EnvironmentTesting { - cfg.EnvDirectory = "./../../.env" - } if cfg.LogLevel == slog.LevelDebug { cfg.LogLevel = slog.LevelDebug } if cfg.Timezone == "" { cfg.Timezone = "UTC" } - if cfg.DatabaseDriverName == "" { - cfg.DatabaseDriverName = "pgx" - } } - envDir := os.Getenv("ENV_DIRECTORY") - if envDir == "" { - envDir = cfg.EnvDirectory + if cfg.Name == "" { + cfg.Name = "no-name-defined" } - err := loadEnvFile(envDir) - if err != nil { - slog.Error("error loading env file", "error", err, "directory", envDir) - } - - if cfg.Name == "" && os.Getenv("APP_NAME") != "" { - cfg.Name = os.Getenv("APP_NAME") - } - - if cfg.Version == "" && os.Getenv("APP_VERSION") != "" { - cfg.Version = os.Getenv("APP_VERSION") + if cfg.Version == "" { + cfg.Version = "v0.0.0" } if cfg.EnvMode == "" && os.Getenv("ENV_MODE") != "" { @@ -260,45 +234,6 @@ func New(config ...Config) *App { } } - if cfg.SMTPHost == "" && os.Getenv("SMTP_HOST") != "" { - cfg.SMTPHost = os.Getenv("SMTP_HOST") - } - - if cfg.SMTPPort == "" && os.Getenv("SMTP_PORT") != "" { - cfg.SMTPPort = os.Getenv("SMTP_PORT") - } - - if cfg.SMTPUser == "" && os.Getenv("SMTP_USER") != "" { - cfg.SMTPUser = os.Getenv("SMTP_USER") - } - - if cfg.SMTPPass == "" && os.Getenv("SMTP_PASS") != "" { - cfg.SMTPPass = os.Getenv("SMTP_PASS") - } - - if cfg.DatabaseDriverName == "" && os.Getenv("DATABASE_DRIVER_NAME") != "" { - cfg.DatabaseDriverName = os.Getenv("DATABASE_DRIVER_NAME") - } - - if strings.HasPrefix(cfg.DatabaseDataSource, "OVERRIDE_") { - envKey := strings.TrimPrefix(cfg.DatabaseDataSource, "OVERRIDE_") - if envValue := os.Getenv(envKey); envValue != "" { - slog.Info("using override database data source", "key", envKey) - cfg.DatabaseDataSource = envValue - } else { - slog.Warn("override database data source key not found in environment", "key", envKey) - if os.Getenv("DATABASE_DATA_SOURCE") != "" { - cfg.DatabaseDataSource = os.Getenv("DATABASE_DATA_SOURCE") - } - } - } else if cfg.DatabaseDataSource == "" && os.Getenv("DATABASE_DATA_SOURCE") != "" { - cfg.DatabaseDataSource = os.Getenv("DATABASE_DATA_SOURCE") - } - - if !cfg.DatabaseMigrate && os.Getenv("DATABASE_MIGRATE") == "true" { - cfg.DatabaseMigrate = true - } - app := &App{ config: cfg, } @@ -307,21 +242,28 @@ func New(config ...Config) *App { "app config", "name", cfg.Name, "version", cfg.Version, - "env_directory", cfg.EnvDirectory, "env_mode", cfg.EnvMode, "log_level", cfg.LogLevel, "timezone", cfg.Timezone, "paseto_public_key", cfg.Paseto.PublicKey.ExportHex(), "paseto_duration", cfg.Paseto.Duration.String(), - "smtp_host", cfg.SMTPHost, - "smtp_port", cfg.SMTPPort, - "smtp_user", cfg.SMTPUser, - "smtp_pass", cfg.SMTPPass, - "database_driver", cfg.DatabaseDriverName, - "database_source", cfg.DatabaseDataSource, - "database_migrate", cfg.DatabaseMigrate, + "databases", cfg.Databases, ) + if cfg.EnvMode != EnvironmentProduction { + slog.Info("paseto_assymetric_key", "key", cfg.Paseto.AsymmetricKey.ExportHex()) + } + + if cfg.CreateSession { + slog.Debug("creating session") + app.Session = scs.New() + } + + if cfg.CreateMailer { + slog.Debug("creating mailer") + app.Mailer = newMailer() + } + return app } @@ -333,10 +275,6 @@ func (a *App) Version() string { return a.config.Version } -func (a *App) EnvDirectory() string { - return a.config.EnvDirectory -} - func (a *App) EnvMode() Environment { return a.config.EnvMode } @@ -349,62 +287,70 @@ func (a *App) Paseto() *Paseto { return a.config.Paseto } -func (a *App) SMTPConfig() (host, port, user, pass string) { - return a.config.SMTPHost, a.config.SMTPPort, a.config.SMTPUser, a.config.SMTPPass -} - -func (a *App) DatabaseDataSource() string { - return a.config.DatabaseDataSource -} - func (a *App) Timezone() string { return a.config.Timezone } +func (a *App) Datasource(name string) string { + config, exists := a.config.Databases[name] + if !exists { + slog.Error("database configuration not found", "name", name) + return "" + } + return config.DataSource +} + // MigrateDB migrates the database. The migrations must stored in the // "database/migrations" directory inside cmd directory along with the main.go. // // cmd/main.go // // cmd/database/migrations/*.sql -func (a *App) Migrate(database embed.FS) { - if !a.config.DatabaseMigrate { - slog.Info("migration disabled") +func (a *App) Migrate(database embed.FS, dbName string) { + dbConfig, exists := a.config.Databases[dbName] + if !exists { + slog.Error("database configuration not found", "name", dbName) return } - dbConn, err := sql.Open(a.config.DatabaseDriverName, a.config.DatabaseDataSource) + + if !dbConfig.Migrate { + slog.Info("migration disabled", "database", dbName) + return + } + + dbConn, err := sql.Open(dbConfig.DriverName, dbConfig.DataSource) if err != nil { - fmt.Println(err) + slog.Error("error opening database connection", "error", err, "database", dbName) return } defer dbConn.Close() d, err := iofs.New(database, "database/migrations") if err != nil { - fmt.Println(err) + slog.Error("error creating migration source", "error", err, "database", dbName) return } - m, err := migrate.NewWithSourceInstance("iofs", d, a.config.DatabaseDataSource) + m, err := migrate.NewWithSourceInstance("iofs", d, dbConfig.DataSource) if err != nil { - fmt.Println(err) + slog.Error("error creating migration instance", "error", err, "database", dbName) return } err = m.Up() if err != nil && !errors.Is(err, migrate.ErrNoChange) { - slog.Error("cannot migrate", "error", err) + slog.Error("cannot migrate", "error", err, "database", dbName) panic(err) } if errors.Is(err, migrate.ErrNoChange) { - slog.Info("migration has no changes") + slog.Info("migration has no changes", "database", dbName) return } - slog.Info("migration done") + slog.Info("migration done", "database", dbName) } -func loadEnvFile(envDirectory string) error { +func LoadEnvFile(envDirectory string) error { file, err := os.Open(envDirectory) if err != nil { return err diff --git a/utils/utils.go b/boiler.go similarity index 93% rename from utils/utils.go rename to boiler.go index fa9fa4d..c6ebb06 100644 --- a/utils/utils.go +++ b/boiler.go @@ -1,4 +1,4 @@ -package utils +package goblocks import ( "fmt" @@ -18,10 +18,6 @@ func CorrectTimezone(timeStamp time.Time) time.Time { return timeStamp.In(loc) } -func GetBool(value string) bool { - return value == "true" -} - func LogAndReturnError(err error, message string) error { slog.Error(message, "error", err.Error()) return fmt.Errorf("%s: %w", message, err) diff --git a/db/mysql.go b/db/mysql.go deleted file mode 100644 index aac2350..0000000 --- a/db/mysql.go +++ /dev/null @@ -1,31 +0,0 @@ -package db - -import ( - "database/sql" - "log/slog" - "time" - - _ "github.com/go-sql-driver/mysql" -) - -const maxOpenDbConn = 10 -const maxIdleDbConn = 5 -const maxDbLifetime = time.Minute * 5 - -func NewMySQL(dataSource string) (*sql.DB, error) { - d, err := sql.Open("mysql", dataSource) - if err != nil { - slog.Error("error connecting to database", "error", err) - } - - d.SetMaxOpenConns(maxOpenDbConn) - d.SetMaxIdleConns(maxIdleDbConn) - d.SetConnMaxLifetime(maxDbLifetime) - - if err := d.Ping(); err != nil { - slog.Error("error pinging database", "error", err) - return nil, err - } - - return d, nil -} diff --git a/db/pgx.go b/db/pgx.go deleted file mode 100644 index e1a466e..0000000 --- a/db/pgx.go +++ /dev/null @@ -1,27 +0,0 @@ -package db - -import ( - "context" - "log/slog" - - _ "github.com/jackc/pgconn" - _ "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" - _ "github.com/jackc/pgx/v5/stdlib" -) - -func NewPGXPool(dataSource string) *pgxpool.Pool { - dbPool, err := pgxpool.New(context.Background(), dataSource) - if err != nil { - slog.Error("error connecting to database", "error", err) - panic(err) - } - - if err := dbPool.Ping(context.Background()); err != nil { - slog.Error("error pinging database, maybe incorrect datasource", "error", err) - panic(err) - } - - slog.Info("connected to database") - return dbPool -} diff --git a/go.mod b/go.mod index 87ab598..06749fb 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/zepyrshut/go-blocks/v2 go 1.24.3 require ( + github.com/alexedwards/scs/v2 v2.8.0 github.com/go-sql-driver/mysql v1.9.2 github.com/golang-migrate/migrate/v4 v4.18.3 github.com/jackc/pgconn v1.14.3 diff --git a/go.sum b/go.sum index 77133ec..e86acc0 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -aidanwoods.dev/go-paseto v1.5.2 h1:9aKbCQQUeHCqis9Y6WPpJpM9MhEOEI5XBmfTkFMSF/o= -aidanwoods.dev/go-paseto v1.5.2/go.mod h1:7eEJZ98h2wFi5mavCcbKfv9h86oQwut4fLVeL/UBFnw= aidanwoods.dev/go-paseto v1.5.4 h1:MH+SBroZEk5Q5pjhVh4l48HIbrdWhWI3SZmA/DXhnuw= aidanwoods.dev/go-paseto v1.5.4/go.mod h1:Rn37AIcqrvSMu0YPw65CrlEUuoyKL6Yw6B0htrGr3EU= -aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8= -aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM= aidanwoods.dev/go-result v0.3.1 h1:ee98hpohYUVYbI+pa6gUHTyoRerIudgjky/IPSowDXQ= aidanwoods.dev/go-result v0.3.1/go.mod h1:GKnFg8p/BKulVD3wsfULiPhpPmrTWyiTIbz8EWuUqSk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -12,11 +8,13 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw= +github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0= -github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs= +github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= +github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= @@ -31,14 +29,10 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= -github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -61,8 +55,6 @@ github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUO github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= @@ -100,18 +92,12 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY= -github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE= github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE= -github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A= -github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q= github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= @@ -122,30 +108,18 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2 go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/mail/mail.go b/mail.go similarity index 89% rename from mail/mail.go rename to mail.go index eb318c4..d8ac556 100644 --- a/mail/mail.go +++ b/mail.go @@ -1,4 +1,4 @@ -package mail +package goblocks import ( "bytes" @@ -15,7 +15,7 @@ type Mailer struct { smtpPass string } -func New() Mailer { +func newMailer() Mailer { return Mailer{ smtpHost: os.Getenv("SMTP_HOST"), smtpPort: os.Getenv("SMTP_PORT"), @@ -24,7 +24,7 @@ func New() Mailer { } } -func (m *Mailer) SendMail(to []string, templateName string, data interface{}) error { +func (m Mailer) SendMail(to []string, templateName string, data interface{}) error { templateContent := getTemplate(templateName) if templateContent == "" { return fmt.Errorf("template %s not found", templateName) diff --git a/mysql.go b/mysql.go new file mode 100644 index 0000000..0f2c081 --- /dev/null +++ b/mysql.go @@ -0,0 +1,65 @@ +package goblocks + +import ( + "database/sql" + "log/slog" + "sync" + "time" + + _ "github.com/go-sql-driver/mysql" +) + +const maxOpenDbConn = 10 +const maxIdleDbConn = 5 +const maxDbLifetime = time.Minute * 5 + +var ( + mysqlDBs = make(map[string]*sql.DB) + mysqlMutex sync.RWMutex +) + +func (a *App) NewMySQL(name string) (*sql.DB, error) { + mysqlMutex.Lock() + defer mysqlMutex.Unlock() + + if db, exists := mysqlDBs[name]; exists { + return db, nil + } + + d, err := sql.Open("mysql", a.Datasource(name)) + if err != nil { + slog.Error("error connecting to database", "error", err, "name", name) + return nil, err + } + + d.SetMaxOpenConns(maxOpenDbConn) + d.SetMaxIdleConns(maxIdleDbConn) + d.SetConnMaxLifetime(maxDbLifetime) + + if err := d.Ping(); err != nil { + slog.Error("error pinging database", "error", err, "name", name) + return nil, err + } + + mysqlDBs[name] = d + slog.Info("connected to database", "name", name) + return d, nil +} + +func (a *App) GetMySQL(name string) (*sql.DB, bool) { + mysqlMutex.RLock() + defer mysqlMutex.RUnlock() + db, exists := mysqlDBs[name] + return db, exists +} + +func (a *App) CloseMySQLDBs() { + mysqlMutex.Lock() + defer mysqlMutex.Unlock() + + for name, db := range mysqlDBs { + db.Close() + delete(mysqlDBs, name) + slog.Info("closed database connection", "name", name) + } +} diff --git a/network/network.go b/network/network.go new file mode 100644 index 0000000..dea60b2 --- /dev/null +++ b/network/network.go @@ -0,0 +1,12 @@ +package network + +import ( + "encoding/json" + "net/http" +) + +func JSON(w http.ResponseWriter, code int, v any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + json.NewEncoder(w).Encode(v) +} diff --git a/pgutils/pgutils.go b/pgutils/pgutils.go index ea6c459..b21bf5e 100644 --- a/pgutils/pgutils.go +++ b/pgutils/pgutils.go @@ -46,10 +46,7 @@ func FloatToNumeric(number float64, precision int) (value pgtype.Numeric) { } func AddNumeric(a, b pgtype.Numeric) pgtype.Numeric { - minExp := a.Exp - if b.Exp < minExp { - minExp = b.Exp - } + minExp := min(a.Exp, b.Exp) aInt := new(big.Int).Set(a.Int) bInt := new(big.Int).Set(b.Int) diff --git a/pgx.go b/pgx.go new file mode 100644 index 0000000..b87712d --- /dev/null +++ b/pgx.go @@ -0,0 +1,59 @@ +package goblocks + +import ( + "context" + "log/slog" + "sync" + + _ "github.com/jackc/pgconn" + _ "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + _ "github.com/jackc/pgx/v5/stdlib" +) + +var ( + pgxPools = make(map[string]*pgxpool.Pool) + pgxMutex sync.RWMutex +) + +func (a *App) NewPGXPool(name string) *pgxpool.Pool { + pgxMutex.Lock() + defer pgxMutex.Unlock() + + if pool, exists := pgxPools[name]; exists { + return pool + } + + dbPool, err := pgxpool.New(context.Background(), a.Datasource(name)) + if err != nil { + slog.Error("error connecting to database", "error", err, "name", name) + panic(err) + } + + if err := dbPool.Ping(context.Background()); err != nil { + slog.Error("error pinging database, maybe incorrect datasource", "error", err, "name", name) + panic(err) + } + + pgxPools[name] = dbPool + slog.Info("connected to database", "name", name) + return dbPool +} + +func (a *App) GetPGXPool(name string) (*pgxpool.Pool, bool) { + pgxMutex.RLock() + defer pgxMutex.RUnlock() + pool, exists := pgxPools[name] + return pool, exists +} + +func (a *App) ClosePGXPools() { + pgxMutex.Lock() + defer pgxMutex.Unlock() + + for name, pool := range pgxPools { + pool.Close() + delete(pgxPools, name) + slog.Info("closed database connection", "name", name) + } +} diff --git a/templates/functions.go b/templates/functions.go index b9da1a2..6b3063f 100644 --- a/templates/functions.go +++ b/templates/functions.go @@ -6,11 +6,11 @@ import ( "time" ) -func Dict(values ...interface{}) (map[string]interface{}, error) { +func Dict(values ...any) (map[string]any, error) { if len(values)%2 != 0 { return nil, errors.New("invalid dict call") } - dict := make(map[string]interface{}, len(values)/2) + dict := make(map[string]any, len(values)/2) for i := 0; i < len(values); i += 2 { key, ok := values[i].(string) if !ok {