This commit is contained in:
Alex Shevchuk
2025-08-18 17:12:04 +03:00
commit d84487d238
157 changed files with 160686 additions and 0 deletions

View File

@@ -0,0 +1,467 @@
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
}