201 lines
6.5 KiB
Go
201 lines
6.5 KiB
Go
package api
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"dana/internal/storage"
|
||
)
|
||
|
||
var bookingProviders = map[string]bool{"none": true, "native": true}
|
||
var bioKinds = map[string]bool{
|
||
"education": true, "experience": true, "training": true,
|
||
"award": true, "conference": true,
|
||
}
|
||
|
||
func (a *API) adminDoctors(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) {
|
||
list, err := a.store.AdminDoctors(r.Context(), r.URL.Query().Get("q"))
|
||
if err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
ok(w, list, map[string]any{"total": len(list)})
|
||
}
|
||
|
||
func (a *API) adminUpdateDoctor(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
|
||
}
|
||
var in storage.DoctorUpdate
|
||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||
fail(w, http.StatusBadRequest, "bad_request", "Некорректный запрос")
|
||
return
|
||
}
|
||
if strings.TrimSpace(in.LastName) == "" {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Фамилия не может быть пустой")
|
||
return
|
||
}
|
||
if strings.TrimSpace(in.Slug) == "" {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Адрес (slug) не может быть пустым")
|
||
return
|
||
}
|
||
if in.Booking == "" {
|
||
in.Booking = "none"
|
||
}
|
||
if !bookingProviders[in.Booking] {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Недопустимый способ записи")
|
||
return
|
||
}
|
||
if in.Rating != nil && (*in.Rating < 1 || *in.Rating > 5) {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Рейтинг должен быть от 1,0 до 5,0")
|
||
return
|
||
}
|
||
oldSlug, err := a.store.PublishedDoctorSlug(r.Context(), id)
|
||
if err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
oldPath := a.doctorPathBySlug(r.Context(), oldSlug)
|
||
if err := a.store.AdminUpdateDoctor(r.Context(), id, in); err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
if oldPath != "" && in.Published {
|
||
a.keepOldPath(r.Context(), oldPath, a.doctorPathBySlug(r.Context(), in.Slug))
|
||
}
|
||
a.store.Audit(r.Context(), u.ID, "update", "doctor", id, clientIP(r))
|
||
ok(w, map[string]any{"id": id}, nil)
|
||
}
|
||
|
||
type doctorLinksRequest struct {
|
||
ServiceIDs []int64 `json:"service_ids"`
|
||
BranchIDs []int64 `json:"branch_ids"`
|
||
}
|
||
|
||
// adminSetDoctorLinks сохраняет услуги, которые ведёт врач, и филиалы, где он принимает.
|
||
func (a *API) adminSetDoctorLinks(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
|
||
}
|
||
var in doctorLinksRequest
|
||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&in); err != nil {
|
||
fail(w, http.StatusBadRequest, "bad_request", "Некорректный запрос")
|
||
return
|
||
}
|
||
if in.ServiceIDs == nil {
|
||
in.ServiceIDs = []int64{}
|
||
}
|
||
if in.BranchIDs == nil {
|
||
in.BranchIDs = []int64{}
|
||
}
|
||
if len(in.ServiceIDs) > 500 || len(in.BranchIDs) > 50 {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Слишком много связей")
|
||
return
|
||
}
|
||
if err := a.store.SetDoctorLinks(r.Context(), id, in.ServiceIDs, in.BranchIDs); err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
a.store.Audit(r.Context(), u.ID, "update_links", "doctor", id, clientIP(r))
|
||
ok(w, map[string]any{"id": id, "services": len(in.ServiceIDs), "branches": len(in.BranchIDs)}, nil)
|
||
}
|
||
|
||
// ---------- Биография врача ----------
|
||
|
||
func (a *API) adminBioEntries(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) {
|
||
doctorID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||
if err != nil {
|
||
fail(w, http.StatusBadRequest, "bad_request", "Некорректный id врача")
|
||
return
|
||
}
|
||
list, err := a.store.AdminBioEntries(r.Context(), doctorID)
|
||
if err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
ok(w, list, map[string]any{"total": len(list)})
|
||
}
|
||
|
||
func (a *API) decodeBioEntry(w http.ResponseWriter, r *http.Request) (storage.BioEntryInput, bool) {
|
||
var in storage.BioEntryInput
|
||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||
fail(w, http.StatusBadRequest, "bad_request", "Некорректный запрос")
|
||
return in, false
|
||
}
|
||
if !bioKinds[in.Kind] {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Недопустимый тип записи")
|
||
return in, false
|
||
}
|
||
if strings.TrimSpace(in.Title) == "" {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Заполните текст записи")
|
||
return in, false
|
||
}
|
||
return in, true
|
||
}
|
||
|
||
func (a *API) adminCreateBioEntry(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) {
|
||
in, okReq := a.decodeBioEntry(w, r)
|
||
if !okReq {
|
||
return
|
||
}
|
||
if in.DoctorID == 0 {
|
||
fail(w, http.StatusUnprocessableEntity, "validation", "Не указан врач")
|
||
return
|
||
}
|
||
id, err := a.store.AdminCreateBioEntry(r.Context(), in)
|
||
if err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
a.store.Audit(r.Context(), u.ID, "create", "doctor_bio", id, clientIP(r))
|
||
writeJSON(w, http.StatusCreated, envelope{Data: map[string]any{"id": id}})
|
||
}
|
||
|
||
func (a *API) adminUpdateBioEntry(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, okReq := a.decodeBioEntry(w, r)
|
||
if !okReq {
|
||
return
|
||
}
|
||
if err := a.store.AdminUpdateBioEntry(r.Context(), id, in); err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
a.store.Audit(r.Context(), u.ID, "update", "doctor_bio", id, clientIP(r))
|
||
ok(w, map[string]any{"id": id}, nil)
|
||
}
|
||
|
||
func (a *API) adminDeleteBioEntry(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.AdminDeleteBioEntry(r.Context(), id); err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
a.store.Audit(r.Context(), u.ID, "delete", "doctor_bio", id, clientIP(r))
|
||
ok(w, map[string]any{"id": id}, nil)
|
||
}
|
||
|
||
// adminDoctorQuality — данные для «Дашборда врачей»: биография по видам, отзывы и заявки по врачам.
|
||
func (a *API) adminDoctorQuality(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) {
|
||
rep, err := a.store.DoctorQuality(r.Context())
|
||
if err != nil {
|
||
handleErr(w, err)
|
||
return
|
||
}
|
||
ok(w, rep, nil)
|
||
}
|