`main.go` only ever loaded `.env`; the `APP_ENV` switch described in `.env.local` / `.env.production` did not exist, and a missing variable surfaced one restart at a time as a log.Fatalf inside db.Connect. config.Load now picks `.env.<APP_ENV>` (default local) then `.env`, with real environment winning, reads every setting into one typed Config and reports everything missing in one message. Production insists on a POS signing secret; local warns when DB_HOST is not a local address. db, redis and the image store take the Config instead of reading env themselves. Also: - livehub read MQTT_USERNAME while everything else uses MQTT_USER, so the console stream connected to the broker unauthenticated. Both accepted. - .dockerignore: `COPY . .` was baking .env.production into the image. Dockerfile sets APP_ENV=production. - Drop utils/config.go (dead viper loader) and create_table.go (unused, hardcoded production DSN); go mod tidy removes viper. - .env.example lists every variable the code reads; docs/ENVIRONMENT.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
2.8 KiB
Go
114 lines
2.8 KiB
Go
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"nearle/config"
|
|
"net/url"
|
|
"time"
|
|
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var (
|
|
DB *gorm.DB
|
|
// CatalogueDB is a separate connection to the pgvector/catalogue database.
|
|
// It is intentionally isolated from DB (nearledb) so catalogue integration
|
|
// never touches the main database.
|
|
CatalogueDB *gorm.DB
|
|
)
|
|
|
|
// --------------------
|
|
// DATABASE CONNECTION
|
|
// --------------------
|
|
|
|
// Connect opens the main database and then the optional catalogue database
|
|
// and image store. Everything it needs has already been validated by
|
|
// config.Load, so a missing variable can no longer surface here as a
|
|
// log.Fatal halfway through boot.
|
|
func Connect(cfg *config.Config) {
|
|
dsn := fmt.Sprintf(
|
|
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata",
|
|
cfg.DB.Host,
|
|
cfg.DB.User,
|
|
cfg.DB.Password,
|
|
cfg.DB.Name,
|
|
cfg.DB.Port,
|
|
)
|
|
|
|
var err error
|
|
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
|
if err != nil {
|
|
log.Fatal("❌ Could not connect to database:", err)
|
|
}
|
|
|
|
setupDB(DB)
|
|
fmt.Println("✅ Database connected")
|
|
|
|
connectCatalogueDB(cfg.Catalogue)
|
|
connectImageStore(cfg.S3)
|
|
}
|
|
|
|
// connectCatalogueDB opens the read-only connection to the catalogue
|
|
// (pgvector) database. If its env vars are not set, catalogue endpoints
|
|
// are simply unavailable — this must never block startup of the main app.
|
|
func connectCatalogueDB(c config.DBConfig) {
|
|
if !c.Enabled() {
|
|
fmt.Println("⚠️ CATALOGUE_DB_HOST not set, skipping catalogue DB connection")
|
|
return
|
|
}
|
|
|
|
// Built as a URL (not a keyword=value DSN) and percent-encoded via
|
|
// net/url, because this credential's password contains characters
|
|
// (quotes, #) that the keyword=value DSN format would misparse as
|
|
// quoting/comment syntax.
|
|
dsnURL := url.URL{
|
|
Scheme: "postgres",
|
|
User: url.UserPassword(c.User, c.Password),
|
|
Host: fmt.Sprintf("%s:%s", c.Host, c.Port),
|
|
Path: "/" + c.Name,
|
|
}
|
|
q := dsnURL.Query()
|
|
q.Set("sslmode", "disable")
|
|
dsnURL.RawQuery = q.Encode()
|
|
|
|
catalogueDB, err := gorm.Open(postgres.Open(dsnURL.String()), &gorm.Config{})
|
|
if err != nil {
|
|
log.Println("❌ Could not connect to catalogue database:", err)
|
|
return
|
|
}
|
|
|
|
setupDB(catalogueDB)
|
|
CatalogueDB = catalogueDB
|
|
fmt.Println("✅ Catalogue database connected")
|
|
}
|
|
|
|
func setupDB(database *gorm.DB) {
|
|
sqlDB, err := database.DB()
|
|
if err != nil {
|
|
log.Fatal("❌ Failed to get DB from GORM:", err)
|
|
}
|
|
sqlDB.SetMaxIdleConns(100)
|
|
sqlDB.SetMaxOpenConns(1000)
|
|
sqlDB.SetConnMaxLifetime(time.Minute * 5)
|
|
}
|
|
|
|
// --------------------
|
|
// DATABASE SHUTDOWN
|
|
// --------------------
|
|
|
|
func CloseDB() {
|
|
if DB != nil {
|
|
if sqlDB, err := DB.DB(); err == nil {
|
|
sqlDB.Close()
|
|
}
|
|
}
|
|
if CatalogueDB != nil {
|
|
if sqlDB, err := CatalogueDB.DB(); err == nil {
|
|
sqlDB.Close()
|
|
}
|
|
}
|
|
fmt.Println("Connection closed Successfully")
|
|
}
|