dana-medical-site/internal/api/admin_specialties.go
2026-10-14 13:33:13 +00:00

154 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"dana/internal/storage"
)
type specialtyRequest struct {
Slug string `json:"slug"`
Name string `json:"name"`
NamePlural string `json:"name_plural"`
Sort int `json:"sort"`
}
func decodeSpecialty(w http.ResponseWriter, r *http.Request, withSlug bool) (storage.SpecialtyInput, bool) {
var req specialtyRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)).Decode(&req); err != nil {
fail(w, http.StatusBadRequest, "bad_request", "Некорректный запрос")
return storage.SpecialtyInput{}, false
}
in := storage.SpecialtyInput{
Slug: strings.ToLower(strings.TrimSpace(req.Slug)),
Name: strings.TrimSpace(req.Name),
NamePlural: strings.TrimSpace(req.NamePlural),
Sort: req.Sort,
}
if in.Sort == 0 {
in.Sort = 500
}
if in.NamePlural == "" {
in.NamePlural = in.Name
}
if in.Name == "" {
in.Name = in.NamePlural
}
if in.Name == "" || len([]rune(in.Name)) > 100 || len([]rune(in.NamePlural)) > 100 {
fail(w, http.StatusUnprocessableEntity, "validation", "Укажите название специальности (до 100 символов)")
return in, false
}
if withSlug && (!slugRE.MatchString(in.Slug) || len(in.Slug) > 100) {
fail(w, http.StatusUnprocessableEntity, "validation", "Адрес — латиница, цифры и дефис, например «kardiologi»")
return in, false
}
return in, true
}
func (a *API) adminSpecialties(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) {
list, err := a.store.AdminSpecialties(r.Context())
if err != nil {
handleErr(w, err)
return
}
ok(w, list, map[string]any{"total": len(list)})
}
func (a *API) adminCreateSpecialty(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) {
in, valid := decodeSpecialty(w, r, true)
if !valid {
return
}
id, err := a.store.AdminCreateSpecialty(r.Context(), in)
if err != nil {
handleErr(w, err)
return
}
a.store.Audit(r.Context(), u.ID, "create", "specialty", id, clientIP(r))
writeJSON(w, http.StatusCreated, envelope{Data: map[string]any{"id": id}})
}
func (a *API) adminUpdateSpecialty(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 := decodeSpecialty(w, r, false)
if !valid {
return
}
if err := a.store.AdminUpdateSpecialty(r.Context(), id, in); err != nil {
handleErr(w, err)
return
}
a.store.Audit(r.Context(), u.ID, "update", "specialty", id, clientIP(r))
ok(w, map[string]any{"id": id}, nil)
}
func (a *API) adminDeleteSpecialty(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.AdminDeleteSpecialty(r.Context(), id); err != nil {
if errors.Is(err, storage.ErrInUse) {
fail(w, http.StatusConflict, "in_use", "В этой специальности есть врачи — сначала перенесите или удалите их")
return
}
handleErr(w, err)
return
}
a.store.Audit(r.Context(), u.ID, "delete", "specialty", id, clientIP(r))
ok(w, map[string]any{"id": id}, nil)
}
type newDoctorRequest struct {
Slug string `json:"slug"`
LastName string `json:"last_name"`
FirstName string `json:"first_name"`
MiddleName string `json:"middle_name"`
Position string `json:"position"`
SpecialtyID int64 `json:"specialty_id"`
}
// adminCreateDoctor заводит врача скрытым; фото, биографию и прочее заполняют в карточке.
func (a *API) adminCreateDoctor(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) {
var req newDoctorRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)).Decode(&req); err != nil {
fail(w, http.StatusBadRequest, "bad_request", "Некорректный запрос")
return
}
in := storage.NewDoctor{
Slug: strings.ToLower(strings.TrimSpace(req.Slug)),
LastName: strings.TrimSpace(req.LastName),
FirstName: strings.TrimSpace(req.FirstName),
MiddleName: strings.TrimSpace(req.MiddleName),
Position: strings.TrimSpace(req.Position),
SpecialtyID: req.SpecialtyID,
}
switch {
case in.LastName == "" || in.FirstName == "":
fail(w, http.StatusUnprocessableEntity, "validation", "Укажите фамилию и имя")
return
case in.SpecialtyID <= 0:
fail(w, http.StatusUnprocessableEntity, "validation", "Выберите специальность — от неё зависит адрес страницы врача")
return
case !slugRE.MatchString(in.Slug) || len(in.Slug) > 100:
fail(w, http.StatusUnprocessableEntity, "validation", "Адрес — латиница, цифры и дефис, например «ivanov-ivan»")
return
}
id, err := a.store.AdminCreateDoctor(r.Context(), in)
if err != nil {
handleErr(w, err)
return
}
a.store.Audit(r.Context(), u.ID, "create", "doctor", id, clientIP(r))
writeJSON(w, http.StatusCreated, envelope{Data: map[string]any{"id": id}})
}