feat: user_rework (todo -> GetAll)
This commit is contained in:
+196
-64
@@ -8,6 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
@@ -29,6 +31,8 @@ type password struct {
|
||||
hash []byte
|
||||
}
|
||||
|
||||
var psql = sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
|
||||
func (p *password) Set(password string) error {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
@@ -42,22 +46,141 @@ func (p *password) Set(password string) error {
|
||||
|
||||
type UserStore struct {
|
||||
db *sql.DB
|
||||
|
||||
getByIDStmt *sql.Stmt
|
||||
getByEmailStmt *sql.Stmt
|
||||
getByInvitationStmt *sql.Stmt
|
||||
updateStmt *sql.Stmt
|
||||
deleteStmt *sql.Stmt
|
||||
inviteCreateStmt *sql.Stmt
|
||||
invitationDeleteStmt *sql.Stmt
|
||||
passwordResetRequestCreateStmt *sql.Stmt
|
||||
}
|
||||
|
||||
func NewUserStore(db *sql.DB) (*UserStore, error) {
|
||||
store := &UserStore{
|
||||
db: db,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
store.getByIDStmt, err = db.Prepare(`
|
||||
SELECT u.id, u.first_name, u.last_name, u.username, u.email, u.password, u.is_active, u.created_at, u.updated_at, roles.*
|
||||
FROM users u
|
||||
JOIN roles ON u.role_id = roles.id
|
||||
WHERE u.id = $1 AND u.is_active = true
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.getByEmailStmt, err = db.Prepare(`
|
||||
SELECT u.id, u.first_name, u.last_name, u.username, u.email, u.password, u.is_active, u.created_at, u.updated_at, roles.*
|
||||
FROM users u
|
||||
JOIN roles ON u.role_id = roles.id
|
||||
WHERE u.email = $1 AND u.is_active = true
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.getByInvitationStmt, err = db.Prepare(`
|
||||
SELECT u.id, u.first_name, u.last_name, u.username, u.email, u.created_at, u.updated_at, is_active
|
||||
FROM users u
|
||||
JOIN invitations i ON u.id = i.user_id
|
||||
WHERE i.token = $1 AND i.expiry > $2
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.updateStmt, err = db.Prepare(`
|
||||
UPDATE users
|
||||
SET first_name = $1, last_name = $2, username = $3, email = $4, password = $5
|
||||
WHERE id = $6
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.deleteStmt, err = db.Prepare(`
|
||||
DELETE FROM users WHERE id = $1
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.invitationDeleteStmt, err = db.Prepare(`
|
||||
DELETE FROM invitations WHERE user_id = $1
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.inviteCreateStmt, err = db.Prepare(`
|
||||
INSERT INTO invitations (user_id, token, expiry) VALUES ($1, $2, $3)
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.passwordResetRequestCreateStmt, err = db.Prepare(`
|
||||
INSERT INTO password_reset_requests (user_id, token, expiry) VALUES ($1, $2, $3)
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (s *UserStore) Close() error {
|
||||
var errs []error
|
||||
if err := s.getByIDStmt.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := s.getByEmailStmt.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := s.getByInvitationStmt.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := s.updateStmt.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := s.deleteStmt.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := s.invitationDeleteStmt.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UserStore) Create(ctx context.Context, tx *sql.Tx, user *User) error {
|
||||
query := `
|
||||
INSERT INTO users (first_name, last_name, username, email, password, role_id)
|
||||
VALUES ($1, $2, $3, $4, $5, (SELECT id FROM roles WHERE name = $6)) RETURNING id, created_at, updated_at
|
||||
`
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
role := user.Role.Name
|
||||
if role == "" {
|
||||
role = "user"
|
||||
}
|
||||
|
||||
err := tx.QueryRowContext(
|
||||
query, _, err := psql.Insert("users").
|
||||
Columns("first_name", "last_name", "username", "email", "password", "role_id").
|
||||
Values(user.FirstName, user.LastName, user.Username, user.Email, user.Password.hash, sq.Expr("(SELECT id FROM roles WHERE name = ?)", role)).
|
||||
Suffix("RETURNING id, created_at, updated_at").ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
err = tx.QueryRowContext(
|
||||
ctx,
|
||||
query,
|
||||
user.FirstName,
|
||||
@@ -81,28 +204,26 @@ func (s *UserStore) Create(ctx context.Context, tx *sql.Tx, user *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UserStore) GetAll(ctx context.Context, isAdmin bool) ([]User, error) {
|
||||
|
||||
}
|
||||
|
||||
func (s *UserStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
||||
query := `
|
||||
SELECT users.id, first_name, last_name, username, email, password, created_at, updated_at, roles.*
|
||||
FROM users
|
||||
JOIN roles ON (users.role_id = roles.id)
|
||||
WHERE users.id = $1
|
||||
AND is_active = true
|
||||
`
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
user := &User{}
|
||||
err := s.db.QueryRowContext(ctx, query, id).Scan(
|
||||
err := s.getByIDStmt.QueryRowContext(ctx, id).Scan(
|
||||
&user.ID,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
&user.Username,
|
||||
&user.Email,
|
||||
&user.Password.hash,
|
||||
&user.IsActive,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
&user.Role.ID,
|
||||
&user.RoleID,
|
||||
&user.Role.Name,
|
||||
&user.Role.Level,
|
||||
&user.Role.Description,
|
||||
@@ -115,25 +236,16 @@ func (s *UserStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *UserStore) GetByEmail(ctx context.Context, email string) (*User, error) {
|
||||
query := `
|
||||
SELECT users.id, first_name, last_name, username, email, password, created_at, updated_at, roles.*
|
||||
FROM users
|
||||
JOIN roles ON (users.role_id = roles.id)
|
||||
WHERE email = $1
|
||||
AND is_active = true
|
||||
`
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
user := &User{}
|
||||
|
||||
err := s.db.QueryRowContext(ctx, query, email).Scan(
|
||||
err := s.getByEmailStmt.QueryRowContext(ctx, email).Scan(
|
||||
&user.ID,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
@@ -142,7 +254,8 @@ func (s *UserStore) GetByEmail(ctx context.Context, email string) (*User, error)
|
||||
&user.Password.hash,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
&user.Role.ID,
|
||||
&user.IsActive,
|
||||
&user.RoleID,
|
||||
&user.Role.Name,
|
||||
&user.Role.Level,
|
||||
&user.Role.Description,
|
||||
@@ -160,17 +273,11 @@ func (s *UserStore) GetByEmail(ctx context.Context, email string) (*User, error)
|
||||
}
|
||||
|
||||
func (s *UserStore) Update(ctx context.Context, user *User) error {
|
||||
query := `
|
||||
UPDATE users
|
||||
SET first_name = $1, last_name = $2, username = $3, email = $4, password = $5
|
||||
WHERE id = $6
|
||||
`
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := s.db.ExecContext(
|
||||
_, err := s.updateStmt.ExecContext(
|
||||
ctx,
|
||||
query,
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
user.Username,
|
||||
@@ -199,10 +306,20 @@ func (s *UserStore) CreateAndInvite(ctx context.Context, user *User, token strin
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserStore) PasswordResetRequest(ctx context.Context, userID int64, token string, expiration time.Duration) error {
|
||||
return withTx(s.db, ctx, func(tx *sql.Tx) error {
|
||||
if err := s.createPasswordResetRequest(ctx, tx, userID, token, expiration); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserStore) Activate(ctx context.Context, token string) error {
|
||||
return withTx(s.db, ctx, func(tx *sql.Tx) error {
|
||||
// Find the user that this token belongs to
|
||||
user, err := getUserFromInvitation(ctx, tx, token)
|
||||
user, err := s.getUserFromInvitation(ctx, tx, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -238,12 +355,18 @@ func (s *UserStore) Delete(ctx context.Context, id int64) error {
|
||||
|
||||
// * Soft delete user
|
||||
func (s *UserStore) DeleteUser(ctx context.Context, id int64) error {
|
||||
query := `UPDATE users SET is_active = false WHERE id = $1`
|
||||
query, _, err := psql.Update("users").
|
||||
Set("is_active", false).
|
||||
Where(sq.Eq{"id": id}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := s.db.ExecContext(ctx, query, id)
|
||||
_, err = s.db.ExecContext(ctx, query, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -255,22 +378,18 @@ func (p *password) Compare(password string) error {
|
||||
return bcrypt.CompareHashAndPassword(p.hash, []byte(password))
|
||||
}
|
||||
|
||||
func getUserFromInvitation(ctx context.Context, tx *sql.Tx, token string) (*User, error) {
|
||||
query := `
|
||||
SELECT u.id, u.first_name, u.last_name, u.username, u.email, u.created_at, u.updated_at, is_active
|
||||
FROM users u
|
||||
JOIN invitations i ON u.id = i.user_id
|
||||
WHERE i.token = $1 AND i.expiry > $2
|
||||
`
|
||||
|
||||
func (s *UserStore) getUserFromInvitation(ctx context.Context, tx *sql.Tx, token string) (*User, error) {
|
||||
hashed := sha256.Sum256([]byte(token))
|
||||
hashedToken := hex.EncodeToString(hashed[:])
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
stmt := tx.Stmt(s.getByInvitationStmt)
|
||||
defer stmt.Close()
|
||||
|
||||
user := &User{}
|
||||
err := tx.QueryRowContext(ctx, query, hashedToken, time.Now()).Scan(
|
||||
err := stmt.QueryRowContext(ctx, hashedToken, time.Now()).Scan(
|
||||
&user.ID,
|
||||
&user.FirstName,
|
||||
&user.LastName,
|
||||
@@ -293,14 +412,28 @@ func getUserFromInvitation(ctx context.Context, tx *sql.Tx, token string) (*User
|
||||
}
|
||||
|
||||
func (s *UserStore) createUserInvitation(ctx context.Context, tx *sql.Tx, userID int64, exp time.Duration, token string) error {
|
||||
query := `
|
||||
INSERT INTO invitations (user_id, token, expiry)
|
||||
VALUES ($1, $2, $3)
|
||||
`
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := tx.ExecContext(ctx, query, userID, token, time.Now().Add(exp))
|
||||
stmt := tx.Stmt(s.inviteCreateStmt)
|
||||
defer stmt.Close()
|
||||
|
||||
_, err := stmt.ExecContext(ctx, userID, token, time.Now().Add(exp))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UserStore) createPasswordResetRequest(ctx context.Context, tx *sql.Tx, userID int64, token string, expiry time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
stmt := tx.Stmt(s.passwordResetRequestCreateStmt)
|
||||
defer stmt.Close()
|
||||
|
||||
_, err := stmt.ExecContext(ctx, userID, token, time.Now().Add(expiry))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -309,17 +442,14 @@ func (s *UserStore) createUserInvitation(ctx context.Context, tx *sql.Tx, userID
|
||||
}
|
||||
|
||||
func (s *UserStore) update(ctx context.Context, tx *sql.Tx, user *User) error {
|
||||
query := `
|
||||
UPDATE users
|
||||
SET first_name = $1, last_name = $2, username = $3, email = $4, is_active = $5
|
||||
WHERE id = $6
|
||||
`
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := tx.ExecContext(
|
||||
stmt := tx.Stmt(s.updateStmt)
|
||||
defer stmt.Close()
|
||||
|
||||
_, err := stmt.ExecContext(
|
||||
ctx,
|
||||
query,
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
user.Username,
|
||||
@@ -335,12 +465,13 @@ func (s *UserStore) update(ctx context.Context, tx *sql.Tx, user *User) error {
|
||||
}
|
||||
|
||||
func (s *UserStore) deleteUserInvitation(ctx context.Context, tx *sql.Tx, userID int64) error {
|
||||
query := `DELETE FROM invitations WHERE user_id = $1`
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := tx.ExecContext(ctx, query, userID)
|
||||
stmt := tx.Stmt(s.invitationDeleteStmt)
|
||||
defer stmt.Close()
|
||||
|
||||
_, err := stmt.ExecContext(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -349,12 +480,13 @@ func (s *UserStore) deleteUserInvitation(ctx context.Context, tx *sql.Tx, userID
|
||||
}
|
||||
|
||||
func (s *UserStore) delete(ctx context.Context, tx *sql.Tx, id int64) error {
|
||||
query := `DELETE FROM users WHERE id = $1`
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := tx.ExecContext(ctx, query, id)
|
||||
stmt := tx.Stmt(s.deleteStmt)
|
||||
defer stmt.Close()
|
||||
|
||||
_, err := stmt.ExecContext(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user