Adds a separate CatalogueDB (pgvector) connection alongside the main nearledb, plus a new catalogue module (repository/service/controller/ routes) to browse it by brand, category, and keyword, with brand optional so the whole ~237-product catalogue can be browsed unfiltered. Adds the actual bridge: importing a catalogue product snapshots it into the tenant's own products table (keyed on brand+catalogueid, since a catalogue row's bare id is only unique within its own brand table), then links it via the existing productlocations upsert. Re-importing tops up stock and refreshes price instead of duplicating. Also adds an imported-refs endpoint so the frontend can badge already-imported items without diffing full product lists, and wires the new AWS S3 image store used to resolve catalogue product photos. Bumps Go/Docker to 1.24 for the AWS SDK dependency this needs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"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
|
|
// --------------------
|
|
|
|
func Connect() {
|
|
dsn := fmt.Sprintf(
|
|
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata",
|
|
mustEnv("DB_HOST"),
|
|
mustEnv("DB_USER"),
|
|
mustEnv("DB_PASSWORD"),
|
|
mustEnv("DB_NAME"),
|
|
getEnv("DB_PORT", "5433"),
|
|
)
|
|
|
|
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()
|
|
connectImageStore()
|
|
}
|
|
|
|
// 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() {
|
|
host := getEnv("CATALOGUE_DB_HOST", "")
|
|
if host == "" {
|
|
fmt.Println("⚠️ Catalogue DB env vars 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(mustEnv("CATALOGUE_DB_USER"), mustEnv("CATALOGUE_DB_PASSWORD")),
|
|
Host: fmt.Sprintf("%s:%s", host, getEnv("CATALOGUE_DB_PORT", "5432")),
|
|
Path: "/" + mustEnv("CATALOGUE_DB_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")
|
|
}
|
|
|
|
// --------------------
|
|
// ENV HELPERS
|
|
// --------------------
|
|
|
|
func mustEnv(key string) string {
|
|
val := os.Getenv(key)
|
|
if val == "" {
|
|
log.Fatalf("Missing required env variable: %s", key)
|
|
}
|
|
return val
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if val := os.Getenv(key); val != "" {
|
|
return val
|
|
}
|
|
return fallback
|
|
}
|