mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-12 10:28:52 +00:00
feat: M11b — ownership tracking, agent groups, interactive renewal approval
Ownership: owners/teams GUI pages, notification email resolution via resolveRecipient (owner_id → owner.email lookup). Agent groups: dynamic device grouping by OS/arch/IP CIDR/version with manual include/exclude membership, migration 000004, full CRUD stack (domain → repo → service → handler → frontend). Interactive approval: AwaitingApproval job state, approve/reject API endpoints with reason tracking. Tests: 12 agent group handler tests, 8 approve/reject job handler tests, integration tests updated for 13-param RegisterHandlers. Docs updated across architecture, concepts, and seed data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
// NotificationService provides business logic for managing notifications.
|
||||
type NotificationService struct {
|
||||
notifRepo repository.NotificationRepository
|
||||
ownerRepo repository.OwnerRepository
|
||||
notifierRegistry map[string]Notifier
|
||||
}
|
||||
|
||||
@@ -35,6 +36,25 @@ func NewNotificationService(
|
||||
}
|
||||
}
|
||||
|
||||
// SetOwnerRepo sets the owner repository for email resolution.
|
||||
// Called after construction to avoid circular dependency during initialization.
|
||||
func (s *NotificationService) SetOwnerRepo(ownerRepo repository.OwnerRepository) {
|
||||
s.ownerRepo = ownerRepo
|
||||
}
|
||||
|
||||
// resolveRecipient resolves an owner ID to an email address.
|
||||
// Falls back to the raw owner ID if the owner repo is not set or lookup fails.
|
||||
func (s *NotificationService) resolveRecipient(ctx context.Context, ownerID string) string {
|
||||
if s.ownerRepo == nil || ownerID == "" {
|
||||
return ownerID
|
||||
}
|
||||
owner, err := s.ownerRepo.Get(ctx, ownerID)
|
||||
if err != nil || owner == nil || owner.Email == "" {
|
||||
return ownerID
|
||||
}
|
||||
return owner.Email
|
||||
}
|
||||
|
||||
// SendExpirationWarning sends a certificate expiration warning for a specific threshold.
|
||||
func (s *NotificationService) SendExpirationWarning(ctx context.Context, cert *domain.ManagedCertificate, daysUntilExpiry int) error {
|
||||
return s.SendThresholdAlert(ctx, cert, daysUntilExpiry, daysUntilExpiry)
|
||||
@@ -56,13 +76,13 @@ func (s *NotificationService) SendThresholdAlert(ctx context.Context, cert *doma
|
||||
)
|
||||
}
|
||||
|
||||
// Create notification record
|
||||
// Create notification record — resolve owner email if possible
|
||||
notif := &domain.NotificationEvent{
|
||||
ID: generateID("notif"),
|
||||
CertificateID: &cert.ID,
|
||||
Type: domain.NotificationTypeExpirationWarning,
|
||||
Channel: domain.NotificationChannelEmail,
|
||||
Recipient: cert.OwnerID,
|
||||
Recipient: s.resolveRecipient(ctx, cert.OwnerID),
|
||||
Message: body,
|
||||
Status: "pending",
|
||||
CreatedAt: time.Now(),
|
||||
@@ -121,7 +141,7 @@ func (s *NotificationService) SendRenewalNotification(ctx context.Context, cert
|
||||
CertificateID: &cert.ID,
|
||||
Type: notifType,
|
||||
Channel: domain.NotificationChannelEmail,
|
||||
Recipient: cert.OwnerID,
|
||||
Recipient: s.resolveRecipient(ctx, cert.OwnerID),
|
||||
Message: body,
|
||||
Status: "pending",
|
||||
CreatedAt: time.Now(),
|
||||
@@ -160,7 +180,7 @@ func (s *NotificationService) SendDeploymentNotification(ctx context.Context, ce
|
||||
CertificateID: &cert.ID,
|
||||
Type: notifType,
|
||||
Channel: domain.NotificationChannelEmail,
|
||||
Recipient: cert.OwnerID,
|
||||
Recipient: s.resolveRecipient(ctx, cert.OwnerID),
|
||||
Message: body,
|
||||
Status: "pending",
|
||||
CreatedAt: time.Now(),
|
||||
|
||||
Reference in New Issue
Block a user