package database import ( "database/sql" "encoding/json" "errors" "time" "github.com/owen/vibeproxy/internal/models" ) func scanCertificate(rs rowScanner) (models.Certificate, error) { var c models.Certificate var domainsJSON string var challengeType, dnsProvider, certPath, keyPath, notBefore, notAfter, lastRenewed, lastErr sql.NullString var autoRenew int var created, updated string err := rs.Scan(&c.ID, &c.Name, &c.Type, &domainsJSON, &challengeType, &dnsProvider, &certPath, &keyPath, ¬Before, ¬After, &lastRenewed, &lastErr, &autoRenew, &created, &updated) if err != nil { return c, err } json.Unmarshal([]byte(domainsJSON), &c.Domains) c.ChallengeType = models.ChallengeType(challengeType.String) c.DNSProvider = dnsProvider.String c.CertPath = certPath.String c.KeyPath = keyPath.String if notBefore.Valid { t := parseTime(notBefore.String) c.NotBefore = &t } if notAfter.Valid { t := parseTime(notAfter.String) c.NotAfter = &t } if lastRenewed.Valid { t := parseTime(lastRenewed.String) c.LastRenewedAt = &t } c.LastError = lastErr.String c.AutoRenew = autoRenew == 1 c.CreatedAt = parseTime(created) c.UpdatedAt = parseTime(updated) return c, nil } const certCols = `id, name, type, domains, challenge_type, dns_provider, cert_path, key_path, not_before, not_after, last_renewed_at, last_error, auto_renew, created_at, updated_at` func ListCertificates(db *sql.DB) ([]models.Certificate, error) { rows, err := db.Query(`SELECT ` + certCols + ` FROM certificates ORDER BY id`) if err != nil { return nil, err } defer rows.Close() var out []models.Certificate for rows.Next() { c, err := scanCertificate(rows) if err != nil { return nil, err } out = append(out, c) } return out, rows.Err() } func GetCertificate(db *sql.DB, id int64) (*models.Certificate, error) { row := db.QueryRow(`SELECT `+certCols+` FROM certificates WHERE id = ?`, id) c, err := scanCertificate(row) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } return nil, err } return &c, nil } func SaveCertificate(db *sql.DB, c *models.Certificate) (int64, error) { domainsJSON, _ := json.Marshal(c.Domains) if c.ID == 0 { res, err := db.Exec(`INSERT INTO certificates (name, type, domains, challenge_type, dns_provider, cert_path, key_path, not_before, not_after, last_renewed_at, last_error, auto_renew) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`, c.Name, c.Type, string(domainsJSON), string(c.ChallengeType), c.DNSProvider, c.CertPath, c.KeyPath, timeOrNil(c.NotBefore), timeOrNil(c.NotAfter), timeOrNil(c.LastRenewedAt), c.LastError, boolToInt(c.AutoRenew)) if err != nil { return 0, err } id, err := res.LastInsertId() c.ID = id return id, err } _, err := db.Exec(`UPDATE certificates SET name=?, type=?, domains=?, challenge_type=?, dns_provider=?, cert_path=?, key_path=?, not_before=?, not_after=?, last_renewed_at=?, last_error=?, auto_renew=?, updated_at=strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE id=?`, c.Name, c.Type, string(domainsJSON), string(c.ChallengeType), c.DNSProvider, c.CertPath, c.KeyPath, timeOrNil(c.NotBefore), timeOrNil(c.NotAfter), timeOrNil(c.LastRenewedAt), c.LastError, boolToInt(c.AutoRenew), c.ID) return c.ID, err } func DeleteCertificate(db *sql.DB, id int64) error { _, err := db.Exec(`DELETE FROM certificates WHERE id = ?`, id) return err } func timeOrNil(t *time.Time) any { if t == nil { return nil } return t.UTC().Format(timeFmt) }