package requestmodel import ( "fmt" "math" "net/url" "strconv" "time" "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants" dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types" ) type VacancyStatus string const ( VacUnspecified VacancyStatus = "unspecified" VacNew VacancyStatus = "new" VacPending VacancyStatus = "pending" VacApproved VacancyStatus = "approved" VacRejected VacancyStatus = "rejected" ) func (s *VacancyStatus) NullString() *string { if s == nil { return nil } return (*string)(s) } type ( // TODO: remove extra fields for agent Vacancy struct { Id string `json:"id"` Company VacancyCompanyInfo `json:"company"` Name string `json:"name"` Address string `json:"address"` WorkFormat string `json:"work_format"` AgentReward *int32 `json:"agent_reward"` SalaryTop int32 `json:"salary_top"` SalaryBottom int32 `json:"salary_bottom"` Requirements *string `json:"requirements"` Responsibilities *string `json:"responsibilities"` ExtraInfo *string `json:"extra_info"` Region string `json:"region"` IsArchived bool `json:"is_archived"` TargetAction VacancyTargetAction `json:"target_action"` Moderation VacancyModeration `json:"moderation"` RequiredCandidates int32 `json:"required_candidates"` CurrentCandidates int32 `json:"current_candidates"` ExtraFields *string `json:"extra_fields"` CreatedAt string `json:"created_at"` } VacancyCompanyInfo struct { Id string `json:"id"` Name string `json:"name"` LogoLink *string `json:"logo_link"` } VacancyTargetAction struct { Action string `json:"action"` Duration int32 `json:"duration"` } VacancyModeration struct { Status VacancyStatus `json:"status"` DescriptionHistory []VacancyModerationDescription `json:"description_history"` } VacancyModerationDescription struct { Description *string `json:"description"` CreatedAt string `json:"created_at"` } ) func (m *VacancyModeration) From(msg *dbtypes.VacancyModeration) *VacancyModeration { if msg == nil { return nil } res := &VacancyModeration{ Status: VacancyStatus(msg.Status), DescriptionHistory: make([]VacancyModerationDescription, len(msg.DescriptionHistory)), } for i, desc := range msg.DescriptionHistory { res.DescriptionHistory[i] = VacancyModerationDescription{ Description: desc.Description, CreatedAt: desc.CreatedAt.Format(time.DateOnly), } } return res } func (v *Vacancy) From(msg *dbtypes.Vacancy) *Vacancy { if msg == nil { return nil } res := &Vacancy{ Id: msg.Id, Company: VacancyCompanyInfo{ Id: msg.Company.Id, Name: msg.Company.Name, }, Name: msg.Name, Address: msg.Address, WorkFormat: msg.WorkFormat, AgentReward: msg.AgentReward, SalaryTop: msg.SalaryTop, SalaryBottom: msg.SalaryBottom, Requirements: msg.Requirements, Responsibilities: msg.Responsibilities, ExtraInfo: msg.ExtraInfo, Region: msg.Region, IsArchived: msg.IsArchived, TargetAction: VacancyTargetAction{ Action: msg.TargetAction.Action, Duration: msg.TargetAction.Duration, }, Moderation: *new(VacancyModeration).From(&msg.Moderation), RequiredCandidates: msg.RequiredCandidates, CurrentCandidates: msg.CurrentCandidates, ExtraFields: msg.ExtraFields, CreatedAt: msg.CreatedAt.Format(time.DateOnly), } return res } type ( VacancyListGetRequest struct { Filters *VacancyListFilters Page uint64 PageSize uint64 } VacancyListFilters struct { DistributorId *string CompanyId *string VacancyId *string Region *string SalaryBottom *int32 SalaryTop *int32 IsArchived *bool Status *VacancyStatus } VacancyListGetResponse struct { Vacancies []Vacancy `json:"vacancies"` } ) func (f *VacancyListGetRequest) FromQuery(query url.Values) (*VacancyListGetRequest, error) { if query == nil { return nil, nil } var filters VacancyListFilters if val, ok := query["distributor_id"]; ok { filters.DistributorId = &val[0] } if val, ok := query["company_id"]; ok { filters.CompanyId = &val[0] } if val, ok := query["vacancy_id"]; ok { filters.VacancyId = &val[0] } if val, ok := query["region"]; ok { filters.Region = &val[0] } if val, ok := query["salary_bottom"]; ok { intVal, err := parseInt32(val[0]) if err != nil { return nil, fmt.Errorf("invalid salary_bottom param: %w", err) } filters.SalaryBottom = &intVal } if val, ok := query["salary_top"]; ok { intVal, err := parseInt32(val[0]) if err != nil { return nil, fmt.Errorf("invalid salary_top param: %w", err) } filters.SalaryTop = &intVal } if val, ok := query["is_archived"]; ok { flag := val[0] == "true" || val[0] == "1" filters.IsArchived = &flag } if val, ok := query["status"]; ok { status := VacancyStatus(val[0]) filters.Status = &status } res := &VacancyListGetRequest{ Filters: &filters, Page: constants.DefaultPaginationPage, PageSize: constants.DefaultPaginationPageSize, } if val, ok := query["page"]; ok { intVal, err := parseUint64(val[0]) if err != nil { return nil, fmt.Errorf("invalid page param: %w", err) } res.Page = intVal } if val, ok := query["page_size"]; ok { intVal, err := parseUint64(val[0]) if err != nil { return nil, fmt.Errorf("invalid page_size param: %w", err) } res.PageSize = intVal } return res, nil } func (r *VacancyListGetRequest) Validate() error { if r == nil { return fmt.Errorf("%w: request is nil", ErrValidation) } if r.Filters != nil { if r.Filters.Status != nil && !(*r.Filters.Status == VacUnspecified || *r.Filters.Status == VacNew || *r.Filters.Status == VacPending || *r.Filters.Status == VacApproved || *r.Filters.Status == VacRejected) { return fmt.Errorf("%w: invalid status: %v", ErrValidation, *r.Filters.Status) } } return nil } func (r *VacancyListGetResponse) From(msg *dbtypes.VacancyListGetResponse) *VacancyListGetResponse { if msg == nil { return nil } res := &VacancyListGetResponse{ Vacancies: make([]Vacancy, len(msg.Vacancies)), } for i, vacancy := range msg.Vacancies { res.Vacancies[i] = *new(Vacancy).From(&vacancy) } return res } type ( VacancyCreateRequest struct { CompanyId string `json:"company_id"` Name string `json:"name"` Address string `json:"address"` WorkFormat string `json:"work_format"` AgentReward *int32 `json:"agent_reward"` SalaryTop int32 `json:"salary_top"` SalaryBottom int32 `json:"salary_bottom"` Requirements *string `json:"requirements"` Responsibilities *string `json:"responsibilities"` ExtraInfo *string `json:"extra_info"` Region string `json:"region"` TargetAction VacancyTargetAction `json:"target_action"` RequiredCandidates int32 `json:"required_candidates"` CurrentCandidates int32 `json:"current_candidates"` ExtraFields *string `json:"extra_fields"` } VacancyCreateResponse struct { Id string `json:"id"` } ) func (r *VacancyCreateRequest) Validate() error { if r == nil { return fmt.Errorf("%w: request is nil", ErrValidation) } if r.CompanyId == "" { return fmt.Errorf("%w: CompanyID is required", ErrValidation) } if r.Name == "" { return fmt.Errorf("%w: Name is required", ErrValidation) } if r.Address == "" { return fmt.Errorf("%w: Address is required", ErrValidation) } if r.WorkFormat == "" { return fmt.Errorf("%w: WorkFormat is required", ErrValidation) } if r.Region == "" { return fmt.Errorf("%w: Region is required", ErrValidation) } if r.TargetAction.Action == "" { return fmt.Errorf("%w: TargetAction.Action is required", ErrValidation) } if r.TargetAction.Duration == 0 { return fmt.Errorf("%w: TargetAction.Duration is required", ErrValidation) } return nil } func (r *VacancyCreateResponse) From(msg *dbtypes.VacancyCreateResponse) *VacancyCreateResponse { if msg == nil { return nil } return &VacancyCreateResponse{ Id: msg.Id, } } type ( VacancyUpdateRequest struct { Id string Name *string `json:"name"` Address *string `json:"address"` WorkFormat *string `json:"work_format"` AgentReward *int32 `json:"agent_reward"` SalaryTop *int32 `json:"salary_top"` SalaryBottom *int32 `json:"salary_bottom"` Requirements *string `json:"requirements"` Responsibilities *string `json:"responsibilities"` ExtraInfo *string `json:"extra_info"` Region *string `json:"region"` TargetAction VacancyTargetActionForUpdate `json:"target_action"` RequiredCandidates *int32 `json:"required_candidates"` ExtraFields *string `json:"extra_fields"` } VacancyTargetActionForUpdate struct { Action *string `json:"action"` Duration *int32 `json:"duration"` } VacancyUpdateResponse struct { } ) func (r *VacancyUpdateRequest) Validate() error { if r == nil { return fmt.Errorf("%w: request is nil", ErrValidation) } if r.Id == "" { return fmt.Errorf("%w: ID is required", ErrValidation) } if r.Name == nil && r.Address == nil && r.WorkFormat == nil && r.AgentReward == nil && r.SalaryTop == nil && r.SalaryBottom == nil && r.Requirements == nil && r.Responsibilities == nil && r.ExtraInfo == nil && r.Region == nil && r.TargetAction.Action == nil && r.TargetAction.Duration == nil && r.RequiredCandidates == nil && r.ExtraFields == nil { return fmt.Errorf("%w: nothing to update", ErrValidation) } return nil } func (r *VacancyUpdateResponse) From(msg *dbtypes.VacancyUpdateResponse) *VacancyUpdateResponse { if msg == nil { return nil } return &VacancyUpdateResponse{} } type ( VacancyDeleteRequest struct { Id string } VacancyDeleteResponse struct { } ) func (r *VacancyDeleteRequest) Validate() error { if r == nil { return fmt.Errorf("%w: request is nil", ErrValidation) } if r.Id == "" { return fmt.Errorf("%w: ID is required", ErrValidation) } return nil } func (r *VacancyDeleteResponse) From(msg *dbtypes.VacancyDeleteResponse) *VacancyDeleteResponse { if msg == nil { return nil } return &VacancyDeleteResponse{} } type ( SendVacancyToModerationRequest struct { Id string } SendVacancyToModerationResponse struct { } ) func (r *SendVacancyToModerationRequest) Validate() error { if r == nil { return fmt.Errorf("%w: request is nil", ErrValidation) } if r.Id == "" { return fmt.Errorf("%w: ID is required", ErrValidation) } return nil } func parseInt32(val string) (int32, error) { intVal, err := strconv.Atoi(val) if err != nil { return 0, err } // Check overflow int32 if intVal < int(math.MinInt32) || intVal > int(math.MaxInt32) { return 0, constants.ErrIntOutOfRange } //nolint:gosec // ignore integer overflow check for Atoi conversion to int32 int32Val := int32(intVal) return int32Val, nil } func parseUint64(val string) (uint64, error) { uintVal, err := strconv.ParseUint(val, 10, 64) if err != nil { return 0, err } return uintVal, nil }