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,48 @@
package agent
import (
"context"
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
)
type AgentService interface {
GetVacancyList(ctx context.Context, request *rmodel.VacancyListGetRequest) (*rmodel.VacancyListGetResponse, error)
GetSubmissionList(ctx context.Context, request *rmodel.SubmissionListGetRequest) (*rmodel.SubmissionListGetResponse, error)
CreateSubmission(ctx context.Context, request *rmodel.SubmissionCreateRequest) (*rmodel.SubmissionCreateResponse, error)
DeleteSubmission(ctx context.Context, request *rmodel.SubmissionDeleteRequest) (*rmodel.SubmissionDeleteResponse, error)
GetProfile(ctx context.Context, request *rmodel.ProfileGetRequest) (*rmodel.ProfileGetResponse, error)
UpdateProfile(ctx context.Context, request *rmodel.ProfileUpdateRequest) (*rmodel.ProfileUpdateResponse, error)
GetCompanyList(ctx context.Context, request *rmodel.CompanyListGetRequest) (*rmodel.CompanyListGetResponse, error)
GetCompanyInfo(ctx context.Context, request *rmodel.CompanyByIdGetRequest) (*rmodel.CompanyByIdGetResponse, error)
CreateCompany(ctx context.Context, request *rmodel.CompanyCreateRequest) (*rmodel.CompanyCreateResponse, error)
UpdateCompanyInfo(ctx context.Context, request *rmodel.CompanyUpdateRequest) (*rmodel.CompanyUpdateResponse, error)
GetBalance(ctx context.Context, request *rmodel.BalanceGetRequest) (*rmodel.BalanceGetResponse, error)
GetTransactionList(ctx context.Context, request *rmodel.TransactionListGetRequest) (*rmodel.TransactionListGetResponse, error)
CreateTransaction(ctx context.Context, request *rmodel.TransactionCreateRequest) (*rmodel.TransactionCreateResponse, error)
GetBankAccountList(ctx context.Context, request *rmodel.BankAccountListGetRequest) (*rmodel.BankAccountListGetResponse, error)
CreateBankAccount(ctx context.Context, request *rmodel.BankAccountCreateRequest) (*rmodel.BankAccountCreateResponse, error)
UpdateBankAccount(ctx context.Context, request *rmodel.BankAccountUpdateRequest) (*rmodel.BankAccountUpdateResponse, error)
DeleteBankAccount(ctx context.Context, request *rmodel.BankAccountDeleteRequest) (*rmodel.BankAccountDeleteResponse, error)
}
func New(typ Type, cfg Config) (AgentService, error) {
switch typ {
case CrmAgentServiceType:
config, ok := cfg.(CrmAgentServiceConfig)
if !ok {
return nil, ErrInvalidConfig
}
return newCrmAgentService(config), nil
default:
return nil, ErrUnknownAgentServiceType
}
}

View File

