package api import ( "encoding/json" "net/http" "strconv" "strings" "dana/internal/storage" ) type vacancyRequest struct { Slug string `json:"slug"` Title string `json:"title"` Description string `json:"description"` Duties string `json:"duties"` Requirements string `json:"requirements"` Conditions string `json:"conditions"` Salary string `json:"salary"` Sort int `json:"sort"` Published bool `json:"published"` } // cleanLines убирает пустые строки и лишние пробелы: на сайте каждая строка — пункт списка. func cleanLines(s string) string { var out []string for _, line := range strings.Split(strings.ReplaceAll(s, "\r", ""), "\n") { line = strings.TrimSpace(strings.TrimLeft(strings.TrimSpace(line), "-•–—*")) if line != "" { out = append(out, line) } } return strings.Join(out, "\n") } func decodeVacancy(w http.ResponseWriter, r *http.Request) (storage.VacancyInput, bool) { var req vacancyRequest if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 256<<10)).Decode(&req); err != nil { fail(w, http.StatusBadRequest, "bad_request", "Некорректный запрос") return storage.VacancyInput{}, false } in := storage.VacancyInput{ Title: strings.TrimSpace(req.Title), Description: strings.TrimSpace(req.Description), Duties: cleanLines(req.Duties), Requirements: cleanLines(req.Requirements), Conditions: cleanLines(req.Conditions), Salary: strings.TrimSpace(req.Salary), Sort: req.Sort, Published: req.Published, } if in.Sort == 0 { in.Sort = 100 } if in.Title == "" { fail(w, http.StatusUnprocessableEntity, "validation", "Укажите название вакансии") return in, false } slug, valid := resolveSlug(w, req.Slug, in.Title) if !valid { return in, false } in.Slug = slug if in.Published && in.Description == "" && in.Duties == "" && in.Requirements == "" && in.Conditions == "" { fail(w, http.StatusUnprocessableEntity, "validation", "Чтобы показать вакансию на сайте, опишите её") return in, false } return in, true } func (a *API) adminVacancies(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) { list, err := a.store.AdminVacancies(r.Context()) if err != nil { handleErr(w, err) return } ok(w, list, map[string]any{"total": len(list)}) } func (a *API) adminCreateVacancy(w http.ResponseWriter, r *http.Request, u *storage.AdminUser) { in, valid := decodeVacancy(w, r) if !valid { return } id, err := a.store.AdminCreateVacancy(r.Context(), in) if err != nil { handleErr(w, err) return } a.store.Audit(r.Context(), u.ID, "create", "vacancy", id, clientIP(r)) writeJSON(w, http.StatusCreated, envelope{Data: map[string]any{"id": id, "slug": in.Slug}}) } func (a *API) adminUpdateVacancy(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 := decodeVacancy(w, r) if !valid { return } if err := a.store.AdminUpdateVacancy(r.Context(), id, in); err != nil { handleErr(w, err) return } a.store.Audit(r.Context(), u.ID, "update", "vacancy", id, clientIP(r)) ok(w, map[string]any{"id": id, "slug": in.Slug}, nil) } func (a *API) adminDeleteVacancy(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.AdminDeleteVacancy(r.Context(), id); err != nil { handleErr(w, err) return } a.store.Audit(r.Context(), u.ID, "delete", "vacancy", id, clientIP(r)) ok(w, map[string]any{"id": id}, nil) }