1
This commit is contained in:
557
internal/http/auth.go
Normal file
557
internal/http/auth.go
Normal file
@@ -0,0 +1,557 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth/keycloak"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/broker"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/feed"
|
||||
notification "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/notifications/git-molva.ru/Molva/molva-notification-service"
|
||||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||||
authinfra "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/auth_infrastructure"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ------------------------------
|
||||
// LOGIN USER
|
||||
// ------------------------------
|
||||
|
||||
func (h *handler) loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "loginHandler"
|
||||
|
||||
var request rmodel.LoginUserRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("Invalid request body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func(body io.ReadCloser) {
|
||||
if err := body.Close(); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error closing body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}(r.Body)
|
||||
|
||||
tokens, err := h.authManager.LoginUser(r.Context(), auth.LoginUserRequest{
|
||||
Email: request.Email,
|
||||
Password: request.Password,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
h.logger.Error("error while login user",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := h.authManager.GetUserInfo(r.Context(), tokens.UserId)
|
||||
if err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
h.logger.Error("error while getting user info",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
emailVerificationStatus, err := h.authManager.GetEmailVerificationStatus(r.Context(), tokens.UserId)
|
||||
if err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
h.logger.Error("error while getting email verification status",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err = json.NewEncoder(w).Encode(rmodel.LoginUserResponse{
|
||||
Uid: tokens.UserId,
|
||||
AccessToken: tokens.AccessToken,
|
||||
RefreshToken: tokens.RefreshToken,
|
||||
UserType: userInfo.UserType,
|
||||
Permissions: rmodel.Permissions{
|
||||
Balance: userInfo.Permissions[keycloak.PermissionBalance],
|
||||
Company: userInfo.Permissions[keycloak.PermissionCompany],
|
||||
Employees: userInfo.Permissions[keycloak.PermissionEmployees],
|
||||
Profile: userInfo.Permissions[keycloak.PermissionProfile],
|
||||
Submissions: userInfo.Permissions[keycloak.PermissionSubmissions],
|
||||
Vacancies: userInfo.Permissions[keycloak.PermissionVacancies],
|
||||
},
|
||||
EmailVerified: emailVerificationStatus.EmailVerified,
|
||||
}); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while encoding response: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// REGISTER USER
|
||||
// ------------------------------
|
||||
|
||||
func (h *handler) registerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "registerHandler"
|
||||
|
||||
var creds rmodel.UserCredentials
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error decoding request body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
if h.env == "production" {
|
||||
isEmailValid, err := h.validateEmail(creds.Email)
|
||||
if err != nil {
|
||||
h.logger.Warn("EmailVerificationService API service error, proceeding with email validation via message",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("email", creds.Email),
|
||||
slog.String("handler", handlerName))
|
||||
} else if !isEmailValid {
|
||||
h.logger.Warn("email validation failed - invalid email address",
|
||||
slog.String("email", creds.Email),
|
||||
slog.String("handler", handlerName))
|
||||
http.Error(w, "Invalid email address", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uid, err := h.authManager.RegisterUser(r.Context(), auth.RegisterUserRequest{
|
||||
User: auth.User{
|
||||
Email: creds.Email,
|
||||
Password: creds.Password,
|
||||
FirstName: creds.FirstName,
|
||||
SecondName: creds.LastName,
|
||||
Patronymic: creds.MiddleName,
|
||||
Number: creds.PhoneNumber,
|
||||
Permissions: creds.Permissions,
|
||||
},
|
||||
UserType: creds.UserType,
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("error while saving user to keycloak",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
h.handleKeycloakError(w, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.saveUser(r.Context(), uid.UserId, creds); err != nil {
|
||||
h.handleDBError(w, err)
|
||||
h.logger.Error("error saving user to DB",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
tokenResp, err := h.authManager.GetUserEmailVerificationToken(r.Context(), uid.UserId)
|
||||
if err != nil {
|
||||
h.logger.Error("error getting user token",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
userName := formatName(creds.LastName, creds.FirstName, creds.MiddleName)
|
||||
|
||||
if err := h.sendConfirmationEmail(r, creds.Email, userName, uid.UserId, tokenResp.AccessToken); err != nil {
|
||||
h.logger.Error("error sending confirmation email",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.createWelcomeEvent(r.Context(), uid.UserId, creds, handlerName)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(&rmodel.RegisterResponse{UUID: uid.UserId}); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while encoding response", slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createWelcomeEvent(ctx context.Context, userID string, creds rmodel.UserCredentials, handlerName string) {
|
||||
ownerType := feed.RoleAgent
|
||||
if creds.UserType == constants.DistributorClientType {
|
||||
ownerType = feed.RoleDistributor
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: userID,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventWelcome,
|
||||
Message: "Добро пожаловать в Molva!",
|
||||
Visibility: feed.VisibilityPrivate,
|
||||
Payload: feed.EventPayload{},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("uid", userID))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) sendConfirmationEmail(r *http.Request, email, userName, userID, accessToken string) error {
|
||||
scheme := "https"
|
||||
if r.TLS == nil {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
confirmURL := fmt.Sprintf("%s://%s/api/v1/confirm_email?uid=%s&token=%s",
|
||||
scheme,
|
||||
r.Host,
|
||||
url.QueryEscape(userID),
|
||||
url.QueryEscape(accessToken),
|
||||
)
|
||||
|
||||
emailBody := fmt.Sprintf(constants.EmailVerificationTemplate,
|
||||
userName,
|
||||
confirmURL,
|
||||
confirmURL,
|
||||
)
|
||||
|
||||
msg, err := proto.Marshal(¬ification.SendEmailRequest{
|
||||
To: []string{email},
|
||||
Subject: constants.EmailVerificationMessageSubject,
|
||||
ContentType: constants.HTMLNotificationContentType,
|
||||
Body: []byte(emailBody),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling email message: %w", err)
|
||||
}
|
||||
|
||||
if err := broker.SendNotification(
|
||||
broker.NotificationQueue,
|
||||
constants.EmailNotificationMessageType,
|
||||
msg,
|
||||
h.logger,
|
||||
); err != nil {
|
||||
return fmt.Errorf("sending notification: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: lastName and firstName are required to be non-empty
|
||||
func formatName(lastName, firstName string, middleName *string) string {
|
||||
name := fmt.Sprintf("%s %s", lastName, firstName)
|
||||
|
||||
if middleName != nil {
|
||||
name = fmt.Sprintf("%s %s", name, *middleName)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func (h *handler) saveUser(ctx context.Context, uid string, creds rmodel.UserCredentials) error {
|
||||
userName := formatName(creds.LastName, creds.FirstName, creds.MiddleName)
|
||||
|
||||
if _, err := h.dbClient.CreateUser(ctx, &dbtypes.UserSaveRequest{
|
||||
Id: uid,
|
||||
FullName: userName,
|
||||
Phone: creds.PhoneNumber,
|
||||
Email: creds.Email,
|
||||
Type: dbtypes.UserType(creds.UserType),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// LOGOUT USER
|
||||
// ------------------------------
|
||||
|
||||
func (h *handler) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "logoutHandler"
|
||||
|
||||
var request rmodel.LogoutUserRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while decoding request body: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authManager.LogoutUser(r.Context(), auth.LogoutUserRequest{
|
||||
RefreshToken: request.RefreshToken,
|
||||
}); err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// REFRESH USER TOKEN
|
||||
// ------------------------------
|
||||
|
||||
func (h *handler) refreshTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "refreshTokenHandler"
|
||||
|
||||
var request rmodel.RefreshTokenRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while decoding request body: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.authManager.GetNewAccessToken(r.Context(), auth.GetNewAccessTokenRequest{
|
||||
RefreshToken: request.RefreshToken,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rmodel.RefreshTokenResponse{
|
||||
AccessToken: resp.AccessToken,
|
||||
RefreshToken: resp.RefreshToken,
|
||||
}); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while encoding response: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) confirmEmailPageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "confirmEmailPageHandler"
|
||||
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
uid := r.URL.Query().Get("uid")
|
||||
token := r.URL.Query().Get("token")
|
||||
|
||||
if uid == "" || token == "" {
|
||||
http.Error(w, "Bad Request: отсутствует uid или token", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
|
||||
if protocol := r.Header.Get("X-Forwarded-Proto"); protocol != "" {
|
||||
scheme = protocol
|
||||
} else if h.env == "production" || h.env == "development" {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
// Формируем URL для PATCH-запроса
|
||||
verificationLink := fmt.Sprintf("%s://%s/api/v1/verify_email?uid=%s&token=%s",
|
||||
scheme,
|
||||
r.Host,
|
||||
url.QueryEscape(uid),
|
||||
url.QueryEscape(token),
|
||||
)
|
||||
|
||||
tmpl, err := template.New("confirm").Parse(constants.EmailConfirmationPage)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
if err := tmpl.Execute(w, struct {
|
||||
VerificationURL string
|
||||
}{
|
||||
VerificationURL: verificationLink,
|
||||
}); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while executing confirm email page template",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// RESET PASSWORD
|
||||
// ------------------------------
|
||||
|
||||
func (h *handler) forgotPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "forgotPasswordHandler"
|
||||
|
||||
var request rmodel.ForgotPasswordRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while decoding request body: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
createOTPResp, err := h.authInfraService.CreatePasswordResetOTP(r.Context(), &authinfra.PasswordResetOTPCreateRequest{
|
||||
Email: request.Email,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAuthInfraError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
message, err := proto.Marshal(¬ification.SendEmailRequest{
|
||||
To: []string{request.Email},
|
||||
Subject: constants.ForgotPasswordNotificationMessageSubject,
|
||||
ContentType: constants.TextNotificationContentType,
|
||||
Body: fmt.Appendf(nil,
|
||||
constants.ForgotPasswordNotificationText,
|
||||
createOTPResp.OTP,
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while marshaling forgot password notification message",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := broker.SendNotification(
|
||||
broker.NotificationQueue,
|
||||
constants.EmailNotificationMessageType,
|
||||
message,
|
||||
h.logger,
|
||||
); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while sending forgot password notification",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h *handler) validateOTPHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "validateOTPHandler"
|
||||
|
||||
query := r.URL.Query()
|
||||
|
||||
email := query.Get("email")
|
||||
if email == "" {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Debug("email is required, but missing",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
otp := query.Get("otp")
|
||||
if otp == "" {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Debug("OTP is required, but missing",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
checkOTPResp, err := h.authInfraService.ValidatePasswordResetOTP(r.Context(), &authinfra.ValidatePasswordResetOTPRequest{
|
||||
Email: email,
|
||||
OTP: otp,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAuthInfraError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rmodel.ValidateOTPResponse{
|
||||
Token: checkOTPResp.Token,
|
||||
}); 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 (h *handler) resetPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "resetPasswordHandler"
|
||||
|
||||
var request rmodel.ResetPasswordRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error decoding request body: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.authInfraService.ValidatePasswordResetToken(r.Context(), &authinfra.ValidatePasswordResetTokenRequest{
|
||||
Email: request.Email,
|
||||
Token: request.Token,
|
||||
}); err != nil {
|
||||
h.handleAuthInfraError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authManager.ResetPassword(r.Context(), auth.ResetPasswordRequest{
|
||||
Email: request.Email,
|
||||
NewPassword: request.Password,
|
||||
}); err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
h.logger.Error("error resetting password",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
769
internal/http/balance.go
Normal file
769
internal/http/balance.go
Normal file
@@ -0,0 +1,769 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (h *handler) getBalanceAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getBalanceAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
result, err := h.agentService.GetBalance(r.Context(), &rmodel.BalanceGetRequest{
|
||||
OwnerId: agentId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error getting balance info",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getTransactionListAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getTransactionListAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
request, err := new(rmodel.TransactionListGetRequest).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.OwnerId = agentId
|
||||
|
||||
result, err := h.agentService.GetTransactionList(r.Context(), request)
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error getting transactions",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createTransactionAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createTransactionAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
var request rmodel.TransactionCreateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func(body io.ReadCloser) {
|
||||
if err := body.Close(); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error closing body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}(r.Body)
|
||||
|
||||
request.OwnerId = agentId
|
||||
|
||||
result, err := h.agentService.CreateTransaction(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error creating transaction",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: fix feed event
|
||||
// go h.createTransactionFeedEvent(
|
||||
// context.Background(),
|
||||
// agentId,
|
||||
// true,
|
||||
// request.Amount,
|
||||
// request.Currency,
|
||||
// request.BankAccountId,
|
||||
// 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 (h *handler) getBankAccountListAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getBankAccountListAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
result, err := h.agentService.GetBankAccountList(r.Context(), &rmodel.BankAccountListGetRequest{
|
||||
OwnerId: agentId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error getting bank accounts",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createBankAccountAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createBankAccountAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
var request rmodel.BankAccountCreateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func(body io.ReadCloser) {
|
||||
if err := body.Close(); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error closing body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}(r.Body)
|
||||
|
||||
request.OwnerId = agentId
|
||||
|
||||
result, err := h.agentService.CreateBankAccount(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error creating bank account",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: possible nil dereference
|
||||
// h.createCreateBankAccountFeedEvent(r.Context(), agentId, true, resp, *request.BankName, 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 (h *handler) updateBankAccountAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "updateBankAccountAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
bankAccountId = vars["bank_account_id"]
|
||||
)
|
||||
|
||||
var request rmodel.BankAccountUpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func(body io.ReadCloser) {
|
||||
if err := body.Close(); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error closing body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}(r.Body)
|
||||
|
||||
request.Id = bankAccountId
|
||||
request.OwnerId = agentId
|
||||
|
||||
if _, err := h.agentService.UpdateBankAccount(r.Context(), &request); err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error editing bank account",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createEditBankAccountFeedEvent(
|
||||
context.Background(),
|
||||
agentId,
|
||||
true,
|
||||
bankAccountId,
|
||||
handlerName,
|
||||
)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// TODO: test when implemented
|
||||
func (h *handler) deleteBankAccountAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "deleteBankAccountAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
bankAccountId = vars["bank_account_id"]
|
||||
)
|
||||
|
||||
result, err := h.agentService.DeleteBankAccount(r.Context(), &rmodel.BankAccountDeleteRequest{
|
||||
Id: bankAccountId,
|
||||
OwnerId: agentId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error deleting bank account",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := h.feed.CancelEvent(context.Background(), bankAccountId, "Банковский счёт удален"); err != nil {
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getBalanceDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getBalanceDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.GetBalance(r.Context(), &rmodel.BalanceGetRequest{
|
||||
OwnerId: distId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting balance",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getCompanyBalanceDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyBalanceDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.GetBalance(r.Context(), &rmodel.BalanceGetRequest{
|
||||
OwnerId: companyId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting balance",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getTransactionListDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getTransactionListDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
request, err := new(rmodel.TransactionListGetRequest).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.OwnerId = distId
|
||||
|
||||
result, err := h.distributorService.GetTransactionList(r.Context(), request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting transactions",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getCompanyTransactionListDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyTransactionListDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
request, err := new(rmodel.TransactionListGetRequest).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.OwnerId = companyId
|
||||
|
||||
result, err := h.distributorService.GetTransactionList(r.Context(), request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting transactions",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createTransactionDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createTransactionDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
var request rmodel.TransactionCreateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func(body io.ReadCloser) {
|
||||
if err := body.Close(); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error closing body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}(r.Body)
|
||||
|
||||
request.OwnerId = distId
|
||||
|
||||
result, err := h.distributorService.CreateTransaction(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error creating transaction",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: fix feed event
|
||||
// go h.createTransactionFeedEvent(
|
||||
// context.Background(),
|
||||
// distId,
|
||||
// false,
|
||||
// request.Amount,
|
||||
// request.Currency,
|
||||
// request.BankAccountId,
|
||||
// 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 (h *handler) getBankAccountListDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getBankAccountListDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.GetBankAccountList(r.Context(), &rmodel.BankAccountListGetRequest{
|
||||
OwnerId: distId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting bank accounts",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getCompanyBankAccountListDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyBankAccountListDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.GetBankAccountList(r.Context(), &rmodel.BankAccountListGetRequest{
|
||||
OwnerId: companyId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting bank accounts",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createBankAccountDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createBankAccountDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
var request rmodel.BankAccountCreateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func(body io.ReadCloser) {
|
||||
if err := body.Close(); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error closing body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}(r.Body)
|
||||
|
||||
request.OwnerId = companyId
|
||||
|
||||
result, err := h.distributorService.CreateBankAccount(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error creating bank account",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: possible nil dereference
|
||||
// go h.createCreateBankAccountFeedEvent(
|
||||
// context.Background(),
|
||||
// distId,
|
||||
// false,
|
||||
// resp,
|
||||
// *request.BankName,
|
||||
// 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 (h *handler) updateBankAccountDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "updateBankAccountDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
bankAccountId = vars["bank_account_id"]
|
||||
)
|
||||
|
||||
var request rmodel.BankAccountUpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer func(body io.ReadCloser) {
|
||||
if err := body.Close(); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error closing body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}(r.Body)
|
||||
|
||||
request.OwnerId = distId
|
||||
request.Id = bankAccountId
|
||||
|
||||
result, err := h.distributorService.UpdateBankAccount(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error editing bank account",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createEditBankAccountFeedEvent(
|
||||
context.Background(),
|
||||
distId,
|
||||
true,
|
||||
bankAccountId,
|
||||
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 (h *handler) deleteBankAccountDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "deleteBankAccountDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
bankAccountId = vars["bank_account_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.DeleteBankAccount(r.Context(), &rmodel.BankAccountDeleteRequest{
|
||||
Id: bankAccountId,
|
||||
OwnerId: distId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error deleting bank account",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := h.feed.CancelEvent(context.Background(), bankAccountId, "Банковский счёт удален"); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while 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),
|
||||
)
|
||||
}
|
||||
}
|
27
internal/http/build_info.go
Normal file
27
internal/http/build_info.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h *handler) getBuildInfoHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
response := struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
Date string `json:"date"`
|
||||
}{
|
||||
Version: h.buildConfig.Version,
|
||||
Commit: h.buildConfig.Commit,
|
||||
Date: h.buildConfig.Date,
|
||||
}
|
||||
|
||||
responseJSON, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(responseJSON)
|
||||
}
|
182
internal/http/client.go
Normal file
182
internal/http/client.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database"
|
||||
dberrors "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/errors"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/feed"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/integration"
|
||||
objectStorage "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/object_storage"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/agent"
|
||||
authinfra "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/auth_infrastructure"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/distributor"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth/keycloak"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/config"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
filemanager "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/file_manager"
|
||||
urlShortener "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/url_shortener"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
env string
|
||||
logger *slog.Logger
|
||||
urlShortener urlShortener.Shortener
|
||||
authManager auth.Manager
|
||||
authInfraService authinfra.AuthInfrastructureService
|
||||
fileManager filemanager.UserFileManager
|
||||
dbClient database.Client
|
||||
agentService agent.AgentService
|
||||
distributorService distributor.DistributorService
|
||||
cacheClient cache.Client
|
||||
objectStorageClient objectStorage.Client
|
||||
jwtManager keycloak.TokenManager
|
||||
tempFileManager *TempFileManager
|
||||
emailVerificationServiceAPIKey string
|
||||
feed *feed.Handler
|
||||
integrationClient integration.Client
|
||||
buildConfig config.BuildInfo
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
env string
|
||||
logger *slog.Logger
|
||||
secretConfig *config.SecretConfig
|
||||
authManager auth.Manager
|
||||
authInfraService authinfra.AuthInfrastructureService
|
||||
fileManager filemanager.UserFileManager
|
||||
dbClient database.Client
|
||||
agentService agent.AgentService
|
||||
distributorService distributor.DistributorService
|
||||
emailVerificationServiceAPIKey string
|
||||
cacheClient cache.Client
|
||||
objectStorageClient objectStorage.Client
|
||||
feed *feed.Handler
|
||||
integrationClient integration.Client
|
||||
buildConfig config.BuildInfo
|
||||
}
|
||||
|
||||
func newHandler(c *Config) *handler {
|
||||
tempManager, err := NewTempFileManager("")
|
||||
if err != nil {
|
||||
c.logger.Error("failed to create temp file manager", slog.String("error", err.Error()))
|
||||
return nil
|
||||
}
|
||||
|
||||
return &handler{
|
||||
env: c.env,
|
||||
logger: c.logger,
|
||||
urlShortener: &urlShortener.LinkEncryption{Key: c.secretConfig.Key},
|
||||
authManager: c.authManager,
|
||||
authInfraService: c.authInfraService,
|
||||
fileManager: c.fileManager,
|
||||
dbClient: c.dbClient,
|
||||
agentService: c.agentService,
|
||||
distributorService: c.distributorService,
|
||||
emailVerificationServiceAPIKey: c.emailVerificationServiceAPIKey,
|
||||
cacheClient: c.cacheClient,
|
||||
objectStorageClient: c.objectStorageClient,
|
||||
jwtManager: keycloak.NewJWTManager(),
|
||||
tempFileManager: tempManager,
|
||||
feed: c.feed,
|
||||
integrationClient: c.integrationClient,
|
||||
buildConfig: c.buildConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) handleKeycloakError(w http.ResponseWriter, err error) {
|
||||
switch {
|
||||
case errors.Is(err, keycloak.ErrUnknownUserType):
|
||||
http.Error(w, keycloak.ErrUnknownUserType.Error(), http.StatusBadRequest)
|
||||
case errors.Is(err, keycloak.ErrInvalidToken):
|
||||
http.Error(w, keycloak.ErrInvalidToken.Error(), http.StatusUnauthorized)
|
||||
case errors.Is(err, keycloak.ErrRealmClientUnauthorized):
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
case errors.Is(err, keycloak.ErrRealmClientNotFound):
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
case errors.Is(err, keycloak.ErrInternal):
|
||||
http.Error(w, keycloak.ErrInternal.Error(), http.StatusInternalServerError)
|
||||
case errors.Is(err, keycloak.ErrAlreadyExists):
|
||||
http.Error(w, keycloak.ErrAlreadyExists.Error(), http.StatusBadRequest)
|
||||
case errors.Is(err, keycloak.ErrBadRequest):
|
||||
http.Error(w, keycloak.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
case errors.Is(err, keycloak.ErrNotFound):
|
||||
http.Error(w, keycloak.ErrNotFound.Error(), http.StatusNotFound)
|
||||
default:
|
||||
h.logger.Error("Unhandled keycloak error", slog.String("error", err.Error()))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) handleAuthInfraError(w http.ResponseWriter, err error, handlerName string) {
|
||||
switch {
|
||||
case errors.Is(err, authinfra.ErrBadRequest):
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
case errors.Is(err, authinfra.ErrUnauthorized):
|
||||
http.Error(w, constants.ErrUnauthorized.Error(), http.StatusUnauthorized)
|
||||
case errors.Is(err, authinfra.ErrNotFound):
|
||||
http.Error(w, constants.ErrUnauthorized.Error(), http.StatusUnauthorized)
|
||||
case errors.Is(err, authinfra.ErrInternal):
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("auth infrastructure error",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
default:
|
||||
h.logger.Error("Unhandled auth infrastructure error", slog.String("error", err.Error()))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) handleDBError(w http.ResponseWriter, err error) {
|
||||
switch {
|
||||
case errors.Is(err, dberrors.ErrBadRequest):
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
case errors.Is(err, dberrors.ErrForbidden):
|
||||
http.Error(w, constants.ErrForbidden.Error(), http.StatusForbidden)
|
||||
case errors.Is(err, dberrors.ErrNotFound):
|
||||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||||
case errors.Is(err, dberrors.ErrInternal):
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
default:
|
||||
h.logger.Error("Unhandled database error", slog.String("error", err.Error()))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) handleAgentError(w http.ResponseWriter, err error) {
|
||||
switch {
|
||||
case errors.Is(err, agent.ErrBadRequest):
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
case errors.Is(err, agent.ErrForbidden):
|
||||
http.Error(w, constants.ErrForbidden.Error(), http.StatusForbidden)
|
||||
case errors.Is(err, agent.ErrNotFound):
|
||||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||||
case errors.Is(err, agent.ErrInternal):
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
default:
|
||||
h.logger.Error("Unhandled agent error", slog.String("error", err.Error()))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) handleDistributorError(w http.ResponseWriter, err error) {
|
||||
switch {
|
||||
case errors.Is(err, distributor.ErrBadRequest):
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
case errors.Is(err, distributor.ErrForbidden):
|
||||
http.Error(w, constants.ErrForbidden.Error(), http.StatusForbidden)
|
||||
case errors.Is(err, distributor.ErrNotFound):
|
||||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||||
case errors.Is(err, distributor.ErrInternal):
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
default:
|
||||
h.logger.Error("Unhandled distributor error", slog.String("error", err.Error()))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
168
internal/http/company_agent.go
Normal file
168
internal/http/company_agent.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||||
)
|
||||
|
||||
func (h *handler) getCompanyListAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyListAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
result, err := h.agentService.GetCompanyList(r.Context(), &rmodel.CompanyListGetRequest{
|
||||
Id: agentId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error getting company list",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getCompanyByIdAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyByIdAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
result, err := h.agentService.GetCompanyInfo(r.Context(), &rmodel.CompanyByIdGetRequest{
|
||||
UserId: agentId,
|
||||
CompanyId: companyId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error getting company info",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: foreign key violation review
|
||||
func (h *handler) createCompanyAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createAgentCompanyHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
var request rmodel.CompanyCreateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
request.OwnerId = agentId
|
||||
|
||||
result, err := h.agentService.CreateCompany(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error creating company",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// go h.createCreateCompanyFeedEvent(r.Context(), agentId, true, resp, handlerName)
|
||||
|
||||
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 (h *handler) updateCompanyAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "updateCompanyAgentHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
var request rmodel.CompanyUpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
request.Id = companyId
|
||||
|
||||
if _, err := h.agentService.UpdateCompanyInfo(r.Context(), &request); err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error updating company info",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createEditCompanyFeedEvent(
|
||||
context.Background(),
|
||||
agentId,
|
||||
true,
|
||||
companyId,
|
||||
handlerName,
|
||||
)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
199
internal/http/company_distributor.go
Normal file
199
internal/http/company_distributor.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (h *handler) getCompanyListDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyListDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.GetCompanyList(r.Context(), &rmodel.CompanyListGetRequest{
|
||||
Id: distId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting company list",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getCompanyByIdDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyByIdDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.GetCompanyInfoById(r.Context(), &rmodel.CompanyByIdGetRequest{
|
||||
UserId: distId,
|
||||
CompanyId: companyId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting company list",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createCompanyDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createCompanyDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
var request rmodel.CompanyCreateRequest
|
||||
|
||||
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.OwnerId = distId
|
||||
|
||||
result, err := h.distributorService.CreateCompany(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error creating company",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// h.createCreateCompanyFeedEvent(r.Context(), distId, false, resp, handlerName)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
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 (h *handler) updateCompanyDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "updateCompanyDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
var request rmodel.CompanyUpdateRequest
|
||||
|
||||
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 = companyId
|
||||
|
||||
if _, err := h.distributorService.UpdateCompanyInfo(r.Context(), &request); err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error updating company info",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BUG: holds request for infinite time
|
||||
// h.createEditCompanyFeedEvent(r.Context(), distId, false, comId, handlerName)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *handler) addCompanyMemberDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "addCompanyMemberDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
var request rmodel.AddDistributorCompanyMemberRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
request.CompanyId = companyId
|
||||
|
||||
if _, err := h.distributorService.AddEmployee(r.Context(), &request); err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error adding new staff member",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// h.createAddNewStaffMemberFeedEvent(r.Context(), distId, true, *req.NewStaffMember, companyId, handlerName)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
75
internal/http/errors.go
Normal file
75
internal/http/errors.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// =============== CUSTOM LOGO ERRORS ===============
|
||||
var (
|
||||
ErrEmptyFile = errors.New("file is empty")
|
||||
ErrMultipartParse = errors.New("failed to parse multipart form")
|
||||
ErrFileNotFound = errors.New("failed to get file from form")
|
||||
ErrTempFileCreate = errors.New("failed to create temporary file")
|
||||
ErrFileOperation = errors.New("file operation failed")
|
||||
)
|
||||
|
||||
// InvalidImageTypeError представляет ошибку неверного типа изображения
|
||||
type InvalidImageTypeError struct {
|
||||
ContentType string
|
||||
}
|
||||
|
||||
func (e *InvalidImageTypeError) Error() string {
|
||||
return fmt.Sprintf("invalid image type %s: only png, jpg, and webp are allowed", e.ContentType)
|
||||
}
|
||||
|
||||
// FileTooLargeError представляет ошибку превышения размера файла
|
||||
type FileTooLargeError struct {
|
||||
Size int64
|
||||
MaxSize int64
|
||||
}
|
||||
|
||||
func (e *FileTooLargeError) Error() string {
|
||||
return fmt.Sprintf("file too large: %d bytes, max %d bytes allowed", e.Size, e.MaxSize)
|
||||
}
|
||||
|
||||
// =============== ERROR HANDLERS ===============
|
||||
|
||||
// handleAccessError обрабатывает ошибки прав или токенов
|
||||
func (h *handler) handleAccessError(w http.ResponseWriter, err error, handlerName string) {
|
||||
if errors.Is(err, constants.ErrUnauthorized) {
|
||||
http.Error(w, constants.ErrUnauthorized.Error(), http.StatusUnauthorized)
|
||||
} else if errors.Is(err, constants.ErrForbidden) {
|
||||
http.Error(w, constants.ErrForbidden.Error(), http.StatusForbidden)
|
||||
} else {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("some permission or authorization error",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}
|
||||
|
||||
// handleUploadError обрабатывает ошибки логотипов
|
||||
func (h *handler) handleUploadError(w http.ResponseWriter, err error, handlerName string) {
|
||||
var invalidTypeErr *InvalidImageTypeError
|
||||
|
||||
var fileTooLargeErr *FileTooLargeError
|
||||
|
||||
// Проверяем типизированные ошибки
|
||||
if errors.As(err, &invalidTypeErr) || errors.As(err, &fileTooLargeErr) ||
|
||||
errors.Is(err, ErrEmptyFile) || errors.Is(err, ErrMultipartParse) ||
|
||||
errors.Is(err, ErrFileNotFound) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("client error uploading logo",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
} else {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error processing logo upload",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}
|
474
internal/http/feed.go
Normal file
474
internal/http/feed.go
Normal file
@@ -0,0 +1,474 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/feed"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/types"
|
||||
pbfAgent "github.com/AlexOreL-272/ProtoMolva/go/gen/agent"
|
||||
pbfDistr "github.com/AlexOreL-272/ProtoMolva/go/gen/distributor"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
//nolint:unused // TODO: review feed handlers
|
||||
func (h *handler) createCreateCompanyFeedEvent(ctx context.Context, uid string, isAgent bool, resp []byte, handlerName string) {
|
||||
var (
|
||||
jsonResp interface{}
|
||||
companyID string
|
||||
companyName string
|
||||
)
|
||||
|
||||
if isAgent {
|
||||
jsonResp = new(pbfAgent.SetCompanyDataResponse)
|
||||
} else {
|
||||
jsonResp = new(pbfDistr.SetCompanyDataResponse)
|
||||
}
|
||||
|
||||
ownerType := feed.RoleDistributor
|
||||
if isAgent {
|
||||
ownerType = feed.RoleAgent
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp, jsonResp); err != nil {
|
||||
h.logger.Error(fmt.Sprintf("failed to unmarshal %s SetCompanyDataResponse", ownerType),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch resp := jsonResp.(type) {
|
||||
case *pbfAgent.SetCompanyDataResponse:
|
||||
company := resp.GetCompany()
|
||||
companyID = company.GetCompanyId()
|
||||
companyName = company.GetName()
|
||||
|
||||
case *pbfDistr.SetCompanyDataResponse:
|
||||
company := resp.GetCompany()
|
||||
companyID = company.GetCompanyId()
|
||||
companyName = company.GetName()
|
||||
|
||||
default:
|
||||
h.logger.Error("unexpected response type",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: uid,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventCompanyCreated,
|
||||
Message: fmt.Sprintf("Компания %s создана", companyName),
|
||||
Visibility: feed.VisibilityCompanyWide,
|
||||
CompanyID: &companyID,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: companyID,
|
||||
AttachmentType: feed.AttachmentTypeCompany,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("uid", uid),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("company_id", companyID),
|
||||
slog.String("company_id", companyID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:unused // TODO: review feed handlers
|
||||
func (h *handler) createAddNewStaffMemberFeedEvent(ctx context.Context, uid string, isAgent bool, newStaffMember, companyId, handlerName string) {
|
||||
ownerType := feed.RoleDistributor
|
||||
|
||||
if isAgent {
|
||||
ownerType = feed.RoleAgent
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: uid,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventNewCompanyMember,
|
||||
Message: "Новый сотрудник добавлен в компанию",
|
||||
Visibility: feed.VisibilityCompanyWide,
|
||||
CompanyID: &companyId,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: newStaffMember,
|
||||
AttachmentType: feed.AttachmentTypeProfile,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("uid", uid),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("company_id", companyId),
|
||||
slog.String("new_staff_member", newStaffMember),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:unused // TODO: review feed handlers
|
||||
func (h *handler) createSetSubmissionStatusFeedEvent(ctx context.Context, distributorId, submissionId string, submissionStatus types.Status, handlerName string) {
|
||||
var message string
|
||||
|
||||
switch submissionStatus {
|
||||
case types.StatusAccepted:
|
||||
message = "Отклик на вакансию принят"
|
||||
case types.StatusDeclined:
|
||||
message = "Отклик на вакансию отклонен"
|
||||
case types.StatusSuspended:
|
||||
message = "Отклик на вакансию отложен"
|
||||
default:
|
||||
message = "Статус отклика на вакансию был изменен"
|
||||
}
|
||||
|
||||
companyIds, err := h.feed.GetCompanyIdByUidTmp(ctx, distributorId)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to get company Id",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("distributor_id", distributorId))
|
||||
}
|
||||
|
||||
agentId, err := h.feed.GetAgentIdBySubmissionId(ctx, submissionId)
|
||||
if err != nil {
|
||||
//я хз стоит ли отдельно как-то обрабатывать ErrorNotFound(вроде как нет)
|
||||
h.logger.Error("failed to get agent Id",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("submission_id", submissionId))
|
||||
}
|
||||
|
||||
for _, companyId := range companyIds {
|
||||
event := feed.Event{
|
||||
OwnerId: distributorId,
|
||||
OwnerType: feed.RoleDistributor,
|
||||
EventType: feed.EventSubmissionStatusChanged,
|
||||
Message: message,
|
||||
Visibility: feed.VisibilityCompanyWide,
|
||||
CompanyID: &companyId,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: submissionId,
|
||||
AttachmentType: feed.AttachmentTypeCV,
|
||||
AdditionalReceiver: agentId,
|
||||
CustomData: submissionStatus.String(),
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("distributor_id", distributorId),
|
||||
slog.String("agent_id", agentId),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("submission_id", submissionId),
|
||||
slog.String("company_id", companyId),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:unused // TODO: review feed handlers
|
||||
func (h *handler) createTransactionFeedEvent(ctx context.Context, uid string, isAgent bool, amount int64, currency, bankAccountId, handlerName string) {
|
||||
var message string
|
||||
|
||||
if amount > 0 {
|
||||
message = fmt.Sprintf("Вы получили %.2f %s", float64(amount)/100, currency)
|
||||
} else {
|
||||
message = fmt.Sprintf("С вашего аккаунта выведено %.2f %s", float64(-amount)/100, currency)
|
||||
}
|
||||
|
||||
ownerType := feed.RoleDistributor
|
||||
|
||||
if isAgent {
|
||||
ownerType = feed.RoleAgent
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: uid,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventTransactionCreated,
|
||||
Message: message,
|
||||
Visibility: feed.VisibilityPrivate,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: bankAccountId,
|
||||
AttachmentType: feed.AttachmentTypeBankAccount,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("uid", uid),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("bank_account_id", bankAccountId))
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:unused // TODO: review feed handlers
|
||||
func (h *handler) createCreateBankAccountFeedEvent(ctx context.Context, uid string, isAgent bool, resp []byte, bankName, handlerName string) {
|
||||
var msg proto.Message
|
||||
|
||||
var ownerType feed.UserRole
|
||||
|
||||
if isAgent {
|
||||
msg = &pbfAgent.CreateBankAccountResponse{}
|
||||
ownerType = feed.RoleAgent
|
||||
} else {
|
||||
msg = &pbfDistr.CreateBankAccountResponse{}
|
||||
ownerType = feed.RoleDistributor
|
||||
}
|
||||
|
||||
if err := protojson.Unmarshal(resp, msg); err != nil {
|
||||
h.logger.Error(fmt.Sprintf("failed to unmarshal %s CreateBankAccountResponse", ownerType),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var bankAccountId string
|
||||
switch v := msg.(type) {
|
||||
case *pbfAgent.CreateBankAccountResponse:
|
||||
bankAccountId = v.GetBankAccountId()
|
||||
case *pbfDistr.CreateBankAccountResponse:
|
||||
bankAccountId = v.GetBankAccountId()
|
||||
default:
|
||||
h.logger.Error("unexpected response type",
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: uid,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventBankAccountCreated,
|
||||
Message: fmt.Sprintf("Банковский счёт %s создан", bankName),
|
||||
Visibility: feed.VisibilityPrivate,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: bankAccountId,
|
||||
AttachmentType: feed.AttachmentTypeBankAccount,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("uid", uid),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("bank_account_id", bankAccountId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createEditBankAccountFeedEvent(ctx context.Context, uid string, isAgent bool, bankAccountID, handlerName string) {
|
||||
ownerType := feed.RoleDistributor
|
||||
|
||||
if isAgent {
|
||||
ownerType = feed.RoleAgent
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: uid,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventBankAccountChanged,
|
||||
Message: "Банковский счёт обновлён",
|
||||
Visibility: feed.VisibilityPrivate,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: bankAccountID,
|
||||
AttachmentType: feed.AttachmentTypeBankAccount,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("uid", uid),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("bank_account_id", bankAccountID))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createEditCompanyFeedEvent(ctx context.Context, uid string, isAgent bool, companyID, handlerName string) {
|
||||
ownerType := feed.RoleDistributor
|
||||
|
||||
if isAgent {
|
||||
ownerType = feed.RoleAgent
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: uid,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventCompanyChanged,
|
||||
Message: "Данные компании обновлены",
|
||||
Visibility: feed.VisibilityCompanyWide,
|
||||
CompanyID: &companyID,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: companyID,
|
||||
AttachmentType: feed.AttachmentTypeCompany,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("uid", uid),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("company_id", companyID))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createSetProfileEvent(ctx context.Context, uid string, isAgent bool, handlerName string) {
|
||||
ownerType := feed.RoleDistributor
|
||||
|
||||
if isAgent {
|
||||
ownerType = feed.RoleAgent
|
||||
}
|
||||
|
||||
event := feed.Event{
|
||||
OwnerId: uid,
|
||||
OwnerType: ownerType,
|
||||
EventType: feed.EventProfileChanged,
|
||||
Message: "Ваши данные профиля обновлены",
|
||||
Visibility: feed.VisibilityPrivate,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: uid,
|
||||
AttachmentType: feed.AttachmentTypeProfile,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("uid", uid),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createSetDistributorVacancyEvent(ctx context.Context, distributorId, vacancyId, vacancyName, handlerName string) {
|
||||
event := feed.Event{
|
||||
OwnerId: distributorId,
|
||||
OwnerType: feed.RoleDistributor,
|
||||
EventType: feed.EventVacancyCreated,
|
||||
Message: fmt.Sprintf("Вакансия %s была создана", vacancyName),
|
||||
Visibility: feed.VisibilityPublic,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: vacancyId,
|
||||
AttachmentType: feed.AttachmentTypeVacancy,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("distributor_id", distributorId),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("vacancy_id", vacancyId),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createEditDistributorVacancyEvent(ctx context.Context, distributorId, vacancyId, handlerName string) {
|
||||
event := feed.Event{
|
||||
OwnerId: distributorId,
|
||||
OwnerType: feed.RoleDistributor,
|
||||
EventType: feed.EventVacancyChanged,
|
||||
Message: "Вакансия была изменена",
|
||||
Visibility: feed.VisibilityPublic,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: vacancyId,
|
||||
AttachmentType: feed.AttachmentTypeVacancy,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("distributor_id", distributorId),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("vacancy_id", vacancyId))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createSendVacancyToModerationEvent(ctx context.Context, distributorId, vacancyId, handlerName string) {
|
||||
companyIds, err := h.feed.GetCompanyIdByUidTmp(ctx, distributorId)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to get company Id",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("distributor_id", distributorId))
|
||||
}
|
||||
|
||||
for _, companyId := range companyIds {
|
||||
event := feed.Event{
|
||||
OwnerId: distributorId,
|
||||
OwnerType: feed.RoleDistributor,
|
||||
EventType: feed.EventVacancyModerationSent,
|
||||
Message: "Вакансия была отправлена на модерацию",
|
||||
Visibility: feed.VisibilityCompanyWide,
|
||||
CompanyID: &companyId,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: vacancyId,
|
||||
AttachmentType: feed.AttachmentTypeVacancy,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("distributor_id", distributorId),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("vacancy_id", vacancyId),
|
||||
slog.String("company_id", companyId),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createPostAnketaEvent(ctx context.Context, agentID, submissionId, handlerName string) {
|
||||
companyIds, err := h.feed.GetCompanyIdByUidTmp(ctx, agentID)
|
||||
if err != nil {
|
||||
h.logger.Error("failed to get company Id",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("agent_id", agentID))
|
||||
}
|
||||
|
||||
for _, companyId := range companyIds {
|
||||
event := feed.Event{
|
||||
OwnerId: agentID,
|
||||
OwnerType: feed.RoleAgent,
|
||||
EventType: feed.EventPostAnketa,
|
||||
Message: "Анкета была заполнена",
|
||||
Visibility: feed.VisibilityPrivate,
|
||||
CompanyID: &companyId,
|
||||
Payload: feed.EventPayload{
|
||||
AttachmentId: submissionId,
|
||||
AttachmentType: feed.AttachmentTypeSubmission,
|
||||
},
|
||||
}
|
||||
|
||||
if err := h.feed.CreateEvent(ctx, &event); err != nil {
|
||||
h.logger.Error("failed to create feed event",
|
||||
slog.String("agent_id", agentID),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("submission_id", submissionId),
|
||||
slog.String("company_id", companyId),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
732
internal/http/handlers.go
Normal file
732
internal/http/handlers.go
Normal file
@@ -0,0 +1,732 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/feed"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/object_storage"
|
||||
notification "github.com/AlexOreL-272/ProtoMolva/go/gen/notifications"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth/keycloak"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/broker"
|
||||
dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
|
||||
formgenerator "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/form_generator"
|
||||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/types"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (h *handler) getPersonalLinkHandler(w http.ResponseWriter, r *http.Request) {
|
||||
handlerName := "getPersonalLinkHandler"
|
||||
vars := mux.Vars(r)
|
||||
agentId := vars["agent_id"]
|
||||
vacancyId := vars["vacancy_id"]
|
||||
|
||||
// formatting string
|
||||
linkParams := fmt.Sprintf("%s|%s", agentId, vacancyId)
|
||||
|
||||
// encryption string
|
||||
encryptedLink, err := h.urlShortener.Shorten(linkParams)
|
||||
if err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while encrypting link: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
personalLink := fmt.Sprintf("%s/api/v1/anketa?link=%s", r.Host, encryptedLink)
|
||||
|
||||
resp := types.PersonalLinkResponse{
|
||||
Link: personalLink,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err = json.NewEncoder(w).Encode(resp); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while marshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:funlen // TODO: make it sudo super clean
|
||||
func (h *handler) getAnketaHandler(w http.ResponseWriter, r *http.Request) {
|
||||
handlerName := "getAnketaHandler"
|
||||
encodedLink := r.URL.Query().Get("link")
|
||||
|
||||
if encodedLink == "" {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("anketa link is empty: ",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
link, err := h.urlShortener.Unshorten(encodedLink)
|
||||
if err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while unshorting link: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
linkParams := strings.Split(link, "|")
|
||||
|
||||
if len(linkParams) != 2 {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("Invalid link",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
agentId, vacancyId := linkParams[0], linkParams[1]
|
||||
|
||||
h.logger.Debug("serving client form: ",
|
||||
slog.String("agentID", agentId),
|
||||
slog.String("vacancyID", vacancyId),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
resp, err := h.dbClient.GetVacancyList(r.Context(), &dbtypes.VacancyListGetRequest{
|
||||
Filters: &dbtypes.VacancyListFilters{
|
||||
VacancyId: &vacancyId,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDBError(w, err)
|
||||
h.logger.Error("error getting vacancy info",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.Vacancies) == 0 {
|
||||
// maybe still serve the form but without vacancy details?
|
||||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||||
h.logger.Error("Vacancy info not found",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
vacancyInfo := resp.Vacancies[0]
|
||||
|
||||
h.logger.Debug("found vacancy info: ",
|
||||
slog.Any("vacancy", vacancyInfo),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
formVacancyInfo := map[string]string{
|
||||
"AgentId": agentId,
|
||||
"VacancyId": vacancyId,
|
||||
|
||||
"VacancyName": vacancyInfo.Name,
|
||||
"Address": vacancyInfo.Address,
|
||||
"WorkFormat": vacancyInfo.WorkFormat,
|
||||
"SalaryTop": fmt.Sprintf("%d", vacancyInfo.SalaryTop),
|
||||
"SalaryBottom": fmt.Sprintf("%d", vacancyInfo.SalaryBottom),
|
||||
}
|
||||
|
||||
if vacancyInfo.Requirements != nil {
|
||||
formVacancyInfo["Requirements"] = *vacancyInfo.Requirements
|
||||
}
|
||||
|
||||
if vacancyInfo.Responsibilities != nil {
|
||||
formVacancyInfo["Responsibilities"] = *vacancyInfo.Responsibilities
|
||||
}
|
||||
|
||||
if vacancyInfo.ExtraInfo != nil {
|
||||
formVacancyInfo["Description"] = *vacancyInfo.ExtraInfo
|
||||
}
|
||||
|
||||
formData, err := formgenerator.GenerateForm(formVacancyInfo)
|
||||
if err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error generating form",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
|
||||
if _, err := w.Write([]byte(formData)); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error writing response: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getEmployeesData(ctx context.Context, staff []string) ([]types.Employee, error) {
|
||||
employees := make([]types.Employee, len(staff))
|
||||
|
||||
handlerName := "getEmployeesData"
|
||||
|
||||
for i, uid := range staff {
|
||||
userInfo, err := h.authManager.GetUserInfo(ctx, uid)
|
||||
if err != nil {
|
||||
h.logger.Error("Error retrieving user info",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("source", handlerName))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
permissions, err := h.authManager.GetPermissionsByUsersId(ctx, uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
employees[i] = types.Employee{
|
||||
UID: uid,
|
||||
FirstName: userInfo.FirstName,
|
||||
LastName: userInfo.SecondName,
|
||||
Email: userInfo.Email,
|
||||
Permissions: h.extractPermissions(permissions),
|
||||
}
|
||||
|
||||
if userInfo.Patronymic != nil {
|
||||
employees[i].MiddleName = *userInfo.Patronymic
|
||||
}
|
||||
}
|
||||
|
||||
return employees, nil
|
||||
}
|
||||
|
||||
func (h *handler) extractPermissions(permMap *auth.GetPermissionsByUsersIdResponse) map[constants.PermissionType]constants.PermissionValue {
|
||||
perm := map[constants.PermissionType]constants.PermissionValue{
|
||||
keycloak.PermissionProfile: constants.PermissionValue(permMap.Profile),
|
||||
keycloak.PermissionEmployees: constants.PermissionValue(permMap.Employees),
|
||||
keycloak.PermissionCompany: constants.PermissionValue(permMap.Company),
|
||||
keycloak.PermissionVacancies: constants.PermissionValue(permMap.Vacancies),
|
||||
keycloak.PermissionBalance: constants.PermissionValue(permMap.Balance),
|
||||
keycloak.PermissionSubmissions: constants.PermissionValue(permMap.Submissions),
|
||||
}
|
||||
|
||||
return perm
|
||||
}
|
||||
|
||||
func (h *handler) getEmployeesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getEmployeesHandler"
|
||||
|
||||
var (
|
||||
uid = r.URL.Query().Get("uid")
|
||||
vars = mux.Vars(r)
|
||||
companyId = vars["company_id"]
|
||||
)
|
||||
|
||||
// TODO: rewrite to request struct
|
||||
if uid == "" {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("UID is required",
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("source", "getEmployeesHandler.checkUserPermissions"))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
company, err := h.distributorService.GetCompanyInfoById(r.Context(), &rmodel.CompanyByIdGetRequest{
|
||||
UserId: uid,
|
||||
CompanyId: companyId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting company info",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
employees, err := h.getEmployeesData(r.Context(), company.Company.Staff)
|
||||
if err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
h.logger.Error("error getting employees data",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response := rmodel.EmployeeResponse{
|
||||
CompanyID: companyId,
|
||||
Employees: employees,
|
||||
}
|
||||
|
||||
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("handler", handlerName))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getUserValidationStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getValidationStatusHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
uid = vars["uid"]
|
||||
)
|
||||
|
||||
if _, err := uuid.Parse(uid); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("Invalid uid",
|
||||
slog.String("error", constants.ErrBadRequest.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.dbClient.GetClientValidation(r.Context(), &dbtypes.ClientValidationGetRequest{
|
||||
UserId: uid,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDBError(w, err)
|
||||
h.logger.Error("error getting user validation",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err = json.NewEncoder(w).Encode(resp); 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 (h *handler) getFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
fileName := vars["file"]
|
||||
|
||||
if fileName == "" {
|
||||
http.Error(w, "file parameter is required", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
link, err := h.cacheClient.Get(r.Context(), fileName, cache.DocumentsValueType)
|
||||
if err != nil && !errors.Is(err, cache.ErrKeyNotFound) {
|
||||
h.logger.Error("error getting file link from cache",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
if errors.Is(err, cache.ErrKeyNotFound) {
|
||||
newLink, err := h.objectStorageClient.GetPresignedLink(r.Context(),
|
||||
fileName, object_storage.DocumentCategory, object_storage.LinkOptions{
|
||||
TTL: ptr.Duration(time.Minute * 30),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, object_storage.ErrObjectNotFound) {
|
||||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Error("error getting file link from object storage",
|
||||
slog.String("error", err.Error()))
|
||||
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
body, err := json.Marshal(types.GetDocumentsResponse{
|
||||
Link: newLink,
|
||||
ExpiresIn: time.Now().Add(time.Minute * 25),
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.cacheClient.Set(r.Context(), fileName, cache.DocumentsValueType, string(body), time.Minute*25); err != nil {
|
||||
h.logger.Error("error setting file link to cache",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
link = string(body)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(link))
|
||||
}
|
||||
|
||||
func (h *handler) verifyEmailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "verifyEmailHandler"
|
||||
|
||||
userID := r.URL.Query().Get("uid")
|
||||
token := r.URL.Query().Get("token")
|
||||
|
||||
if userID == "" || token == "" {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("missing parameters: uid or token",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authManager.VerifyEmail(r.Context(), userID, token); err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
h.logger.Error("error verifying email",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := h.authManager.GetUserInfo(r.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("error getting user info after verification",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
userName := h.formatUserFullName(userInfo)
|
||||
|
||||
if err := h.sendWelcomeEmail(userInfo.Email, userName); err != nil {
|
||||
h.logger.Error("error sending welcome email to user",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("user_id", userID),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
|
||||
if err := h.sendNewUserNotificationToAdmin(userInfo, userName); err != nil {
|
||||
h.logger.Error("error sending new user notification to admin",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("user_id", userID),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
|
||||
h.logger.Info("successfully sent new user notification to admin",
|
||||
slog.String("user_email", userInfo.Email),
|
||||
slog.String("user_type", getUserTypeName(userInfo.UserType)),
|
||||
slog.String("admin_email", constants.AdminNotificationEmail))
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *handler) sendWelcomeEmail(email, userName string) error {
|
||||
msg, err := proto.Marshal(¬ification.SendEmailRequest{
|
||||
To: []string{email},
|
||||
Subject: constants.RegistrationNotificationMessageSubject,
|
||||
ContentType: constants.TextNotificationContentType,
|
||||
Body: []byte(fmt.Sprintf(
|
||||
constants.RegistrationNotificationText,
|
||||
userName,
|
||||
)),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling welcome email: %w", err)
|
||||
}
|
||||
|
||||
if err := broker.SendNotification(
|
||||
broker.NotificationQueue,
|
||||
constants.EmailNotificationMessageType,
|
||||
msg,
|
||||
h.logger,
|
||||
); err != nil {
|
||||
return fmt.Errorf("sending welcome email: %w", err)
|
||||
}
|
||||
|
||||
h.logger.Info("successfully sent welcome email after verification",
|
||||
slog.String("user_email", email))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserTypeName(userType int32) string {
|
||||
switch userType {
|
||||
case keycloak.UserAgentType:
|
||||
return constants.UserTypeAgentName
|
||||
case keycloak.UserDistributorType:
|
||||
return constants.UserTypeDistributorName
|
||||
default:
|
||||
return fmt.Sprintf("Неизвестный тип (%d)", userType)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) formatUserFullName(userInfo *auth.UserInfo) string {
|
||||
fullName := fmt.Sprintf("%s %s", userInfo.SecondName, userInfo.FirstName)
|
||||
if userInfo.Patronymic != nil && *userInfo.Patronymic != "" {
|
||||
fullName = fmt.Sprintf("%s %s", fullName, *userInfo.Patronymic)
|
||||
}
|
||||
|
||||
return fullName
|
||||
}
|
||||
|
||||
func (h *handler) sendNewUserNotificationToAdmin(userInfo *auth.UserInfo, userName string) error {
|
||||
userTypeName := getUserTypeName(userInfo.UserType)
|
||||
|
||||
emailBody := fmt.Sprintf(
|
||||
constants.EmailNewUserRegistrationAdminMessage,
|
||||
userInfo.Email,
|
||||
userName,
|
||||
userTypeName,
|
||||
)
|
||||
|
||||
msg, err := proto.Marshal(¬ification.SendEmailRequest{
|
||||
SenderId: constants.AdminNotificationId,
|
||||
To: constants.AdminNotificationEmails,
|
||||
Subject: constants.RegistrationNewUserAdmin,
|
||||
ContentType: constants.TextNotificationContentType,
|
||||
Body: []byte(emailBody),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling admin notification: %w", err)
|
||||
}
|
||||
|
||||
if err := broker.SendNotification(
|
||||
broker.NotificationQueue,
|
||||
constants.EmailNotificationMessageType,
|
||||
msg,
|
||||
h.logger,
|
||||
); err != nil {
|
||||
return fmt.Errorf("sending admin notification: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) getEmailVerificationStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getEmailVerificationStatusHandler"
|
||||
|
||||
// Get user ID from query parameters
|
||||
userID := r.URL.Query().Get("uid")
|
||||
|
||||
if userID == "" {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("missing parameter: uid",
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Check if email is verified
|
||||
response, err := h.authManager.GetEmailVerificationStatus(r.Context(), userID)
|
||||
if err != nil {
|
||||
h.handleKeycloakError(w, err)
|
||||
h.logger.Error("error getting email verification status",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Return response
|
||||
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("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) validateEmail(email string) (bool, error) {
|
||||
apiURL := fmt.Sprintf("%s?api_key=%s&email=%s",
|
||||
constants.EmailVerificationServiceURL,
|
||||
url.QueryEscape(h.emailVerificationServiceAPIKey),
|
||||
url.QueryEscape(email),
|
||||
)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
context.Background(),
|
||||
http.MethodGet,
|
||||
apiURL,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
//стоит что-то такое добавить req.Header.Set("User-Agent", "MyAppSecurity/1.0")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("API request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result struct {
|
||||
Deliverability string `json:"deliverability"`
|
||||
QualityScore string `json:"quality_score"`
|
||||
IsDisposable struct {
|
||||
Value bool `json:"value"`
|
||||
} `json:"is_disposable_email"`
|
||||
IsMxFound struct {
|
||||
Value bool `json:"value"`
|
||||
} `json:"is_mx_found"`
|
||||
IsSmtpValid struct {
|
||||
Value bool `json:"value"`
|
||||
} `json:"is_smtp_valid"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return false, fmt.Errorf("failed to parse validation response: %w", err)
|
||||
}
|
||||
|
||||
qualityScore, err := strconv.ParseFloat(result.QualityScore, 64)
|
||||
if err != nil {
|
||||
h.logger.Warn("could not parse quality score, ignoring in validation",
|
||||
slog.String("quality_score", result.QualityScore))
|
||||
|
||||
qualityScore = 0
|
||||
}
|
||||
|
||||
isValidEmail := result.Deliverability == "DELIVERABLE" && // Доставляемость
|
||||
!result.IsDisposable.Value && // Не одноразовый адрес
|
||||
result.IsMxFound.Value && // Есть MX-записи DNS
|
||||
result.IsSmtpValid.Value && // Сервер отвечает на SMTP
|
||||
qualityScore >= 0.7 // Высокий общий показатель качества
|
||||
|
||||
h.logger.Debug("email validation details",
|
||||
slog.String("email", email),
|
||||
slog.String("deliverability", result.Deliverability),
|
||||
slog.String("quality_score", result.QualityScore),
|
||||
slog.Bool("is_disposable", result.IsDisposable.Value),
|
||||
slog.Bool("is_mx_found", result.IsMxFound.Value),
|
||||
slog.Bool("is_smtp_valid", result.IsSmtpValid.Value),
|
||||
slog.Bool("is_valid_result", isValidEmail))
|
||||
|
||||
return isValidEmail, nil
|
||||
}
|
||||
|
||||
func (h *handler) GetUserEventsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
handlerName := "GetUserEventsHandler"
|
||||
query := r.URL.Query()
|
||||
vars := mux.Vars(r)
|
||||
ownerID := vars["uid"]
|
||||
|
||||
ownerType := query.Get("user_type")
|
||||
if ownerType == "" {
|
||||
h.logger.Error("Missing required query parameter",
|
||||
slog.String("parameter", "user_type"),
|
||||
slog.String("handler", handlerName))
|
||||
http.Error(w, "Missing required query parameter: type", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Debug("Request received",
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("userID", ownerID),
|
||||
slog.Any("queryParams", query))
|
||||
|
||||
eventTypesStr := query.Get("event_type")
|
||||
|
||||
eventTypesSlice := strings.Split(eventTypesStr, ",")
|
||||
|
||||
eventTypes := make([]feed.EventType, len(eventTypesStr))
|
||||
for i, s := range eventTypesSlice {
|
||||
eventTypes[i] = feed.EventType(s)
|
||||
}
|
||||
|
||||
filter := feed.Filter{
|
||||
OwnerID: ownerID,
|
||||
EventTypes: eventTypes,
|
||||
ShowCancelled: query.Get("show_cancelled") != "false",
|
||||
}
|
||||
|
||||
if limitStr := query.Get("limit"); limitStr != "" {
|
||||
limit, err := strconv.ParseUint(limitStr, 10, 64)
|
||||
if err == nil && limit > 0 {
|
||||
filter.Limit = limit
|
||||
} else {
|
||||
h.logger.Error("Invalid limit parameter",
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("userID", ownerID),
|
||||
slog.String("invalidValue", limitStr),
|
||||
slog.String("error", "invalid integer format or value"))
|
||||
http.Error(w, "Invalid limit parameter", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
} else {
|
||||
filter.Limit = constants.DefaultFeedFilterLimit
|
||||
}
|
||||
|
||||
if offsetStr := query.Get("offset"); offsetStr != "" {
|
||||
offset, err := strconv.ParseUint(offsetStr, 10, 64)
|
||||
if err == nil {
|
||||
filter.Offset = offset
|
||||
} else {
|
||||
h.logger.Error("Invalid offset parameter",
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("userID", ownerID),
|
||||
slog.String("invalidValue", offsetStr),
|
||||
slog.String("error", "invalid integer format or value"))
|
||||
http.Error(w, "Invalid offset parameter", http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
} else {
|
||||
filter.Offset = 0
|
||||
}
|
||||
|
||||
events, err := h.feed.Service.GetUserEvents(r.Context(), filter, ownerType)
|
||||
if err != nil {
|
||||
h.logger.Error("Error getting events",
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("userID", ownerID),
|
||||
slog.String("error", err.Error()))
|
||||
http.Error(w, "Failed to retrieve events", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(events); err != nil {
|
||||
h.logger.Error("Error encoding response",
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("userID", ownerID),
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
}
|
61
internal/http/integration.go
Normal file
61
internal/http/integration.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/integration"
|
||||
intrmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model/integration"
|
||||
)
|
||||
|
||||
func (h *handler) vkusvillIntegrationCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "vkusvillIntegrationCallbackHandler"
|
||||
|
||||
var request intrmodel.VkusvillSaveCandidateCallbackRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while decoding request body",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
h.logger.Debug("vkusvill integration callback request received",
|
||||
slog.String("message", request.Message),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
status, err := integration.VkusvillGetCandidateStatus(request.Params.Status)
|
||||
if err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error getting vkusvill candidate status",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.dbClient.UpdateSubmissionStatus(r.Context(), &dbtypes.SubmissionStatusUpdateRequest{
|
||||
Id: request.Params.CandidateId,
|
||||
Status: *dbtypes.NewSubmissionStatus(status.NullString()),
|
||||
}); err != nil {
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error while building and processing request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: add event to feed
|
||||
// go ...
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
612
internal/http/logo.go
Normal file
612
internal/http/logo.go
Normal file
@@ -0,0 +1,612 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth/keycloak"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/object_storage"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/types"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// =============== FILE MANAGEMENT ===============
|
||||
|
||||
// TempFileManager управляет временными файлами с автоматической очисткой
|
||||
type TempFileManager struct {
|
||||
baseDir string
|
||||
mu sync.Mutex
|
||||
files map[string]*os.File
|
||||
}
|
||||
|
||||
// Cleanup очищает все временные файлы при завершении работы
|
||||
func (h *handler) Cleanup() {
|
||||
h.tempFileManager.CleanupAll()
|
||||
}
|
||||
|
||||
func NewTempFileManager(baseDir string) (*TempFileManager, error) {
|
||||
if baseDir == "" {
|
||||
baseDir = filepath.Join(os.TempDir(), "logo-uploads")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(baseDir, constants.TempDirPermission); err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
|
||||
return &TempFileManager{
|
||||
baseDir: baseDir,
|
||||
files: make(map[string]*os.File),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tm *TempFileManager) CreateTempFile(pattern string) (*os.File, error) {
|
||||
tempFile, err := os.CreateTemp(tm.baseDir, pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tm.mu.Lock()
|
||||
tm.files[tempFile.Name()] = tempFile
|
||||
tm.mu.Unlock()
|
||||
|
||||
return tempFile, nil
|
||||
}
|
||||
|
||||
func (tm *TempFileManager) CleanupFile(file *os.File) error {
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tm.mu.Lock()
|
||||
delete(tm.files, file.Name())
|
||||
tm.mu.Unlock()
|
||||
|
||||
name := file.Name()
|
||||
if err := file.Close(); err != nil {
|
||||
slog.Warn("failed to close temp file", slog.String("file", name), slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
if err := os.Remove(name); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove temp file %s: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tm *TempFileManager) CleanupAll() {
|
||||
tm.mu.Lock()
|
||||
defer tm.mu.Unlock()
|
||||
|
||||
for name, file := range tm.files {
|
||||
_ = file.Close()
|
||||
_ = os.Remove(name)
|
||||
}
|
||||
|
||||
tm.files = make(map[string]*os.File)
|
||||
}
|
||||
|
||||
type LogoFile struct {
|
||||
File *os.File
|
||||
ContentType string
|
||||
Size int64
|
||||
manager *TempFileManager
|
||||
}
|
||||
|
||||
// Close закрывает файл и удаляет временный файл
|
||||
func (lf *LogoFile) Close() error {
|
||||
if lf.manager != nil {
|
||||
return lf.manager.CleanupFile(lf.File)
|
||||
}
|
||||
|
||||
if lf.File != nil {
|
||||
name := lf.File.Name()
|
||||
_ = lf.File.Close()
|
||||
|
||||
return os.Remove(name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset сбрасывает позицию чтения в начало файла
|
||||
func (lf *LogoFile) Reset() error {
|
||||
_, err := lf.File.Seek(0, io.SeekStart)
|
||||
return err
|
||||
}
|
||||
|
||||
// =============== LOGO HANDLERS ===============
|
||||
|
||||
func (h *handler) getCompanyLogoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getCompanyLogoHandler"
|
||||
|
||||
distributorID, companyID, token := h.extractLogoRequestData(r)
|
||||
if err := h.validateUserAccess(token, distributorID); err != nil {
|
||||
h.handleAccessError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
logoLink, err := h.getLogoLinkWithCache(r.Context(), companyID)
|
||||
if err != nil {
|
||||
if errors.Is(err, object_storage.ErrObjectNotFound) {
|
||||
response := types.GetLogoResponse{
|
||||
Link: "",
|
||||
ExpiresIn: time.Time{},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
h.logger.Error("error encoding response",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error getting logo link",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response := types.GetLogoResponse{
|
||||
Link: logoLink,
|
||||
ExpiresIn: time.Now().Add(constants.LogoLinkTTL),
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
h.logger.Error("error encoding response",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createCompanyLogoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createCompanyLogoHandler"
|
||||
|
||||
distributorID, companyID, token := h.extractLogoRequestData(r)
|
||||
|
||||
if err := h.validateUserAccess(token, distributorID); err != nil {
|
||||
h.handleAccessError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.validateUserPermissions(r.Context(), distributorID, keycloak.PermissionCompany, keycloak.PermissionLevelCanEdit); err != nil {
|
||||
h.handleAccessError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
logoFile, err := h.extractAndValidateLogoFile(r)
|
||||
if err != nil {
|
||||
h.handleUploadError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
defer logoFile.Close()
|
||||
|
||||
// Сбрасываем позицию после валидации, чтобы не потерять данные
|
||||
if err := logoFile.Reset(); err != nil {
|
||||
h.logger.Error("failed to reset file position",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// может стоит добавить таймаут записи в кэш
|
||||
err = h.objectStorageClient.PutNewObject(
|
||||
r.Context(),
|
||||
companyID,
|
||||
object_storage.LogoCategory,
|
||||
logoFile.File,
|
||||
object_storage.PutOptions{
|
||||
ContentType: logoFile.ContentType,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, object_storage.ErrObjectAlreadyExists) {
|
||||
http.Error(w, "Logo already exists. Use PUT to update", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error uploading logo",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.refreshLogoCacheAsync(companyID)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
func (h *handler) updateCompanyLogoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "updateCompanyLogoHandler"
|
||||
|
||||
distributorID, companyID, token := h.extractLogoRequestData(r)
|
||||
|
||||
if err := h.validateUserAccess(token, distributorID); err != nil {
|
||||
h.handleAccessError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.validateUserPermissions(r.Context(), distributorID, keycloak.PermissionCompany, keycloak.PermissionLevelCanEdit); err != nil {
|
||||
h.handleAccessError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
logoFile, err := h.extractAndValidateLogoFile(r)
|
||||
if err != nil {
|
||||
h.handleUploadError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
defer logoFile.Close()
|
||||
|
||||
// Сбрасываем позицию после валидации, чтобы не потерять данные
|
||||
if err := logoFile.Reset(); err != nil {
|
||||
h.logger.Error("failed to reset file position",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем в хранилище
|
||||
err = h.objectStorageClient.UpdateObject(
|
||||
r.Context(),
|
||||
companyID,
|
||||
object_storage.LogoCategory,
|
||||
logoFile.File,
|
||||
object_storage.UpdateOptions{
|
||||
ContentType: logoFile.ContentType,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, object_storage.ErrObjectNotFound) {
|
||||
http.Error(w, "Logo not found. Use POST to create", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error updating logo",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.refreshLogoCacheAsync(companyID)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *handler) deleteCompanyLogoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "deleteCompanyLogoHandler"
|
||||
|
||||
distributorID, companyID, token := h.extractLogoRequestData(r)
|
||||
|
||||
if err := h.validateUserAccess(token, distributorID); err != nil {
|
||||
h.handleAccessError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.validateUserPermissions(r.Context(), distributorID, keycloak.PermissionCompany, keycloak.PermissionLevelCanEdit); err != nil {
|
||||
h.handleAccessError(w, err, handlerName)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.objectStorageClient.DeleteObject(r.Context(), companyID, object_storage.LogoCategory)
|
||||
if err != nil {
|
||||
if errors.Is(err, object_storage.ErrObjectNotFound) {
|
||||
http.Error(w, constants.ErrNotFound.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, constants.ErrInternalServerError.Error(), http.StatusInternalServerError)
|
||||
h.logger.Error("error deleting logo",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Очищаем кэш
|
||||
if err := h.cacheClient.Del(r.Context(), companyID, cache.LogoValueType); err != nil {
|
||||
h.logger.Error("error deleting logo from cache",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// =============== CORE LOGIC FUNCTIONS ===============
|
||||
|
||||
// extractAndValidateLogoFile извлекает файл из запроса и сохраняет во временный файл
|
||||
func (h *handler) extractAndValidateLogoFile(r *http.Request) (*LogoFile, error) {
|
||||
err := r.ParseMultipartForm(constants.ParseMultipartFormAllFile) //сохраняем на диск весь файл
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrMultipartParse, err)
|
||||
}
|
||||
|
||||
defer func() { _ = r.MultipartForm.RemoveAll() }()
|
||||
|
||||
file, header, err := r.FormFile("logo")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrFileNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Проверяем размер файла
|
||||
if header.Size == 0 {
|
||||
return nil, ErrEmptyFile
|
||||
}
|
||||
|
||||
if header.Size > constants.LogoMaxSize {
|
||||
return nil, &FileTooLargeError{Size: header.Size, MaxSize: constants.LogoMaxSize}
|
||||
}
|
||||
|
||||
contentType := header.Header.Get("Content-Type")
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// Создаем временный файл для копирования
|
||||
tempFile, err := h.tempFileManager.CreateTempFile(constants.TempLogoFilePattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrTempFileCreate, err)
|
||||
}
|
||||
|
||||
// Копируем содержимое во временный файл
|
||||
copiedBytes, err := io.CopyN(tempFile, file, constants.LogoMaxSize+1)
|
||||
if err != nil && err != io.EOF {
|
||||
_ = h.tempFileManager.CleanupFile(tempFile)
|
||||
return nil, fmt.Errorf("%w: failed to copy file: %v", ErrFileOperation, err)
|
||||
}
|
||||
|
||||
logoFile := &LogoFile{
|
||||
File: tempFile,
|
||||
ContentType: contentType,
|
||||
Size: copiedBytes,
|
||||
manager: h.tempFileManager,
|
||||
}
|
||||
|
||||
if err := h.validateLogoFile(logoFile); err != nil {
|
||||
_ = logoFile.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return logoFile, nil
|
||||
}
|
||||
|
||||
// validateLogoFile валидирует файл изображения
|
||||
func (h *handler) validateLogoFile(logoFile *LogoFile) error {
|
||||
// Проверяем заявленный тип
|
||||
if !isValidImageType(logoFile.ContentType) {
|
||||
h.logger.Error("invalid image type",
|
||||
slog.String("image_type", logoFile.ContentType),
|
||||
slog.String("handler", "validateLogoFile"))
|
||||
|
||||
return &InvalidImageTypeError{ContentType: logoFile.ContentType}
|
||||
}
|
||||
|
||||
if err := logoFile.Reset(); err != nil {
|
||||
return fmt.Errorf("%w: failed to reset file position before validation: %v", ErrFileOperation, err)
|
||||
}
|
||||
|
||||
// Читаем первые 512 байт для определения реального типа
|
||||
buffer := make([]byte, constants.BytesToDetectContentType)
|
||||
|
||||
firstBytes, err := logoFile.File.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
return fmt.Errorf("%w: failed to read file header: %v", ErrFileOperation, err)
|
||||
}
|
||||
|
||||
detectedType := http.DetectContentType(buffer[:firstBytes])
|
||||
if !isValidImageType(detectedType) {
|
||||
h.logger.Error("invalid image type after detecting",
|
||||
slog.String("image_type", detectedType),
|
||||
slog.String("handler", "validateLogoFile"))
|
||||
|
||||
return &InvalidImageTypeError{ContentType: detectedType}
|
||||
}
|
||||
|
||||
// Возвращаем позицию в начало
|
||||
if err := logoFile.Reset(); err != nil {
|
||||
return fmt.Errorf("%w: failed to reset file position: %v", ErrFileOperation, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractLogoRequestData извлекает данные из HTTP запроса
|
||||
func (h *handler) extractLogoRequestData(r *http.Request) (distributorID, companyID, token string) {
|
||||
vars := mux.Vars(r)
|
||||
distributorID = vars["distributor_id"]
|
||||
companyID = vars["company_id"]
|
||||
token = strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// =============== VALIDATION FUNCTIONS ===============
|
||||
|
||||
// validateUserAccess проверяет авторизацию и соответствие пользователя
|
||||
func (h *handler) validateUserAccess(token, expectedUserID string) error {
|
||||
if token == "" {
|
||||
return constants.ErrUnauthorized
|
||||
}
|
||||
|
||||
userInfo, err := h.jwtManager.GetUserInfoFromToken(token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid token: %w", err)
|
||||
}
|
||||
|
||||
if expectedUserID != userInfo.UserId {
|
||||
return constants.ErrForbidden
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUserPermissions проверяет права доступа пользователя
|
||||
func (h *handler) validateUserPermissions(ctx context.Context, userID, permission, level string) error {
|
||||
ok, err := h.authManager.CheckPermissions(ctx, auth.CheckPermissionsRequest{
|
||||
UserId: userID,
|
||||
RequiredPermission: permission,
|
||||
RequiredPermissionLevel: level,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("permission check failed: %w", err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return constants.ErrForbidden
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isValidImageType проверяет допустимость типа изображения
|
||||
func isValidImageType(contentType string) bool {
|
||||
contentType = strings.ToLower(strings.TrimSpace(contentType))
|
||||
|
||||
if idx := strings.Index(contentType, ";"); idx != -1 {
|
||||
contentType = contentType[:idx]
|
||||
}
|
||||
|
||||
return constants.ValidImageTypes[contentType]
|
||||
}
|
||||
|
||||
// =============== CACHE FUNCTIONS ===============
|
||||
|
||||
// refreshLogoCacheAsync асинхронно обновляет кэш логотипа
|
||||
func (h *handler) refreshLogoCacheAsync(companyID string) {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), constants.RefreshLogoCacheAsyncTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Получаем новую ссылку на логотип
|
||||
logoLink, err := h.objectStorageClient.GetPresignedLink(ctx,
|
||||
companyID, object_storage.LogoCategory, object_storage.LinkOptions{
|
||||
TTL: ptr.Duration(constants.LogoLinkTTL),
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Warn("failed to get logo link for cache refresh",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.cacheClient.Del(ctx, companyID, cache.LogoValueType); err != nil {
|
||||
h.logger.Warn("failed to delete logo from cache, it's ok if it first time creation",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
}
|
||||
|
||||
response := types.GetLogoResponse{
|
||||
Link: logoLink,
|
||||
ExpiresIn: time.Now().Add(constants.LogoLinkTTL),
|
||||
}
|
||||
|
||||
body, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
h.logger.Warn("failed to marshal logo response for cache",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.cacheClient.Set(ctx, companyID, cache.LogoValueType, string(body), constants.LogoLinkTTL); err != nil {
|
||||
h.logger.Warn("failed to update logo cache",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
} else {
|
||||
h.logger.Debug("logo cache refreshed successfully",
|
||||
slog.String("company_id", companyID))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// getLogoLinkWithCache получает ссылку на логотип из кэша или хранилища
|
||||
func (h *handler) getLogoLinkWithCache(ctx context.Context, companyID string) (string, error) {
|
||||
// Проверяем кэш
|
||||
cachedData, err := h.cacheClient.Get(ctx, companyID, cache.LogoValueType)
|
||||
if err == nil {
|
||||
var response types.GetLogoResponse
|
||||
if err := json.Unmarshal([]byte(cachedData), &response); err == nil {
|
||||
return response.Link, nil
|
||||
}
|
||||
|
||||
h.logger.Warn("failed to unmarshal logo response from cache",
|
||||
slog.String("company_id", companyID))
|
||||
}
|
||||
|
||||
if !errors.Is(err, cache.ErrKeyNotFound) {
|
||||
h.logger.Warn("failed to get logo from cache",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
}
|
||||
|
||||
// Получаем из хранилища ссылку на логотип
|
||||
logoLink, err := h.objectStorageClient.GetPresignedLink(ctx,
|
||||
companyID, object_storage.LogoCategory, object_storage.LinkOptions{
|
||||
TTL: ptr.Duration(constants.LogoLinkTTL),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
response := types.GetLogoResponse{
|
||||
Link: logoLink,
|
||||
ExpiresIn: time.Now().Add(constants.LogoLinkTTL),
|
||||
}
|
||||
|
||||
body, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
h.logger.Warn("failed to marshal logo response for cache",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
} else {
|
||||
if err := h.cacheClient.Set(ctx, companyID, cache.LogoValueType, string(body), constants.LogoLinkTTL); err != nil {
|
||||
h.logger.Warn("failed to cache logo response",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("company_id", companyID))
|
||||
}
|
||||
}
|
||||
|
||||
return logoLink, nil
|
||||
}
|
||||
|
||||
// =============== GRACEFUL SHUTDOWN ===============
|
||||
|
||||
// SetupGracefulShutdown делает graceful shutdown)
|
||||
func (h *handler) SetupGracefulShutdown(ctx context.Context) {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
h.logger.Info("shutting down, cleaning up temporary files")
|
||||
h.Cleanup()
|
||||
}()
|
||||
}
|
70
internal/http/middleware.go
Normal file
70
internal/http/middleware.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth/keycloak"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/config"
|
||||
)
|
||||
|
||||
type Middleware struct {
|
||||
logger *slog.Logger
|
||||
authManager auth.Manager
|
||||
}
|
||||
|
||||
type ConfigMiddleware struct {
|
||||
logger *slog.Logger
|
||||
keycloakCfg *config.Keycloak
|
||||
}
|
||||
|
||||
func NewMiddleware(c *ConfigMiddleware) (*Middleware, error) {
|
||||
authManager, err := keycloak.New(&keycloak.Config{
|
||||
AuthServerAddr: c.keycloakCfg.BaseURL,
|
||||
Realm: c.keycloakCfg.Realm,
|
||||
ClientId: c.keycloakCfg.ClientId,
|
||||
ClientSecret: c.keycloakCfg.ClientSecret,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Middleware{
|
||||
logger: c.logger,
|
||||
authManager: authManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// loggingMiddleware middleware для логирования запросов
|
||||
func (m *Middleware) loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
m.logger.Info("Started request", slog.String("method", req.Method), slog.String("url", req.URL.Path))
|
||||
|
||||
next.ServeHTTP(w, req)
|
||||
|
||||
m.logger.Info("Completed request",
|
||||
slog.String("path", req.URL.Path),
|
||||
slog.Duration("duration", time.Since(start)))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// authMiddleware middleware для валидации и обновления токена при истечении
|
||||
func (m *Middleware) authMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
token := strings.TrimPrefix(req.Header.Get("Authorization"), "Bearer ")
|
||||
|
||||
ok, err := m.authManager.CheckTokenIsValid(req.Context(), token)
|
||||
if err != nil || !ok {
|
||||
http.Error(w, "", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
161
internal/http/profile.go
Normal file
161
internal/http/profile.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||||
)
|
||||
|
||||
func (h *handler) getProfileAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getAgentProfileHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
result, err := h.agentService.GetProfile(r.Context(), &rmodel.ProfileGetRequest{
|
||||
Id: agentId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error getting agent profile",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) updateProfileAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "setAgentProfileHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
agentId = vars["agent_id"]
|
||||
)
|
||||
|
||||
var request rmodel.ProfileUpdateRequest
|
||||
|
||||
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 = agentId
|
||||
|
||||
if _, err := h.agentService.UpdateProfile(r.Context(), &request); err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error updating agent profile",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createSetProfileEvent(
|
||||
context.Background(),
|
||||
agentId,
|
||||
true,
|
||||
handlerName,
|
||||
)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *handler) getProfileDisributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getProfileDisributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
result, err := h.distributorService.GetProfile(r.Context(), &rmodel.ProfileGetRequest{
|
||||
Id: distId,
|
||||
})
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting distributor profile",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) updateProfileDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "updateProfileDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
var request rmodel.ProfileUpdateRequest
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
http.Error(w, constants.ErrBadRequest.Error(), http.StatusBadRequest)
|
||||
h.logger.Error("error while unmarshalling request: ",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
request.Id = distId
|
||||
|
||||
if _, err := h.distributorService.UpdateProfile(r.Context(), &request); err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error updating distributor profile",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createSetProfileEvent(
|
||||
context.Background(),
|
||||
distId,
|
||||
false,
|
||||
handlerName,
|
||||
)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
335
internal/http/router.go
Normal file
335
internal/http/router.go
Normal file
@@ -0,0 +1,335 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"strings"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/logger"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/feed"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/file_manager/s3_storage"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/integration"
|
||||
objectStorage "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/object_storage"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/agent"
|
||||
authinfra "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/auth_infrastructure"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/services/distributor"
|
||||
|
||||
pgdb "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/postgres"
|
||||
formgenerator "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/form_generator"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/config"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
Mux *mux.Router
|
||||
env string
|
||||
logger *slog.Logger
|
||||
middleware *Middleware
|
||||
shortenerCfg *config.SecretConfig
|
||||
abstractAPI string
|
||||
cache cache.Client
|
||||
objectStorage objectStorage.Client
|
||||
feedHandler *feed.Handler
|
||||
integrationClient integration.Client
|
||||
databaseClient database.Client
|
||||
agentService agent.AgentService
|
||||
distributorService distributor.DistributorService
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, cfg *config.Config) (*Router, error) {
|
||||
middleware, err := NewMiddleware(&ConfigMiddleware{
|
||||
logger: logger,
|
||||
keycloakCfg: &cfg.Keycloak,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheClient, err := cache.New(cache.ValKeyCacheConfig{
|
||||
Addrs: strings.Split(cfg.ValkeyCache.Addrs, ","),
|
||||
Password: cfg.ValkeyCache.Password,
|
||||
ReadOnly: cfg.ValkeyCache.ReadOnly,
|
||||
DialTimeout: cfg.ValkeyCache.DialTimeout,
|
||||
PoolSize: cfg.ValkeyCache.PoolSize,
|
||||
DefaultTTL: cfg.ValkeyCache.DefaultTTL,
|
||||
RootCaFilePath: cfg.ValkeyCache.RootCaFilePath,
|
||||
}, cache.ValkeyCacheType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
databaseClient, err := database.New(database.PostgresClientType, pgdb.PostgresConfig{
|
||||
Host: cfg.Database.Host,
|
||||
Port: cfg.Database.Port,
|
||||
Username: cfg.Database.Username,
|
||||
Password: cfg.Database.Password,
|
||||
Database: cfg.Database.DBName,
|
||||
Schema: cfg.Database.Schema,
|
||||
SSLMode: cfg.Database.SSLMode,
|
||||
SSLRootCert: cfg.Database.RootCaFilePath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
agentService, err := agent.New(agent.CrmAgentServiceType, agent.CrmAgentServiceConfig{
|
||||
DbClient: databaseClient,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
distributorService, err := distributor.New(distributor.CrmDistributorServiceType, distributor.CrmDistributorServiceConfig{
|
||||
DbClient: databaseClient,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objectStorageClient, err := objectStorage.New(objectStorage.S3StorageConfig{
|
||||
Bucket: cfg.S3Storage.Bucket,
|
||||
DefaultLinkTTL: cfg.ValkeyCache.DefaultTTL,
|
||||
}, objectStorage.ClientTypeS3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
integrationClient, err := integration.New(integration.Config{
|
||||
Cache: cacheClient,
|
||||
Db: databaseClient,
|
||||
Logger: logger,
|
||||
Secrets: map[string]integration.CompanySecrets{
|
||||
integration.VkusvillCompanyName: integration.VkusvillSecretsConfig{
|
||||
ApiToken: cfg.Integration.Vkusvill.ApiToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: вырезать ненужные аргументы в рамках https://tracker.yandex.ru/MOLVARAZRABOTKA-363
|
||||
service, err := feed.NewService(fmt.Sprintf(
|
||||
"postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
||||
cfg.Database.Username,
|
||||
cfg.Database.Password,
|
||||
cfg.Database.Host,
|
||||
cfg.Database.Port,
|
||||
cfg.Database.DBName,
|
||||
cfg.Database.SSLMode,
|
||||
), cfg.Database.Schema, logger, databaseClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Router{
|
||||
Mux: mux.NewRouter(),
|
||||
env: cfg.Env,
|
||||
logger: logger,
|
||||
middleware: middleware,
|
||||
shortenerCfg: &cfg.Secret,
|
||||
abstractAPI: cfg.EmailVerificationService.APIKey, // may be nil dereference if no email verification service is configured
|
||||
cache: cacheClient,
|
||||
objectStorage: objectStorageClient,
|
||||
feedHandler: feed.NewFeedHandler(logger, service),
|
||||
databaseClient: databaseClient,
|
||||
agentService: agentService,
|
||||
distributorService: distributorService,
|
||||
integrationClient: integrationClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetupRouter(r *Router, buildCfg config.BuildInfo) {
|
||||
authInfraService, err := authinfra.New(authinfra.CacheAuthInfrastructureServiceType, authinfra.CacheAuthInfraServiceConfig{
|
||||
CacheClient: r.cache,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s3FileManager := s3_storage.NewS3Storage(
|
||||
r.objectStorage,
|
||||
r.cache,
|
||||
)
|
||||
|
||||
h := newHandler(&Config{
|
||||
env: r.env,
|
||||
logger: r.logger,
|
||||
authManager: r.middleware.authManager,
|
||||
authInfraService: authInfraService,
|
||||
fileManager: s3FileManager,
|
||||
dbClient: r.databaseClient,
|
||||
agentService: r.agentService,
|
||||
distributorService: r.distributorService,
|
||||
secretConfig: r.shortenerCfg,
|
||||
emailVerificationServiceAPIKey: r.abstractAPI,
|
||||
cacheClient: r.cache,
|
||||
objectStorageClient: r.objectStorage,
|
||||
feed: r.feedHandler,
|
||||
integrationClient: r.integrationClient,
|
||||
buildConfig: buildCfg,
|
||||
})
|
||||
|
||||
r.Mux.Use(r.middleware.loggingMiddleware)
|
||||
|
||||
setupAuthHandlers(r, h)
|
||||
setupIntegrationHandlers(r, h)
|
||||
setupClientHandlers(r, h)
|
||||
setupValidationHandlers(r, h)
|
||||
|
||||
if r.env != logger.EnvProd {
|
||||
setupSystemInfoHandlers(r, h)
|
||||
}
|
||||
|
||||
subRouter := r.Mux.PathPrefix("/api/v1").Subrouter()
|
||||
subRouter.Use(r.middleware.authMiddleware)
|
||||
|
||||
setupAgentHandlers(subRouter, h)
|
||||
setupDistributorHandlers(subRouter, h)
|
||||
setupEmployeesHandlers(subRouter, h)
|
||||
setupLogoHandlers(subRouter, h)
|
||||
|
||||
// --------------- DOCUMENTS FOR FORM ------------
|
||||
r.Mux.HandleFunc("/api/v1/docs/{file}", h.getFileHandler).Methods(http.MethodGet)
|
||||
|
||||
// --------------- FEED ------------
|
||||
subRouter.HandleFunc("/feed/{uid}/events", h.GetUserEventsHandler).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func setupAuthHandlers(r *Router, h *handler) {
|
||||
r.Mux.HandleFunc("/api/v1/login", h.loginHandler).Methods(http.MethodPost)
|
||||
r.Mux.HandleFunc("/api/v1/register", h.registerHandler).Methods(http.MethodPost)
|
||||
r.Mux.HandleFunc("/api/v1/logout", h.logoutHandler).Methods(http.MethodPost)
|
||||
r.Mux.HandleFunc("/api/v1/refresh-token", h.refreshTokenHandler).Methods(http.MethodPost)
|
||||
|
||||
r.Mux.HandleFunc("/api/v1/verify_email", h.verifyEmailHandler).Methods(http.MethodPatch)
|
||||
r.Mux.HandleFunc("/api/v1/verify_email", h.getEmailVerificationStatusHandler).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/api/v1/confirm_email", h.confirmEmailPageHandler).Methods(http.MethodGet)
|
||||
|
||||
r.Mux.HandleFunc("/api/v1/forgot_password", h.forgotPasswordHandler).Methods(http.MethodPost)
|
||||
r.Mux.HandleFunc("/api/v1/validate_otp", h.validateOTPHandler).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/api/v1/reset_password", h.resetPasswordHandler).Methods(http.MethodPut)
|
||||
}
|
||||
|
||||
func setupAgentHandlers(subRouter *mux.Router, h *handler) {
|
||||
// // =============== COMPANY HANDLERS ===============
|
||||
subRouter.HandleFunc("/agents/{agent_id}/companies", h.getCompanyListAgentHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/company/{company_id}", h.getCompanyByIdAgentHandler).Methods(http.MethodGet)
|
||||
// TODO: review
|
||||
// subRouter.HandleFunc("/agents/{agent_id}/company/{company_id}", h.addNewAgentCompanyMemberHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/company", h.createCompanyAgentHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/company/{company_id}", h.updateCompanyAgentHandler).Methods(http.MethodPatch)
|
||||
|
||||
// // =============== VACANCY HANDLERS ===============
|
||||
subRouter.HandleFunc("/agents/{agent_id}/vacancies", h.getVacancyListAgentHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/vacancies/{vacancy_id}", h.getPersonalLinkHandler).Methods(http.MethodGet)
|
||||
|
||||
// // =============== SUBMISSION HANDLERS ===============
|
||||
subRouter.HandleFunc("/agents/{agent_id}/submissions", h.getSubmissionListAgentHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/submissions/{submission_id}", h.deleteSubmissionAgentHandler).Methods(http.MethodDelete)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/submissions/{submission_id}/cv", h.getSubmissionCVHandler).Methods(http.MethodGet)
|
||||
|
||||
// // =============== PROFILE HANDLERS ===============
|
||||
subRouter.HandleFunc("/agents/{agent_id}/profile", h.getProfileAgentHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/profile", h.updateProfileAgentHandler).Methods(http.MethodPatch)
|
||||
|
||||
// // =============== BALANCE HANDLERS ===============
|
||||
subRouter.HandleFunc("/agents/{agent_id}/balance", h.getBalanceAgentHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/transactions", h.getTransactionListAgentHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/transactions", h.createTransactionAgentHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/bank_accounts", h.getBankAccountListAgentHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/bank_accounts", h.createBankAccountAgentHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/bank_accounts/{bank_account_id}", h.updateBankAccountAgentHandler).Methods(http.MethodPatch)
|
||||
subRouter.HandleFunc("/agents/{agent_id}/bank_accounts/{bank_account_id}", h.deleteBankAccountAgentHandler).Methods(http.MethodDelete)
|
||||
}
|
||||
|
||||
func setupDistributorHandlers(subRouter *mux.Router, h *handler) {
|
||||
// // =============== COMPANY HANDLERS ===============
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/companies", h.getCompanyListDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}", h.getCompanyByIdDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}", h.addCompanyMemberDistributorHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}", h.updateCompanyDistributorHandler).Methods(http.MethodPatch)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company", h.createCompanyDistributorHandler).Methods(http.MethodPost)
|
||||
|
||||
// // =============== VACANCY HANDLERS ===============
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/vacancies", h.getVacancyListDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/vacancies", h.createVacancyDistributorHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/vacancies/{vacancy_id}", h.updateVacancyDistributorHandler).Methods(http.MethodPatch)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/vacancies/{vacancy_id}", h.deleteVacancyDistributorHandler).Methods(http.MethodDelete)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/vacancies/{vacancy_id}/moderation", h.sendVacancyToModerationHandler).Methods(http.MethodPost)
|
||||
|
||||
// // =============== SUBMISSION HANDLERS ===============
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/vacancies/{vacancy_id}/submissions", h.getSubmissionListDistributorHandler).Methods(http.MethodGet)
|
||||
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/vacancies/{vacancy_id}/submissions/{submission_id}/status", h.updateSubmissionStatusDistributorHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/submissions/{submission_id}/cv", h.getSubmissionCVHandler).Methods(http.MethodGet)
|
||||
|
||||
// // =============== PROFILE HANDLERS ===============
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/profile", h.getProfileDisributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/profile", h.updateProfileDistributorHandler).Methods(http.MethodPatch)
|
||||
|
||||
// // =============== BALANCE HANDLERS ===============
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/balance", h.getBalanceDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/balance", h.getCompanyBalanceDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/transactions", h.getTransactionListDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/transactions", h.getCompanyTransactionListDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/transactions", h.createTransactionDistributorHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/bank_accounts", h.getBankAccountListDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/bank_accounts", h.getCompanyBankAccountListDistributorHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/bank_accounts", h.createBankAccountDistributorHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/bank_accounts/{bank_account_id}", h.updateBankAccountDistributorHandler).Methods(http.MethodPut)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/bank_accounts/{bank_account_id}", h.deleteBankAccountDistributorHandler).Methods(http.MethodDelete)
|
||||
}
|
||||
|
||||
func setupEmployeesHandlers(subRouter *mux.Router, h *handler) {
|
||||
// // =============== EMPLOYEES HANDLERS ===============
|
||||
subRouter.HandleFunc("/companies/{company_id}/employees", h.getEmployeesHandler).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func setupClientHandlers(r *Router, h *handler) {
|
||||
// // =============== CLIENT HANDLERS ===============
|
||||
r.Mux.HandleFunc("/api/v1/anketa", h.getAnketaHandler).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/api/v1/anketa", h.postAnketaHandler).Methods(http.MethodPost)
|
||||
|
||||
// --------------- FORM METADATA ---------------
|
||||
//TODO FROM ALEXANDER: make err validation
|
||||
formSys, _ := formgenerator.GetFileSystem()
|
||||
|
||||
r.Mux.PathPrefix("/api/v1/anketa/static/").Handler(http.StripPrefix("/api/v1/anketa/static/", http.FileServer(http.FS(formSys))))
|
||||
}
|
||||
|
||||
func setupValidationHandlers(r *Router, h *handler) {
|
||||
// // =============== VALIDATION HANDLERS ===============
|
||||
r.Mux.HandleFunc("/api/v1/{uid}/validation", h.getUserValidationStatusHandler).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func setupLogoHandlers(subRouter *mux.Router, h *handler) {
|
||||
// // =============== LOGO HANDLERS ===============
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/logo", h.getCompanyLogoHandler).Methods(http.MethodGet)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/logo", h.createCompanyLogoHandler).Methods(http.MethodPost)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/logo", h.updateCompanyLogoHandler).Methods(http.MethodPut)
|
||||
subRouter.HandleFunc("/distributor/{distributor_id}/company/{company_id}/logo", h.deleteCompanyLogoHandler).Methods(http.MethodDelete)
|
||||
}
|
||||
|
||||
func setupIntegrationHandlers(r *Router, h *handler) {
|
||||
r.Mux.HandleFunc("/api/v1/integration/vkusvill/callback", h.vkusvillIntegrationCallbackHandler).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
func setupSystemInfoHandlers(r *Router, h *handler) {
|
||||
r.Mux.HandleFunc("/api/v1/healthcheck", h.getBuildInfoHandler).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/debug/pprof/", pprof.Index).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/debug/pprof/profile", pprof.Profile).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol).Methods(http.MethodGet)
|
||||
r.Mux.HandleFunc("/debug/pprof/trace", pprof.Trace).Methods(http.MethodGet)
|
||||
r.Mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")).Methods(http.MethodGet)
|
||||
r.Mux.Handle("/debug/pprof/heap", pprof.Handler("heap")).Methods(http.MethodGet)
|
||||
r.Mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")).Methods(http.MethodGet)
|
||||
r.Mux.Handle("/debug/pprof/block", pprof.Handler("block")).Methods(http.MethodGet)
|
||||
}
|
375
internal/http/submission.go
Normal file
375
internal/http/submission.go
Normal file
@@ -0,0 +1,375 @@
|
||||
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"
|
||||
)
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
//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
|
||||
}
|
267
internal/http/vacancy.go
Normal file
267
internal/http/vacancy.go
Normal file
@@ -0,0 +1,267 @@
|
||||
package http_router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// TODO: review
|
||||
func (h *handler) setVacancyLogoLinks(
|
||||
ctx context.Context,
|
||||
vacancyList *rmodel.VacancyListGetResponse,
|
||||
) {
|
||||
const handlerName = "setVacancyLogoLinks"
|
||||
|
||||
for _, vacancy := range vacancyList.Vacancies {
|
||||
logoLink, err := h.getLogoLinkWithCache(ctx, vacancy.Company.Id)
|
||||
if err != nil {
|
||||
h.logger.Warn("error getting logo link",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("vacancy_id", vacancy.Id),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
vacancy.Company.LogoLink = &logoLink
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getVacancyListAgentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getVacancyListAgentHandler"
|
||||
|
||||
request, err := new(rmodel.VacancyListGetRequest).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
|
||||
}
|
||||
|
||||
result, err := h.agentService.GetVacancyList(r.Context(), request)
|
||||
if err != nil {
|
||||
h.handleAgentError(w, err)
|
||||
h.logger.Error("error getting vacancy list",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.setVacancyLogoLinks(r.Context(), result)
|
||||
|
||||
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 (h *handler) getVacancyListDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "getVacancyListDistributorHandler"
|
||||
|
||||
request, err := new(rmodel.VacancyListGetRequest).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
|
||||
}
|
||||
|
||||
result, err := h.distributorService.GetVacancyList(r.Context(), request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error getting vacancy list",
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) createVacancyDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "createVacancyDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
)
|
||||
|
||||
var request rmodel.VacancyCreateRequest
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
result, err := h.distributorService.CreateVacancy(r.Context(), &request)
|
||||
if err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error creating vacancy",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createSetDistributorVacancyEvent(
|
||||
context.Background(),
|
||||
distId,
|
||||
result.Id,
|
||||
request.Name,
|
||||
handlerName,
|
||||
)
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
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 (h *handler) updateVacancyDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "updateVacancyDistributorHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
vacancyId = vars["vacancy_id"]
|
||||
)
|
||||
|
||||
var request rmodel.VacancyUpdateRequest
|
||||
|
||||
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 = vacancyId
|
||||
|
||||
if _, err := h.distributorService.UpdateVacancy(r.Context(), &request); err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error updating vacancy",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createEditDistributorVacancyEvent(
|
||||
context.Background(),
|
||||
distId,
|
||||
vacancyId,
|
||||
handlerName,
|
||||
)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *handler) deleteVacancyDistributorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "deleteDistributorVacancyHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
vacancyId = vars["vacancy_id"]
|
||||
)
|
||||
|
||||
if _, err := h.distributorService.DeleteVacancy(r.Context(), &rmodel.VacancyDeleteRequest{
|
||||
Id: vacancyId,
|
||||
}); err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error deleting vacancy",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func(hdn *handler) {
|
||||
if err := h.feed.CancelEvent(r.Context(), vacancyId, "Вакансия была удалена"); err != nil {
|
||||
h.logger.Error("failed to cancel feed event",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
slog.String("vacancy_id", vacancyId),
|
||||
slog.String("distributor_id", distId))
|
||||
}
|
||||
}(h)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *handler) sendVacancyToModerationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
const handlerName = "sendVacancyToModerationHandler"
|
||||
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
distId = vars["distributor_id"]
|
||||
vacancyId = vars["vacancy_id"]
|
||||
)
|
||||
|
||||
if _, err := h.distributorService.SendVacancyToModeration(r.Context(), &rmodel.SendVacancyToModerationRequest{
|
||||
Id: vacancyId,
|
||||
}); err != nil {
|
||||
h.handleDistributorError(w, err)
|
||||
h.logger.Error("error sending vacancy to moderation",
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("handler", handlerName),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go h.createSendVacancyToModerationEvent(
|
||||
context.Background(),
|
||||
distId,
|
||||
vacancyId,
|
||||
handlerName,
|
||||
)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
Reference in New Issue
Block a user