dana-medical-site/internal/api/admin_vacancies.go
2026-10-14 13:33:13 +00:00

125 lines
3.8 KiB
Go

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