133 lines
4.9 KiB
Go
133 lines
4.9 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
)
|
|
|
|
func (s *Store) UpsertSource(item Source) (Source, error) {
|
|
now := Now()
|
|
if item.SourceID == "" {
|
|
item.SourceID = item.CategoryID + "-" + item.Name
|
|
}
|
|
if item.Method == "" {
|
|
item.Method = "GET"
|
|
}
|
|
item.ProxyMode = normalizeProxyMode(firstNonEmpty(item.ProxyMode, "client_direct"), item.CategoryID, item.Name, item.APIURL)
|
|
if item.URLTemplate == "" {
|
|
item.URLTemplate = item.APIURL
|
|
}
|
|
if item.TimeoutMS <= 0 {
|
|
item.TimeoutMS = 8000
|
|
}
|
|
if item.RetryCount <= 0 {
|
|
item.RetryCount = 1
|
|
}
|
|
if item.CacheSeconds <= 0 {
|
|
item.CacheSeconds = item.CheckIntervalSec
|
|
}
|
|
if item.CacheSeconds <= 0 {
|
|
item.CacheSeconds = 300
|
|
}
|
|
if item.CheckIntervalSec <= 0 {
|
|
item.CheckIntervalSec = item.CacheSeconds
|
|
}
|
|
if item.SupportedFormats == "" {
|
|
item.SupportedFormats = "[]"
|
|
}
|
|
if item.LastStatus == "" {
|
|
item.LastStatus = "unknown"
|
|
}
|
|
if item.CategoryID == "" {
|
|
item.CategoryID = "custom"
|
|
}
|
|
if item.CategoryName == "" {
|
|
item.CategoryName = item.CategoryID
|
|
}
|
|
_, _ = s.exec(`INSERT INTO source_categories (category_id, name, enabled, ui_config, created_at, updated_at)
|
|
VALUES (?, ?, 1, '{}', ?, ?)
|
|
ON CONFLICT (category_id) DO UPDATE SET name = excluded.name, updated_at = excluded.updated_at`,
|
|
item.CategoryID, item.CategoryName, now, now)
|
|
conn, d := s.active()
|
|
query := d.upsert("source_endpoints",
|
|
[]string{"category_id", "category_name", "source_id", "name", "description", "method", "api_url", "url_template", "thumbnail_url", "proxy_mode", "timeout_ms", "retry_count", "cache_seconds", "check_interval_sec", "enabled", "client_visible", "supported_formats", "last_status", "last_latency_ms", "last_checked_at", "last_error", "consecutive_failure", "created_at", "updated_at"},
|
|
[]string{"source_id"})
|
|
if _, err := conn.Exec(d.rebind(query), item.CategoryID, item.CategoryName, item.SourceID, item.Name, item.Description, item.Method, item.APIURL, item.URLTemplate, item.ThumbnailURL,
|
|
item.ProxyMode, item.TimeoutMS, item.RetryCount, item.CacheSeconds, item.CheckIntervalSec, boolInt(item.Enabled), boolInt(item.ClientVisible), item.SupportedFormats,
|
|
item.LastStatus, item.LastLatencyMS, item.LastCheckedAt, item.LastError, item.ConsecutiveFailure, firstNonEmpty(item.CreatedAt, now), now); err != nil {
|
|
s.markFailover(err)
|
|
return Source{}, err
|
|
}
|
|
return s.GetSourceBySourceID(item.SourceID)
|
|
}
|
|
|
|
func (s *Store) GetSourceBySourceID(sourceID string) (Source, error) {
|
|
item, err := scanSourceRow(s.queryRow(sourceSelectSQL()+` WHERE source_id = ?`, sourceID))
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return Source{}, errors.New("source not found")
|
|
}
|
|
return item, err
|
|
}
|
|
|
|
func (s *Store) ListSources(includeHidden bool) ([]Source, error) {
|
|
where := ""
|
|
args := []any{}
|
|
if !includeHidden {
|
|
where = " WHERE enabled = 1 AND client_visible = 1"
|
|
}
|
|
rows, err := s.query(sourceSelectSQL()+where+` ORDER BY category_id ASC, name ASC`, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []Source{}
|
|
for rows.Next() {
|
|
item, err := scanSourceRowsCurrent(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return items, rows.Err()
|
|
}
|
|
|
|
func (s *Store) CountSources() (int, error) {
|
|
var count int
|
|
err := s.queryRow(`SELECT COUNT(*) FROM source_endpoints`).Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
func (s *Store) DeleteSource(sourceID string) error {
|
|
_, err := s.exec(`DELETE FROM source_endpoints WHERE source_id = ?`, sourceID)
|
|
return err
|
|
}
|
|
|
|
func (s *Store) RecordSourceCheck(sourceDBID int64, status string, latency int, message string) error {
|
|
now := Now()
|
|
_, err := s.exec(`INSERT INTO endpoint_health_checks (source_db_id, status, latency_ms, error, checked_at) VALUES (?, ?, ?, ?, ?)`,
|
|
sourceDBID, status, latency, sanitize(message), now)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if status == "ok" {
|
|
_, err = s.exec(`UPDATE source_endpoints SET last_status = ?, last_latency_ms = ?, last_checked_at = ?, last_error = ?, consecutive_failure = 0, updated_at = ? WHERE id = ?`,
|
|
status, latency, now, sanitize(message), now, sourceDBID)
|
|
} else if status == "redirected" {
|
|
_, err = s.exec(`UPDATE source_endpoints SET last_status = ?, last_latency_ms = ?, last_checked_at = ?, last_error = ?, consecutive_failure = 0, updated_at = ? WHERE id = ?`,
|
|
status, latency, now, sanitize(message), now, sourceDBID)
|
|
} else {
|
|
_, err = s.exec(`UPDATE source_endpoints SET last_status = ?, last_latency_ms = ?, last_checked_at = ?, last_error = ?, consecutive_failure = consecutive_failure + 1, updated_at = ? WHERE id = ?`,
|
|
status, latency, now, sanitize(message), now, sourceDBID)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *Store) RecordSourceCall(call SourceCall) error {
|
|
if call.CreatedAt == "" {
|
|
call.CreatedAt = Now()
|
|
}
|
|
_, err := s.exec(`INSERT INTO endpoint_call_logs (source_id, status, latency_ms, error, client, created_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
sanitize(call.SourceID), sanitize(call.Status), call.LatencyMS, sanitize(call.Error), sanitize(call.Client), call.CreatedAt)
|
|
return err
|
|
}
|