733 lines
21 KiB
Go
733 lines
21 KiB
Go
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()))
|
||
}
|
||
}
|