feat: M20 Enhanced Query API — sort, time-range filters, cursor pagination, sparse fields, deployments endpoint

V2 (free) query enhancements for certificates:
- `sort` param with direction (`?sort=-notAfter` for descending)
- Time-range filters: `expires_before`, `expires_after`, `created_after`, `updated_after`
- Cursor-based pagination (`?cursor=token&page_size=100`) alongside page-based
- Sparse field selection (`?fields=id,commonName,status`)
- Additional filters: `agent_id`, `profile_id`
- New endpoint: `GET /api/v1/certificates/{id}/deployments`

25 new tests (12 handler + 13 e2e) covering all M20 features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shankar0123
2026-03-23 18:56:02 -04:00
parent f0db02d8ef
commit e078a686bf
10 changed files with 1041 additions and 42 deletions
+48
View File
@@ -14,6 +14,7 @@ import (
// CertificateService provides business logic for certificate management.
type CertificateService struct {
certRepo repository.CertificateRepository
targetRepo repository.TargetRepository
revocationRepo repository.RevocationRepository
profileRepo repository.CertificateProfileRepository
policyService *PolicyService
@@ -55,6 +56,11 @@ func (s *CertificateService) SetProfileRepo(repo repository.CertificateProfileRe
s.profileRepo = repo
}
// SetTargetRepo sets the target repository for deployment queries.
func (s *CertificateService) SetTargetRepo(repo repository.TargetRepository) {
s.targetRepo = repo
}
// List returns a paginated list of certificates matching the filter.
func (s *CertificateService) List(ctx context.Context, filter *repository.CertificateFilter) ([]*domain.ManagedCertificate, int, error) {
certs, total, err := s.certRepo.List(ctx, filter)
@@ -64,6 +70,22 @@ func (s *CertificateService) List(ctx context.Context, filter *repository.Certif
return certs, total, nil
}
// ListCertificatesWithFilter returns a list of certificates with advanced filtering (M20).
// This method supports the new M20 filters and returns domain.ManagedCertificate (not pointers).
func (s *CertificateService) ListCertificatesWithFilter(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
certs, total, err := s.certRepo.List(context.Background(), filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list certificates with filter: %w", err)
}
// Convert pointers to values for handler compatibility
result := make([]domain.ManagedCertificate, len(certs))
for i, cert := range certs {
result[i] = *cert
}
return result, total, nil
}
// Get retrieves a certificate by ID.
func (s *CertificateService) Get(ctx context.Context, id string) (*domain.ManagedCertificate, error) {
cert, err := s.certRepo.Get(ctx, id)
@@ -597,3 +619,29 @@ func (s *CertificateService) GetOCSPResponse(issuerID string, serialHex string)
NextUpdate: now.Add(1 * time.Hour),
})
}
// GetCertificateDeployments returns all deployment targets for a certificate (M20).
func (s *CertificateService) GetCertificateDeployments(certID string) ([]domain.DeploymentTarget, error) {
// Verify certificate exists
_, err := s.certRepo.Get(context.Background(), certID)
if err != nil {
return nil, fmt.Errorf("certificate not found: %w", err)
}
if s.targetRepo == nil {
return []domain.DeploymentTarget{}, nil
}
// Get targets from repository
targets, err := s.targetRepo.ListByCertificate(context.Background(), certID)
if err != nil {
return nil, fmt.Errorf("failed to list deployment targets: %w", err)
}
// Convert pointers to values
result := make([]domain.DeploymentTarget, len(targets))
for i, target := range targets {
result[i] = *target
}
return result, nil
}