package api import ( "encoding/json" "net/http" "net/url" "strconv" "strings" "dana/internal/storage" ) type partnerRequest struct { Name string `json:"name"` URL string `json:"url"` LogoID *int64 `json:"logo_id"` Sort int `json:"sort"` Published bool `json:"published"` } // partnerURL проверяет ссылку на сайт партнёра: только http(s) с доменом. // Адрес «glaz-almaz05.ru» без схемы дополняем до https://. func partnerURL(raw string) (string, bool) { raw = strings.TrimSpace(raw) if raw == "" { return "", true } if !strings.Contains(raw, "://") { raw = "https://" + raw } u, err := url.Parse(raw) if err != nil || (u.Scheme != "https" && u.Scheme != "http") || !strings.Contains(u.Hostname(), ".") || u.User != nil { return "", false } return u.String(), true } func decodePartner(w http.ResponseWriter, r *http.Request) (storage.PartnerInput, bool) { var req partnerRequest if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)).Decode(&req); err != nil { fail(w, http.StatusBadRequest, "bad_request", "Некорректный запрос") return storage.PartnerInput{}, false } in := storage.PartnerInput{ Name: strings.TrimSpace(req.Name), LogoID: req.LogoID, Sort: req.Sort, Published: req.Published, } if in.Sort == 0 { in.Sort = 100 } if in.Name == "" { fail(w, http.StatusUnprocessableEntity, "validation", "Укажите название партнёра") return in, false } link, valid := partnerURL(req.URL) if !valid { fail(w, http.StatusUnprocessableEntity, "validation", "Сайт — адрес вида https://example.ru") return in, false } in.URL = link return in, true } func (a *API) adminPartners(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) { list, err := a.store.AdminPartners(r.Context()) if err != nil { handleErr(w, err) return } ok(w, list, map[string]any{"total": len(list)}) } func (a *API) adminCreatePartner(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) { in, valid := decodePartner(w, r) if !valid { return } id, err := a.store.AdminCreatePartner(r.Context(), in) if err != nil { handleErr(w, err) return } a.store.Audit(r.Context(), u.ID, "create", "partner", id, clientIP(r)) writeJSON(w, http.StatusCreated, envelope{Data: map[string]any{"id": id}}) } func (a *API) adminUpdatePartner(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) { id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { fail(w, http.StatusBadRequest, "bad_request", "Некорректный id") return } in, valid := decodePartner(w, r) if !valid { return } if err := a.store.AdminUpdatePartner(r.Context(), id, in); err != nil { handleErr(w, err) return } a.store.Audit(r.Context(), u.ID, "update", "partner", id, clientIP(r)) ok(w, map[string]any{"id": id}, nil) } func (a *API) adminDeletePartner(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) { id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { fail(w, http.StatusBadRequest, "bad_request", "Некорректный id") return } if err := a.store.AdminDeletePartner(r.Context(), id); err != nil { handleErr(w, err) return } a.store.Audit(r.Context(), u.ID, "delete", "partner", id, clientIP(r)) ok(w, map[string]any{"id": id}, nil) }