73 lines
2.2 KiB
Go
73 lines
2.2 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// schemaSQL — схема таблицы устройств.
|
|
// PRIMARY KEY (namespace, name) — как в REST-маршруте /devices/{name}.
|
|
// Уникальность device_id в рамках namespace — для MQTT username.
|
|
const schemaSQL = `
|
|
CREATE TABLE IF NOT EXISTS iot_devices (
|
|
namespace TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
device_id TEXT NOT NULL,
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
mqtt_password TEXT NOT NULL,
|
|
metadata JSONB,
|
|
phase TEXT NOT NULL DEFAULT 'Active',
|
|
last_connected TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (namespace, name)
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_iot_devices_ns_device
|
|
ON iot_devices (namespace, device_id);
|
|
`
|
|
|
|
// DeviceStore — доступ к таблице iot_devices (собственный пул соединений).
|
|
type DeviceStore struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// Open подключается к PostgreSQL по DSN и инициализирует схему.
|
|
func Open(ctx context.Context, dsn string) (*DeviceStore, error) {
|
|
db, err := sql.Open("postgres", dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("store: open DB: %w", err)
|
|
}
|
|
// Пул устройств: дефолт 20 (ревью 2026-08-16: 5 мало для API+MQTT auth),
|
|
// переопределяется IOT_PG_MAX_CONNS.
|
|
maxConns := 20
|
|
if v := os.Getenv("IOT_PG_MAX_CONNS"); v != "" {
|
|
if n, convErr := strconv.Atoi(v); convErr == nil && n > 0 {
|
|
maxConns = n
|
|
}
|
|
}
|
|
db.SetMaxOpenConns(maxConns)
|
|
db.SetMaxIdleConns(2)
|
|
db.SetConnMaxLifetime(5 * time.Minute)
|
|
|
|
pingCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
if err := db.PingContext(pingCtx); err != nil {
|
|
db.Close()
|
|
return nil, fmt.Errorf("store: ping DB: %w", err)
|
|
}
|
|
if _, err := db.ExecContext(ctx, schemaSQL); err != nil {
|
|
db.Close()
|
|
return nil, fmt.Errorf("store: init schema: %w", err)
|
|
}
|
|
return &DeviceStore{db: db}, nil
|
|
}
|
|
|
|
// Close закрывает пул соединений.
|
|
func (s *DeviceStore) Close() error {
|
|
return s.db.Close()
|
|
}
|