154 lines
5.2 KiB
Go
154 lines
5.2 KiB
Go
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}})
|
||
}
|