Files
sub2api-cn-relay-manager/internal/store/sqlite/hosts_repo.go
phamnazage-jpg 85d495dd16 feat(control-plane): harden host-scoped reconcile and acceptance evidence
- add batch-scoped reconcile_runs persistence and queries
- route batch detail and reconcile writes through batch_id/host_id
- refresh production boards with host-scope acceptance artifacts
- include latest real-host acceptance evidence for self_service and subscription
2026-05-18 22:22:22 +08:00

218 lines
6.4 KiB
Go

package sqlite
import (
"context"
"fmt"
"strings"
)
type Host struct {
ID int64
HostID string
BaseURL string
HostVersion string
CapabilityProbeJSON string
AuthType string
AuthToken string
}
type HostsRepo struct {
db execQuerier
}
func newHostsRepo(db execQuerier) *HostsRepo {
return &HostsRepo{db: db}
}
func (r *HostsRepo) GetByID(ctx context.Context, id int64) (Host, error) {
if id <= 0 {
return Host{}, fmt.Errorf("id is required")
}
var host Host
if err := r.db.QueryRowContext(ctx, `SELECT id, host_id, base_url, host_version, capability_probe_json, auth_type, auth_token FROM hosts WHERE id = ?`, id).Scan(&host.ID, &host.HostID, &host.BaseURL, &host.HostVersion, &host.CapabilityProbeJSON, &host.AuthType, &host.AuthToken); err != nil {
return Host{}, err
}
return host, nil
}
func (r *HostsRepo) GetByHostID(ctx context.Context, hostID string) (Host, error) {
hostID = strings.TrimSpace(hostID)
if hostID == "" {
return Host{}, fmt.Errorf("host_id is required")
}
var host Host
if err := r.db.QueryRowContext(ctx, `SELECT id, host_id, base_url, host_version, capability_probe_json, auth_type, auth_token FROM hosts WHERE host_id = ?`, hostID).Scan(&host.ID, &host.HostID, &host.BaseURL, &host.HostVersion, &host.CapabilityProbeJSON, &host.AuthType, &host.AuthToken); err != nil {
return Host{}, err
}
return host, nil
}
func (r *HostsRepo) GetByBaseURL(ctx context.Context, baseURL string) (Host, error) {
baseURL = strings.TrimSpace(baseURL)
if baseURL == "" {
return Host{}, fmt.Errorf("base_url is required")
}
var host Host
if err := r.db.QueryRowContext(ctx, `SELECT id, host_id, base_url, host_version, capability_probe_json, auth_type, auth_token FROM hosts WHERE base_url = ?`, baseURL).Scan(&host.ID, &host.HostID, &host.BaseURL, &host.HostVersion, &host.CapabilityProbeJSON, &host.AuthType, &host.AuthToken); err != nil {
return Host{}, err
}
return host, nil
}
func (r *HostsRepo) Create(ctx context.Context, host Host) (int64, error) {
hostID := strings.TrimSpace(host.HostID)
baseURL := strings.TrimSpace(host.BaseURL)
hostVersion := strings.TrimSpace(host.HostVersion)
capabilityProbeJSON := strings.TrimSpace(host.CapabilityProbeJSON)
authType := firstNonEmptyTrimmed(host.AuthType, "apikey")
authToken := strings.TrimSpace(host.AuthToken)
switch {
case hostID == "":
return 0, fmt.Errorf("host_id is required")
case baseURL == "":
return 0, fmt.Errorf("base_url is required")
case hostVersion == "":
return 0, fmt.Errorf("host_version is required")
case capabilityProbeJSON == "":
capabilityProbeJSON = "{}"
}
result, err := r.db.ExecContext(
ctx,
`INSERT INTO hosts (host_id, base_url, host_version, capability_probe_json, auth_type, auth_token)
VALUES (?, ?, ?, ?, ?, ?)`,
hostID,
baseURL,
hostVersion,
capabilityProbeJSON,
authType,
authToken,
)
if err != nil {
return 0, fmt.Errorf("insert host %q: %w", hostID, err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("read inserted host id for %q: %w", hostID, err)
}
return id, nil
}
func (r *HostsRepo) UpdateConnectionByHostID(ctx context.Context, hostID, baseURL, hostVersion, capabilityProbeJSON, authType, authToken string) error {
hostID = strings.TrimSpace(hostID)
baseURL = strings.TrimSpace(baseURL)
hostVersion = strings.TrimSpace(hostVersion)
capabilityProbeJSON = strings.TrimSpace(capabilityProbeJSON)
authType = firstNonEmptyTrimmed(authType, "apikey")
authToken = strings.TrimSpace(authToken)
if hostID == "" {
return fmt.Errorf("host_id is required")
}
if baseURL == "" {
return fmt.Errorf("base_url is required")
}
if hostVersion == "" {
return fmt.Errorf("host_version is required")
}
if capabilityProbeJSON == "" {
capabilityProbeJSON = "{}"
}
result, err := r.db.ExecContext(ctx, `UPDATE hosts SET base_url = ?, host_version = ?, capability_probe_json = ?, auth_type = ?, auth_token = ? WHERE host_id = ?`, baseURL, hostVersion, capabilityProbeJSON, authType, authToken, hostID)
if err != nil {
return fmt.Errorf("update host %q connection: %w", hostID, err)
}
rows, err := result.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return fmt.Errorf("host %q not found", hostID)
}
return nil
}
func (r *HostsRepo) ListAll(ctx context.Context) ([]Host, error) {
rows, err := r.db.QueryContext(ctx, `SELECT id, host_id, base_url, host_version, capability_probe_json, auth_type, auth_token FROM hosts ORDER BY id`)
if err != nil {
return nil, fmt.Errorf("list hosts: %w", err)
}
defer rows.Close()
var hosts []Host
for rows.Next() {
var host Host
if err := rows.Scan(&host.ID, &host.HostID, &host.BaseURL, &host.HostVersion, &host.CapabilityProbeJSON, &host.AuthType, &host.AuthToken); err != nil {
return nil, fmt.Errorf("scan host: %w", err)
}
hosts = append(hosts, host)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate hosts: %w", err)
}
return hosts, nil
}
func (r *HostsRepo) DeleteByHostID(ctx context.Context, hostID string) error {
hostID = strings.TrimSpace(hostID)
if hostID == "" {
return fmt.Errorf("host_id is required")
}
result, err := r.db.ExecContext(ctx, `DELETE FROM hosts WHERE host_id = ?`, hostID)
if err != nil {
return fmt.Errorf("delete host %q: %w", hostID, err)
}
rows, err := result.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return fmt.Errorf("host %q not found", hostID)
}
return nil
}
func (r *HostsRepo) UpdateProbeByHostID(ctx context.Context, hostID, hostVersion, capabilityProbeJSON string) error {
hostID = strings.TrimSpace(hostID)
hostVersion = strings.TrimSpace(hostVersion)
capabilityProbeJSON = strings.TrimSpace(capabilityProbeJSON)
if hostID == "" {
return fmt.Errorf("host_id is required")
}
if hostVersion == "" {
return fmt.Errorf("host_version is required")
}
if capabilityProbeJSON == "" {
capabilityProbeJSON = "{}"
}
result, err := r.db.ExecContext(ctx, `UPDATE hosts SET host_version = ?, capability_probe_json = ? WHERE host_id = ?`, hostVersion, capabilityProbeJSON, hostID)
if err != nil {
return fmt.Errorf("update host %q probe: %w", hostID, err)
}
rows, err := result.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return fmt.Errorf("host %q not found", hostID)
}
return nil
}
func firstNonEmptyTrimmed(values ...string) string {
for _, value := range values {
if trimmed := strings.TrimSpace(value); trimmed != "" {
return trimmed
}
}
return ""
}