@@ -0,0 +1,455 @@
package agent
import (
"context"
"errors"
"fmt"
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database"
dberrors "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/errors"
dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
)
type CrmAgentService struct {
dbClient database.Client
}
type CrmAgentServiceConfig struct {
DbClient database.Client
}
func newCrmAgentService(cfg CrmAgentServiceConfig) *CrmAgentService {
return &CrmAgentService{
dbClient: cfg.DbClient,
}
}
func (s *CrmAgentService) GetVacancyList(
ctx context.Context,
request *rmodel.VacancyListGetRequest,
) (*rmodel.VacancyListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetVacancyList(ctx, &dbtypes.VacancyListGetRequest{
Filters: &dbtypes.VacancyListFilters{
DistributorId: request.Filters.DistributorId,
CompanyId: request.Filters.CompanyId,
VacancyId: request.Filters.VacancyId,
Region: request.Filters.Region,
SalaryBottom: request.Filters.SalaryBottom,
SalaryTop: request.Filters.SalaryTop,
IsArchived: request.Filters.IsArchived,
Status: dbtypes.NewVacancyStatus(request.Filters.Status.NullString()),
},
Page: request.Page,
PageSize: request.PageSize,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting vacancy list: %v", s.handleDBError(err), err)
}
result := new(rmodel.VacancyListGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) GetSubmissionList(
ctx context.Context,
request *rmodel.SubmissionListGetRequest,
) (*rmodel.SubmissionListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
req := dbtypes.SubmissionListGetRequest{
Filters: &dbtypes.SubmissionListGetFilters{
AgentId: &request.AgentId,
},
}
if request.Filters != nil {
if request.Filters.VacancyId != nil {
req.Filters.VacancyId = request.Filters.VacancyId
}
if request.Filters.Status != nil {
req.Filters.Status = dbtypes.NewSubmissionStatus(request.Filters.Status.NullString())
}
}
resp, err := s.dbClient.GetSubmissionList(ctx, &req)
if err != nil {
return nil, fmt.Errorf("%w: error getting submission list: %v", s.handleDBError(err), err)
}
result := new(rmodel.SubmissionListGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) CreateSubmission(
ctx context.Context,
request *rmodel.SubmissionCreateRequest,
) (*rmodel.SubmissionCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
req := dbtypes.SubmissionCreateRequest{
AgentId: request.AgentId,
VacancyId: request.VacancyId,
CandidateInfo: &dbtypes.CandidateInfo{
FirstName: request.CandidateInfo.FirstName,
LastName: request.CandidateInfo.LastName,
MiddleName: request.CandidateInfo.MiddleName,
PhoneNumber: request.CandidateInfo.PhoneNumber,
Email: request.CandidateInfo.Email,
Birthday: request.CandidateInfo.Birthday,
CvLink: request.CandidateInfo.CvLink,
Resume: request.CandidateInfo.Resume,
},
}
resp, err := s.dbClient.CreateSubmission(ctx, &req)
if err != nil {
return nil, fmt.Errorf("%w: error creating submission: %v", s.handleDBError(err), err)
}
result := new(rmodel.SubmissionCreateResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) DeleteSubmission(
ctx context.Context,
request *rmodel.SubmissionDeleteRequest,
) (*rmodel.SubmissionDeleteResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.DeleteSubmission(ctx, &dbtypes.SubmissionDeleteRequest{
Id: request.Id,
}); err != nil {
return nil, fmt.Errorf("%w: error deleting submission: %v", s.handleDBError(err), err)
}
return &rmodel.SubmissionDeleteResponse{}, nil
}
func (s *CrmAgentService) GetProfile(
ctx context.Context,
request *rmodel.ProfileGetRequest,
) (*rmodel.ProfileGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetProfileById(ctx, &dbtypes.ProfileGetRequest{
Id: request.Id,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting agent info: %v", s.handleDBError(err), err)
}
result := new(rmodel.ProfileGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) UpdateProfile(
ctx context.Context,
request *rmodel.ProfileUpdateRequest,
) (*rmodel.ProfileUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateProfile(ctx, &dbtypes.ProfileUpdateRequest{
Id: request.Id,
Name: request.Name,
PhoneNumber: request.PhoneNumber,
Email: request.Email,
}); err != nil {
return nil, fmt.Errorf("%w: error updating profile: %v", s.handleDBError(err), err)
}
return &rmodel.ProfileUpdateResponse{}, nil
}
func (s *CrmAgentService) GetCompanyList(
ctx context.Context,
request *rmodel.CompanyListGetRequest,
) (*rmodel.CompanyListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetCompanyList(ctx, &dbtypes.CompanyListGetRequest{
Id: request.Id,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting company list: %v", ErrInternal, err)
}
result := new(rmodel.CompanyListGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) GetCompanyInfo(
ctx context.Context,
request *rmodel.CompanyByIdGetRequest,
) (*rmodel.CompanyByIdGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetCompanyById(ctx, &dbtypes.CompanyByIdGetRequest{
Id: request.UserId,
CompanyId: request.CompanyId,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting company info: %v", s.handleDBError(err), err)
}
result := new(rmodel.CompanyByIdGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) CreateCompany(
ctx context.Context,
request *rmodel.CompanyCreateRequest,
) (*rmodel.CompanyCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.CreateCompany(ctx, &dbtypes.CompanyCreateRequest{
OwnerId: request.OwnerId,
Name: request.Name,
LegalPerson: request.LegalPerson,
Description: request.Description,
Website: request.Website,
PhysicalAddress: request.PhysicalAddress,
LegalAddress: request.LegalAddress,
Inn: request.Inn,
Kpp: request.Kpp,
Staff: request.Staff,
})
if err != nil {
return nil, fmt.Errorf("%w: error creating company: %v", s.handleDBError(err), err)
}
result := new(rmodel.CompanyCreateResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) UpdateCompanyInfo(
ctx context.Context,
request *rmodel.CompanyUpdateRequest,
) (*rmodel.CompanyUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateCompany(ctx, &dbtypes.CompanyUpdateRequest{
Id: request.Id,
Name: request.Name,
LegalPerson: request.LegalPerson,
Description: request.Description,
Website: request.Website,
PhysicalAddress: request.PhysicalAddress,
LegalAddress: request.LegalAddress,
Inn: request.Inn,
Kpp: request.Kpp,
Staff: request.Staff,
}); err != nil {
return nil, fmt.Errorf("%w: error updating company info: %v", s.handleDBError(err), err)
}
return &rmodel.CompanyUpdateResponse{}, nil
}
func (s *CrmAgentService) GetBalance(
ctx context.Context,
request *rmodel.BalanceGetRequest,
) (*rmodel.BalanceGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetBalance(ctx, &dbtypes.BalanceGetRequest{
OwnerId: request.OwnerId,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting balance: %v", s.handleDBError(err), err)
}
result := new(rmodel.BalanceGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) GetTransactionList(
ctx context.Context,
request *rmodel.TransactionListGetRequest,
) (*rmodel.TransactionListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
req := dbtypes.TransactionListGetRequest{
OwnerId: request.OwnerId,
Page: request.Page,
PageSize: request.PageSize,
}
if request.Filters != nil {
req.Filters = new(dbtypes.TransactionListFilters)
req.Filters.Type = dbtypes.NewTransactionType(request.Filters.Type.NullString())
req.Filters.Status = dbtypes.NewTransactionStatus(request.Filters.Status.NullString())
req.Filters.BankAccountId = request.Filters.BankAccountId
}
resp, err := s.dbClient.GetTransactionList(ctx, &req)
if err != nil {
return nil, fmt.Errorf("%w: error getting transactions: %v", s.handleDBError(err), err)
}
result := new(rmodel.TransactionListGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) CreateTransaction(
ctx context.Context,
request *rmodel.TransactionCreateRequest,
) (*rmodel.TransactionCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
req := dbtypes.TransactionCreateRequest{
OwnerId: request.OwnerId,
Amount: request.Amount,
Currency: request.Currency,
BankAccountId: request.BankAccountId,
RequestId: request.RequestId,
}
if request.Payload != nil {
req.Payload = &dbtypes.TransactionPayload{
Origin: request.Payload.Origin,
CompanyId: request.Payload.CompanyId,
CompanyName: request.Payload.CompanyName,
VacancyId: request.Payload.VacancyId,
VacancyName: request.Payload.VacancyName,
}
}
resp, err := s.dbClient.CreateTransaction(ctx, &req)
if err != nil {
return nil, fmt.Errorf("%w: error creating transaction: %v", s.handleDBError(err), err)
}
result := new(rmodel.TransactionCreateResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) GetBankAccountList(
ctx context.Context,
request *rmodel.BankAccountListGetRequest,
) (*rmodel.BankAccountListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetBankAccountList(ctx, &dbtypes.BankAccountListGetRequest{
OwnerId: request.OwnerId,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting bank accounts: %v", s.handleDBError(err), err)
}
result := new(rmodel.BankAccountListGetResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) CreateBankAccount(
ctx context.Context,
request *rmodel.BankAccountCreateRequest,
) (*rmodel.BankAccountCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.CreateBankAccount(ctx, &dbtypes.BankAccountCreateRequest{
OwnerId: request.OwnerId,
AccountNumber: request.AccountNumber,
AccountName: request.AccountName,
BankName: request.BankName,
Bik: request.Bik,
CorrespondentAccount: request.CorrespondentAccount,
IsPrimary: request.IsPrimary,
})
if err != nil {
return nil, fmt.Errorf("%w: error creating bank account: %v", s.handleDBError(err), err)
}
result := new(rmodel.BankAccountCreateResponse).From(resp)
return result, nil
}
func (s *CrmAgentService) UpdateBankAccount(
ctx context.Context,
request *rmodel.BankAccountUpdateRequest,
) (*rmodel.BankAccountUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateBankAccount(ctx, &dbtypes.BankAccountUpdateRequest{
Id: request.Id,
AccountNumber: request.AccountNumber,
AccountName: request.AccountName,
BankName: request.BankName,
Bik: request.Bik,
CorrespondentAccount: request.CorrespondentAccount,
IsPrimary: request.IsPrimary,
}); err != nil {
return nil, fmt.Errorf("%w: error updating bank account: %v", s.handleDBError(err), err)
}
return &rmodel.BankAccountUpdateResponse{}, nil
}
func (s *CrmAgentService) DeleteBankAccount(
ctx context.Context,
request *rmodel.BankAccountDeleteRequest,
) (*rmodel.BankAccountDeleteResponse, error) {
return nil, ErrUnimplemented
}
func (s *CrmAgentService) handleDBError(err error) error {
switch {
case errors.Is(err, dberrors.ErrBadRequest):
return ErrBadRequest
case errors.Is(err, dberrors.ErrForbidden):
return ErrForbidden
case errors.Is(err, dberrors.ErrNotFound):
return ErrNotFound
case errors.Is(err, dberrors.ErrInternal):
return ErrInternal
default:
return ErrInternal
}
}

View File

@@ -0,0 +1,10 @@
package agent
type (
Type uint
Config any
)
const (
CrmAgentServiceType Type = iota
)

View File

@@ -0,0 +1,15 @@
package agent
import "errors"
var (
ErrUnknownAgentServiceType = errors.New("unknown agent service type")
ErrInvalidConfig = errors.New("invalid config")
ErrBadRequest = errors.New("bad request")
ErrForbidden = errors.New("forbidden")
ErrNotFound = errors.New("not found")
ErrInternal = errors.New("internal error")
ErrUnimplemented = errors.New("unimplemented")
)

View File

@@ -0,0 +1,159 @@
package authinfra
import (
"context"
"crypto/rand"
"errors"
"fmt"
"math/big"
"time"
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
"github.com/google/uuid"
)
const (
passwordResetOTPTTL = time.Duration(5 * time.Minute)
passwordResetTokenTTL = time.Duration(1 * time.Hour)
)
type CacheAuthInfraServiceConfig struct {
CacheClient cache.Client
}
type CacheAuthInfraService struct {
cacheClient cache.Client
}
func newCacheAuthInfraService(cfg CacheAuthInfraServiceConfig) *CacheAuthInfraService {
return &CacheAuthInfraService{
cacheClient: cfg.CacheClient,
}
}
func (s *CacheAuthInfraService) generatePasswordResetOTP(size int) (string, error) {
const digits = "0123456789"
otp := make([]byte, size)
for i := range size {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(digits))))
if err != nil {
return "", err
}
otp[i] = digits[num.Int64()]
}
return string(otp), nil
}
func (s *CacheAuthInfraService) generatePasswordResetToken() string {
return uuid.NewString()
}
func (s *CacheAuthInfraService) CreatePasswordResetOTP(
ctx context.Context,
request *PasswordResetOTPCreateRequest,
) (*PasswordResetOTPCreateResponse, error) {
if request == nil {
return nil, fmt.Errorf("%w: request is nil", ErrBadRequest)
}
if request.Email == "" {
return nil, fmt.Errorf("%w: email is required", ErrBadRequest)
}
otp, err := s.generatePasswordResetOTP(4)
if err != nil {
return nil, fmt.Errorf("%w: error generating OTP key: %v", ErrInternal, err)
}
if err := s.cacheClient.Set(ctx, request.Email, cache.PswResetOTPValueType, otp, passwordResetOTPTTL); err != nil {
return nil, fmt.Errorf("%w: error setting OTP key to cache: %v", ErrInternal, err)
}
return &PasswordResetOTPCreateResponse{
OTP: otp,
}, nil
}
func (s *CacheAuthInfraService) ValidatePasswordResetOTP(
ctx context.Context,
request *ValidatePasswordResetOTPRequest,
) (*ValidatePasswordResetOTPResponse, error) {
if request == nil {
return nil, fmt.Errorf("%w: request is nil", ErrBadRequest)
}
if request.Email == "" {
return nil, fmt.Errorf("%w: email is required", ErrBadRequest)
}
if request.OTP == "" {
return nil, fmt.Errorf("%w: OTP is required", ErrBadRequest)
}
val, err := s.cacheClient.Get(ctx, request.Email, cache.PswResetOTPValueType)
if err != nil {
if errors.Is(err, cache.ErrKeyNotFound) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("%w: error getting OTP key from cache: %w", ErrInternal, err)
}
if val != request.OTP {
return nil, ErrUnauthorized
}
if err := s.cacheClient.Del(ctx, request.Email, cache.PswResetOTPValueType); err != nil {
return nil, fmt.Errorf("%w: error deleting OTP key from cache: %v", ErrInternal, err)
}
token := s.generatePasswordResetToken()
if err := s.cacheClient.Set(ctx, request.Email, cache.PswResetTokenValueType, token, passwordResetTokenTTL); err != nil {
return nil, fmt.Errorf("%w: error setting token key to cache: %v", ErrInternal, err)
}
return &ValidatePasswordResetOTPResponse{
Token: token,
}, nil
}
func (s *CacheAuthInfraService) ValidatePasswordResetToken(
ctx context.Context,
request *ValidatePasswordResetTokenRequest,
) (*ValidatePasswordResetTokenResponse, error) {
if request == nil {
return nil, fmt.Errorf("%w: request is nil", ErrBadRequest)
}
if request.Email == "" {
return nil, fmt.Errorf("%w: email is required", ErrBadRequest)
}
if request.Token == "" {
return nil, fmt.Errorf("%w: token is required", ErrBadRequest)
}
val, err := s.cacheClient.Get(ctx, request.Email, cache.PswResetTokenValueType)
if err != nil {
if errors.Is(err, cache.ErrKeyNotFound) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("%w: error getting token key from cache: %w", ErrInternal, err)
}
if val != request.Token {
return nil, ErrUnauthorized
}
if err := s.cacheClient.Del(ctx, request.Email, cache.PswResetTokenValueType); err != nil {
return nil, fmt.Errorf("%w: error deleting token key from cache: %v", ErrInternal, err)
}
return &ValidatePasswordResetTokenResponse{}, nil
}

View File

@@ -0,0 +1,24 @@
package authinfra
import "context"
type AuthInfrastructureService interface {
CreatePasswordResetOTP(ctx context.Context, request *PasswordResetOTPCreateRequest) (*PasswordResetOTPCreateResponse, error)
ValidatePasswordResetOTP(ctx context.Context, request *ValidatePasswordResetOTPRequest) (*ValidatePasswordResetOTPResponse, error)
ValidatePasswordResetToken(ctx context.Context, request *ValidatePasswordResetTokenRequest) (*ValidatePasswordResetTokenResponse, error)
}
func New(typ Type, cfg Config) (AuthInfrastructureService, error) {
switch typ {
case CacheAuthInfrastructureServiceType:
config, ok := cfg.(CacheAuthInfraServiceConfig)
if !ok {
return nil, ErrInvalidConfig
}
return newCacheAuthInfraService(config), nil
default:
return nil, ErrUnknownAuthInfrastructureServiceType
}
}

View File

@@ -0,0 +1,41 @@
package authinfra
type (
PasswordResetOTPCreateRequest struct {
Email string
}
PasswordResetOTPCreateResponse struct {
OTP string
}
)
type (
ValidatePasswordResetOTPRequest struct {
Email string
OTP string
}
ValidatePasswordResetOTPResponse struct {
Token string
}
)
type (
ValidatePasswordResetTokenRequest struct {
Email string
Token string
}
ValidatePasswordResetTokenResponse struct {
}
)
type (
Type uint
Config any
)
const (
CacheAuthInfrastructureServiceType Type = iota
)

View File

@@ -0,0 +1,13 @@
package authinfra
import "errors"
var (
ErrInvalidConfig = errors.New("invalid config")
ErrUnknownAuthInfrastructureServiceType = errors.New("unknown auth infrastructure service type")
ErrBadRequest = errors.New("bad request")
ErrUnauthorized = errors.New("unauthorized")
ErrNotFound = errors.New("not found")
ErrInternal = errors.New("internal error")
)

View File

@@ -0,0 +1,52 @@
package distributor
import (
"context"
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
)
type DistributorService interface {
GetVacancyList(ctx context.Context, request *rmodel.VacancyListGetRequest) (*rmodel.VacancyListGetResponse, error)
CreateVacancy(ctx context.Context, request *rmodel.VacancyCreateRequest) (*rmodel.VacancyCreateResponse, error)
UpdateVacancy(ctx context.Context, request *rmodel.VacancyUpdateRequest) (*rmodel.VacancyUpdateResponse, error)
DeleteVacancy(ctx context.Context, request *rmodel.VacancyDeleteRequest) (*rmodel.VacancyDeleteResponse, error)
SendVacancyToModeration(ctx context.Context, request *rmodel.SendVacancyToModerationRequest) (*rmodel.SendVacancyToModerationResponse, error)
GetSubmissionListForVacancy(ctx context.Context, request *rmodel.SubmissionListForVacancyGetRequest) (*rmodel.SubmissionListForVacancyGetResponse, error)
UpdateSubmissionStatus(ctx context.Context, request *rmodel.SubmissionStatusUpdateRequest) (*rmodel.SubmissionStatusUpdateResponse, error)
GetProfile(ctx context.Context, request *rmodel.ProfileGetRequest) (*rmodel.ProfileGetResponse, error)
UpdateProfile(ctx context.Context, request *rmodel.ProfileUpdateRequest) (*rmodel.ProfileUpdateResponse, error)
GetCompanyList(ctx context.Context, request *rmodel.CompanyListGetRequest) (*rmodel.CompanyListGetResponse, error)
GetCompanyInfoById(ctx context.Context, request *rmodel.CompanyByIdGetRequest) (*rmodel.CompanyByIdGetResponse, error)
CreateCompany(ctx context.Context, request *rmodel.CompanyCreateRequest) (*rmodel.CompanyCreateResponse, error)
UpdateCompanyInfo(ctx context.Context, request *rmodel.CompanyUpdateRequest) (*rmodel.CompanyUpdateResponse, error)
AddEmployee(ctx context.Context, request *rmodel.AddDistributorCompanyMemberRequest) (*rmodel.AddDistributorCompanyMemberResponse, error)
GetBalance(ctx context.Context, request *rmodel.BalanceGetRequest) (*rmodel.BalanceGetResponse, error)
GetTransactionList(ctx context.Context, request *rmodel.TransactionListGetRequest) (*rmodel.TransactionListGetResponse, error)
CreateTransaction(ctx context.Context, request *rmodel.TransactionCreateRequest) (*rmodel.TransactionCreateResponse, error)
GetBankAccountList(ctx context.Context, request *rmodel.BankAccountListGetRequest) (*rmodel.BankAccountListGetResponse, error)
CreateBankAccount(ctx context.Context, request *rmodel.BankAccountCreateRequest) (*rmodel.BankAccountCreateResponse, error)
UpdateBankAccount(ctx context.Context, request *rmodel.BankAccountUpdateRequest) (*rmodel.BankAccountUpdateResponse, error)
DeleteBankAccount(ctx context.Context, request *rmodel.BankAccountDeleteRequest) (*rmodel.BankAccountDeleteResponse, error)
}
func New(typ Type, cfg Config) (DistributorService, error) {
switch typ {
case CrmDistributorServiceType:
config, ok := cfg.(CrmDistributorServiceConfig)
if !ok {
return nil, ErrInvalidConfig
}
return newCrmDistributorService(config), nil
default:
return nil, ErrUnknownDistributorServiceType
}
}

View File

@@ -0,0 +1,535 @@
package distributor
import (
"context"
"errors"
"fmt"
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database"
dberrors "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/errors"
dbtypes "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database/types"
rmodel "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/request_model"
)
type CrmDistributorService struct {
dbClient database.Client
}
type CrmDistributorServiceConfig struct {
DbClient database.Client
}
func newCrmDistributorService(cfg CrmDistributorServiceConfig) *CrmDistributorService {
return &CrmDistributorService{
dbClient: cfg.DbClient,
}
}
func (s *CrmDistributorService) GetVacancyList(
ctx context.Context,
request *rmodel.VacancyListGetRequest,
) (*rmodel.VacancyListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetVacancyList(ctx, &dbtypes.VacancyListGetRequest{
Filters: &dbtypes.VacancyListFilters{
DistributorId: request.Filters.DistributorId,
CompanyId: request.Filters.CompanyId,
VacancyId: request.Filters.VacancyId,
Region: request.Filters.Region,
SalaryBottom: request.Filters.SalaryBottom,
SalaryTop: request.Filters.SalaryTop,
IsArchived: request.Filters.IsArchived,
Status: dbtypes.NewVacancyStatus(request.Filters.Status.NullString()),
},
Page: request.Page,
PageSize: request.PageSize,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting vacancy list: %v", s.handleDBError(err), err)
}
result := new(rmodel.VacancyListGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) CreateVacancy(
ctx context.Context,
request *rmodel.VacancyCreateRequest,
) (*rmodel.VacancyCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.CreateVacancy(ctx, &dbtypes.VacancyCreateRequest{
CompanyId: request.CompanyId,
Name: request.Name,
Address: request.Address,
WorkFormat: request.WorkFormat,
AgentReward: request.AgentReward,
SalaryTop: request.SalaryTop,
SalaryBottom: request.SalaryBottom,
Requirements: request.Requirements,
Responsibilities: request.Responsibilities,
ExtraInfo: request.ExtraInfo,
Region: request.Region,
TargetAction: dbtypes.VacancyTargetAction{
Action: request.TargetAction.Action,
Duration: request.TargetAction.Duration,
},
RequiredCandidates: request.RequiredCandidates,
CurrentCandidates: request.CurrentCandidates,
ExtraFields: request.ExtraFields,
})
if err != nil {
return nil, fmt.Errorf("%w: error creating vacancy: %v", s.handleDBError(err), err)
}
result := new(rmodel.VacancyCreateResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) UpdateVacancy(
ctx context.Context,
request *rmodel.VacancyUpdateRequest,
) (*rmodel.VacancyUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateVacancy(ctx, &dbtypes.VacancyUpdateRequest{
Id: request.Id,
Name: request.Name,
Address: request.Address,
WorkFormat: request.WorkFormat,
AgentReward: request.AgentReward,
SalaryTop: request.SalaryTop,
SalaryBottom: request.SalaryBottom,
Requirements: request.Requirements,
Responsibilities: request.Responsibilities,
ExtraInfo: request.ExtraInfo,
Region: request.Region,
TargetAction: dbtypes.VacancyTargetActionForUpdate{
Action: request.TargetAction.Action,
Duration: request.TargetAction.Duration,
},
RequiredCandidates: request.RequiredCandidates,
ExtraFields: request.ExtraFields,
}); err != nil {
return nil, fmt.Errorf("%w: error updating vacancy: %v", s.handleDBError(err), err)
}
return &rmodel.VacancyUpdateResponse{}, nil
}
func (s *CrmDistributorService) DeleteVacancy(
ctx context.Context,
request *rmodel.VacancyDeleteRequest,
) (*rmodel.VacancyDeleteResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.DeleteVacancy(ctx, &dbtypes.VacancyDeleteRequest{
Id: request.Id,
}); err != nil {
return nil, fmt.Errorf("%w: error deleting vacancy: %v", s.handleDBError(err), err)
}
return &rmodel.VacancyDeleteResponse{}, nil
}
func (s *CrmDistributorService) SendVacancyToModeration(
ctx context.Context,
request *rmodel.SendVacancyToModerationRequest,
) (*rmodel.SendVacancyToModerationResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.SendVacancyToModeration(ctx, &dbtypes.SendVacancyToModerationRequest{
Id: request.Id,
}); err != nil {
return nil, fmt.Errorf("%w: error sending vacancy to moderation: %v", s.handleDBError(err), err)
}
return &rmodel.SendVacancyToModerationResponse{}, nil
}
func (s *CrmDistributorService) GetSubmissionListForVacancy(
ctx context.Context,
request *rmodel.SubmissionListForVacancyGetRequest,
) (*rmodel.SubmissionListForVacancyGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
req := dbtypes.SubmissionListGetRequest{
Filters: &dbtypes.SubmissionListGetFilters{
VacancyId: &request.VacancyId,
},
Page: request.Page,
PageSize: request.PageSize,
}
if request.Filters != nil {
req.Filters.Status = dbtypes.NewSubmissionStatus(request.Filters.Status.NullString())
}
resp, err := s.dbClient.GetSubmissionList(ctx, &req)
if err != nil {
return nil, fmt.Errorf("%w: error getting submission list: %v", s.handleDBError(err), err)
}
result := new(rmodel.SubmissionListForVacancyGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) UpdateSubmissionStatus(
ctx context.Context,
request *rmodel.SubmissionStatusUpdateRequest,
) (*rmodel.SubmissionStatusUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateSubmissionStatus(ctx, &dbtypes.SubmissionStatusUpdateRequest{
Id: request.Id,
Status: *dbtypes.NewSubmissionStatus(request.Status.NullString()),
}); err != nil {
return nil, fmt.Errorf("%w: error updating submission status: %v", s.handleDBError(err), err)
}
return &rmodel.SubmissionStatusUpdateResponse{}, nil
}
func (s *CrmDistributorService) GetProfile(
ctx context.Context,
request *rmodel.ProfileGetRequest,
) (*rmodel.ProfileGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetProfileById(ctx, &dbtypes.ProfileGetRequest{
Id: request.Id,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting user info: %v", ErrInternal, err)
}
result := new(rmodel.ProfileGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) UpdateProfile(
ctx context.Context,
request *rmodel.ProfileUpdateRequest,
) (*rmodel.ProfileUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateProfile(ctx, &dbtypes.ProfileUpdateRequest{
Id: request.Id,
Name: request.Name,
PhoneNumber: request.PhoneNumber,
Email: request.Email,
}); err != nil {
return nil, fmt.Errorf("%w: error updating profile: %v", s.handleDBError(err), err)
}
return &rmodel.ProfileUpdateResponse{}, nil
}
func (s *CrmDistributorService) GetCompanyList(
ctx context.Context,
request *rmodel.CompanyListGetRequest,
) (*rmodel.CompanyListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetCompanyList(ctx, &dbtypes.CompanyListGetRequest{
Id: request.Id,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting company list: %v", s.handleDBError(err), err)
}
result := new(rmodel.CompanyListGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) GetCompanyInfoById(
ctx context.Context,
request *rmodel.CompanyByIdGetRequest,
) (*rmodel.CompanyByIdGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetCompanyById(ctx, &dbtypes.CompanyByIdGetRequest{
Id: request.UserId,
CompanyId: request.CompanyId,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting company info: %v", s.handleDBError(err), err)
}
result := new(rmodel.CompanyByIdGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) CreateCompany(
ctx context.Context,
request *rmodel.CompanyCreateRequest,
) (*rmodel.CompanyCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.CreateCompany(ctx, &dbtypes.CompanyCreateRequest{
OwnerId: request.OwnerId,
Name: request.Name,
LegalPerson: request.LegalPerson,
Description: request.Description,
Website: request.Website,
PhysicalAddress: request.PhysicalAddress,
LegalAddress: request.LegalAddress,
Inn: request.Inn,
Kpp: request.Kpp,
Staff: request.Staff,
Metadata: request.Metadata,
ExtraFieldsTemplate: request.ExtraFieldsTemplate,
})
if err != nil {
return nil, fmt.Errorf("%w: error creating company: %v", s.handleDBError(err), err)
}
result := new(rmodel.CompanyCreateResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) UpdateCompanyInfo(
ctx context.Context,
request *rmodel.CompanyUpdateRequest,
) (*rmodel.CompanyUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateCompany(ctx, &dbtypes.CompanyUpdateRequest{
Id: request.Id,
Name: request.Name,
LegalPerson: request.LegalPerson,
Description: request.Description,
Website: request.Website,
PhysicalAddress: request.PhysicalAddress,
LegalAddress: request.LegalAddress,
Inn: request.Inn,
Kpp: request.Kpp,
Staff: request.Staff,
Metadata: request.Metadata,
ExtraFields: request.ExtraFields,
}); err != nil {
return nil, fmt.Errorf("%w: error updating company info: %v", s.handleDBError(err), err)
}
return &rmodel.CompanyUpdateResponse{}, nil
}
func (s *CrmDistributorService) AddEmployee(
ctx context.Context,
request *rmodel.AddDistributorCompanyMemberRequest,
) (*rmodel.AddDistributorCompanyMemberResponse, error) {
return nil, ErrUnimplemented
}
func (s *CrmDistributorService) GetBalance(
ctx context.Context,
request *rmodel.BalanceGetRequest,
) (*rmodel.BalanceGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetBalance(ctx, &dbtypes.BalanceGetRequest{
OwnerId: request.OwnerId,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting balance: %v", s.handleDBError(err), err)
}
result := new(rmodel.BalanceGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) GetTransactionList(
ctx context.Context,
request *rmodel.TransactionListGetRequest,
) (*rmodel.TransactionListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
req := dbtypes.TransactionListGetRequest{
OwnerId: request.OwnerId,
Page: request.Page,
PageSize: request.PageSize,
}
if request.Filters != nil {
req.Filters = new(dbtypes.TransactionListFilters)
req.Filters.Type = dbtypes.NewTransactionType(request.Filters.Type.NullString())
req.Filters.Status = dbtypes.NewTransactionStatus(request.Filters.Status.NullString())
req.Filters.BankAccountId = request.Filters.BankAccountId
}
resp, err := s.dbClient.GetTransactionList(ctx, &req)
if err != nil {
return nil, fmt.Errorf("%w: error getting transactions: %v", s.handleDBError(err), err)
}
result := new(rmodel.TransactionListGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) CreateTransaction(
ctx context.Context,
request *rmodel.TransactionCreateRequest,
) (*rmodel.TransactionCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
req := dbtypes.TransactionCreateRequest{
OwnerId: request.OwnerId,
Amount: request.Amount,
Currency: request.Currency,
BankAccountId: request.BankAccountId,
RequestId: request.RequestId,
}
if request.Payload != nil {
req.Payload = &dbtypes.TransactionPayload{
Origin: request.Payload.Origin,
CompanyId: request.Payload.CompanyId,
CompanyName: request.Payload.CompanyName,
VacancyId: request.Payload.VacancyId,
VacancyName: request.Payload.VacancyName,
}
}
resp, err := s.dbClient.CreateTransaction(ctx, &req)
if err != nil {
return nil, fmt.Errorf("%w: error creating transaction: %v", s.handleDBError(err), err)
}
result := new(rmodel.TransactionCreateResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) GetBankAccountList(
ctx context.Context,
request *rmodel.BankAccountListGetRequest,
) (*rmodel.BankAccountListGetResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.GetBankAccountList(ctx, &dbtypes.BankAccountListGetRequest{
OwnerId: request.OwnerId,
})
if err != nil {
return nil, fmt.Errorf("%w: error getting bank accounts: %v", s.handleDBError(err), err)
}
result := new(rmodel.BankAccountListGetResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) CreateBankAccount(
ctx context.Context,
request *rmodel.BankAccountCreateRequest,
) (*rmodel.BankAccountCreateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
resp, err := s.dbClient.CreateBankAccount(ctx, &dbtypes.BankAccountCreateRequest{
OwnerId: request.OwnerId,
AccountNumber: request.AccountNumber,
AccountName: request.AccountName,
BankName: request.BankName,
Bik: request.Bik,
CorrespondentAccount: request.CorrespondentAccount,
IsPrimary: request.IsPrimary,
})
if err != nil {
return nil, fmt.Errorf("%w: error creating bank account: %v", s.handleDBError(err), err)
}
result := new(rmodel.BankAccountCreateResponse).From(resp)
return result, nil
}
func (s *CrmDistributorService) UpdateBankAccount(
ctx context.Context,
request *rmodel.BankAccountUpdateRequest,
) (*rmodel.BankAccountUpdateResponse, error) {
if err := request.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrBadRequest, err)
}
if _, err := s.dbClient.UpdateBankAccount(ctx, &dbtypes.BankAccountUpdateRequest{
Id: request.Id,
AccountNumber: request.AccountNumber,
AccountName: request.AccountName,
BankName: request.BankName,
Bik: request.Bik,
CorrespondentAccount: request.CorrespondentAccount,
IsPrimary: request.IsPrimary,
}); err != nil {
return nil, fmt.Errorf("%w: error updating bank account: %v", s.handleDBError(err), err)
}
return &rmodel.BankAccountUpdateResponse{}, nil
}
func (s *CrmDistributorService) DeleteBankAccount(
ctx context.Context,
request *rmodel.BankAccountDeleteRequest,
) (*rmodel.BankAccountDeleteResponse, error) {
return nil, ErrUnimplemented
}
func (s *CrmDistributorService) handleDBError(err error) error {
switch {
case errors.Is(err, dberrors.ErrBadRequest):
return ErrBadRequest
case errors.Is(err, dberrors.ErrForbidden):
return ErrForbidden
case errors.Is(err, dberrors.ErrNotFound):
return ErrNotFound
case errors.Is(err, dberrors.ErrInternal):
return ErrInternal
default:
return ErrInternal
}
}

View File

@@ -0,0 +1,10 @@
package distributor
type (
Type uint
Config any
)
const (
CrmDistributorServiceType Type = iota
)

View File

@@ -0,0 +1,15 @@
package distributor
import "errors"
var (
ErrUnknownDistributorServiceType = errors.New("unknown distributor service type")
ErrInvalidConfig = errors.New("invalid config")
ErrBadRequest = errors.New("bad request")
ErrForbidden = errors.New("forbidden")
ErrNotFound = errors.New("not found")
ErrInternal = errors.New("internal error")
ErrUnimplemented = errors.New("unimplemented")
)