2024-08-14 10:36:43 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2024-08-14 15:24:57 +00:00
|
|
|
"git.sch9.ru/new_gate/ms-auth/internal/lib"
|
|
|
|
"git.sch9.ru/new_gate/ms-auth/internal/models"
|
2024-08-14 10:36:43 +00:00
|
|
|
"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
|
|
|
|
}
|