refactor: updated store to use squirrel for complex querries
This commit is contained in:
@@ -28,6 +28,11 @@ func (m *MockUserStore) Activate(ctx context.Context, token string) error {
|
|||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockUserStore) GetAll(ctx context.Context, userID int64, roleLevel int64) ([]*User, error) {
|
||||||
|
args := m.Called(userID, roleLevel)
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockUserStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
func (m *MockUserStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
||||||
args := m.Called(id)
|
args := m.Called(id)
|
||||||
return nil, args.Error(1)
|
return nil, args.Error(1)
|
||||||
@@ -53,6 +58,11 @@ func (m *MockUserStore) PasswordResetRequest(ctx context.Context, userID int64,
|
|||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockUserStore) ResetPassword(ctx context.Context, token string, password string) error {
|
||||||
|
args := m.Called(ctx, token, password)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockUserStore) Delete(ctx context.Context, id int64) error {
|
func (m *MockUserStore) Delete(ctx context.Context, id int64) error {
|
||||||
args := m.Called(id)
|
args := m.Called(id)
|
||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ type Storage struct {
|
|||||||
Users interface {
|
Users interface {
|
||||||
Create(context.Context, *sql.Tx, *User) error
|
Create(context.Context, *sql.Tx, *User) error
|
||||||
Activate(context.Context, string) error
|
Activate(context.Context, string) error
|
||||||
|
GetAll(context.Context, int64, int64) ([]*User, error)
|
||||||
GetByID(context.Context, int64) (*User, error)
|
GetByID(context.Context, int64) (*User, error)
|
||||||
GetByEmail(context.Context, string) (*User, error)
|
GetByEmail(context.Context, string) (*User, error)
|
||||||
Update(context.Context, *User) error
|
Update(context.Context, *User) error
|
||||||
CreateAndInvite(context.Context, *User, string, time.Duration) error
|
CreateAndInvite(context.Context, *User, string, time.Duration) error
|
||||||
PasswordResetRequest(context.Context, int64, string, time.Duration) error
|
PasswordResetRequest(context.Context, int64, string, time.Duration) error
|
||||||
|
ResetPassword(context.Context, string, string) error
|
||||||
Delete(context.Context, int64) error
|
Delete(context.Context, int64) error
|
||||||
DeleteUser(context.Context, int64) error
|
DeleteUser(context.Context, int64) error
|
||||||
}
|
}
|
||||||
|
|||||||
+149
-11
@@ -12,6 +12,12 @@ import (
|
|||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
adminLevel = 3
|
||||||
|
modLevel = 2
|
||||||
|
userLevel = 1
|
||||||
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
@@ -50,10 +56,12 @@ type UserStore struct {
|
|||||||
getByIDStmt *sql.Stmt
|
getByIDStmt *sql.Stmt
|
||||||
getByEmailStmt *sql.Stmt
|
getByEmailStmt *sql.Stmt
|
||||||
getByInvitationStmt *sql.Stmt
|
getByInvitationStmt *sql.Stmt
|
||||||
|
getUserFromPasswordTokenStmt *sql.Stmt
|
||||||
updateStmt *sql.Stmt
|
updateStmt *sql.Stmt
|
||||||
deleteStmt *sql.Stmt
|
deleteStmt *sql.Stmt
|
||||||
inviteCreateStmt *sql.Stmt
|
inviteCreateStmt *sql.Stmt
|
||||||
invitationDeleteStmt *sql.Stmt
|
invitationDeleteStmt *sql.Stmt
|
||||||
|
deletePasswordResetRequestStmt *sql.Stmt
|
||||||
passwordResetRequestCreateStmt *sql.Stmt
|
passwordResetRequestCreateStmt *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,10 +102,20 @@ func NewUserStore(db *sql.DB) (*UserStore, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.getUserFromPasswordTokenStmt, 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
|
||||||
|
FROM users u
|
||||||
|
JOIN password_reset_requests pr ON u.id = pr.user_id
|
||||||
|
WHERE u.id = $1 AND pr.expiry > $2
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
store.updateStmt, err = db.Prepare(`
|
store.updateStmt, err = db.Prepare(`
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET first_name = $1, last_name = $2, username = $3, email = $4, password = $5
|
SET first_name = $1, last_name = $2, username = $3, email = $4, is_active = $5, updated_at = $6
|
||||||
WHERE id = $6
|
WHERE id = $7
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -117,6 +135,13 @@ func NewUserStore(db *sql.DB) (*UserStore, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.deletePasswordResetRequestStmt, err = db.Prepare(`
|
||||||
|
DELETE FROM password_reset_requests WHERE user_id = $1
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
store.inviteCreateStmt, err = db.Prepare(`
|
store.inviteCreateStmt, err = db.Prepare(`
|
||||||
INSERT INTO invitations (user_id, token, expiry) VALUES ($1, $2, $3)
|
INSERT INTO invitations (user_id, token, expiry) VALUES ($1, $2, $3)
|
||||||
`)
|
`)
|
||||||
@@ -169,7 +194,7 @@ func (s *UserStore) Create(ctx context.Context, tx *sql.Tx, user *User) error {
|
|||||||
role = "user"
|
role = "user"
|
||||||
}
|
}
|
||||||
|
|
||||||
query, _, err := psql.Insert("users").
|
query, args, err := psql.Insert("users").
|
||||||
Columns("first_name", "last_name", "username", "email", "password", "role_id").
|
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)).
|
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()
|
Suffix("RETURNING id, created_at, updated_at").ToSql()
|
||||||
@@ -183,12 +208,7 @@ func (s *UserStore) Create(ctx context.Context, tx *sql.Tx, user *User) error {
|
|||||||
err = tx.QueryRowContext(
|
err = tx.QueryRowContext(
|
||||||
ctx,
|
ctx,
|
||||||
query,
|
query,
|
||||||
user.FirstName,
|
args...,
|
||||||
user.LastName,
|
|
||||||
user.Username,
|
|
||||||
user.Email,
|
|
||||||
user.Password.hash,
|
|
||||||
role,
|
|
||||||
).Scan(&user.ID, &user.CreatedAt, &user.UpdatedAt)
|
).Scan(&user.ID, &user.CreatedAt, &user.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
@@ -204,8 +224,58 @@ func (s *UserStore) Create(ctx context.Context, tx *sql.Tx, user *User) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserStore) GetAll(ctx context.Context, isAdmin bool) ([]User, error) {
|
func (s *UserStore) GetAll(ctx context.Context, userID int64, roleLevel int64) ([]*User, error) {
|
||||||
|
|
||||||
|
var whereConditions []sq.Sqlizer
|
||||||
|
|
||||||
|
whereConditions = append(whereConditions, sq.Eq{"u.is_active": true})
|
||||||
|
|
||||||
|
if roleLevel == modLevel {
|
||||||
|
whereConditions = append(whereConditions, sq.Or{
|
||||||
|
sq.Lt{"roles.level": roleLevel},
|
||||||
|
sq.Eq{"u.id": userID},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
query, args, err := psql.Select("u.id", "u.first_name", "u.last_name", "u.username", "u.email", "u.is_active", "u.created_at", "u.updated_at", "roles.*").
|
||||||
|
From("users u").Join("roles ON u.role_id = roles.id").Where(sq.And(
|
||||||
|
whereConditions,
|
||||||
|
)).OrderBy("u.created_at DESC").
|
||||||
|
ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := s.db.QueryContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
users := make([]*User, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
user := &User{}
|
||||||
|
err := rows.Scan(
|
||||||
|
&user.ID,
|
||||||
|
&user.FirstName,
|
||||||
|
&user.LastName,
|
||||||
|
&user.Username,
|
||||||
|
&user.Email,
|
||||||
|
&user.IsActive,
|
||||||
|
&user.CreatedAt,
|
||||||
|
&user.UpdatedAt,
|
||||||
|
&user.RoleID,
|
||||||
|
&user.Role.Name,
|
||||||
|
&user.Role.Level,
|
||||||
|
&user.Role.Description,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
func (s *UserStore) GetByID(ctx context.Context, id int64) (*User, error) {
|
||||||
@@ -252,9 +322,9 @@ func (s *UserStore) GetByEmail(ctx context.Context, email string) (*User, error)
|
|||||||
&user.Username,
|
&user.Username,
|
||||||
&user.Email,
|
&user.Email,
|
||||||
&user.Password.hash,
|
&user.Password.hash,
|
||||||
|
&user.IsActive,
|
||||||
&user.CreatedAt,
|
&user.CreatedAt,
|
||||||
&user.UpdatedAt,
|
&user.UpdatedAt,
|
||||||
&user.IsActive,
|
|
||||||
&user.RoleID,
|
&user.RoleID,
|
||||||
&user.Role.Name,
|
&user.Role.Name,
|
||||||
&user.Role.Level,
|
&user.Role.Level,
|
||||||
@@ -316,6 +386,26 @@ func (s *UserStore) PasswordResetRequest(ctx context.Context, userID int64, toke
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserStore) ResetPassword(ctx context.Context, token string, newPassword string) error {
|
||||||
|
return withTx(s.db, ctx, func(tx *sql.Tx) error {
|
||||||
|
user, err := s.getUserFromPaswordToken(ctx, tx, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Password.Set(newPassword)
|
||||||
|
if err := s.update(ctx, tx, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.deletePasswordResetRequest(ctx, tx, user.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserStore) Activate(ctx context.Context, token string) error {
|
func (s *UserStore) Activate(ctx context.Context, token string) error {
|
||||||
return withTx(s.db, ctx, func(tx *sql.Tx) error {
|
return withTx(s.db, ctx, func(tx *sql.Tx) error {
|
||||||
// Find the user that this token belongs to
|
// Find the user that this token belongs to
|
||||||
@@ -441,6 +531,39 @@ func (s *UserStore) createPasswordResetRequest(ctx context.Context, tx *sql.Tx,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserStore) getUserFromPaswordToken(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.getUserFromPasswordTokenStmt)
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
user := &User{}
|
||||||
|
err := stmt.QueryRowContext(ctx, hashedToken, time.Now()).Scan(
|
||||||
|
&user.ID,
|
||||||
|
&user.FirstName,
|
||||||
|
&user.LastName,
|
||||||
|
&user.Username,
|
||||||
|
&user.Email,
|
||||||
|
&user.CreatedAt,
|
||||||
|
&user.UpdatedAt,
|
||||||
|
&user.IsActive,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
switch err {
|
||||||
|
case sql.ErrNoRows:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserStore) update(ctx context.Context, tx *sql.Tx, user *User) error {
|
func (s *UserStore) update(ctx context.Context, tx *sql.Tx, user *User) error {
|
||||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -479,6 +602,21 @@ func (s *UserStore) deleteUserInvitation(ctx context.Context, tx *sql.Tx, userID
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserStore) deletePasswordResetRequest(ctx context.Context, tx *sql.Tx, userID int64) error {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stmt := tx.Stmt(s.deletePasswordResetRequestStmt)
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
_, err := stmt.ExecContext(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserStore) delete(ctx context.Context, tx *sql.Tx, id int64) error {
|
func (s *UserStore) delete(ctx context.Context, tx *sql.Tx, id int64) error {
|
||||||
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
Reference in New Issue
Block a user