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 }