package db import ( "database/sql" "fmt" "strings" "time" "github.com/google/uuid" "github.com/orohi/vpn-panel/internal/models" ) func scanNode(scanner interface { Scan(dest ...any) error }) (*models.Node, error) { n := &models.Node{} var lastSeen sql.NullTime var status string err := scanner.Scan( &n.ID, &n.Name, &n.Host, &n.SSHPort, &n.SSHUser, &n.SSHAuthType, &n.SSHSecretEnc, &n.NodePort, &n.SecretKey, &status, &n.InstallMode, &n.LastError, &n.InstallLog, &n.AgentVersion, &lastSeen, &n.CreatedAt, &n.UpdatedAt, ) if err != nil { return nil, err } n.Status = models.NodeStatus(status) if lastSeen.Valid { t := lastSeen.Time n.LastSeenAt = &t } return n, nil } const nodeColumns = ` id, name, host, ssh_port, ssh_user, ssh_auth_type, ssh_secret_enc, node_port, secret_key, status, install_mode, last_error, install_log, agent_version, last_seen_at, created_at, updated_at` func CreateNode(db *sql.DB, n *models.Node) error { if n.ID == uuid.Nil { n.ID = uuid.New() } now := time.Now().UTC() n.CreatedAt = now n.UpdatedAt = now if n.Status == "" { n.Status = models.NodeStatusPending } _, err := db.Exec(` INSERT INTO nodes ( id, name, host, ssh_port, ssh_user, ssh_auth_type, ssh_secret_enc, node_port, secret_key, status, install_mode, last_error, install_log, agent_version, last_seen_at, created_at, updated_at ) VALUES ( $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17 )`, n.ID, n.Name, n.Host, n.SSHPort, n.SSHUser, n.SSHAuthType, n.SSHSecretEnc, n.NodePort, n.SecretKey, string(n.Status), n.InstallMode, n.LastError, n.InstallLog, n.AgentVersion, n.LastSeenAt, n.CreatedAt, n.UpdatedAt, ) return err } func GetNode(db *sql.DB, id uuid.UUID) (*models.Node, error) { row := db.QueryRow(`SELECT `+nodeColumns+` FROM nodes WHERE id = $1`, id) n, err := scanNode(row) if err == sql.ErrNoRows { return nil, nil } return n, err } func ListNodes(db *sql.DB) ([]models.Node, error) { rows, err := db.Query(`SELECT ` + nodeColumns + ` FROM nodes ORDER BY created_at DESC`) if err != nil { return nil, err } defer rows.Close() var list []models.Node for rows.Next() { n, err := scanNode(rows) if err != nil { return nil, err } list = append(list, *n) } return list, rows.Err() } func UpdateNodeStatus(db *sql.DB, id uuid.UUID, status models.NodeStatus, lastError string) error { _, err := db.Exec(` UPDATE nodes SET status = $2, last_error = $3, updated_at = NOW() WHERE id = $1`, id, string(status), lastError) return err } func ClearNodeInstallLog(db *sql.DB, id uuid.UUID) error { _, err := db.Exec(`UPDATE nodes SET install_log = '', updated_at = NOW() WHERE id = $1`, id) return err } func AppendNodeInstallLog(db *sql.DB, id uuid.UUID, chunk string) error { _, err := db.Exec(` UPDATE nodes SET install_log = CASE WHEN install_log = '' OR install_log IS NULL THEN $2 ELSE install_log || E'\n' || $2 END, updated_at = NOW() WHERE id = $1`, id, strings.TrimRight(chunk, "\n")) return err } func MarkNodeSeen(db *sql.DB, id uuid.UUID, version string, status models.NodeStatus) error { _, err := db.Exec(` UPDATE nodes SET last_seen_at = NOW(), agent_version = $2, status = $3, last_error = '', updated_at = NOW() WHERE id = $1`, id, version, string(status)) return err } func DeleteNode(db *sql.DB, id uuid.UUID) error { res, err := db.Exec(`DELETE FROM nodes WHERE id = $1`, id) if err != nil { return err } n, err := res.RowsAffected() if err != nil { return err } if n == 0 { return fmt.Errorf("node not found") } return nil }