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
+25 -7
View File
@@ -33,8 +33,11 @@ type Server struct {
server *http.Server
}
// NewServer собирает Server с in-memory репозиторием, mock NSDSender,
// SeedStore и REST + Admin маршрутами.
// NewServer собирает Server с репозиторием, mock NSDSender, SeedStore
// и REST + Admin маршрутами. Выбор Repository:
// - если в runtime-конфиге (или ENV-fallback в cfg) задан PostgresDSN
// и pgx-Pool успешно создаётся — используется PostgresRepository;
// - иначе fallback на MemoryRepository (M2-демо).
func NewServer(cfg ServerConfig) (*Server, error) {
store := NewSeedStore()
mockCfg := mock.DefaultConfig()
@@ -44,8 +47,27 @@ func NewServer(cfg ServerConfig) (*Server, error) {
}
sender := mock.NewSender(mockCfg)
rc, err := NewRuntimeConfig(cfg.SetupPath)
if err != nil {
return nil, err
}
// Repository: pgx если DSN указан, иначе in-memory.
var repo m2mcore.Repository = m2mcore.NewMemoryRepository()
if dsn := rc.Snapshot().Postgres.DSN; dsn != "" {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
pgRepo, pgErr := m2mcore.NewPostgresRepository(ctx, dsn)
cancel()
if pgErr != nil {
log.Printf("lk-gateway: PostgresRepository отказал, fallback MemoryRepository: %v", pgErr)
} else {
repo = pgRepo
log.Printf("lk-gateway: PostgresRepository подключён (m2m_core.deals)")
}
}
svc := NewService(Config{
Repository: m2mcore.NewMemoryRepository(),
Repository: repo,
Sender: sender,
Store: store,
Recorder: m2mcore.NewMemoryRecorder(),
@@ -53,10 +75,6 @@ func NewServer(cfg ServerConfig) (*Server, error) {
DefaultReceiver: cfg.DefaultReceiver,
})
rc, err := NewRuntimeConfig(cfg.SetupPath)
if err != nil {
return nil, err
}
// Если runtime-конфиг уже содержит callback URL — применяем его.
if s := rc.Snapshot(); s.LK.CallbackURL != "" {
svc.callbackURL = s.LK.CallbackURL