feat(api): CRUD accounts/templates/domains + import зон (полный цикл), secret не в ответах

Task 9 Фазы 1B: узкий интерфейс TenantStore (внутри store.Account/Template/Domain,
без db.* в api) реализован тонкими обёртками в internal/store/tenant.go; API.Store/
Cipher/Reg добавлены к существующему Svc. Роуты POST/GET/DELETE для accounts/
templates/domains + POST /accounts/{aid}/import (ListZones -> CreateDomain на зону).
accountResponse не содержит секрет ни в каком виде.
This commit is contained in:
2026-07-03 14:53:29 +07:00
parent 763919d23f
commit ae6a4d7f4c
6 changed files with 918 additions and 8 deletions
+64 -7
View File
@@ -9,7 +9,10 @@ import (
"github.com/google/uuid"
"github.com/vasyakrg/dns-autoresolver/internal/diff"
"github.com/vasyakrg/dns-autoresolver/internal/provider"
"github.com/vasyakrg/dns-autoresolver/internal/service"
"github.com/vasyakrg/dns-autoresolver/internal/store"
"github.com/vasyakrg/dns-autoresolver/internal/store/dto"
)
// CheckApplier is the service surface the API depends on.
@@ -18,10 +21,42 @@ type CheckApplier interface {
Apply(ctx context.Context, domainID uuid.UUID, req service.ApplyRequest) (diff.Changeset, error)
}
// API holds handler dependencies. Store/Cipher are used by CRUD handlers
// (added by the implementer following the accounts pattern).
// TenantStore is the narrow persistence surface the CRUD handlers depend on.
// *store.Store satisfies it directly via its thin wrapper methods (see
// internal/store/tenant.go); tests can supply their own mock.
type TenantStore interface {
CreateAccount(ctx context.Context, projectID uuid.UUID, provider, secretEnc, comment string) (store.Account, error)
ListAccounts(ctx context.Context, projectID uuid.UUID) ([]store.Account, error)
GetAccount(ctx context.Context, id, projectID uuid.UUID) (store.Account, error)
DeleteAccount(ctx context.Context, id, projectID uuid.UUID) error
CreateTemplate(ctx context.Context, projectID uuid.UUID, name string, doc dto.TemplateDoc) (store.Template, error)
ListTemplates(ctx context.Context, projectID uuid.UUID) ([]store.Template, error)
UpdateTemplate(ctx context.Context, id, projectID uuid.UUID, name string, doc dto.TemplateDoc) (store.Template, error)
DeleteTemplate(ctx context.Context, id, projectID uuid.UUID) error
CreateDomain(ctx context.Context, projectID, accountID uuid.UUID, zoneName, zoneID string, templateID *uuid.UUID) (store.Domain, error)
ListDomains(ctx context.Context, projectID uuid.UUID) ([]store.Domain, error)
DeleteDomain(ctx context.Context, id, projectID uuid.UUID) error
}
// Cipher encrypts/decrypts provider account secrets. *crypto.Cipher satisfies it.
type Cipher interface {
Encrypt(plaintext []byte) (string, error)
Decrypt(enc string) ([]byte, error)
}
// ProviderRegistry resolves a provider.Provider by name. *registry.Registry satisfies it.
type ProviderRegistry interface {
ByName(name string) (provider.Provider, error)
}
// API holds handler dependencies.
type API struct {
Svc CheckApplier
Svc CheckApplier
Store TenantStore
Cipher Cipher
Reg ProviderRegistry
}
func NewRouter(a *API) http.Handler {
@@ -30,11 +65,33 @@ func NewRouter(a *API) http.Handler {
r.Use(middleware.Recoverer)
r.Route("/api/v1/projects/{pid}", func(r chi.Router) {
r.Route("/domains/{did}", func(r chi.Router) {
r.Get("/check", a.handleCheck)
r.Post("/apply", a.handleApply)
r.Route("/domains", func(r chi.Router) {
r.Post("/", a.handleCreateDomain)
r.Get("/", a.handleListDomains)
r.Route("/{did}", func(r chi.Router) {
r.Get("/check", a.handleCheck)
r.Post("/apply", a.handleApply)
r.Delete("/", a.handleDeleteDomain)
})
})
r.Route("/accounts", func(r chi.Router) {
r.Post("/", a.handleCreateAccount)
r.Get("/", a.handleListAccounts)
r.Route("/{aid}", func(r chi.Router) {
r.Delete("/", a.handleDeleteAccount)
r.Post("/import", a.handleImportZones)
})
})
r.Route("/templates", func(r chi.Router) {
r.Post("/", a.handleCreateTemplate)
r.Get("/", a.handleListTemplates)
r.Route("/{tid}", func(r chi.Router) {
r.Put("/", a.handleUpdateTemplate)
r.Delete("/", a.handleDeleteTemplate)
})
})
// accounts/templates/domains CRUD маунтятся тем же паттерном (Task 4 sqlc-методы)
})
return r
}