0b26923586
RecordDiff.Key() gives a stable normalized identifier ("TYPE name.") for
every diff kind, exposed as recordView.Key. ApplyRequest now takes
Updates/Prunes key lists instead of two booleans, so callers can apply a
subset of records. service.Apply builds the applied set with selected
prunes (Delete) added before selected updates (Add/Update) — an
invariant, not an option — since the provider rejects an Add/Update
whose name still conflicts with an existing record (e.g. a CNAME cannot
be created while an A on the same name still exists).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
package api
|
|
|
|
import (
|
|
"github.com/vasyakrg/dns-autoresolver/internal/diff"
|
|
"github.com/vasyakrg/dns-autoresolver/internal/store"
|
|
)
|
|
|
|
type registerRequest struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type loginRequest struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// userResponse and projectResponse deliberately expose only id/email and
|
|
// id/name — password_hash must never reach a client response.
|
|
type userResponse struct {
|
|
ID string `json:"id"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
type projectResponse struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type authResponse struct {
|
|
User userResponse `json:"user"`
|
|
Project projectResponse `json:"project"`
|
|
}
|
|
|
|
func toAuthResponse(u store.User, p store.Project) authResponse {
|
|
return authResponse{
|
|
User: userResponse{ID: u.ID.String(), Email: u.Email},
|
|
Project: projectResponse{ID: p.ID.String(), Name: p.Name},
|
|
}
|
|
}
|
|
|
|
type applyRequest struct {
|
|
Updates []string `json:"updates"`
|
|
Prunes []string `json:"prunes"`
|
|
}
|
|
|
|
type recordView struct {
|
|
Key string `json:"key"`
|
|
Kind string `json:"kind"`
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Desired []string `json:"desired,omitempty"`
|
|
Actual []string `json:"actual,omitempty"`
|
|
ReadOnly bool `json:"readOnly"`
|
|
}
|
|
|
|
type changesetResponse struct {
|
|
Updates []recordView `json:"updates"`
|
|
Prunes []recordView `json:"prunes"`
|
|
ReadOnly []recordView `json:"readOnly"`
|
|
InSync int `json:"inSyncCount"`
|
|
}
|
|
|
|
func toRecordView(d diff.RecordDiff) recordView {
|
|
rv := recordView{Key: d.Key(), Kind: string(d.Kind), Type: string(d.Type), Name: d.Name, ReadOnly: d.ReadOnly}
|
|
if d.Desired != nil {
|
|
rv.Desired = d.Desired.Values
|
|
}
|
|
if d.Actual != nil {
|
|
rv.Actual = d.Actual.Values
|
|
}
|
|
return rv
|
|
}
|
|
|
|
func toChangesetResponse(cs diff.Changeset) changesetResponse {
|
|
// Initialise the slices so an empty changeset (e.g. a zone that exactly
|
|
// matches its template right after a snapshot) marshals to [] rather than
|
|
// null — a nil slice becomes JSON null, which crashes clients that call
|
|
// .length/.map on the field.
|
|
resp := changesetResponse{
|
|
Updates: []recordView{},
|
|
Prunes: []recordView{},
|
|
ReadOnly: []recordView{},
|
|
}
|
|
for _, d := range cs.Updates() {
|
|
resp.Updates = append(resp.Updates, toRecordView(d))
|
|
}
|
|
for _, d := range cs.Prunes() {
|
|
resp.Prunes = append(resp.Prunes, toRecordView(d))
|
|
}
|
|
for _, d := range cs.Diffs {
|
|
if d.ReadOnly {
|
|
resp.ReadOnly = append(resp.ReadOnly, toRecordView(d))
|
|
}
|
|
if d.Kind == diff.InSync {
|
|
resp.InSync++
|
|
}
|
|
}
|
|
return resp
|
|
}
|