120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
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)
|
||
}
|