Files
certctl/internal/service/issuer.go
T
shankar0123 e03a75ed9a fix: replace fmt.Printf with structured slog logging across all services
All 10 service files now use slog.Error for failure logging instead of
fmt.Printf. Audit event recording errors are checked and logged rather
than silently discarded. Adds consistent structured context (resource IDs,
operation names) to all error log statements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 01:20:03 -04:00

209 lines
5.6 KiB
Go

package service
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/shankar0123/certctl/internal/domain"
"github.com/shankar0123/certctl/internal/repository"
)
// IssuerService provides business logic for certificate issuer management.
type IssuerService struct {
issuerRepo repository.IssuerRepository
auditService *AuditService
}
// NewIssuerService creates a new issuer service.
func NewIssuerService(
issuerRepo repository.IssuerRepository,
auditService *AuditService,
) *IssuerService {
return &IssuerService{
issuerRepo: issuerRepo,
auditService: auditService,
}
}
// List returns a paginated list of issuers.
func (s *IssuerService) List(ctx context.Context, page, perPage int) ([]*domain.Issuer, int64, error) {
if page < 1 {
page = 1
}
if perPage < 1 {
perPage = 50
}
issuers, err := s.issuerRepo.List(ctx)
if err != nil {
return nil, 0, fmt.Errorf("failed to list issuers: %w", err)
}
total := int64(len(issuers))
start := (page - 1) * perPage
if start >= int(total) {
return nil, total, nil
}
end := start + perPage
if end > int(total) {
end = int(total)
}
return issuers[start:end], total, nil
}
// Get retrieves an issuer by ID.
func (s *IssuerService) Get(ctx context.Context, id string) (*domain.Issuer, error) {
issuer, err := s.issuerRepo.Get(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get issuer %s: %w", id, err)
}
return issuer, nil
}
// Create validates and stores a new issuer.
func (s *IssuerService) Create(ctx context.Context, issuer *domain.Issuer, actor string) error {
if issuer.Name == "" {
return fmt.Errorf("issuer name is required")
}
if issuer.ID == "" {
issuer.ID = generateID("issuer")
}
now := time.Now()
if issuer.CreatedAt.IsZero() {
issuer.CreatedAt = now
}
if issuer.UpdatedAt.IsZero() {
issuer.UpdatedAt = now
}
if err := s.issuerRepo.Create(ctx, issuer); err != nil {
return fmt.Errorf("failed to create issuer: %w", err)
}
if s.auditService != nil {
if auditErr := s.auditService.RecordEvent(ctx, actor, domain.ActorTypeUser, "create_issuer", "issuer", issuer.ID, nil); auditErr != nil {
slog.Error("failed to record audit event", "error", auditErr)
}
}
return nil
}
// Update modifies an existing issuer.
func (s *IssuerService) Update(ctx context.Context, id string, issuer *domain.Issuer, actor string) error {
if issuer.Name == "" {
return fmt.Errorf("issuer name is required")
}
issuer.ID = id
if err := s.issuerRepo.Update(ctx, issuer); err != nil {
return fmt.Errorf("failed to update issuer %s: %w", id, err)
}
if s.auditService != nil {
if auditErr := s.auditService.RecordEvent(ctx, actor, domain.ActorTypeUser, "update_issuer", "issuer", id, nil); auditErr != nil {
slog.Error("failed to record audit event", "error", auditErr)
}
}
return nil
}
// Delete removes an issuer.
func (s *IssuerService) Delete(ctx context.Context, id string, actor string) error {
if err := s.issuerRepo.Delete(ctx, id); err != nil {
return fmt.Errorf("failed to delete issuer %s: %w", id, err)
}
if s.auditService != nil {
if auditErr := s.auditService.RecordEvent(ctx, actor, domain.ActorTypeUser, "delete_issuer", "issuer", id, nil); auditErr != nil {
slog.Error("failed to record audit event", "error", auditErr)
}
}
return nil
}
// TestConnectionWithContext verifies the issuer connection with context.
func (s *IssuerService) TestConnectionWithContext(ctx context.Context, id string) error {
issuer, err := s.issuerRepo.Get(ctx, id)
if err != nil {
return fmt.Errorf("issuer not found: %w", err)
}
// TODO: Implement actual connection test based on issuer type
if issuer == nil {
return fmt.Errorf("issuer not found")
}
return nil
}
// TestConnection verifies the issuer connection (handler interface method).
func (s *IssuerService) TestConnection(id string) error {
return s.TestConnectionWithContext(context.Background(), id)
}
// ListIssuers returns paginated issuers (handler interface method).
func (s *IssuerService) ListIssuers(page, perPage int) ([]domain.Issuer, int64, error) {
if page < 1 {
page = 1
}
if perPage < 1 {
perPage = 50
}
issuers, err := s.issuerRepo.List(context.Background())
if err != nil {
return nil, 0, fmt.Errorf("failed to list issuers: %w", err)
}
total := int64(len(issuers))
var result []domain.Issuer
for _, i := range issuers {
if i != nil {
result = append(result, *i)
}
}
return result, total, nil
}
// GetIssuer returns a single issuer (handler interface method).
func (s *IssuerService) GetIssuer(id string) (*domain.Issuer, error) {
return s.issuerRepo.Get(context.Background(), id)
}
// CreateIssuer creates a new issuer (handler interface method).
func (s *IssuerService) CreateIssuer(issuer domain.Issuer) (*domain.Issuer, error) {
if issuer.ID == "" {
issuer.ID = generateID("issuer")
}
now := time.Now()
if issuer.CreatedAt.IsZero() {
issuer.CreatedAt = now
}
if issuer.UpdatedAt.IsZero() {
issuer.UpdatedAt = now
}
if err := s.issuerRepo.Create(context.Background(), &issuer); err != nil {
return nil, fmt.Errorf("failed to create issuer: %w", err)
}
return &issuer, nil
}
// UpdateIssuer modifies an issuer (handler interface method).
func (s *IssuerService) UpdateIssuer(id string, issuer domain.Issuer) (*domain.Issuer, error) {
issuer.ID = id
if err := s.issuerRepo.Update(context.Background(), &issuer); err != nil {
return nil, fmt.Errorf("failed to update issuer: %w", err)
}
return &issuer, nil
}
// DeleteIssuer removes an issuer (handler interface method).
func (s *IssuerService) DeleteIssuer(id string) error {
return s.issuerRepo.Delete(context.Background(), id)
}