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) }