Files
dns-autoresolver/internal/api/dto.go
T
vasyansk bc2e77ad4e fix: empty changeset must serialize as [] not null (white-screen after snapshot)
toChangesetResponse initialises updates/prunes/readOnly so a zone matching
its template exactly (e.g. right after 'create template from zone') marshals
arrays, not null. DiffView/DomainDiffPage also normalise null defensively.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
2026-07-05 13:10:08 +07:00

100 lines
2.5 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 {
ApplyUpdates bool `json:"applyUpdates"`
ApplyPrunes bool `json:"applyPrunes"`
}
type recordView struct {
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{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
}