125 lines
3.8 KiB
Go
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)
|
|
}
|