package repository import ( "context" "errors" "fmt" "time" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) type SSLCertificate struct { ID int64 `json:"id"` DomainID int64 `json:"domain_id"` Type string `json:"type"` Status string `json:"status"` Issuer *string `json:"issuer,omitempty"` ErrorMessage *string `json:"error_message,omitempty"` IssuedAt *time.Time `json:"issued_at,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"` AutoRenew bool `json:"auto_renew"` } type SSLRepository struct { pool *pgxpool.Pool } func NewSSLRepository(pool *pgxpool.Pool) *SSLRepository { return &SSLRepository{pool: pool} } func (r *SSLRepository) CreatePending(ctx context.Context, domainID int64) (int64, error) { const q = ` INSERT INTO ssl_certificates (domain_id, type, status, auto_renew) VALUES ($1, 'letsencrypt', 'pending', true) RETURNING id ` var id int64 err := r.pool.QueryRow(ctx, q, domainID).Scan(&id) if err != nil { return 0, fmt.Errorf("create ssl cert: %w", err) } return id, nil } func (r *SSLRepository) MarkActive(ctx context.Context, id int64, issuer, certPEM, keyPEM, chainPEM string, issuedAt, expiresAt time.Time) error { const q = ` UPDATE ssl_certificates SET status = 'active', issuer = $2, cert_pem = $3, key_pem = $4, chain_pem = $5, issued_at = $6, expires_at = $7, error_message = NULL, updated_at = now() WHERE id = $1 ` _, err := r.pool.Exec(ctx, q, id, issuer, certPEM, keyPEM, chainPEM, issuedAt, expiresAt) return err } func (r *SSLRepository) MarkError(ctx context.Context, id int64, msg string) error { const q = ` UPDATE ssl_certificates SET status = 'error', error_message = $2, updated_at = now() WHERE id = $1 ` _, err := r.pool.Exec(ctx, q, id, msg) return err } func (r *SSLRepository) GetByDomainID(ctx context.Context, domainID int64) (*SSLCertificate, error) { const q = ` SELECT id, domain_id, type::text, status::text, issuer, error_message, issued_at, expires_at, auto_renew FROM ssl_certificates WHERE domain_id = $1 ORDER BY id DESC LIMIT 1 ` var c SSLCertificate err := r.pool.QueryRow(ctx, q, domainID).Scan( &c.ID, &c.DomainID, &c.Type, &c.Status, &c.Issuer, &c.ErrorMessage, &c.IssuedAt, &c.ExpiresAt, &c.AutoRenew, ) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } if err != nil { return nil, err } return &c, nil } func (r *SSLRepository) GetDomainInfo(ctx context.Context, domainID int64) (domain string, webroot string, err error) { const q = ` SELECT d.domain::text, s.document_root FROM domains d JOIN sites s ON s.id = d.site_id WHERE d.id = $1 ` err = r.pool.QueryRow(ctx, q, domainID).Scan(&domain, &webroot) return } func (r *SSLRepository) GetSiteOwner(ctx context.Context, siteID int64) (int64, error) { var ownerID int64 err := r.pool.QueryRow(ctx, `SELECT owner_id FROM sites WHERE id = $1`, siteID).Scan(&ownerID) return ownerID, err } func (r *SSLRepository) GetDomainIDBySite(ctx context.Context, siteID int64) (int64, error) { var id int64 err := r.pool.QueryRow(ctx, ` SELECT id FROM domains WHERE site_id = $1 AND is_primary = true LIMIT 1 `, siteID).Scan(&id) return id, err }