add service
All checks were successful
Deploy Smart Search Backend Test / deploy (push) Successful in 1m24s

This commit is contained in:
vallyenfail
2026-01-18 01:48:46 +03:00
parent b6f7323c58
commit 80e5f318a9
23 changed files with 2300 additions and 26 deletions

View File

@@ -12,6 +12,7 @@ type UserRepository interface {
FindByEmailHash(ctx context.Context, emailHash string) (*model.User, error)
FindByID(ctx context.Context, userID int) (*model.User, error)
Create(ctx context.Context, user *model.User) error
CreateTx(ctx context.Context, tx pgx.Tx, user *model.User) error
UpdateBalance(ctx context.Context, userID int, delta float64) error
UpdateBalanceTx(ctx context.Context, tx pgx.Tx, userID int, delta float64) error
GetBalance(ctx context.Context, userID int) (float64, error)
@@ -35,7 +36,9 @@ type InviteRepository interface {
Create(ctx context.Context, invite *model.InviteCode) error
CreateTx(ctx context.Context, tx pgx.Tx, invite *model.InviteCode) error
FindByCode(ctx context.Context, code int64) (*model.InviteCode, error)
FindActiveByCode(ctx context.Context, code int64) (*model.InviteCode, error)
IncrementUsedCount(ctx context.Context, code int64) error
DecrementCanBeUsedCountTx(ctx context.Context, tx pgx.Tx, code int64) error
DeactivateExpired(ctx context.Context) (int, error)
GetUserInvites(ctx context.Context, userID int) ([]*model.InviteCode, error)
}

View File

@@ -78,6 +78,38 @@ func (r *inviteRepository) FindByCode(ctx context.Context, code int64) (*model.I
return invite, nil
}
func (r *inviteRepository) FindActiveByCode(ctx context.Context, code int64) (*model.InviteCode, error) {
query := r.qb.Select(
"id", "user_id", "code", "can_be_used_count", "used_count",
"is_active", "created_at", "expires_at",
).From("invite_codes").Where(sq.And{
sq.Eq{"code": code},
sq.Eq{"is_active": true},
sq.Expr("expires_at > now()"),
sq.Expr("can_be_used_count > used_count"),
})
sqlQuery, args, err := query.ToSql()
if err != nil {
return nil, errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
}
invite := &model.InviteCode{}
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(
&invite.ID, &invite.UserID, &invite.Code, &invite.CanBeUsedCount,
&invite.UsedCount, &invite.IsActive, &invite.CreatedAt, &invite.ExpiresAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, errs.NewBusinessError(errs.InviteInvalidOrExpired, "invite code is invalid or expired")
}
if err != nil {
return nil, errs.NewInternalError(errs.DatabaseError, "failed to find active invite code", err)
}
return invite, nil
}
func (r *inviteRepository) IncrementUsedCount(ctx context.Context, code int64) error {
query := r.qb.Update("invite_codes").
Set("used_count", sq.Expr("used_count + 1")).
@@ -96,6 +128,25 @@ func (r *inviteRepository) IncrementUsedCount(ctx context.Context, code int64) e
return nil
}
func (r *inviteRepository) DecrementCanBeUsedCountTx(ctx context.Context, tx pgx.Tx, code int64) error {
query := r.qb.Update("invite_codes").
Set("used_count", sq.Expr("used_count + 1")).
Set("is_active", sq.Expr("CASE WHEN used_count + 1 >= can_be_used_count THEN false ELSE is_active END")).
Where(sq.Eq{"code": code})
sqlQuery, args, err := query.ToSql()
if err != nil {
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
}
_, err = tx.Exec(ctx, sqlQuery, args...)
if err != nil {
return errs.NewInternalError(errs.DatabaseError, "failed to decrement can_be_used_count", err)
}
return nil
}
func (r *inviteRepository) DeactivateExpired(ctx context.Context) (int, error) {
query := r.qb.Update("invite_codes").
Set("is_active", false).

View File

@@ -86,6 +86,14 @@ func (r *userRepository) FindByID(ctx context.Context, userID int) (*model.User,
}
func (r *userRepository) Create(ctx context.Context, user *model.User) error {
return r.createWithExecutor(ctx, r.pool, user)
}
func (r *userRepository) CreateTx(ctx context.Context, tx pgx.Tx, user *model.User) error {
return r.createWithExecutor(ctx, tx, user)
}
func (r *userRepository) createWithExecutor(ctx context.Context, exec DBTX, user *model.User) error {
encryptedEmail, err := r.cryptoHelper.Encrypt(user.Email)
if err != nil {
return errs.NewInternalError(errs.EncryptionError, "failed to encrypt email", err)
@@ -114,7 +122,7 @@ func (r *userRepository) Create(ctx context.Context, user *model.User) error {
return errs.NewInternalError(errs.DatabaseError, "failed to build query", err)
}
err = r.pool.QueryRow(ctx, sqlQuery, args...).Scan(&user.ID)
err = exec.QueryRow(ctx, sqlQuery, args...).Scan(&user.ID)
if err != nil {
return errs.NewInternalError(errs.DatabaseError, "failed to create user", err)
}