feat(api): read zone records without template + snapshot-to-template

LoadDomain requires a template, so a zone without one could never be
viewed or snapshotted. Adds a template-free path: store.LoadZone /
service.ZoneRef / DomainService.ZoneRecords read a zone's live records
straight from the provider (no diff, no template). GET
/domains/{did}/records exposes read-only viewing; POST
/domains/{did}/template-from-zone snapshots only managed record types
(NS/SOA excluded) into a new template and auto-attaches it to the domain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
2026-07-05 12:00:27 +07:00
parent 1540140542
commit 9ccb304d2e
9 changed files with 294 additions and 1 deletions
+12
View File
@@ -33,6 +33,18 @@ func (s *Store) LoadDomain(ctx context.Context, projectID, domainID uuid.UUID) (
}, nil
}
// LoadZone returns just the provider-access half of a domain (provider name,
// encrypted secret, zone id), WITHOUT requiring an attached template — so a
// zone's live records can be read for viewing/snapshot even when no template
// is set. Scoped by projectID (same IDOR closure as LoadDomain).
func (s *Store) LoadZone(ctx context.Context, projectID, domainID uuid.UUID) (service.ZoneRef, error) {
row, err := s.q.LoadDomainFull(ctx, db.LoadDomainFullParams{ID: domainID, ProjectID: projectID})
if err != nil {
return service.ZoneRef{}, err
}
return service.ZoneRef{ZoneID: row.ZoneID, Provider: row.Provider, SecretEnc: row.SecretEnc}, nil
}
// SaveCheckRun persists a summary of the changeset (counts of updates/prunes)
// as a check_runs row.
func (s *Store) SaveCheckRun(ctx context.Context, domainID uuid.UUID, cs diff.Changeset) error {
+32
View File
@@ -91,3 +91,35 @@ func TestLoadDomainNoTemplate(t *testing.T) {
t.Fatal("expected error for domain without template, got nil")
}
}
// TestLoadZoneNoTemplate covers the Task 1 gap: a domain with no attached
// template must still be resolvable to its provider-access ref (ZoneRef) so
// its live records can be read/snapshotted — unlike LoadDomain, LoadZone
// must NOT error out on a nil template doc.
func TestLoadZoneNoTemplate(t *testing.T) {
s, ctx := newStore(t)
acc, err := s.Queries().CreateAccount(ctx, db.CreateAccountParams{
ID: uuid.New(), ProjectID: defaultProject,
Provider: "selectel", SecretEnc: "enc-blob", Comment: "prod",
})
if err != nil {
t.Fatal(err)
}
domain, err := s.Queries().CreateDomain(ctx, db.CreateDomainParams{
ID: uuid.New(), ProjectID: defaultProject, ProviderAccountID: acc.ID,
ZoneName: "example.com", ZoneID: "zone-3", TemplateID: nil,
})
if err != nil {
t.Fatal(err)
}
ref, err := s.LoadZone(ctx, defaultProject, domain.ID)
if err != nil {
t.Fatalf("expected LoadZone to succeed without a template, got error: %v", err)
}
if ref.ZoneID != "zone-3" || ref.Provider != "selectel" || ref.SecretEnc != "enc-blob" {
t.Fatalf("zone ref mismatch: %+v", ref)
}
}