1
This commit is contained in:
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()))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user