dana-medical-site/internal/api/admin_doctors.go
2026-10-22 07:40:00 +00:00

201 lines
6.5 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"
"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)
}