feat(m2mcore): PgRepository через pgx + интеграция в lk-gateway

PostgreSQL-репозиторий для m2m_core.deals — реальное хранилище сделок
вместо in-memory. Выбор Repository происходит автоматически в
lkgateway.NewServer: если в runtime-конфиге задан Postgres DSN, поднимается
pgxpool и используется PostgresRepository; иначе fallback на MemoryRepository.

internal/m2mcore/pgrepo.go:
- PostgresRepository: Create (идемпотентный по guid через ON CONFLICT DO NOTHING),
  GetByGUID, GetByID, Update, List (с фильтрами state/investor/created_*),
  AppendEvent для журнала deal_events
- request_xml/response_xml/decision_xml хранятся как windows-1251 XML через nsdxml,
  на чтении парсятся обратно в m2m.M2M* структуры
- stages — jsonb с историей FSM-переходов

migrations/m2m-core/002__stages.sql:
- ALTER TABLE deals ADD COLUMN stages jsonb DEFAULT '[]'

internal/lkgateway/server.go:
- При NewServer проверяется runtime-config: если есть DSN → PostgresRepository,
  иначе MemoryRepository; ошибка подключения логируется с fallback на in-memory
- Тесты используют tempdir SetupPath для изоляции от реальной БД

internal/lkgateway/setup.go:
- tryPingPostgres переписан с database/sql (требует регистрации драйвера)
  на pgx.Connect — теперь форма /admin/setup/postgres реально проверяет
  подключение перед сохранением DSN

Проверено сквозным smoke-тестом: введение DSN через UI →
сохранение в ~/.bj/setup.json → перезапуск lk-gateway → лог
"PostgresRepository подключён (m2m_core.deals)" → сделки реально пишутся
в БД.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
fontvielle
2026-05-14 13:43:49 +03:00
parent 958d777751
commit ee642e5eaa
8 changed files with 373 additions and 25 deletions
+8 -14
View File
@@ -2,7 +2,6 @@ package lkgateway
import (
"context"
"database/sql"
"errors"
"fmt"
"net/http"
@@ -10,6 +9,8 @@ import (
"os"
"strings"
"time"
"github.com/jackc/pgx/v5"
)
// setupHandlers — обработчики /admin/setup/*.
@@ -278,23 +279,16 @@ func (h *setupHandlers) runTestClaim() {
_ = h.rc.RecordTestRun(res)
}
// tryPingPostgres пытается sql.Open + Ping с прокачкой драйвера; без
// драйвера вернёт «unknown driver pgx»/«unknown driver postgres» —
// тоже считаем ошибкой и показываем пользователю.
// tryPingPostgres делает короткое подключение через pgx и Ping.
func tryPingPostgres(dsn string) error {
// Угадываем имя драйвера по префиксу.
driver := "postgres"
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
driver = "postgres"
}
db, err := sql.Open(driver, dsn)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
conn, err := pgx.Connect(ctx, dsn)
if err != nil {
return err
}
defer db.Close()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return db.PingContext(ctx)
defer conn.Close(ctx)
return conn.Ping(ctx)
}
// tryHTTPHealth делает GET и ждёт 2xx.