Some checks failed
Deploy Production / Deploy to Staging (push) Has been skipped
Go Linter / Run golangci-lint (api_gateway) (push) Failing after 2m31s
Go Linter / Build golang services (api_gateway) (push) Has been skipped
Go Linter / Tag Commit (push) Has been skipped
Go Linter / Push Docker Images (api_gateway) (push) Has been skipped
463 lines
15 KiB
Go
463 lines
15 KiB
Go
package http_router
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"log/slog"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/integration"
|
||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/types"
|
||
"github.com/gorilla/mux"
|
||
|
||
filemanager "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/file_manager"
|
||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||
)
|
||
|
||
// @Summary Получить список заявок агента
|
||
// @Description Получение списка заявок агента с возможностью фильтрации
|
||
// @Tags agents
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param agent_id path string true "ID агента"
|
||
// @Param vacancy_id query string false "ID вакансии"
|
||
// @Param status query string false "Статус заявки"
|
||
// @Param page query int false "Номер страницы"
|
||
// @Param page_size query int false "Размер страницы"
|
||
// @Success 200 {object} rmodel.SubmissionListGetResponse "Список заявок"
|
||
// @Failure 400 {object} map[string]string "Неверные данные запроса"
|
||
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
|
||
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
|
||
// @Security BearerAuth
|
||
// @Router /api/v1/agents/{agent_id}/submissions [get]
|
||
func (h *handler) getSubmissionListAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||
const handlerName = "getSubmissionListAgentHandler"
|
||
|
||
var (
|
||
vars = mux.Vars(r)
|
||
agentId = vars["agent_id"]
|
||
)
|
||
|
||
request, err := new(rmodel.SubmissionListGetRequest).FromQuery(r.URL.Query())
|
||
if err != nil {
|
||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||
h.logger.Error("error parsing request: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName))
|
||
|
||
return
|
||
}
|
||
|
||
request.AgentId = agentId
|
||
|
||
result, err := h.agentService.GetSubmissionList(r.Context(), request)
|
||
if err != nil {
|
||
h.handleAgentError(w, err)
|
||
h.logger.Error("error getting submission list",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
|
||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error encoding response",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
}
|
||
}
|
||
|
||
// @Summary Получить CV заявки
|
||
// @Description Получение CV файла заявки
|
||
// @Tags agents
|
||
// @Accept json
|
||
// @Produce application/octet-stream
|
||
// @Param agent_id path string true "ID агента"
|
||
// @Param submission_id path string true "ID заявки"
|
||
// @Success 200 {file} file "CV файл"
|
||
// @Failure 400 {object} map[string]string "Неверные данные запроса"
|
||
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
|
||
// @Failure 404 {object} map[string]string "CV не найден"
|
||
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
|
||
// @Security BearerAuth
|
||
// @Router /api/v1/agents/{agent_id}/submissions/{submission_id}/cv [get]
|
||
func (h *handler) getSubmissionCVHandler(w http.ResponseWriter, r *http.Request) {
|
||
const handlerName = "getSubmissionCVHandler"
|
||
|
||
var (
|
||
vars = mux.Vars(r)
|
||
submissionId = vars["submission_id"]
|
||
)
|
||
|
||
params := filemanager.ParameterTable{
|
||
filemanager.SubmissionIdParam: submissionId,
|
||
}
|
||
|
||
urls, err := h.fileManager.GetFilePaths(r.Context(), filemanager.CVFileType, params)
|
||
if err != nil {
|
||
if errors.Is(err, filemanager.ErrFileNotFound) {
|
||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||
h.logger.Error("CV file not found",
|
||
slog.String("error", err.Error()),
|
||
slog.String("submissionId", submissionId),
|
||
slog.String("handler", handlerName))
|
||
|
||
return
|
||
}
|
||
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error while getting file URLs: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("submissionId", submissionId),
|
||
slog.String("handler", handlerName))
|
||
|
||
return
|
||
}
|
||
|
||
if len(urls) == 0 {
|
||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||
h.logger.Error("No CV URLs found",
|
||
slog.String("submissionId", submissionId),
|
||
slog.String("handler", handlerName))
|
||
|
||
return
|
||
}
|
||
|
||
response := types.CVURLResponse{
|
||
URL: urls[0],
|
||
SubmissionID: submissionId,
|
||
ExpiresAt: time.Now().Add(constants.DefaultFileTTL - time.Hour),
|
||
}
|
||
|
||
h.logger.Debug("CV URL retrieved successfully",
|
||
slog.String("submissionId", submissionId),
|
||
slog.String("handler", handlerName))
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
|
||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error encoding response: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("submissionId", submissionId),
|
||
slog.String("handler", handlerName))
|
||
}
|
||
}
|
||
|
||
// @Summary Удалить заявку агента
|
||
// @Description Удаление заявки агента
|
||
// @Tags agents
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param agent_id path string true "ID агента"
|
||
// @Param submission_id path string true "ID заявки"
|
||
// @Success 204 "Заявка удалена"
|
||
// @Failure 400 {object} map[string]string "Неверные данные запроса"
|
||
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
|
||
// @Failure 404 {object} map[string]string "Заявка не найдена"
|
||
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
|
||
// @Security BearerAuth
|
||
// @Router /api/v1/agents/{agent_id}/submissions/{submission_id} [delete]
|
||
func (h *handler) deleteSubmissionAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||
const handlerName = "deleteSubmissionHandler"
|
||
|
||
var (
|
||
vars = mux.Vars(r)
|
||
submissionId = vars["submission_id"]
|
||
)
|
||
|
||
result, err := h.agentService.DeleteSubmission(r.Context(), &rmodel.SubmissionDeleteRequest{
|
||
Id: submissionId,
|
||
})
|
||
if err != nil {
|
||
h.handleAgentError(w, err)
|
||
h.logger.Error("error deleting submission",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
|
||
return
|
||
}
|
||
|
||
go func() {
|
||
if err := h.feed.CancelEvent(r.Context(), submissionId, "Отклик был удален"); err != nil {
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error cancelling event: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName))
|
||
}
|
||
}()
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
|
||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error encoding response",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
}
|
||
}
|
||
|
||
// @Summary Получить список заявок дистрибьютора
|
||
// @Description Получение списка заявок дистрибьютора с возможностью фильтрации
|
||
// @Tags distributors
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param distributor_id path string true "ID дистрибьютора"
|
||
// @Param vacancy_id path string true "ID вакансии"
|
||
// @Param status query string false "Статус заявки"
|
||
// @Param page query int false "Номер страницы"
|
||
// @Param page_size query int false "Размер страницы"
|
||
// @Success 200 {object} rmodel.SubmissionListGetResponse "Список заявок"
|
||
// @Failure 400 {object} map[string]string "Неверные данные запроса"
|
||
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
|
||
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
|
||
// @Security BearerAuth
|
||
// @Router /api/v1/distributor/{distributor_id}/vacancies/{vacancy_id}/submissions [get]
|
||
func (h *handler) getSubmissionListDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||
const handlerName = "getSubmissionsDistributorHandler"
|
||
|
||
var (
|
||
vars = mux.Vars(r)
|
||
vacancyId = vars["vacancy_id"]
|
||
)
|
||
|
||
request, err := new(rmodel.SubmissionListForVacancyGetRequest).FromQuery(r.URL.Query())
|
||
if err != nil {
|
||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||
h.logger.Error("error parsing request: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
|
||
return
|
||
}
|
||
|
||
request.VacancyId = vacancyId
|
||
|
||
result, err := h.distributorService.GetSubmissionListForVacancy(r.Context(), request)
|
||
if err != nil {
|
||
h.handleDistributorError(w, err)
|
||
h.logger.Error("error getting submissions for vacancy",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
|
||
return
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
|
||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error encoding response",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
}
|
||
}
|
||
|
||
// @Summary Обновить статус заявки
|
||
// @Description Обновление статуса заявки дистрибьютором
|
||
// @Tags distributors
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param distributor_id path string true "ID дистрибьютора"
|
||
// @Param vacancy_id path string true "ID вакансии"
|
||
// @Param submission_id path string true "ID заявки"
|
||
// @Param request body rmodel.SubmissionStatusUpdateRequest true "Новый статус заявки"
|
||
// @Success 200 "Статус обновлен"
|
||
// @Failure 400 {object} map[string]string "Неверные данные запроса"
|
||
// @Failure 401 {object} map[string]string "Неавторизованный доступ"
|
||
// @Failure 404 {object} map[string]string "Заявка не найдена"
|
||
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
|
||
// @Security BearerAuth
|
||
// @Router /api/v1/distributor/{distributor_id}/vacancies/{vacancy_id}/submissions/{submission_id}/status [post]
|
||
func (h *handler) updateSubmissionStatusDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||
const handlerName = "updateSubmissionStatusDistributorHandler"
|
||
|
||
var (
|
||
vars = mux.Vars(r)
|
||
submissionId = vars["submission_id"]
|
||
)
|
||
|
||
var request rmodel.SubmissionStatusUpdateRequest
|
||
|
||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||
h.logger.Error("error unmarshalling request: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
|
||
return
|
||
}
|
||
|
||
request.Id = submissionId
|
||
|
||
if _, err := h.distributorService.UpdateSubmissionStatus(r.Context(), &request); err != nil {
|
||
h.handleDistributorError(w, err)
|
||
h.logger.Error("error updating submission status",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
|
||
return
|
||
}
|
||
|
||
// TODO:
|
||
// h.createSetSubmissionStatusFeedEvent(r.Context(), distId, submissionId, status.Status, handlerName)
|
||
|
||
w.WriteHeader(http.StatusNoContent)
|
||
}
|
||
|
||
// @Summary Отправить анкету
|
||
// @Description Отправка заполненной анкеты клиента
|
||
// @Tags clients
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param request body map[string]interface{} true "Данные анкеты"
|
||
// @Success 201 {object} map[string]string "Анкета отправлена"
|
||
// @Failure 400 {object} map[string]string "Неверные данные запроса"
|
||
// @Failure 500 {object} map[string]string "Внутренняя ошибка сервера"
|
||
// @Router /api/v1/anketa [post]
|
||
//
|
||
//nolint:funlen // TODO: refactor
|
||
func (h *handler) postAnketaHandler(w http.ResponseWriter, r *http.Request) {
|
||
handlerName := "postAnketaHandler"
|
||
|
||
var (
|
||
agentID = r.FormValue("agent_id")
|
||
vacancyID = r.FormValue("vacancy_id")
|
||
cvLink = r.FormValue("cv")
|
||
name = r.FormValue("name")
|
||
phone = r.FormValue("phone_number")
|
||
email = r.FormValue("email")
|
||
birthday = r.FormValue("birthday")
|
||
info = r.FormValue("info")
|
||
|
||
firstName, lastName, middleName = splitName(name)
|
||
)
|
||
|
||
result, err := h.agentService.CreateSubmission(r.Context(), &rmodel.SubmissionCreateRequest{
|
||
AgentId: agentID,
|
||
VacancyId: vacancyID,
|
||
CandidateInfo: &rmodel.CandidateInfo{
|
||
FirstName: firstName,
|
||
LastName: lastName,
|
||
MiddleName: middleName,
|
||
PhoneNumber: phone,
|
||
Email: email,
|
||
Birthday: birthday,
|
||
CvLink: &cvLink,
|
||
Resume: &info,
|
||
},
|
||
})
|
||
if err != nil {
|
||
h.handleAgentError(w, err)
|
||
h.logger.Error("error creating submission",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName))
|
||
|
||
return
|
||
}
|
||
|
||
file, header, err := r.FormFile("cv_file")
|
||
if err == nil {
|
||
params := filemanager.ParameterTable{
|
||
filemanager.SubmissionIdParam: result.Id,
|
||
}
|
||
|
||
if err := h.fileManager.SaveFile(
|
||
r.Context(),
|
||
filemanager.CVFileType,
|
||
file,
|
||
header,
|
||
params,
|
||
); err != nil {
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error saving CV to file storage: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName))
|
||
|
||
return
|
||
}
|
||
} else if header != nil {
|
||
// CV is provided, but there are other errors
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error while extracting CV from request: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName))
|
||
}
|
||
|
||
go h.createPostAnketaEvent(
|
||
context.Background(),
|
||
agentID,
|
||
result.Id,
|
||
handlerName,
|
||
)
|
||
|
||
go func() {
|
||
if err := h.integrationClient.HandleVacancyResponse(integration.HandleVacancy{
|
||
AgentId: agentID,
|
||
VacancyId: vacancyID,
|
||
SourceLid: r.URL.RawPath,
|
||
Candidate: integration.Candidate{
|
||
Id: result.Id,
|
||
Name: name,
|
||
Email: email,
|
||
Phone: phone,
|
||
Birthday: birthday,
|
||
Info: info,
|
||
CvLink: cvLink,
|
||
},
|
||
}); err != nil {
|
||
h.logger.Error("error while handling vacancy response integration",
|
||
slog.String("error", err.Error()),
|
||
slog.String("agent_id", agentID),
|
||
slog.String("vacancy_id", vacancyID),
|
||
slog.String("submission_id", result.Id),
|
||
slog.String("handler", handlerName),
|
||
)
|
||
}
|
||
}()
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
|
||
if err = json.NewEncoder(w).Encode(result); err != nil {
|
||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||
h.logger.Error("error encoding response: ",
|
||
slog.String("error", err.Error()),
|
||
slog.String("handler", handlerName))
|
||
}
|
||
}
|
||
|
||
func splitName(name string) (string, string, string) {
|
||
var (
|
||
parts = strings.SplitN(name, " ", 3)
|
||
lastName, firstName, middleName string
|
||
)
|
||
|
||
if len(parts) > 0 {
|
||
lastName = parts[0]
|
||
}
|
||
|
||
if len(parts) > 1 {
|
||
firstName = parts[1]
|
||
}
|
||
|
||
if len(parts) > 2 {
|
||
middleName = parts[2]
|
||
}
|
||
|
||
return lastName, firstName, middleName
|
||
}
|