package db import ( "database/sql" "fmt" "log" "os" "regexp" "rmser/internal/domain/account" "rmser/internal/domain/catalog" "rmser/internal/domain/drafts" "rmser/internal/domain/invoices" "rmser/internal/domain/ocr" "rmser/internal/domain/operations" "rmser/internal/domain/recipes" "rmser/internal/domain/recommendations" "rmser/internal/domain/suppliers" "time" _ "github.com/jackc/pgx/v5/stdlib" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) func NewPostgresDB(dsn string) *gorm.DB { // 1. Проверка и создание БД перед основным подключением ensureDBExists(dsn) // 2. Настройка логгера GORM newLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Second, LogLevel: logger.Warn, IgnoreRecordNotFoundError: true, Colorful: true, }, ) // 3. Основное подключение db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: newLogger, }) if err != nil { panic(fmt.Sprintf("не удалось подключиться к БД: %v", err)) } // 4. Автомиграция err = db.AutoMigrate( &account.User{}, &account.RMSServer{}, &account.ServerUser{}, &catalog.Product{}, &catalog.MeasureUnit{}, &catalog.ProductContainer{}, &catalog.Store{}, &suppliers.Supplier{}, &recipes.Recipe{}, &recipes.RecipeItem{}, &invoices.Invoice{}, &invoices.InvoiceItem{}, &drafts.DraftInvoice{}, &drafts.DraftInvoiceItem{}, &operations.StoreOperation{}, &recommendations.Recommendation{}, &ocr.ProductMatch{}, &ocr.UnmatchedItem{}, ) if err != nil { panic(fmt.Sprintf("ошибка миграции БД: %v", err)) } return db } // ensureDBExists подключается к системной БД 'postgres' и создает целевую, если её нет func ensureDBExists(fullDSN string) { // Регулярка для извлечения имени базы из DSN (ищем dbname=... ) re := regexp.MustCompile(`dbname=([^\s]+)`) matches := re.FindStringSubmatch(fullDSN) if len(matches) < 2 { // Если не нашли dbname, возможно формат URL (postgres://...), // пропускаем авто-создание, полагаемся на ошибку драйвера return } targetDB := matches[1] // Заменяем целевую БД на системную 'postgres' для подключения maintenanceDSN := re.ReplaceAllString(fullDSN, "dbname=postgres") // Используем стандартный sql драйвер через pgx (который под капотом у gorm/postgres) // Важно: нам не нужен GORM здесь, нужен чистый SQL для CREATE DATABASE db, err := sql.Open("pgx", maintenanceDSN) if err != nil { // Если не вышло подключиться к postgres, просто выходим, // основная ошибка вылетит при попытке gorm.Open log.Printf("[WARN] Не удалось подключиться к системной БД для проверки: %v", err) return } defer db.Close() // Проверяем существование базы var exists bool checkSQL := fmt.Sprintf("SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = '%s')", targetDB) err = db.QueryRow(checkSQL).Scan(&exists) if err != nil { log.Printf("[WARN] Ошибка проверки существования БД: %v", err) return } if !exists { log.Printf("[INFO] База данных '%s' не найдена. Создаю...", targetDB) // CREATE DATABASE не может быть выполнен в транзакции, поэтому Exec _, err = db.Exec(fmt.Sprintf("CREATE DATABASE \"%s\"", targetDB)) if err != nil { panic(fmt.Sprintf("не удалось создать базу данных %s: %v", targetDB, err)) } log.Printf("[INFO] База данных '%s' успешно создана", targetDB) } }