Files
test_deploy/internal/http/submission.go
Alex Shevchuk 61fc0d2747
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
71
2025-09-17 14:32:06 +03:00

463 lines
15 KiB
Go
Raw 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 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
}