1
This commit is contained in:
100
internal/database/postgres/balance.go
Normal file
100
internal/database/postgres/balance.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
func (c *client) GetBalance(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BalanceGetRequest,
|
||||
) (*dbtypes.BalanceGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
balancesTable = fmt.Sprintf("%s.%s", c.config.Schema, BalancesTableName)
|
||||
)
|
||||
|
||||
getBalance := psql.Select(
|
||||
"raw_balance", "clean_balance",
|
||||
).From(balancesTable).
|
||||
Where(squirrel.Eq{"owner_id": request.OwnerId})
|
||||
|
||||
query, args, err := getBalance.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get balance query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := c.db.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
balance dbtypes.Balance
|
||||
)
|
||||
|
||||
if err := row.Scan(&balance.RawBalance, &balance.CleanBalance); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return &dbtypes.BalanceGetResponse{
|
||||
Balance: &balance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) createBalance(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.BalanceCreateRequest,
|
||||
) error {
|
||||
if request == nil {
|
||||
return fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
balancesTable = fmt.Sprintf("%s.%s", c.config.Schema, BalancesTableName)
|
||||
)
|
||||
|
||||
createBalance := psql.Insert(balancesTable).
|
||||
Columns(
|
||||
"id", "owner_id", "raw_balance", "clean_balance",
|
||||
).
|
||||
Values(
|
||||
request.Id, request.OwnerId, request.RawBalance, request.CleanBalance,
|
||||
)
|
||||
|
||||
query, args, err := createBalance.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'create balance' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing 'create balance' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for 'create balance' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
313
internal/database/postgres/bank_account.go
Normal file
313
internal/database/postgres/bank_account.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ALERT: intergrate safety checks
|
||||
|
||||
func (c *client) GetBankAccountList(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountListGetRequest,
|
||||
) (*dbtypes.BankAccountListGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
getAccounts := psql.Select(
|
||||
"id", "owner_id", "account_number", "bank_name", "bik",
|
||||
"correspondent_account", "is_primary", "created_at", "updated_at",
|
||||
).From(bankAccountsTable).
|
||||
Where(squirrel.Eq{"owner_id": request.OwnerId})
|
||||
|
||||
query, args, err := getAccounts.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get bank accounts query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rows, err := c.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing get bank accounts query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var accounts []dbtypes.BankAccount
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
accountNumber, bankName, bik, correspondentAccount sql.NullString
|
||||
bankAccount dbtypes.BankAccount
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&bankAccount.Id, &bankAccount.OwnerId, &accountNumber, &bankName, &bik,
|
||||
&correspondentAccount, &bankAccount.IsPrimary, &bankAccount.CreatedAt, &bankAccount.UpdatedAt,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if accountNumber.Valid {
|
||||
bankAccount.AccountName = accountNumber.String
|
||||
}
|
||||
|
||||
if bankName.Valid {
|
||||
bankAccount.BankName = bankName.String
|
||||
}
|
||||
|
||||
if bik.Valid {
|
||||
bankAccount.Bik = bik.String
|
||||
}
|
||||
|
||||
if correspondentAccount.Valid {
|
||||
bankAccount.CorrespondentAccount = correspondentAccount.String
|
||||
}
|
||||
|
||||
accounts = append(accounts, bankAccount)
|
||||
}
|
||||
|
||||
return &dbtypes.BankAccountListGetResponse{
|
||||
BankAccounts: accounts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) CreateBankAccount(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountCreateRequest,
|
||||
) (*dbtypes.BankAccountCreateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
// TODO: use normal uuid after DB reengineering
|
||||
bankAccountId := fmt.Sprintf("%sBNK", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
|
||||
createBankAccount := psql.Insert(bankAccountsTable).
|
||||
Columns(
|
||||
"id", "owner_id", "account_number", "bank_name", "bik", "correspondent_account", "is_primary",
|
||||
).
|
||||
Values(
|
||||
bankAccountId, request.OwnerId, request.AccountNumber, request.BankName,
|
||||
request.Bik, request.CorrespondentAccount, request.IsPrimary,
|
||||
)
|
||||
|
||||
query, args, err := createBankAccount.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building create bank account query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing create bank account query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for create bank account query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.BankAccountCreateResponse{
|
||||
Id: bankAccountId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) UpdateBankAccount(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountUpdateRequest,
|
||||
) (*dbtypes.BankAccountUpdateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
result, err := c.updateBankAccount(ctx, tx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating bank account: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("%w: error committing transaction: %w", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *client) updateBankAccount(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.BankAccountUpdateRequest,
|
||||
) (*dbtypes.BankAccountUpdateResponse, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
updateAccount := psql.Update(bankAccountsTable).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
if request.AccountNumber != nil {
|
||||
updateAccount = updateAccount.Set("account_number", *request.AccountNumber)
|
||||
}
|
||||
|
||||
// TODO: uncomment when DB supports it
|
||||
// if request.AccountName != nil {
|
||||
// updateAccount = updateAccount.Set("account_name", *request.AccountName)
|
||||
// }
|
||||
|
||||
if request.BankName != nil {
|
||||
updateAccount = updateAccount.Set("bank_name", *request.BankName)
|
||||
}
|
||||
|
||||
if request.Bik != nil {
|
||||
updateAccount = updateAccount.Set("bik", *request.Bik)
|
||||
}
|
||||
|
||||
if request.CorrespondentAccount != nil {
|
||||
updateAccount = updateAccount.Set("correspondent_account", *request.CorrespondentAccount)
|
||||
}
|
||||
|
||||
if request.IsPrimary != nil {
|
||||
if *request.IsPrimary {
|
||||
if err := c.unmarkPrimaryBankAccount(ctx, driver, request.Id); err != nil {
|
||||
return nil, fmt.Errorf("error unmarking currently primary bank account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// QnA: what if the update makes all BAs non-primary?
|
||||
updateAccount = updateAccount.Set("is_primary", *request.IsPrimary)
|
||||
}
|
||||
|
||||
query, args, err := updateAccount.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building 'update bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing 'update bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for 'update bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return &dbtypes.BankAccountUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *client) unmarkPrimaryBankAccount(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
bankAccountId string,
|
||||
) error {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTable = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
getOwnerId := psql.Select(
|
||||
"owner_id", "is_primary",
|
||||
).From(bankAccountsTable).
|
||||
Where(squirrel.Eq{"id": bankAccountId}).
|
||||
Suffix("FOR UPDATE")
|
||||
|
||||
query, args, err := getOwnerId.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'get owner id of bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
ownerId string
|
||||
isPrimary bool
|
||||
)
|
||||
|
||||
if err := row.Scan(&ownerId, &isPrimary); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: error scanning row for 'get owner id of bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if isPrimary {
|
||||
return nil
|
||||
}
|
||||
|
||||
unmarkPrimaryBA := psql.Update(bankAccountsTable).
|
||||
Set("is_primary", false).
|
||||
Where(squirrel.Eq{
|
||||
"owner_id": ownerId,
|
||||
"is_primary": true,
|
||||
})
|
||||
|
||||
query, args, err = unmarkPrimaryBA.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for 'unmark primary bank account' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return fmt.Errorf("%w: error unmarking primary bank account: no rows affected", dberrors.ErrInternal)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) DeleteBankAccount(
|
||||
ctx context.Context,
|
||||
request *dbtypes.BankAccountDeleteRequest,
|
||||
) (*dbtypes.BankAccountDeleteResponse, error) {
|
||||
return nil, dberrors.ErrUnimplemented
|
||||
}
|
107
internal/database/postgres/client.go
Normal file
107
internal/database/postgres/client.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Интерфейс передается в неэкспортируемые функции
|
||||
// нужен для того, чтобы не зависило от того, что передаём
|
||||
// транзацию или обычное соединение
|
||||
type Driver interface {
|
||||
// QueryContext executes a query that returns rows, typically a SELECT.
|
||||
// The args are for any placeholder parameters in the query.
|
||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||||
|
||||
// QueryRowContext executes a query that is expected to return at most one row.
|
||||
// QueryRowContext always returns a non-nil value. Errors are deferred until
|
||||
// [Row]'s Scan method is called.
|
||||
// If the query selects no rows, the [*Row.Scan] will return [ErrNoRows].
|
||||
// Otherwise, [*Row.Scan] scans the first selected row and discards
|
||||
// the rest.
|
||||
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
|
||||
|
||||
// ExecContext executes a query without returning any rows.
|
||||
// The args are for any placeholder parameters in the query.
|
||||
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
|
||||
}
|
||||
|
||||
type PostgresConfig struct {
|
||||
Host string
|
||||
Port uint16
|
||||
Username string
|
||||
Password string
|
||||
Database string
|
||||
Schema string
|
||||
SSLMode string
|
||||
SSLRootCert string
|
||||
}
|
||||
|
||||
type client struct {
|
||||
config PostgresConfig
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewClient(cfg PostgresConfig) (*client, error) {
|
||||
rootCertPool := x509.NewCertPool()
|
||||
|
||||
caCert, err := os.ReadFile(cfg.SSLRootCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read CA cert: %w", err)
|
||||
}
|
||||
|
||||
if ok := rootCertPool.AppendCertsFromPEM(caCert); !ok {
|
||||
return nil, fmt.Errorf("failed to append CA cert to pool")
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: rootCertPool,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
//nolint:gosec // TODO: set server name
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
config, err := pgx.ParseConfig("")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse [empty] config: %w", err)
|
||||
}
|
||||
|
||||
config.Host = cfg.Host
|
||||
config.Port = cfg.Port
|
||||
config.Database = cfg.Database
|
||||
config.User = cfg.Username
|
||||
config.Password = cfg.Password
|
||||
config.TLSConfig = tlsConfig
|
||||
config.RuntimeParams = map[string]string{"sslmode": cfg.SSLMode}
|
||||
|
||||
db := sqlx.NewDb(stdlib.OpenDB(*config), "pgx")
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("failed to ping postgres: %w", err)
|
||||
}
|
||||
|
||||
return &client{
|
||||
config: cfg,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func countOffset(page, pageSize uint64) uint64 {
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
if pageSize == 0 {
|
||||
pageSize = DefaultPaginationPageSize
|
||||
}
|
||||
|
||||
return (page - 1) * pageSize
|
||||
}
|
506
internal/database/postgres/company.go
Normal file
506
internal/database/postgres/company.go
Normal file
@@ -0,0 +1,506 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
func CompanyModerationStatusIdToString(status int32) dbtypes.CompanyModerationStatus {
|
||||
switch status {
|
||||
case 0:
|
||||
return dbtypes.CompanyModerationStatusPending
|
||||
case 1:
|
||||
return dbtypes.CompanyModerationStatusApproved
|
||||
case 2:
|
||||
return dbtypes.CompanyModerationStatusRejected
|
||||
case 3:
|
||||
return dbtypes.CompanyModerationStatusNew
|
||||
default:
|
||||
return dbtypes.CompanyModerationStatusNew
|
||||
}
|
||||
}
|
||||
|
||||
func CompanyModerationStatusStringToId(status dbtypes.CompanyModerationStatus) int32 {
|
||||
switch status {
|
||||
case dbtypes.CompanyModerationStatusPending:
|
||||
return 0
|
||||
case dbtypes.CompanyModerationStatusApproved:
|
||||
return 1
|
||||
case dbtypes.CompanyModerationStatusRejected:
|
||||
return 2
|
||||
case dbtypes.CompanyModerationStatusNew:
|
||||
return 3
|
||||
default:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocognit // TODO: refactor
|
||||
func (c *client) GetCompanyList(
|
||||
ctx context.Context,
|
||||
request *dbtypes.CompanyListGetRequest,
|
||||
) (*dbtypes.CompanyListGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
companiesTable = fmt.Sprintf("%s.%s", c.config.Schema, CompaniesTableName)
|
||||
)
|
||||
|
||||
getComList := psql.Select(
|
||||
"id", "uid", "name", "legal_person", "description", "website",
|
||||
"physical_address", "legal_address", "inn", "is_active", // TODO: add KPP when DB supports it
|
||||
"has_moderation_ticket", "staff", "metadata", "additional_fields_tmpl",
|
||||
).
|
||||
From(companiesTable).
|
||||
Where(squirrel.Eq{"uid": request.Id})
|
||||
|
||||
query, args, err := getComList.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get distributor company list query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rows, err := c.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing get distributor company list query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var res dbtypes.CompanyListGetResponse
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
name, legalPerson, description, website, physicalAddress sql.NullString
|
||||
legalAddress, inn, metadata, additionalFieldsTmpl sql.NullString
|
||||
staff pq.StringArray
|
||||
company dbtypes.Company
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&company.Id, &company.OwnerId, &name, &legalPerson, &description, &website,
|
||||
&physicalAddress, &legalAddress, &inn, &company.IsActive,
|
||||
&company.HasModerationTicket, &staff, &metadata, &additionalFieldsTmpl,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if name.Valid {
|
||||
company.Name = &name.String
|
||||
}
|
||||
|
||||
if legalPerson.Valid {
|
||||
company.LegalPerson = &legalPerson.String
|
||||
}
|
||||
|
||||
if description.Valid {
|
||||
company.Description = &description.String
|
||||
}
|
||||
|
||||
if website.Valid {
|
||||
company.Website = &website.String
|
||||
}
|
||||
|
||||
if physicalAddress.Valid {
|
||||
company.PhysicalAddress = &physicalAddress.String
|
||||
}
|
||||
|
||||
if legalAddress.Valid {
|
||||
company.LegalAddress = &legalAddress.String
|
||||
}
|
||||
|
||||
if inn.Valid {
|
||||
company.Inn = &inn.String
|
||||
}
|
||||
|
||||
company.Staff = staff
|
||||
|
||||
if metadata.Valid {
|
||||
company.Metadata = &metadata.String
|
||||
}
|
||||
|
||||
if additionalFieldsTmpl.Valid {
|
||||
company.ExtraFieldsTemplate = &additionalFieldsTmpl.String
|
||||
}
|
||||
|
||||
res.Companies = append(res.Companies, company)
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (c *client) GetCompanyById(
|
||||
ctx context.Context,
|
||||
request *dbtypes.CompanyByIdGetRequest,
|
||||
) (*dbtypes.CompanyByIdGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
companiesTable = fmt.Sprintf("%s.%s", c.config.Schema, CompaniesTableName)
|
||||
)
|
||||
|
||||
getComList := psql.Select(
|
||||
"id", "uid", "name", "legal_person", "description", "website",
|
||||
"physical_address", "legal_address", "inn", "is_active", // TODO: add KPP when DB supports it
|
||||
"has_moderation_ticket", "staff", "metadata", "additional_fields_tmpl",
|
||||
).
|
||||
From(companiesTable).
|
||||
Where(squirrel.And{
|
||||
squirrel.Eq{"id": request.CompanyId},
|
||||
squirrel.Eq{"uid": request.Id},
|
||||
})
|
||||
|
||||
query, args, err := getComList.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get distributor company list query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := c.db.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
name, legalPerson, description, website, physicalAddress sql.NullString
|
||||
legalAddress, inn, metadata, additionalFieldsTmpl sql.NullString
|
||||
staff pq.StringArray
|
||||
company dbtypes.Company
|
||||
)
|
||||
|
||||
if err := row.Scan(
|
||||
&company.Id, &company.OwnerId, &name, &legalPerson, &description, &website,
|
||||
&physicalAddress, &legalAddress, &inn, &company.IsActive,
|
||||
&company.HasModerationTicket, &staff, &metadata, &additionalFieldsTmpl,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if name.Valid {
|
||||
company.Name = &name.String
|
||||
}
|
||||
|
||||
if legalPerson.Valid {
|
||||
company.LegalPerson = &legalPerson.String
|
||||
}
|
||||
|
||||
if description.Valid {
|
||||
company.Description = &description.String
|
||||
}
|
||||
|
||||
if website.Valid {
|
||||
company.Website = &website.String
|
||||
}
|
||||
|
||||
if physicalAddress.Valid {
|
||||
company.PhysicalAddress = &physicalAddress.String
|
||||
}
|
||||
|
||||
if legalAddress.Valid {
|
||||
company.LegalAddress = &legalAddress.String
|
||||
}
|
||||
|
||||
if inn.Valid {
|
||||
company.Inn = &inn.String
|
||||
}
|
||||
|
||||
company.Staff = staff
|
||||
|
||||
if metadata.Valid {
|
||||
company.Metadata = &metadata.String
|
||||
}
|
||||
|
||||
if additionalFieldsTmpl.Valid {
|
||||
company.ExtraFieldsTemplate = &additionalFieldsTmpl.String
|
||||
}
|
||||
|
||||
return &dbtypes.CompanyByIdGetResponse{Company: company}, nil
|
||||
}
|
||||
|
||||
func (c *client) CreateCompany(
|
||||
ctx context.Context,
|
||||
request *dbtypes.CompanyCreateRequest,
|
||||
) (*dbtypes.CompanyCreateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
res, err := c.createCompany(ctx, tx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating company: %w", err)
|
||||
}
|
||||
|
||||
if err := c.createCompanyValidationTicket(ctx, tx, res.Id, request); err != nil {
|
||||
return nil, fmt.Errorf("error creating company validation ticket: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("error committing transaction: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *client) createCompany(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.CompanyCreateRequest,
|
||||
) (*dbtypes.CompanyCreateResponse, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
companyTable = fmt.Sprintf("%s.%s", c.config.Schema, CompaniesTableName)
|
||||
)
|
||||
|
||||
// TODO: use normal uuid after DB reengineering
|
||||
comId := fmt.Sprintf("%sCOM", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
|
||||
createCompany := psql.Insert(companyTable).
|
||||
Columns(
|
||||
"id", "uid", "is_active", "has_moderation_ticket", "metadata", "additional_fields_tmpl",
|
||||
).
|
||||
Values(
|
||||
comId, request.OwnerId, false, true, request.Metadata, request.ExtraFieldsTemplate,
|
||||
)
|
||||
|
||||
query, args, err := createCompany.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building create company query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing create company query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for create company query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.CompanyCreateResponse{Id: comId}, nil
|
||||
}
|
||||
|
||||
func (c *client) createCompanyValidationTicket(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
companyId string,
|
||||
request *dbtypes.CompanyCreateRequest,
|
||||
) error {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
comValTable = fmt.Sprintf("%s.%s", c.config.Schema, CompanyValidationTicketsTableName)
|
||||
)
|
||||
|
||||
var (
|
||||
ticketId = fmt.Sprintf("%sTCK", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
)
|
||||
|
||||
createCompany := psql.Insert(comValTable).
|
||||
Columns(
|
||||
"id", "company_id", "name", "legal_person", "description", "website",
|
||||
"physical_address", "legal_address", "inn", // TODO: add KPP when DB supports it
|
||||
"staff", "status",
|
||||
).
|
||||
Values(
|
||||
ticketId, companyId, request.Name, request.LegalPerson, request.Description, request.Website,
|
||||
request.PhysicalAddress, request.LegalAddress, request.Inn,
|
||||
request.Staff, CompanyModerationStatusStringToId(dbtypes.CompanyModerationStatusPending), // TODO: switch to status "NEW"
|
||||
)
|
||||
|
||||
query, args, err := createCompany.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building create company moderation ticket query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing create company moderation ticket query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for create company moderation ticket query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) UpdateCompany(
|
||||
ctx context.Context,
|
||||
request *dbtypes.CompanyUpdateRequest,
|
||||
) (*dbtypes.CompanyUpdateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
res, err := c.updateCompany(ctx, tx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating company: %w", err)
|
||||
}
|
||||
|
||||
if err := c.updateCompanyValidationTicket(ctx, tx, request.Id, request); err != nil {
|
||||
return nil, fmt.Errorf("error updating company validation ticket: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("error committing transaction: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *client) updateCompany(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.CompanyUpdateRequest,
|
||||
) (*dbtypes.CompanyUpdateResponse, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
companyTable = fmt.Sprintf("%s.%s", c.config.Schema, CompaniesTableName)
|
||||
)
|
||||
|
||||
updateCompany := psql.Update(companyTable).
|
||||
SetMap(map[string]any{
|
||||
"is_active": false,
|
||||
"has_moderation_ticket": true,
|
||||
"metadata": request.Metadata,
|
||||
"additional_fields_tmpl": request.ExtraFields,
|
||||
}).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
query, args, err := updateCompany.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building update company query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing update company query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for update company query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.CompanyUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
// NOTE: do we believe that every company has a moderation ticket?
|
||||
func (c *client) updateCompanyValidationTicket(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
companyId string,
|
||||
request *dbtypes.CompanyUpdateRequest,
|
||||
) error {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
comValTable = fmt.Sprintf("%s.%s", c.config.Schema, CompanyValidationTicketsTableName)
|
||||
)
|
||||
|
||||
updateCompany := psql.Update(comValTable).
|
||||
Where(squirrel.Eq{"company_id": companyId})
|
||||
|
||||
if request.Name != nil {
|
||||
updateCompany = updateCompany.Set("name", *request.Name)
|
||||
}
|
||||
|
||||
if request.LegalPerson != nil {
|
||||
updateCompany = updateCompany.Set("legal_person", *request.LegalPerson)
|
||||
}
|
||||
|
||||
if request.Description != nil {
|
||||
updateCompany = updateCompany.Set("description", *request.Description)
|
||||
}
|
||||
|
||||
if request.Website != nil {
|
||||
updateCompany = updateCompany.Set("website", *request.Website)
|
||||
}
|
||||
|
||||
if request.PhysicalAddress != nil {
|
||||
updateCompany = updateCompany.Set("physical_address", *request.PhysicalAddress)
|
||||
}
|
||||
|
||||
if request.LegalAddress != nil {
|
||||
updateCompany = updateCompany.Set("legal_address", *request.LegalAddress)
|
||||
}
|
||||
|
||||
if request.Inn != nil {
|
||||
updateCompany = updateCompany.Set("inn", *request.Inn)
|
||||
}
|
||||
|
||||
if len(request.Staff) > 0 {
|
||||
updateCompany = updateCompany.Set("staff", request.Staff)
|
||||
}
|
||||
|
||||
query, args, err := updateCompany.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building update company moderation ticket query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing update company moderation ticket query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for update company moderation ticket query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
23
internal/database/postgres/domain.go
Normal file
23
internal/database/postgres/domain.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package pgdb
|
||||
|
||||
const (
|
||||
BalancesTableName = "balances"
|
||||
BankAccountsTableName = "company_bank_accounts"
|
||||
CompaniesTableName = "company"
|
||||
CompanyValidationTicketsTableName = "company_validation"
|
||||
SubmissionsTableName = "submission"
|
||||
TransactionsTableName = "transactions"
|
||||
VacanciesTableName = "vacancy"
|
||||
VacancyCandidatesTableName = "vacancy_candidates"
|
||||
VacancyDescriptionHistoryTableName = "vacancy_moderation_descriptions"
|
||||
UsersTableName = "client"
|
||||
UserValidationTableName = "client_validation"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPaginationPageSize = 20
|
||||
)
|
||||
|
||||
const (
|
||||
PGErrUniqueViolation = "23505"
|
||||
)
|
128
internal/database/postgres/integration.go
Normal file
128
internal/database/postgres/integration.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
func (c *client) GetVacancyIntegrationInfoById(
|
||||
ctx context.Context,
|
||||
vacancyId string,
|
||||
) (string, *dbtypes.VacancyExtraFieldsTemplate, error) {
|
||||
return c.getVacancyIntegrationInfoById(ctx, c.db, vacancyId)
|
||||
}
|
||||
|
||||
func (c *client) getVacancyIntegrationInfoById(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
vacancyId string,
|
||||
) (string, *dbtypes.VacancyExtraFieldsTemplate, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyTable = fmt.Sprintf("%s.%s", c.config.Schema, VacanciesTableName)
|
||||
)
|
||||
|
||||
getCompanyIdQuery := psql.Select("company_id", "additional_fields").
|
||||
From(vacancyTable).
|
||||
Where(squirrel.Eq{"id": vacancyId})
|
||||
|
||||
query, args, err := getCompanyIdQuery.ToSql()
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("%w: error building query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
companyId string
|
||||
fieldsString sql.NullString
|
||||
fields dbtypes.VacancyExtraFieldsTemplate
|
||||
)
|
||||
|
||||
if err := row.Scan(&companyId, &fieldsString); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return "", nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if fieldsString.Valid {
|
||||
if err := json.Unmarshal([]byte(fieldsString.String), &fields); err != nil {
|
||||
return "", nil, fmt.Errorf("%w: error unmarshalling fields: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
}
|
||||
|
||||
return companyId, &fields, nil
|
||||
}
|
||||
|
||||
func (c *client) GetCompanyMetadataById(
|
||||
ctx context.Context,
|
||||
companyId string,
|
||||
) (*dbtypes.CompanyMetadata, *dbtypes.CompanyExtraFieldsTemplate, error) {
|
||||
return c.getCompanyMetadataById(ctx, c.db, companyId)
|
||||
}
|
||||
|
||||
func (c *client) getCompanyMetadataById(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
companyId string,
|
||||
) (*dbtypes.CompanyMetadata, *dbtypes.CompanyExtraFieldsTemplate, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
companyTable = fmt.Sprintf("%s.%s", c.config.Schema, CompaniesTableName)
|
||||
)
|
||||
|
||||
getCompanyMetadataQuery := psql.Select(
|
||||
"metadata",
|
||||
"additional_fields_tmpl",
|
||||
).
|
||||
From(companyTable).
|
||||
Where(squirrel.Eq{"id": companyId})
|
||||
|
||||
query, args, err := getCompanyMetadataQuery.ToSql()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: error building query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
metadataString sql.NullString
|
||||
metadata dbtypes.CompanyMetadata
|
||||
|
||||
fieldsTemplateString sql.NullString
|
||||
fieldsTemplate dbtypes.CompanyExtraFieldsTemplate
|
||||
)
|
||||
|
||||
if err := row.Scan(&metadataString, &fieldsTemplateString); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if metadataString.Valid {
|
||||
if err := json.Unmarshal([]byte(metadataString.String), &metadata); err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: error unmarshalling metadata: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
}
|
||||
|
||||
if fieldsTemplateString.Valid {
|
||||
if err := json.Unmarshal([]byte(fieldsTemplateString.String), &fieldsTemplate); err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: error unmarshalling fields template: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
}
|
||||
|
||||
return &metadata, &fieldsTemplate, nil
|
||||
}
|
130
internal/database/postgres/profile.go
Normal file
130
internal/database/postgres/profile.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
func (c *client) GetProfileById(
|
||||
ctx context.Context,
|
||||
request *dbtypes.ProfileGetRequest,
|
||||
) (*dbtypes.ProfileGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
usersTable = fmt.Sprintf("%s.%s", c.config.Schema, UsersTableName)
|
||||
)
|
||||
|
||||
getProfile := psql.Select(
|
||||
"uid", "name", "phone", "email",
|
||||
).From(usersTable).
|
||||
Where(squirrel.Eq{"uid": request.Id})
|
||||
|
||||
query, args, err := getProfile.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get profile query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := c.db.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
name, phoneNumber, email sql.NullString
|
||||
profile dbtypes.Profile
|
||||
)
|
||||
|
||||
if err := row.Scan(&profile.Id, &name, &phoneNumber, &email); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if name.Valid {
|
||||
profile.Name = name.String
|
||||
}
|
||||
|
||||
if phoneNumber.Valid {
|
||||
profile.PhoneNumber = phoneNumber.String
|
||||
}
|
||||
|
||||
if email.Valid {
|
||||
profile.Email = email.String
|
||||
}
|
||||
|
||||
return &dbtypes.ProfileGetResponse{
|
||||
Profile: &profile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) UpdateProfile(
|
||||
ctx context.Context,
|
||||
request *dbtypes.ProfileUpdateRequest,
|
||||
) (*dbtypes.ProfileUpdateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
if request.Name == nil && request.PhoneNumber == nil && request.Email == nil {
|
||||
return nil, fmt.Errorf("%w: nothing to update", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
usersTable = fmt.Sprintf("%s.%s", c.config.Schema, UsersTableName)
|
||||
)
|
||||
|
||||
updateProfile := psql.Update(usersTable).
|
||||
Where(squirrel.Eq{"uid": request.Id})
|
||||
|
||||
if request.Name != nil {
|
||||
updateProfile = updateProfile.Set("name", *request.Name)
|
||||
}
|
||||
|
||||
if request.PhoneNumber != nil {
|
||||
updateProfile = updateProfile.Set("phone", *request.PhoneNumber)
|
||||
}
|
||||
|
||||
if request.Email != nil {
|
||||
updateProfile = updateProfile.Set("email", *request.Email)
|
||||
}
|
||||
|
||||
query, args, err := updateProfile.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building update profile query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
if pqErr, ok := err.(*pq.Error); ok {
|
||||
if pqErr.Code == PGErrUniqueViolation {
|
||||
return nil, dberrors.ErrConflict
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error executing update profile query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for update profile query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.ProfileUpdateResponse{}, nil
|
||||
}
|
494
internal/database/postgres/submission.go
Normal file
494
internal/database/postgres/submission.go
Normal file
@@ -0,0 +1,494 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func SubmissionStatusIdToString(status int32) dbtypes.SubmissionStatus {
|
||||
switch status {
|
||||
case 0:
|
||||
return dbtypes.SubStatusUnspecified
|
||||
case 1:
|
||||
return dbtypes.SubStatusNew
|
||||
case 2:
|
||||
return dbtypes.SubStatusPending
|
||||
case 3:
|
||||
return dbtypes.SubStatusOnInterview
|
||||
case 4:
|
||||
return dbtypes.SubStatusRejected
|
||||
case 5:
|
||||
return dbtypes.SubStatusCancelled
|
||||
case 6:
|
||||
return dbtypes.SubStatusApproved
|
||||
default:
|
||||
return dbtypes.SubStatusUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func SubmissionStatusStringToId(status dbtypes.SubmissionStatus) int32 {
|
||||
switch status {
|
||||
case dbtypes.SubStatusUnspecified:
|
||||
return 0
|
||||
case dbtypes.SubStatusNew:
|
||||
return 1
|
||||
case dbtypes.SubStatusPending:
|
||||
return 2
|
||||
case dbtypes.SubStatusOnInterview:
|
||||
return 3
|
||||
case dbtypes.SubStatusRejected:
|
||||
return 4
|
||||
case dbtypes.SubStatusCancelled:
|
||||
return 5
|
||||
case dbtypes.SubStatusApproved:
|
||||
return 6
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocognit // not so hard
|
||||
func (c *client) GetSubmissionList(
|
||||
ctx context.Context,
|
||||
request *dbtypes.SubmissionListGetRequest,
|
||||
) (*dbtypes.SubmissionListGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
submissionsTable = fmt.Sprintf("%s.%s", c.config.Schema, SubmissionsTableName)
|
||||
)
|
||||
|
||||
getSubmissions := psql.Select(
|
||||
"id", "uid", "vacancy_id", "submission_status_id",
|
||||
"cv", "name", "phone", "email", "birthday", "info",
|
||||
).From(submissionsTable).
|
||||
Limit(request.PageSize).
|
||||
Offset(countOffset(request.Page, request.PageSize))
|
||||
|
||||
if request.Filters != nil {
|
||||
if request.Filters.AgentId != nil {
|
||||
getSubmissions = getSubmissions.Where(squirrel.Eq{"uid": *request.Filters.AgentId})
|
||||
}
|
||||
|
||||
if request.Filters.VacancyId != nil {
|
||||
getSubmissions = getSubmissions.Where(squirrel.Eq{"vacancy_id": *request.Filters.VacancyId})
|
||||
}
|
||||
|
||||
if request.Filters.Status != nil && *request.Filters.Status != dbtypes.SubStatusUnspecified {
|
||||
getSubmissions = getSubmissions.Where(squirrel.Eq{"submission_status_id": SubmissionStatusStringToId(*request.Filters.Status)})
|
||||
}
|
||||
}
|
||||
|
||||
query, args, err := getSubmissions.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get submissions query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rows, err := c.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing get submissions query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
submissionsList := &dbtypes.SubmissionListGetResponse{
|
||||
Submissions: make([]dbtypes.Submission, 0, request.PageSize),
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
cvLink, info sql.NullString
|
||||
statusId int32
|
||||
candidateName string
|
||||
|
||||
sub = dbtypes.Submission{
|
||||
AgentInfo: new(dbtypes.AgentInfo),
|
||||
VacancyInfo: new(dbtypes.VacancyInfo),
|
||||
CandidateInfo: new(dbtypes.CandidateInfo),
|
||||
}
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&sub.Id, &sub.AgentInfo.Id, &sub.VacancyInfo.Id, &statusId,
|
||||
&cvLink, &candidateName, &sub.CandidateInfo.PhoneNumber, &sub.CandidateInfo.Email,
|
||||
&sub.CandidateInfo.Birthday, &info,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
sub.CandidateInfo.LastName, sub.CandidateInfo.FirstName, sub.CandidateInfo.MiddleName = splitName(candidateName)
|
||||
|
||||
if cvLink.Valid {
|
||||
sub.CandidateInfo.CvLink = &cvLink.String
|
||||
}
|
||||
|
||||
if info.Valid {
|
||||
sub.CandidateInfo.Resume = &info.String
|
||||
}
|
||||
|
||||
sub.Status = SubmissionStatusIdToString(statusId)
|
||||
|
||||
submissionsList.Submissions = append(submissionsList.Submissions, sub)
|
||||
}
|
||||
|
||||
return submissionsList, nil
|
||||
}
|
||||
|
||||
func splitName(name string) (string, string, string) {
|
||||
var (
|
||||
parts = strings.SplitN(name, " ", 3)
|
||||
lastName, firstName, middleName string
|
||||
)
|
||||
|
||||
if len(parts) > 0 {
|
||||
lastName = parts[0]
|
||||
}
|
||||
|
||||
if len(parts) > 1 {
|
||||
firstName = parts[1]
|
||||
}
|
||||
|
||||
if len(parts) > 2 {
|
||||
middleName = parts[2]
|
||||
}
|
||||
|
||||
return lastName, firstName, middleName
|
||||
}
|
||||
|
||||
func (c *client) CreateSubmission(
|
||||
ctx context.Context,
|
||||
request *dbtypes.SubmissionCreateRequest,
|
||||
) (*dbtypes.SubmissionCreateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
if request.CandidateInfo == nil {
|
||||
return nil, fmt.Errorf("%w: request.CandidateInfo is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
submissionsTable = fmt.Sprintf("%s.%s", c.config.Schema, SubmissionsTableName)
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO: use normal uuid after DB reengineering
|
||||
subId = fmt.Sprintf("%sSUB", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
fullName = formatName(request.CandidateInfo.LastName, request.CandidateInfo.FirstName, request.CandidateInfo.MiddleName)
|
||||
)
|
||||
|
||||
createSubmission := psql.Insert(submissionsTable).
|
||||
Columns(
|
||||
"id", "uid", "vacancy_id", "submission_status_id",
|
||||
"cv", "name", "phone", "email", "birthday", "info",
|
||||
).
|
||||
Values(
|
||||
subId, request.AgentId, request.VacancyId, SubmissionStatusStringToId(dbtypes.SubStatusNew),
|
||||
request.CandidateInfo.CvLink, fullName, request.CandidateInfo.PhoneNumber,
|
||||
request.CandidateInfo.Email, request.CandidateInfo.Birthday, request.CandidateInfo.Resume,
|
||||
)
|
||||
|
||||
query, args, err := createSubmission.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building create submission query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing create submission query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for create submission query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.SubmissionCreateResponse{
|
||||
Id: subId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NOTE: lastName and firstName are required to be non-empty
|
||||
func formatName(lastName, firstName, middleName string) string {
|
||||
name := fmt.Sprintf("%s %s", lastName, firstName)
|
||||
|
||||
if middleName != "" {
|
||||
name = fmt.Sprintf("%s %s", name, middleName)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
//nolint:funlen,gocognit // TODO: refactor
|
||||
func (c *client) UpdateSubmissionStatus(
|
||||
ctx context.Context,
|
||||
request *dbtypes.SubmissionStatusUpdateRequest,
|
||||
) (*dbtypes.SubmissionStatusUpdateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
submissionsTable = fmt.Sprintf("%s.%s", c.config.Schema, SubmissionsTableName)
|
||||
)
|
||||
|
||||
updateSubmissionStatus := psql.Update(submissionsTable).
|
||||
Set("submission_status_id", SubmissionStatusStringToId(request.Status)).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
query, args, err := updateSubmissionStatus.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building 'update submission status' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if request.Status != dbtypes.SubStatusApproved {
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing 'update submission status' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for 'update submission status' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return &dbtypes.SubmissionStatusUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
vacancy, agentId, err := c.getSubmissionMetadata(ctx, tx, request.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting vacancy info: %w", err)
|
||||
}
|
||||
|
||||
if vacancy.AgentReward == nil {
|
||||
return nil, fmt.Errorf("%w: agent reward is unknown", dberrors.ErrForbidden)
|
||||
}
|
||||
|
||||
if vacancy.IsArchived {
|
||||
return nil, fmt.Errorf("%w: vacancy is closed", dberrors.ErrForbidden)
|
||||
}
|
||||
|
||||
if vacancy.CurrentCandidates+1 > vacancy.RequiredCandidates {
|
||||
return nil, fmt.Errorf("%w: vacancy quota exceeded", dberrors.ErrForbidden)
|
||||
}
|
||||
|
||||
if err := c.addSubmissionCandidate(ctx, tx, vacancy.Id); err != nil {
|
||||
return nil, fmt.Errorf("%w: error adding submission candidate: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
payload := dbtypes.TransactionPayload{
|
||||
Origin: "submission",
|
||||
CompanyId: vacancy.Company.Id,
|
||||
CompanyName: vacancy.Company.Name,
|
||||
VacancyId: vacancy.Id,
|
||||
VacancyName: vacancy.Name,
|
||||
}
|
||||
|
||||
molvaAgentTransId := uuid.NewString()
|
||||
|
||||
if _, err := c.createTransactionWithDriver(ctx, tx, &dbtypes.TransactionCreateRequest{
|
||||
OwnerId: agentId,
|
||||
Amount: int64(*vacancy.AgentReward),
|
||||
Currency: "RUB",
|
||||
RequestId: molvaAgentTransId,
|
||||
Payload: &payload,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("error creating molva -> agent transaction: %w", err)
|
||||
}
|
||||
|
||||
distMolvaTransId := uuid.NewString()
|
||||
|
||||
if _, err := c.createTransactionWithDriver(ctx, tx, &dbtypes.TransactionCreateRequest{
|
||||
OwnerId: vacancy.Company.Id,
|
||||
Amount: -int64(*vacancy.AgentReward),
|
||||
Currency: "RUB",
|
||||
RequestId: distMolvaTransId,
|
||||
Payload: &payload,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("error creating dist -> molva transaction: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("%w: error committing transaction: %w", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := tx.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing 'update submission status' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for 'update submission status' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return &dbtypes.SubmissionStatusUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *client) getSubmissionMetadata(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
submissionId string,
|
||||
) (*dbtypes.Vacancy, string, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
submissionsTable = fmt.Sprintf("%s.%s s", c.config.Schema, SubmissionsTableName)
|
||||
companiesTable = fmt.Sprintf("%s.%s c", c.config.Schema, CompaniesTableName)
|
||||
vacanciesTable = fmt.Sprintf("%s.%s v", c.config.Schema, VacanciesTableName)
|
||||
vacancyCandidatesTable = fmt.Sprintf("%s.%s vc", c.config.Schema, VacancyCandidatesTableName)
|
||||
)
|
||||
|
||||
getMetadata := psql.Select(
|
||||
"s.uid", "v.id", "v.name", "v.company_id", "c.name", "v.agent_reward", "v.is_archived",
|
||||
"vc.current_candidates_amt", "vc.required_candidates_amt",
|
||||
).From(submissionsTable).
|
||||
InnerJoin(fmt.Sprintf("%s on s.vacancy_id = v.id", vacanciesTable)).
|
||||
InnerJoin(fmt.Sprintf("%s on v.id = vc.vacancy_id", vacancyCandidatesTable)).
|
||||
InnerJoin(fmt.Sprintf("%s on v.company_id = c.id", companiesTable)).
|
||||
Where(squirrel.Eq{"s.id": submissionId}).
|
||||
Suffix("FOR UPDATE OF vc")
|
||||
|
||||
query, args, err := getMetadata.ToSql()
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("%w: error building 'get submission metadata' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
agentId string
|
||||
vacancy dbtypes.Vacancy
|
||||
)
|
||||
|
||||
if err := row.Scan(
|
||||
&agentId,
|
||||
&vacancy.Id, &vacancy.Name, &vacancy.Company.Id, &vacancy.Company.Name,
|
||||
&vacancy.AgentReward, &vacancy.IsArchived,
|
||||
&vacancy.CurrentCandidates, &vacancy.RequiredCandidates,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, "", dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, "", fmt.Errorf("%w: error scanning row for 'get submission metadata' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return &vacancy, agentId, nil
|
||||
}
|
||||
|
||||
func (c *client) addSubmissionCandidate(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
vacancyId string,
|
||||
) error {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyCandisTable = fmt.Sprintf("%s.%s", c.config.Schema, VacancyCandidatesTableName)
|
||||
)
|
||||
|
||||
addSubmissionCandidate := psql.Update(vacancyCandisTable).
|
||||
Set("current_candidates_amt", squirrel.Expr("current_candidates_amt + 1")).
|
||||
Where(squirrel.Eq{"vacancy_id": vacancyId})
|
||||
|
||||
query, args, err := addSubmissionCandidate.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'add submission candidate' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing 'add submission candidate' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for 'add submission candidate' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) DeleteSubmission(
|
||||
ctx context.Context,
|
||||
request *dbtypes.SubmissionDeleteRequest,
|
||||
) (*dbtypes.SubmissionDeleteResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
submissionsTable = fmt.Sprintf("%s.%s", c.config.Schema, SubmissionsTableName)
|
||||
)
|
||||
|
||||
deleteSubmission := psql.Update(submissionsTable).
|
||||
Set("submission_status_id", SubmissionStatusStringToId(dbtypes.SubStatusCancelled)).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
query, args, err := deleteSubmission.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building delete submission query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing delete submission query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for delete submission query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return &dbtypes.SubmissionDeleteResponse{}, nil
|
||||
}
|
485
internal/database/postgres/transaction.go
Normal file
485
internal/database/postgres/transaction.go
Normal file
@@ -0,0 +1,485 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
func getTransactionTypeByAmount(amount int64) dbtypes.TransactionType {
|
||||
if amount > 0 {
|
||||
return dbtypes.TransactionTypeDeposit
|
||||
}
|
||||
|
||||
return dbtypes.TransactionTypeWithdrawal
|
||||
}
|
||||
|
||||
// TODO: add migration to rebind statuses
|
||||
|
||||
func TransactionStatusIdToString(status int32) dbtypes.TransactionStatus {
|
||||
switch status {
|
||||
case 0:
|
||||
return dbtypes.TransactionStatusPending
|
||||
case 1:
|
||||
return dbtypes.TransactionStatusApproved
|
||||
case 2:
|
||||
return dbtypes.TransactionStatusRejected
|
||||
case 3:
|
||||
return dbtypes.TransactionStatusNew
|
||||
default:
|
||||
return dbtypes.TransactionStatusPending
|
||||
}
|
||||
}
|
||||
|
||||
func TransactionStatusStringToId(status dbtypes.TransactionStatus) int32 {
|
||||
switch status {
|
||||
case dbtypes.TransactionStatusNew:
|
||||
return 3
|
||||
case dbtypes.TransactionStatusPending:
|
||||
return 0
|
||||
case dbtypes.TransactionStatusApproved:
|
||||
return 1
|
||||
case dbtypes.TransactionStatusRejected:
|
||||
return 2
|
||||
default:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocognit // not so hard
|
||||
func (c *client) GetTransactionList(
|
||||
ctx context.Context,
|
||||
request *dbtypes.TransactionListGetRequest,
|
||||
) (*dbtypes.TransactionListGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
transactionsTable = fmt.Sprintf("%s.%s", c.config.Schema, TransactionsTableName)
|
||||
)
|
||||
|
||||
getTransactions := psql.Select(
|
||||
"id", "owner_id", "bank_account_id", "amount", "currency", "status", "created_at", "payload",
|
||||
).From(transactionsTable).
|
||||
Where(squirrel.Eq{"owner_id": request.OwnerId}).
|
||||
Limit(request.PageSize).
|
||||
Offset(countOffset(request.Page, request.PageSize))
|
||||
|
||||
getTransactions, err := c.setGetTransactionsQueryFilters(getTransactions, request.Filters)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error setting get transactions query filters: %v", dberrors.ErrBadRequest, err)
|
||||
}
|
||||
|
||||
query, args, err := getTransactions.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get transactions query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rows, err := c.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing get transactions query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
transList := &dbtypes.TransactionListGetResponse{
|
||||
Transactions: make([]dbtypes.Transaction, 0, request.PageSize),
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
transStatus int32
|
||||
ownerId string
|
||||
payload, bankAccountId sql.NullString
|
||||
transaction dbtypes.Transaction
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&transaction.Id, &ownerId, &bankAccountId, &transaction.Amount, &transaction.Currency,
|
||||
&transStatus, &transaction.CreatedAt, &payload,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if bankAccountId.Valid {
|
||||
bankAccountInfo, err := c.getBankAccountInfoById(ctx, c.db, bankAccountId.String)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting bank account info: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
transaction.BankAccountInfo = bankAccountInfo
|
||||
|
||||
ownerInfo, err := c.getOwnerInfoById(ctx, c.db, ownerId, bankAccountInfo.OwnerType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting owner info: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
transaction.OwnerInfo = ownerInfo
|
||||
}
|
||||
|
||||
transaction.Type = getTransactionTypeByAmount(transaction.Amount)
|
||||
transaction.Status = TransactionStatusIdToString(transStatus)
|
||||
|
||||
if payload.Valid {
|
||||
var payloadData dbtypes.TransactionPayload
|
||||
|
||||
if err := json.Unmarshal([]byte(payload.String), &payloadData); err != nil {
|
||||
return nil, fmt.Errorf("%w: error unmarshaling transaction payload: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
transaction.Payload = &payloadData
|
||||
}
|
||||
|
||||
transList.Transactions = append(transList.Transactions, transaction)
|
||||
}
|
||||
|
||||
return transList, nil
|
||||
}
|
||||
|
||||
func (c *client) setGetTransactionsQueryFilters(
|
||||
query squirrel.SelectBuilder,
|
||||
filters *dbtypes.TransactionListFilters,
|
||||
) (squirrel.SelectBuilder, error) {
|
||||
if filters == nil {
|
||||
return query, nil
|
||||
}
|
||||
|
||||
if filters.Type != nil {
|
||||
switch *filters.Type {
|
||||
case dbtypes.TransactionTypeDeposit:
|
||||
query = query.Where(squirrel.Gt{
|
||||
"amount": 0,
|
||||
})
|
||||
|
||||
case dbtypes.TransactionTypeWithdrawal:
|
||||
query = query.Where(squirrel.Lt{
|
||||
"amount": 0,
|
||||
})
|
||||
|
||||
default:
|
||||
return query, fmt.Errorf("%w: invalid transaction type: %v", dberrors.ErrBadRequest, *filters.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if filters.Status != nil {
|
||||
query = query.Where(squirrel.Eq{"status": TransactionStatusStringToId(*filters.Status)})
|
||||
}
|
||||
|
||||
if filters.BankAccountId != nil {
|
||||
query = query.Where(squirrel.Eq{"bank_account_id": *filters.BankAccountId})
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func (c *client) getOwnerInfoById(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
ownerId string,
|
||||
ownerType string,
|
||||
) (*dbtypes.TransactionOwnerInfo, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
clientTableName = fmt.Sprintf("%s.%s", c.config.Schema, UsersTableName)
|
||||
companyTableName = fmt.Sprintf("%s.%s", c.config.Schema, CompaniesTableName)
|
||||
)
|
||||
|
||||
var getOwnerInfoById squirrel.SelectBuilder
|
||||
|
||||
// TODO: reingeneer the DB
|
||||
switch ownerType {
|
||||
case "agent":
|
||||
getOwnerInfoById = psql.Select(
|
||||
"uid",
|
||||
"name",
|
||||
).
|
||||
From(clientTableName).
|
||||
Where(squirrel.Eq{"uid": ownerId})
|
||||
|
||||
case "company":
|
||||
getOwnerInfoById = psql.Select(
|
||||
"id",
|
||||
"name",
|
||||
).
|
||||
From(companyTableName).
|
||||
Where(squirrel.Eq{"id": ownerId})
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: invalid owner type", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
query, args, err := getOwnerInfoById.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get owner info by id query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var ownerInfo dbtypes.TransactionOwnerInfo
|
||||
|
||||
if err := row.Scan(
|
||||
&ownerInfo.Id,
|
||||
&ownerInfo.Name,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row for get owner info by id query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return &ownerInfo, nil
|
||||
}
|
||||
|
||||
func (c *client) getBankAccountInfoById(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
bankAccountId string,
|
||||
) (*dbtypes.BankAccountInfo, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
bankAccountsTableName = fmt.Sprintf("%s.%s", c.config.Schema, BankAccountsTableName)
|
||||
)
|
||||
|
||||
getBankAccountInfoById := psql.Select(
|
||||
"id",
|
||||
"account_number",
|
||||
"bank_name",
|
||||
"bik",
|
||||
"correspondent_account",
|
||||
"owner_type",
|
||||
).
|
||||
From(bankAccountsTableName).
|
||||
Where(squirrel.Eq{"id": bankAccountId})
|
||||
|
||||
query, args, err := getBankAccountInfoById.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get bank account info by id query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var accountInfo dbtypes.BankAccountInfo
|
||||
|
||||
if err := row.Scan(
|
||||
&accountInfo.Id,
|
||||
&accountInfo.AccountNumber,
|
||||
&accountInfo.BankName,
|
||||
&accountInfo.Bik,
|
||||
&accountInfo.CorrespondentAccount,
|
||||
&accountInfo.OwnerType,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row for get bank account info by id query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return &accountInfo, nil
|
||||
}
|
||||
|
||||
func (c *client) CreateTransaction(
|
||||
ctx context.Context,
|
||||
request *dbtypes.TransactionCreateRequest,
|
||||
) (*dbtypes.TransactionCreateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
result, err := c.createTransactionWithDriver(ctx, tx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating transaction: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("%w: error committing transaction: %w", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *client) createTransactionWithDriver(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.TransactionCreateRequest,
|
||||
) (*dbtypes.TransactionCreateResponse, error) {
|
||||
if _, err := c.getRawBalanceForUpdate(ctx, driver, request.OwnerId); err != nil {
|
||||
return nil, fmt.Errorf("error getting raw balance for update: %w", err)
|
||||
}
|
||||
|
||||
result, err := c.createTransaction(ctx, driver, request)
|
||||
if err != nil {
|
||||
if errors.Is(err, dberrors.ErrConflict) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error creating transaction: %w", err)
|
||||
}
|
||||
|
||||
if err := c.updateBalance(ctx, driver, request.Amount, request.OwnerId); err != nil {
|
||||
return nil, fmt.Errorf("error updating balance: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *client) getRawBalanceForUpdate(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
ownerId string,
|
||||
) (int64, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
balancesTable = fmt.Sprintf("%s.%s", c.config.Schema, BalancesTableName)
|
||||
)
|
||||
|
||||
getBalance := psql.Select(
|
||||
"raw_balance",
|
||||
).From(balancesTable).
|
||||
Where(squirrel.Eq{"owner_id": ownerId}).
|
||||
Suffix("FOR UPDATE")
|
||||
|
||||
query, args, err := getBalance.ToSql()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%w: error building 'get balance' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var rawBalance int64
|
||||
|
||||
if err := row.Scan(&rawBalance); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return rawBalance, nil
|
||||
}
|
||||
|
||||
func (c *client) updateBalance(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
amountDelta int64,
|
||||
ownerId string,
|
||||
) error {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
balancesTable = fmt.Sprintf("%s.%s", c.config.Schema, BalancesTableName)
|
||||
)
|
||||
|
||||
updateBalance := psql.Update(balancesTable).
|
||||
SetMap(map[string]any{
|
||||
"raw_balance": squirrel.Expr("raw_balance + ?", amountDelta),
|
||||
"updated_at": squirrel.Expr("NOW()"),
|
||||
}).
|
||||
Where(squirrel.Eq{"owner_id": ownerId})
|
||||
|
||||
query, args, err := updateBalance.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'update balance' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing 'update balance' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for 'update balance' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) createTransaction(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.TransactionCreateRequest,
|
||||
) (*dbtypes.TransactionCreateResponse, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
transactionsTable = fmt.Sprintf("%s.%s", c.config.Schema, TransactionsTableName)
|
||||
)
|
||||
|
||||
var payload []byte
|
||||
|
||||
if request.Payload == nil {
|
||||
payload = []byte("{}")
|
||||
} else {
|
||||
payloadBytes, err := json.Marshal(request.Payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error marshaling transaction payload: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
payload = payloadBytes
|
||||
}
|
||||
|
||||
createTransaction := psql.Insert(transactionsTable).
|
||||
Columns(
|
||||
"id", "owner_id", "bank_account_id", "amount", "currency", "status", "created_at", "payload",
|
||||
).
|
||||
Values(
|
||||
request.RequestId, request.OwnerId, request.BankAccountId, request.Amount, request.Currency,
|
||||
dbtypes.TransactionStatusNew, squirrel.Expr("CURRENT_TIMESTAMP"), payload,
|
||||
).
|
||||
Suffix("ON CONFLICT (id, owner_id) DO NOTHING")
|
||||
|
||||
query, args, err := createTransaction.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building 'create transaction' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing 'create transaction' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for 'create transaction' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return &dbtypes.TransactionCreateResponse{
|
||||
Id: request.RequestId,
|
||||
}, dberrors.ErrConflict
|
||||
}
|
||||
|
||||
return &dbtypes.TransactionCreateResponse{
|
||||
Id: request.RequestId,
|
||||
}, nil
|
||||
}
|
203
internal/database/postgres/user.go
Normal file
203
internal/database/postgres/user.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (c *client) GetClientValidation(
|
||||
ctx context.Context,
|
||||
request *dbtypes.ClientValidationGetRequest,
|
||||
) (*dbtypes.ClientValidationGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
userValidationTable = fmt.Sprintf("%s.%s", c.config.Schema, UserValidationTableName)
|
||||
)
|
||||
|
||||
getUserValidation := psql.Select(
|
||||
"status", "description",
|
||||
).
|
||||
From(userValidationTable).
|
||||
Where(squirrel.Eq{"uid": request.UserId})
|
||||
|
||||
query, args, err := getUserValidation.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building 'get user validation' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := c.db.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var result dbtypes.ClientValidation
|
||||
|
||||
if err := row.Scan(&result.Status, &result.Description); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row for 'get user validation' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return &dbtypes.ClientValidationGetResponse{
|
||||
ClientValidation: &result,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) CreateUser(
|
||||
ctx context.Context,
|
||||
request *dbtypes.UserSaveRequest,
|
||||
) (*dbtypes.UserSaveResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
result, err := c.createUser(ctx, tx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating user: %w", err)
|
||||
}
|
||||
|
||||
if err := c.createUserValidationTicket(ctx, tx, request); err != nil {
|
||||
return nil, fmt.Errorf("error creating user validation ticket: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.createCompany(ctx, tx, &dbtypes.CompanyCreateRequest{
|
||||
OwnerId: request.Id,
|
||||
Staff: []string{
|
||||
request.Id,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating company: %w", err)
|
||||
}
|
||||
|
||||
var ownerId string
|
||||
|
||||
switch request.Type {
|
||||
case dbtypes.UserTypeAgent:
|
||||
ownerId = request.Id
|
||||
case dbtypes.UserTypeDistributor:
|
||||
ownerId = resp.Id
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: unknown user type: %v", dberrors.ErrBadRequest, request.Type)
|
||||
}
|
||||
|
||||
balanceId := fmt.Sprintf("%sBAL", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
|
||||
if err := c.createBalance(ctx, tx, &dbtypes.BalanceCreateRequest{
|
||||
Id: balanceId,
|
||||
OwnerId: ownerId,
|
||||
RawBalance: 0,
|
||||
CleanBalance: 0,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("error creating balance: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("%w: error committing transaction: %w", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *client) createUser(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.UserSaveRequest,
|
||||
) (*dbtypes.UserSaveResponse, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
userTable = fmt.Sprintf("%s.%s", c.config.Schema, UsersTableName)
|
||||
)
|
||||
|
||||
saveUser := psql.Insert(userTable).
|
||||
Columns(
|
||||
"uid", "name", "phone", "email", "client_type_id",
|
||||
).
|
||||
Values(
|
||||
request.Id, request.FullName, request.Phone, request.Email, request.Type,
|
||||
)
|
||||
|
||||
query, args, err := saveUser.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building 'save user' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing 'save user' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for 'save user' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.UserSaveResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *client) createUserValidationTicket(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.UserSaveRequest,
|
||||
) error {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
userValidationTable = fmt.Sprintf("%s.%s", c.config.Schema, UserValidationTableName)
|
||||
)
|
||||
|
||||
// TODO: use normal uuid after DB reengineering
|
||||
ticketId := fmt.Sprintf("%sVAL", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
|
||||
createUserValidation := psql.Insert(userValidationTable).
|
||||
Columns(
|
||||
"id", "uid", "status", "description", "last_update",
|
||||
).
|
||||
Values(
|
||||
ticketId, request.Id, dbtypes.ClientValStatusNew, request.FullName, squirrel.Expr("CURRENT_TIMESTAMP"),
|
||||
)
|
||||
|
||||
query, args, err := createUserValidation.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error building 'create user validation' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error executing 'create user validation' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: error getting rows affected for 'create user validation' query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
573
internal/database/postgres/vacancy.go
Normal file
573
internal/database/postgres/vacancy.go
Normal file
@@ -0,0 +1,573 @@
|
||||
package pgdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
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"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func VacancyStatusIdToString(status int32) dbtypes.VacancyStatus {
|
||||
switch status {
|
||||
case 0:
|
||||
return dbtypes.VacUnspecified
|
||||
case 1:
|
||||
return dbtypes.VacNew
|
||||
case 2:
|
||||
return dbtypes.VacPending
|
||||
case 3:
|
||||
return dbtypes.VacApproved
|
||||
case 4:
|
||||
return dbtypes.VacRejected
|
||||
default:
|
||||
return dbtypes.VacUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func VacancyStatusStringToId(status dbtypes.VacancyStatus) int32 {
|
||||
switch status {
|
||||
case dbtypes.VacUnspecified:
|
||||
return 0
|
||||
case dbtypes.VacNew:
|
||||
return 1
|
||||
case dbtypes.VacPending:
|
||||
return 2
|
||||
case dbtypes.VacApproved:
|
||||
return 3
|
||||
case dbtypes.VacRejected:
|
||||
return 4
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:funlen,gocognit // maybe refactor later
|
||||
func (c *client) GetVacancyList(
|
||||
ctx context.Context,
|
||||
request *dbtypes.VacancyListGetRequest,
|
||||
) (*dbtypes.VacancyListGetResponse, error) {
|
||||
if request == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyTable = fmt.Sprintf("%s.%s v", c.config.Schema, VacanciesTableName)
|
||||
companyTable = fmt.Sprintf("%s.%s c", c.config.Schema, CompaniesTableName)
|
||||
)
|
||||
|
||||
getVacList := psql.Select(
|
||||
"v.id", "v.company_id", "c.name", "v.name", "v.address", "v.work_format",
|
||||
"v.agent_reward", "v.region", "v.salary_top", "v.salary_bottom", "v.requirements",
|
||||
"v.responsibilities", "v.additional_info", "v.is_archived",
|
||||
"v.target_action", "v.target_action_amount", "v.publish_date",
|
||||
"v.moderation_status_id", "v.required_candidates", "v.current_candidates",
|
||||
"v.additional_fields",
|
||||
).
|
||||
From(vacancyTable).
|
||||
LeftJoin(fmt.Sprintf("%s on v.company_id = c.id", companyTable)).
|
||||
Limit(request.PageSize).
|
||||
Offset(countOffset(request.Page, request.PageSize))
|
||||
|
||||
getVacList = c.setVacancyListFilters(getVacList, request.Filters)
|
||||
|
||||
query, args, err := getVacList.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building get vacancy list query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rows, err := c.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing get vacancy list query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var (
|
||||
res dbtypes.VacancyListGetResponse
|
||||
vacancyIds = make([]string, 0, request.PageSize)
|
||||
)
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
status int32
|
||||
agentReward sql.NullInt32
|
||||
comName, reqs, resps, extraInfo, extraFields sql.NullString
|
||||
vacancy dbtypes.Vacancy
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&vacancy.Id, &vacancy.Company.Id, &comName, &vacancy.Name, &vacancy.Address,
|
||||
&vacancy.WorkFormat, &agentReward, &vacancy.Region, &vacancy.SalaryTop, &vacancy.SalaryBottom,
|
||||
&reqs, &resps, &extraInfo, &vacancy.IsArchived, &vacancy.TargetAction.Action, &vacancy.TargetAction.Duration,
|
||||
&vacancy.CreatedAt, &status, &vacancy.RequiredCandidates, &vacancy.CurrentCandidates,
|
||||
&extraFields,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if agentReward.Valid {
|
||||
vacancy.AgentReward = &agentReward.Int32
|
||||
}
|
||||
|
||||
if comName.Valid {
|
||||
vacancy.Company.Name = comName.String
|
||||
}
|
||||
|
||||
if reqs.Valid {
|
||||
vacancy.Requirements = &reqs.String
|
||||
}
|
||||
|
||||
if resps.Valid {
|
||||
vacancy.Responsibilities = &resps.String
|
||||
}
|
||||
|
||||
if extraInfo.Valid {
|
||||
vacancy.ExtraInfo = &extraInfo.String
|
||||
}
|
||||
|
||||
if extraFields.Valid {
|
||||
vacancy.ExtraFields = &extraFields.String
|
||||
}
|
||||
|
||||
vacancy.Moderation.Status = VacancyStatusIdToString(status)
|
||||
|
||||
res.Vacancies = append(res.Vacancies, vacancy)
|
||||
vacancyIds = append(vacancyIds, vacancy.Id)
|
||||
}
|
||||
|
||||
descriptions, err := c.getVacancyModerationDescriptionHistory(ctx, c.db, vacancyIds)
|
||||
if err != nil {
|
||||
if errors.Is(err, dberrors.ErrNotFound) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error getting descriptions for vacancies: %w", err)
|
||||
}
|
||||
|
||||
for i, vacancy := range res.Vacancies {
|
||||
res.Vacancies[i].Moderation.DescriptionHistory = descriptions[vacancy.Id]
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (c *client) setVacancyListFilters(
|
||||
query squirrel.SelectBuilder,
|
||||
filters *dbtypes.VacancyListFilters,
|
||||
) squirrel.SelectBuilder {
|
||||
if filters.DistributorId != nil {
|
||||
query = query.Where(squirrel.Eq{"c.uid": *filters.DistributorId})
|
||||
}
|
||||
|
||||
if filters.CompanyId != nil {
|
||||
query = query.Where(squirrel.Eq{"v.company_id": *filters.CompanyId})
|
||||
}
|
||||
|
||||
if filters.VacancyId != nil {
|
||||
query = query.Where(squirrel.Eq{"v.id": *filters.VacancyId})
|
||||
}
|
||||
|
||||
if filters.Region != nil {
|
||||
query = query.Where(squirrel.Eq{"v.region": *filters.Region})
|
||||
}
|
||||
|
||||
if filters.SalaryBottom != nil {
|
||||
query = query.Where(squirrel.Or{
|
||||
squirrel.GtOrEq{"v.salary_bottom": *filters.SalaryBottom},
|
||||
squirrel.Eq{"v.salary_bottom": nil},
|
||||
})
|
||||
}
|
||||
|
||||
if filters.SalaryTop != nil {
|
||||
query = query.Where(squirrel.Or{
|
||||
squirrel.LtOrEq{"v.salary_top": *filters.SalaryTop},
|
||||
squirrel.Eq{"v.salary_top": nil},
|
||||
})
|
||||
}
|
||||
|
||||
if filters.IsArchived != nil {
|
||||
query = query.Where(squirrel.Eq{"v.is_archived": *filters.IsArchived})
|
||||
}
|
||||
|
||||
if filters.Status != nil {
|
||||
query = query.Where(squirrel.Eq{"v.moderation_status_id": VacancyStatusStringToId(*filters.Status)})
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func (c *client) getVacancyModerationDescriptionHistory(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
vacancyIds []string,
|
||||
) (map[string][]dbtypes.VacancyModerationDescription, error) {
|
||||
if len(vacancyIds) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyModerDescriptionsTable = fmt.Sprintf("%s.%s", c.config.Schema, VacancyDescriptionHistoryTableName)
|
||||
)
|
||||
|
||||
getVacancyModerationDescriptionHistory := psql.Select(
|
||||
"vacancy_id",
|
||||
"description",
|
||||
"created_at",
|
||||
).
|
||||
From(vacancyModerDescriptionsTable).
|
||||
Where(squirrel.Eq{"vacancy_id": vacancyIds}).
|
||||
OrderBy("vacancy_id", "created_at ASC")
|
||||
|
||||
query, args, err := getVacancyModerationDescriptionHistory.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building getVacancyModerationDescriptionHistory query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rows, err := driver.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing getVacancyModerationDescriptionHistory query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
descriptionsMap := make(map[string][]dbtypes.VacancyModerationDescription)
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
vacancyId string
|
||||
description dbtypes.VacancyModerationDescription
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&vacancyId,
|
||||
&description.Description,
|
||||
&description.CreatedAt,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%w: error scanning row for getVacancyModerationDescriptionHistory: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
descriptionsMap[vacancyId] = append(descriptionsMap[vacancyId], description)
|
||||
}
|
||||
|
||||
return descriptionsMap, nil
|
||||
}
|
||||
|
||||
func (c *client) CreateVacancy(
|
||||
ctx context.Context,
|
||||
request *dbtypes.VacancyCreateRequest,
|
||||
) (*dbtypes.VacancyCreateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyTable = fmt.Sprintf("%s.%s", c.config.Schema, VacanciesTableName)
|
||||
)
|
||||
|
||||
// TODO: use normal uuid after DB reengineering
|
||||
vacancyId := fmt.Sprintf("%sVAC", strings.ReplaceAll(uuid.NewString(), "-", ""))
|
||||
|
||||
createVacancy := psql.Insert(vacancyTable).
|
||||
Columns(
|
||||
"id", "company_id", "name", "address", "work_format", "agent_reward",
|
||||
"salary_top", "salary_bottom", "requirements", "responsibilities", "additional_info",
|
||||
"region", "is_archived", "target_action", "target_action_amount", "moderation_status_id",
|
||||
"required_candidates", "publish_date", "current_candidates", "additional_fields",
|
||||
).
|
||||
Values(
|
||||
vacancyId, request.CompanyId, request.Name, request.Address, request.WorkFormat, request.AgentReward,
|
||||
request.SalaryTop, request.SalaryBottom, request.Requirements, request.Responsibilities, request.ExtraInfo,
|
||||
request.Region, false, request.TargetAction.Action, request.TargetAction.Duration, VacancyStatusStringToId(dbtypes.VacNew),
|
||||
request.RequiredCandidates, squirrel.Expr("now()"), request.CurrentCandidates, request.ExtraFields,
|
||||
)
|
||||
|
||||
query, args, err := createVacancy.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building create vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
// TODO: process conflict via err.(*pq.Error).Code
|
||||
return nil, fmt.Errorf("%w: error executing create vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for create vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.VacancyCreateResponse{Id: vacancyId}, nil
|
||||
}
|
||||
|
||||
func (c *client) UpdateVacancy(
|
||||
ctx context.Context,
|
||||
request *dbtypes.VacancyUpdateRequest,
|
||||
) (*dbtypes.VacancyUpdateResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
tx, err := c.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error starting transaction: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
status, err := c.getVacancyModerStatusForUpdate(ctx, tx, request.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting vacancy moder status: %w", err)
|
||||
}
|
||||
|
||||
if status == dbtypes.VacApproved {
|
||||
return nil, dberrors.ErrForbidden
|
||||
}
|
||||
|
||||
res, err := c.updateVacancy(ctx, tx, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating vacancy: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("error committing transaction: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *client) getVacancyModerStatusForUpdate(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
vacancyId string,
|
||||
) (dbtypes.VacancyStatus, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyTable = fmt.Sprintf("%s.%s", c.config.Schema, VacanciesTableName)
|
||||
)
|
||||
|
||||
getVacancyModerStatus := psql.Select(
|
||||
"moderation_status_id",
|
||||
).
|
||||
From(vacancyTable).
|
||||
Where(squirrel.Eq{"id": vacancyId}).
|
||||
Suffix("FOR UPDATE")
|
||||
|
||||
query, args, err := getVacancyModerStatus.ToSql()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: error building get vacancy moder status query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
row := driver.QueryRowContext(ctx, query, args...)
|
||||
|
||||
var (
|
||||
statusId int32
|
||||
)
|
||||
|
||||
if err := row.Scan(&statusId); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%w: error scanning row for get vacancy moder status query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
return VacancyStatusIdToString(statusId), nil
|
||||
}
|
||||
|
||||
func (c *client) updateVacancy(
|
||||
ctx context.Context,
|
||||
driver Driver,
|
||||
request *dbtypes.VacancyUpdateRequest,
|
||||
) (*dbtypes.VacancyUpdateResponse, error) {
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyTable = fmt.Sprintf("%s.%s", c.config.Schema, VacanciesTableName)
|
||||
)
|
||||
|
||||
updateVacancy := psql.Update(vacancyTable).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
if request.Name != nil {
|
||||
updateVacancy = updateVacancy.Set("name", *request.Name)
|
||||
}
|
||||
|
||||
if request.Address != nil {
|
||||
updateVacancy = updateVacancy.Set("address", *request.Address)
|
||||
}
|
||||
|
||||
if request.WorkFormat != nil {
|
||||
updateVacancy = updateVacancy.Set("work_format", *request.WorkFormat)
|
||||
}
|
||||
|
||||
if request.AgentReward != nil {
|
||||
updateVacancy = updateVacancy.Set("agent_reward", *request.AgentReward)
|
||||
}
|
||||
|
||||
if request.SalaryTop != nil {
|
||||
updateVacancy = updateVacancy.Set("salary_top", *request.SalaryTop)
|
||||
}
|
||||
|
||||
if request.SalaryBottom != nil {
|
||||
updateVacancy = updateVacancy.Set("salary_bottom", *request.SalaryBottom)
|
||||
}
|
||||
|
||||
if request.Requirements != nil {
|
||||
updateVacancy = updateVacancy.Set("requirements", *request.Requirements)
|
||||
}
|
||||
|
||||
if request.Responsibilities != nil {
|
||||
updateVacancy = updateVacancy.Set("responsibilities", *request.Responsibilities)
|
||||
}
|
||||
|
||||
if request.ExtraInfo != nil {
|
||||
updateVacancy = updateVacancy.Set("extra_info", *request.ExtraInfo)
|
||||
}
|
||||
|
||||
if request.Region != nil {
|
||||
updateVacancy = updateVacancy.Set("region", *request.Region)
|
||||
}
|
||||
|
||||
if request.TargetAction.Action != nil {
|
||||
updateVacancy = updateVacancy.Set("target_action", *request.TargetAction.Action)
|
||||
}
|
||||
|
||||
if request.TargetAction.Duration != nil {
|
||||
updateVacancy = updateVacancy.Set("target_action_amount", *request.TargetAction.Duration)
|
||||
}
|
||||
|
||||
if request.RequiredCandidates != nil {
|
||||
updateVacancy = updateVacancy.Set("required_candidates", *request.RequiredCandidates)
|
||||
}
|
||||
|
||||
if request.ExtraFields != nil {
|
||||
updateVacancy = updateVacancy.Set("additional_fields", *request.ExtraFields)
|
||||
}
|
||||
|
||||
query, args, err := updateVacancy.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building update vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := driver.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing update vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for update vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrInternal
|
||||
}
|
||||
|
||||
return &dbtypes.VacancyUpdateResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *client) DeleteVacancy(
|
||||
ctx context.Context,
|
||||
request *dbtypes.VacancyDeleteRequest,
|
||||
) (*dbtypes.VacancyDeleteResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyTable = fmt.Sprintf("%s.%s", c.config.Schema, VacanciesTableName)
|
||||
)
|
||||
|
||||
deleteVacancy := psql.Update(vacancyTable).
|
||||
Set("is_archived", true).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
query, args, err := deleteVacancy.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building delete vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing delete vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for delete vacancy query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return &dbtypes.VacancyDeleteResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *client) SendVacancyToModeration(
|
||||
ctx context.Context,
|
||||
request *dbtypes.SendVacancyToModerationRequest,
|
||||
) (*dbtypes.SendVacancyToModerationResponse, error) {
|
||||
if request == nil {
|
||||
return nil, fmt.Errorf("%w: request is nil", dberrors.ErrBadRequest)
|
||||
}
|
||||
|
||||
var (
|
||||
psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
|
||||
|
||||
vacancyTable = fmt.Sprintf("%s.%s", c.config.Schema, VacanciesTableName)
|
||||
)
|
||||
|
||||
sendVacancyToModeration := psql.Update(vacancyTable).
|
||||
Set("moderation_status_id", VacancyStatusStringToId(dbtypes.VacPending)).
|
||||
Where(squirrel.Eq{"id": request.Id})
|
||||
|
||||
query, args, err := sendVacancyToModeration.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error building send vacancy to moderation query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
res, err := c.db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error executing send vacancy to moderation query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: error getting rows affected for send vacancy to moderation query: %v", dberrors.ErrInternal, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, dberrors.ErrNotFound
|
||||
}
|
||||
|
||||
return &dbtypes.SendVacancyToModerationResponse{}, nil
|
||||
}
|
Reference in New Issue
Block a user