mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-09 16:58:51 +00:00
17a3e4a4b1
- Add alert_thresholds_days JSONB column to renewal_policies (default [30,14,7,0]) - Add RenewalPolicy.AlertThresholdsDays field + EffectiveAlertThresholds() helper - Add RenewalPolicyRepository interface + postgres implementation - Rewrite CheckExpiringCertificates with per-policy threshold alerting - Add SendThresholdAlert + HasThresholdNotification for deduplication via [threshold:N] tags - Add Type and MessageLike filters to NotificationFilter + postgres query support - Auto-transition certs to Expiring (>0 days) or Expired (<=0 days) status - Record expiration_alert_sent audit events per threshold crossing - Fix .gitignore: allow SQL migration files, scope server/agent build artifact rules - Track previously untracked cmd/ and migrations/ directories - Update docs (README, architecture, demo-advanced) for threshold alerting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
93 lines
2.7 KiB
Go
93 lines
2.7 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
)
|
|
|
|
// RenewalPolicyRepository implements repository.RenewalPolicyRepository
|
|
type RenewalPolicyRepository struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewRenewalPolicyRepository creates a new RenewalPolicyRepository
|
|
func NewRenewalPolicyRepository(db *sql.DB) *RenewalPolicyRepository {
|
|
return &RenewalPolicyRepository{db: db}
|
|
}
|
|
|
|
// Get retrieves a renewal policy by ID
|
|
func (r *RenewalPolicyRepository) Get(ctx context.Context, id string) (*domain.RenewalPolicy, error) {
|
|
var policy domain.RenewalPolicy
|
|
var thresholdsJSON []byte
|
|
|
|
err := r.db.QueryRowContext(ctx, `
|
|
SELECT id, name, renewal_window_days, auto_renew, max_retries,
|
|
retry_interval_minutes, alert_thresholds_days, created_at, updated_at
|
|
FROM renewal_policies
|
|
WHERE id = $1
|
|
`, id).Scan(&policy.ID, &policy.Name, &policy.RenewalWindowDays, &policy.AutoRenew,
|
|
&policy.MaxRetries, &policy.RetryInterval, &thresholdsJSON,
|
|
&policy.CreatedAt, &policy.UpdatedAt)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("renewal policy not found: %s", id)
|
|
}
|
|
return nil, fmt.Errorf("failed to query renewal policy: %w", err)
|
|
}
|
|
|
|
// Parse alert thresholds from JSONB
|
|
if len(thresholdsJSON) > 0 {
|
|
if err := json.Unmarshal(thresholdsJSON, &policy.AlertThresholdsDays); err != nil {
|
|
// Fall back to defaults if JSON is malformed
|
|
policy.AlertThresholdsDays = domain.DefaultAlertThresholds()
|
|
}
|
|
}
|
|
|
|
return &policy, nil
|
|
}
|
|
|
|
// List returns all renewal policies
|
|
func (r *RenewalPolicyRepository) List(ctx context.Context) ([]*domain.RenewalPolicy, error) {
|
|
rows, err := r.db.QueryContext(ctx, `
|
|
SELECT id, name, renewal_window_days, auto_renew, max_retries,
|
|
retry_interval_minutes, alert_thresholds_days, created_at, updated_at
|
|
FROM renewal_policies
|
|
ORDER BY name
|
|
`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query renewal policies: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var policies []*domain.RenewalPolicy
|
|
for rows.Next() {
|
|
var policy domain.RenewalPolicy
|
|
var thresholdsJSON []byte
|
|
|
|
if err := rows.Scan(&policy.ID, &policy.Name, &policy.RenewalWindowDays, &policy.AutoRenew,
|
|
&policy.MaxRetries, &policy.RetryInterval, &thresholdsJSON,
|
|
&policy.CreatedAt, &policy.UpdatedAt); err != nil {
|
|
return nil, fmt.Errorf("failed to scan renewal policy: %w", err)
|
|
}
|
|
|
|
if len(thresholdsJSON) > 0 {
|
|
if err := json.Unmarshal(thresholdsJSON, &policy.AlertThresholdsDays); err != nil {
|
|
policy.AlertThresholdsDays = domain.DefaultAlertThresholds()
|
|
}
|
|
}
|
|
|
|
policies = append(policies, &policy)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating renewal policy rows: %w", err)
|
|
}
|
|
|
|
return policies, nil
|
|
}
|