Files
vibedns/internal/web/handlers_records.go
T
2026-08-16 21:18:45 -05:00

166 lines
4.0 KiB
Go

package web
import (
"fmt"
"io"
"net/http"
"strings"
"github.com/owen/vibedns/internal/app"
"github.com/owen/vibedns/internal/validate"
)
// recordInputFromForm assembles a record from either the type-specific editor
// (field_* inputs) or the advanced raw editor.
func recordInputFromForm(r *http.Request) app.RecordInput {
enabled := formBool(r, "enabled")
in := app.RecordInput{
Name: formString(r, "name"),
Type: formString(r, "type"),
TTL: formUint32Ptr(r, "ttl"),
Enabled: &enabled,
Comment: formString(r, "comment"),
}
if formBool(r, "advanced") {
// The advanced editor supplies rdata verbatim, and may override the
// record type with one that has no dedicated editor.
in.Data = formString(r, "data")
if t := formString(r, "field_rtype"); t != "" {
in.Type = t
}
if d := formString(r, "field_rdata"); d != "" {
in.Data = d
}
return in
}
fields := map[string]string{}
for key, values := range r.Form {
if !strings.HasPrefix(key, "field_") || len(values) == 0 {
continue
}
fields[strings.TrimPrefix(key, "field_")] = values[0]
}
if len(fields) > 0 {
in.Fields = fields
} else {
in.Data = formString(r, "data")
}
return in
}
func (s *Server) handleRecordCreate(w http.ResponseWriter, r *http.Request) error {
zoneID, err := pathID(r, "id")
if err != nil {
return err
}
if err := parseForm(r); err != nil {
return err
}
rec, err := s.app.CreateRecord(r.Context(), s.actor(r), zoneID, recordInputFromForm(r))
if err != nil {
return err
}
setFlash(w, r, "success", fmt.Sprintf("Added %s %s record.", rec.Name, rec.Type))
s.redirectBack(w, r)
return nil
}
func (s *Server) handleRecordUpdate(w http.ResponseWriter, r *http.Request) error {
id, err := pathID(r, "id")
if err != nil {
return err
}
if err := parseForm(r); err != nil {
return err
}
rec, err := s.app.UpdateRecord(r.Context(), s.actor(r), id, recordInputFromForm(r))
if err != nil {
return err
}
setFlash(w, r, "success", fmt.Sprintf("Saved %s %s record.", rec.Name, rec.Type))
s.redirectBack(w, r)
return nil
}
func (s *Server) handleRecordDelete(w http.ResponseWriter, r *http.Request) error {
id, err := pathID(r, "id")
if err != nil {
return err
}
if err := parseForm(r); err != nil {
return err
}
if err := s.app.DeleteRecord(r.Context(), s.actor(r), id); err != nil {
return err
}
setFlash(w, r, "success", "Record deleted.")
s.redirectBack(w, r)
return nil
}
func (s *Server) handleRecordToggle(w http.ResponseWriter, r *http.Request) error {
id, err := pathID(r, "id")
if err != nil {
return err
}
if err := parseForm(r); err != nil {
return err
}
enabled := formBool(r, "enabled")
if err := s.app.SetRecordEnabled(r.Context(), s.actor(r), id, enabled); err != nil {
return err
}
state := "disabled"
if enabled {
state = "enabled"
}
setFlash(w, r, "success", "Record "+state+".")
s.redirectBack(w, r)
return nil
}
func (s *Server) handleRecordBulk(w http.ResponseWriter, r *http.Request) error {
zoneID, err := pathID(r, "id")
if err != nil {
return err
}
if err := parseForm(r); err != nil {
return err
}
ids := formInt64s(r, "record_id")
action := app.BulkAction(formString(r, "action"))
n, err := s.app.BulkRecords(r.Context(), s.actor(r), zoneID, ids, action)
if err != nil {
return err
}
verb := map[app.BulkAction]string{
app.BulkDelete: "deleted",
app.BulkEnable: "enabled",
app.BulkDisable: "disabled",
}[action]
setFlash(w, r, "success", fmt.Sprintf("%d record%s %s.", n, plural(n), verb))
s.redirectBack(w, r)
return nil
}
func plural(n int) string {
if n == 1 {
return ""
}
return "s"
}
// recordFieldValues splits a stored record back into editor fields, so an
// existing record opens in its dedicated form rather than as raw rdata.
func recordFieldValues(rtype, data string) map[string]string {
return validate.SplitRData(rtype, data)
}
// copyLimited copies at most n bytes, reporting an error past the limit.
func copyLimited(dst io.Writer, src io.Reader, n int64) (int64, error) {
return io.Copy(dst, io.LimitReader(src, n))
}