ms-auth/internal/storage/user.go
2024-08-14 20:24:57 +05:00

179 lines
4.1 KiB
Go

package storage
import (
"context"
"errors"
"git.sch9.ru/new_gate/ms-auth/internal/lib"
"git.sch9.ru/new_gate/ms-auth/internal/models"
"github.com/jackc/pgerrcode"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
"time"
)
type UserStorage struct {
db *sqlx.DB
logger *zap.Logger
}
func NewUserStorage(db *sqlx.DB, logger *zap.Logger) *UserStorage {
return &UserStorage{
db: db,
logger: logger,
}
}
const year = time.Hour * 24 * 365
func (storage *UserStorage) CreateUser(ctx context.Context, user *models.User) (int32, error) {
if err := user.ValidUsername(); err != nil {
return 0, err
}
if err := user.ValidPassword(); err != nil {
return 0, err
}
if err := user.ValidEmail(); err != nil {
return 0, err
}
if err := user.ValidRole(); err != nil {
return 0, err
}
if err := user.HashPassword(); err != nil {
return 0, err
}
query := storage.db.Rebind(`
INSERT INTO users
(username, hashed_pwd, email, expires_at, role)
VALUES (?, ?, ?, ?, ?)
RETURNING id
`)
rows, err := storage.db.QueryxContext(
ctx,
query,
user.Username,
user.Password,
user.Email,
time.Now().Add(100*year),
user.Role,
)
if err != nil {
return 0, storage.handlePgErr(err)
}
defer rows.Close()
var id int32
err = rows.StructScan(&id)
if err != nil {
return 0, storage.handlePgErr(err)
}
return id, nil
}
func (storage *UserStorage) ReadUserByEmail(ctx context.Context, email string) (*models.User, error) {
var user models.User
query := storage.db.Rebind("SELECT * from users WHERE email=? LIMIT 1")
err := storage.db.GetContext(ctx, &user, query, email)
if err != nil {
return nil, storage.handlePgErr(err)
}
return &user, nil
}
func (storage *UserStorage) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) {
var user models.User
query := storage.db.Rebind("SELECT * from users WHERE username=? LIMIT 1")
err := storage.db.GetContext(ctx, &user, query, username)
if err != nil {
return nil, storage.handlePgErr(err)
}
return &user, nil
}
func (storage *UserStorage) ReadUserById(ctx context.Context, id int32) (*models.User, error) {
var user models.User
query := storage.db.Rebind("SELECT * from users WHERE id=? LIMIT 1")
err := storage.db.GetContext(ctx, &user, query, id)
if err != nil {
return nil, storage.handlePgErr(err)
}
return &user, nil
}
func (storage *UserStorage) UpdateUser(ctx context.Context, user *models.User) error {
var err error
if user.Username != nil {
if err = user.ValidUsername(); err != nil {
return err
}
}
if user.Password != nil {
if err = user.ValidPassword(); err != nil {
return err
}
if err = user.HashPassword(); err != nil {
return err
}
}
if user.Email != nil {
if err = lib.ValidEmail(*user.Email); err != nil {
return err
}
}
if user.Role != nil {
if err = user.Role.Valid(); err != nil {
return err
}
}
query := storage.db.Rebind(`
UPDATE users
SET username = COALESCE(?, username),
hashed_pwd = COALESCE(?, hashed_pwd),
email = COALESCE(?, email),
expires_at = COALESCE(?, expires_at),
role = COALESCE(?, role)
WHERE id = ?`)
_, err = storage.db.ExecContext(
ctx,
query,
user.Username,
user.Password,
user.Email,
user.ExpiresAt,
user.Role,
user.Id,
)
if err != nil {
return storage.handlePgErr(err)
}
return nil
}
func (storage *UserStorage) DeleteUser(ctx context.Context, id int32) error {
query := storage.db.Rebind("UPDATE users SET expired_at=now() WHERE id = ?")
_, err := storage.db.ExecContext(ctx, query, id)
if err != nil {
return storage.handlePgErr(err)
}
return nil
}
func (storage *UserStorage) handlePgErr(err error) error {
var pgErr *pgconn.PgError
if !errors.As(err, &pgErr) {
storage.logger.DPanic("unexpected error from postgres", zap.String("err", err.Error()))
return lib.ErrUnexpected
}
if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) {
return errors.New("unique key violation") // FIXME
}
storage.logger.DPanic("unexpected internal error from postgres", zap.String("err", err.Error()))
return lib.ErrInternal
}