feat(api): endpoints to mark and unmark custom records

This commit is contained in:
2026-08-19 18:10:46 +07:00
parent 208e73980d
commit da267b3cef
5 changed files with 211 additions and 2 deletions
+52
View File
@@ -98,6 +98,58 @@ func (a *API) handleApply(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, toChangesetResponse(cs))
}
// handleAddCustom marks an RRset as deliberately kept outside the template:
// it moves from Prunes to its own section, stops counting as drift and can
// never be applied. The key travels in the body, not the path — it contains
// a space and dots ("CNAME admin.example.com.").
func (a *API) handleAddCustom(w http.ResponseWriter, r *http.Request) {
pid, _ := projectIDFrom(r.Context())
did, err := uuid.Parse(chi.URLParam(r, "did"))
if err != nil {
writeErr(w, http.StatusBadRequest, "invalid domain id")
return
}
var req customRequest
if !decodeBody(w, r, &req) {
return
}
if req.Key == "" {
writeErr(w, http.StatusBadRequest, "key is required")
return
}
if err := a.Store.AddCustomRecord(r.Context(), did, pid, req.Key); err != nil {
// Either the domain doesn't exist or it belongs to another project —
// both are "not found" from this tenant's point of view.
log.Printf("api: add custom record failed: %v", err)
writeErr(w, http.StatusNotFound, "domain not found")
return
}
w.WriteHeader(http.StatusCreated)
}
// handleDeleteCustom removes the mark, returning the record to the regular
// diff. The key comes as a query parameter for the same reason it comes in
// the body on POST.
func (a *API) handleDeleteCustom(w http.ResponseWriter, r *http.Request) {
pid, _ := projectIDFrom(r.Context())
did, err := uuid.Parse(chi.URLParam(r, "did"))
if err != nil {
writeErr(w, http.StatusBadRequest, "invalid domain id")
return
}
key := r.URL.Query().Get("key")
if key == "" {
writeErr(w, http.StatusBadRequest, "key is required")
return
}
if err := a.Store.DeleteCustomRecord(r.Context(), did, pid, key); err != nil {
log.Printf("api: delete custom record failed: %v", err)
writeErr(w, http.StatusNotFound, "domain not found")
return
}
w.WriteHeader(http.StatusNoContent)
}
// handleZoneRecords reads a zone's current records straight from the
// provider — no template required, no diff computed. Backs read-only zone
// viewing for domains that don't have a template attached (yet).