package repository import ( "context" "errors" "git.sch9.ru/new_gate/ms-auth/internal/models" "git.sch9.ru/new_gate/ms-auth/pkg/utils" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" "github.com/jmoiron/sqlx" "go.uber.org/zap" "time" ) type UsersRepository struct { db *sqlx.DB logger *zap.Logger } func NewUserRepository(db *sqlx.DB, logger *zap.Logger) *UsersRepository { return &UsersRepository{ db: db, logger: logger, } } const year = time.Hour * 24 * 365 const createUser = ` INSERT INTO users (username, hashed_pwd, email, expires_at, role) VALUES (?, ?, ?, ?, ?) RETURNING id ` func (storage *UsersRepository) 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 { // FIXME: get rid of mutation return 0, err } user.ExpiresAt = utils.AsTimeP(time.Now().Add(100 * year)) // FIXME: get rid of mutation query := storage.db.Rebind(createUser) rows, err := storage.db.QueryxContext( ctx, query, user.Username, user.Password, user.Email, user.ExpiresAt, user.Role, ) if err != nil { return 0, storage.handlePgErr(err) } defer rows.Close() var id int32 rows.Next() err = rows.Scan(&id) if err != nil { return 0, storage.handlePgErr(err) } return id, nil } const readUserByEmail = "SELECT * from users WHERE email=? LIMIT 1" func (storage *UsersRepository) ReadUserByEmail(ctx context.Context, email string) (*models.User, error) { var user models.User query := storage.db.Rebind(readUserByEmail) err := storage.db.GetContext(ctx, &user, query, email) if err != nil { return nil, storage.handlePgErr(err) } return &user, nil } const readUserByUsername = "SELECT * from users WHERE username=? LIMIT 1" func (storage *UsersRepository) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) { var user models.User query := storage.db.Rebind(readUserByUsername) err := storage.db.GetContext(ctx, &user, query, username) if err != nil { return nil, storage.handlePgErr(err) } return &user, nil } const readUserById = "SELECT * from users WHERE id=? LIMIT 1" func (storage *UsersRepository) ReadUserById(ctx context.Context, id int32) (*models.User, error) { var user models.User query := storage.db.Rebind(readUserById) err := storage.db.GetContext(ctx, &user, query, id) if err != nil { return nil, storage.handlePgErr(err) } return &user, nil } const updateUser = ` UPDATE users SET username = COALESCE(?, username), hashed_pwd = COALESCE(?, hashed_pwd), email = COALESCE(?, email), expires_at = COALESCE(?, expires_at), role = COALESCE(?, role) WHERE id = ? ` func (storage *UsersRepository) 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 = utils.ValidEmail(*user.Email); err != nil { return err } } if user.Role != nil { if err = user.Role.Valid(); err != nil { return err } } query := storage.db.Rebind(updateUser) _, 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 } const deleteUser = "UPDATE users SET expired_at=now() WHERE id = ?" func (storage *UsersRepository) DeleteUser(ctx context.Context, id int32) error { query := storage.db.Rebind(deleteUser) _, err := storage.db.ExecContext(ctx, query, id) if err != nil { return storage.handlePgErr(err) } return nil } func (storage *UsersRepository) 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 utils.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 utils.ErrInternal }