Add user repo and keycloak
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserState struct {
|
||||
SomeValue string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Id int `db:"id"`
|
||||
Email string `db:"email"`
|
||||
State UserState `db:"state"`
|
||||
Admin bool `db:"admin"`
|
||||
Password string `db:"password"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
func (s User) String() string {
|
||||
return fmt.Sprint("User{ ",
|
||||
"Id: ", s.Id, ", ",
|
||||
"Email: ", s.Email, ", ",
|
||||
"State: ", s.State, ", ",
|
||||
"Admin: ", s.Admin, ", ",
|
||||
"Password: ", s.Password, ", ",
|
||||
"CreatedAt: ", s.CreatedAt, ", ",
|
||||
"UpdatedAt: ", s.UpdatedAt, ", ",
|
||||
"}")
|
||||
}
|
||||
|
||||
func (s User) GetId() int {
|
||||
return s.Id
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"ersteller-lib"
|
||||
"fmt"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
|
||||
"github.com/doug-martin/goqu/v9/exp"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
connPool *pgxpool.Pool
|
||||
dialect goqu.DialectWrapper
|
||||
}
|
||||
|
||||
func NewUserRepository(connPool *pgxpool.Pool) *UserRepository {
|
||||
return &UserRepository{
|
||||
connPool: connPool,
|
||||
dialect: goqu.Dialect("postgres"),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserRepository) Create(user User) (int, error) {
|
||||
sql, args, err := r.dialect.Insert("user").
|
||||
Prepared(true).
|
||||
Rows(goqu.Record{
|
||||
|
||||
"updated_at": time.Now(),
|
||||
"email": user.Email,
|
||||
"state": r.jsonToString(user.State),
|
||||
"admin": user.Admin,
|
||||
"password": user.Password,
|
||||
}).
|
||||
Returning("id").
|
||||
ToSQL()
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating create User sql: %v", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
rows, err := r.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating User: %v", err)
|
||||
return -1, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var id int
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error scanning User: %v", err)
|
||||
return -1, err
|
||||
}
|
||||
} else {
|
||||
ersteller_lib.Error("User already exists")
|
||||
return -1, UserAlreadyExistsError{User: user}
|
||||
}
|
||||
|
||||
return id, nil
|
||||
|
||||
}
|
||||
|
||||
type UserAlreadyExistsError struct {
|
||||
User User
|
||||
}
|
||||
|
||||
func (e UserAlreadyExistsError) Error() string {
|
||||
return fmt.Sprint("User ", e.User, " already exists")
|
||||
}
|
||||
|
||||
func (r *UserRepository) getSelectColumns() []any {
|
||||
return []any{"id", "created_at", "updated_at",
|
||||
"email", "state", "admin", "password",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserRepository) Read(id int) (User, error) {
|
||||
ersteller_lib.Debug("Getting User by id ", id)
|
||||
sql, args, _ := r.dialect.From("user").
|
||||
Prepared(true).
|
||||
Select(r.getSelectColumns()...).
|
||||
Where(goqu.Ex{
|
||||
"id": id,
|
||||
}).
|
||||
ToSQL()
|
||||
|
||||
rows, err := r.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.Error("Failed to get User: ", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
item, _, err := r.rowToItem(rows, false)
|
||||
return item, err
|
||||
}
|
||||
return User{}, errors.New("no rows found")
|
||||
}
|
||||
|
||||
type UserItemScan struct {
|
||||
User
|
||||
RowId int
|
||||
Count int
|
||||
}
|
||||
|
||||
func (r *UserRepository) rowToItem(rows pgx.Rows, rowId bool) (User, int, error) {
|
||||
var item UserItemScan
|
||||
if rowId {
|
||||
err := rows.Scan(
|
||||
&item.RowId,
|
||||
&item.Count,
|
||||
&item.Id,
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
&item.Email,
|
||||
&item.State,
|
||||
&item.Admin,
|
||||
&item.Password,
|
||||
)
|
||||
if err != nil {
|
||||
return User{}, -1, err
|
||||
}
|
||||
} else {
|
||||
err := rows.Scan(
|
||||
&item.Id,
|
||||
&item.CreatedAt,
|
||||
&item.UpdatedAt,
|
||||
&item.Email,
|
||||
&item.State,
|
||||
&item.Admin,
|
||||
&item.Password,
|
||||
)
|
||||
if err != nil {
|
||||
return User{}, -1, err
|
||||
}
|
||||
}
|
||||
return User{
|
||||
Id: item.Id,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
Email: item.Email,
|
||||
State: item.State,
|
||||
Admin: item.Admin,
|
||||
Password: item.Password,
|
||||
}, item.Count, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) Update(user User) error {
|
||||
sql, args, err := r.dialect.Update("user").
|
||||
Prepared(true).
|
||||
Set(goqu.Record{
|
||||
|
||||
"updated_at": time.Now(),
|
||||
"email": user.Email,
|
||||
"state": r.jsonToString(user.State),
|
||||
"admin": user.Admin,
|
||||
"password": user.Password,
|
||||
}).
|
||||
Where(goqu.Ex{
|
||||
"id": user.Id,
|
||||
}).
|
||||
ToSQL()
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating update User sql: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.connPool.Exec(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error updating User: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) Delete(id int) error {
|
||||
sql, args, err := r.dialect.Delete("user").
|
||||
Prepared(true).
|
||||
Where(goqu.Ex{
|
||||
"id": id,
|
||||
}).
|
||||
ToSQL()
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error creating delete User sql: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.connPool.Exec(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("error deleting User: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserField string
|
||||
|
||||
const (
|
||||
UserFieldEmail UserField = "email"
|
||||
UserFieldState UserField = "state"
|
||||
UserFieldAdmin UserField = "admin"
|
||||
UserFieldPassword UserField = "password"
|
||||
)
|
||||
|
||||
type UserEmailFilter struct {
|
||||
Active bool
|
||||
Value string
|
||||
}
|
||||
|
||||
type UserAdminFilter struct {
|
||||
Active bool
|
||||
Value bool
|
||||
}
|
||||
|
||||
type UserPasswordFilter struct {
|
||||
Active bool
|
||||
Value string
|
||||
}
|
||||
|
||||
type UserOrderDirection string
|
||||
|
||||
const (
|
||||
UserOrderDirectionAsc UserOrderDirection = "asc"
|
||||
UserOrderDirectionDesc UserOrderDirection = "desc"
|
||||
)
|
||||
|
||||
type UserPaginationParams struct {
|
||||
RowId int
|
||||
PageSize int
|
||||
OrderBy UserField
|
||||
OrderDirection UserOrderDirection
|
||||
|
||||
EmailFilter UserEmailFilter
|
||||
|
||||
AdminFilter UserAdminFilter
|
||||
|
||||
PasswordFilter UserPasswordFilter
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetPage(params UserPaginationParams) ([]User, int, error) {
|
||||
var orderByWindow exp.WindowExpression
|
||||
if params.OrderDirection == UserOrderDirectionAsc {
|
||||
orderByWindow = goqu.W().OrderBy(goqu.C(string(params.OrderBy)).Asc())
|
||||
} else {
|
||||
orderByWindow = goqu.W().OrderBy(goqu.C(string(params.OrderBy)).Desc())
|
||||
}
|
||||
selectColumns := []any{
|
||||
goqu.ROW_NUMBER().Over(orderByWindow).As("row_id"),
|
||||
goqu.COUNT("*"),
|
||||
}
|
||||
selectColumns = append(selectColumns, r.getSelectColumns()...)
|
||||
whereExpressions := []goqu.Expression{
|
||||
goqu.Ex{},
|
||||
}
|
||||
whereExpressions = r.addPageFilters(params, whereExpressions)
|
||||
var colOrder exp.OrderedExpression
|
||||
if params.OrderDirection == UserOrderDirectionAsc {
|
||||
colOrder = goqu.C(string(params.OrderBy)).Asc()
|
||||
} else {
|
||||
colOrder = goqu.C(string(params.OrderBy)).Desc()
|
||||
}
|
||||
dialect := goqu.Dialect("postgres")
|
||||
innerFrom := dialect.From("user").
|
||||
Prepared(true).
|
||||
Select(selectColumns...).
|
||||
Where(whereExpressions...).
|
||||
Order(colOrder)
|
||||
|
||||
outerFrom := dialect.From(innerFrom).
|
||||
Prepared(true).
|
||||
Where(goqu.Ex{"row_id": goqu.Op{"gt": params.RowId}})
|
||||
if params.PageSize > 0 {
|
||||
outerFrom = outerFrom.Limit(uint(params.PageSize))
|
||||
}
|
||||
sql, args, _ := outerFrom.ToSQL()
|
||||
sql = strings.Replace(sql, "COUNT(*)", "COUNT(*) over()", 1)
|
||||
|
||||
rows, err := r.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed to run sql query: %v", err)
|
||||
return nil, -1, err
|
||||
}
|
||||
defer rows.Close()
|
||||
results := make([]User, 0)
|
||||
totalCount := 0
|
||||
for rows.Next() {
|
||||
parsed, count, err := r.rowToItem(rows, true)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
totalCount = count
|
||||
results = append(results, parsed)
|
||||
}
|
||||
return results, totalCount, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) addPageFilters(params UserPaginationParams, whereExpressions []goqu.Expression) []goqu.Expression {
|
||||
|
||||
if params.EmailFilter.Active {
|
||||
whereExpressions = append(whereExpressions, goqu.Ex{
|
||||
"email": goqu.Op{"ilike": fmt.Sprint("%", params.EmailFilter.Value, "%")},
|
||||
})
|
||||
}
|
||||
|
||||
if params.AdminFilter.Active {
|
||||
whereExpressions = append(whereExpressions, goqu.Ex{
|
||||
"admin": params.AdminFilter.Value,
|
||||
})
|
||||
}
|
||||
|
||||
if params.PasswordFilter.Active {
|
||||
whereExpressions = append(whereExpressions, goqu.Ex{
|
||||
"password": goqu.Op{"ilike": fmt.Sprint("%", params.PasswordFilter.Value, "%")},
|
||||
})
|
||||
}
|
||||
|
||||
return whereExpressions
|
||||
}
|
||||
|
||||
func (r *UserRepository) jsonToString(jsonData any) string {
|
||||
bytes, err := json.Marshal(jsonData)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func (u *UserRepository) DoesUserEmailExist(email string) (bool, error) {
|
||||
sql, args, _ := u.dialect.From("user").
|
||||
Prepared(true).
|
||||
Select(goqu.COUNT("email")).
|
||||
Where(goqu.Ex{"email": email}).
|
||||
ToSQL()
|
||||
|
||||
rows, err := u.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed to run sql query: %v", err)
|
||||
return false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
var count int
|
||||
err = rows.Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count == 1, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetUserId(email string) (int, error) {
|
||||
sql, args, _ := u.dialect.From("user").
|
||||
Prepared(true).
|
||||
Select("id").
|
||||
Where(goqu.Ex{"email": email}).
|
||||
ToSQL()
|
||||
|
||||
rows, err := u.connPool.Query(context.Background(), sql, args...)
|
||||
if err != nil {
|
||||
ersteller_lib.LogError("failed to run sql query: %v", err)
|
||||
return -1, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
var id int
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
return -1, errors.New("did not find user with email " + email)
|
||||
}
|
||||
|
||||
func (r *UserRepository) VerifyPassword(email string, password string) (bool, int, error) {
|
||||
userId, err := r.GetUserId(email)
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
user, err := r.Read(userId)
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
return ersteller_lib.VerifyPassword(password, user.Password), userId, nil
|
||||
}
|
||||
Reference in New Issue
Block a user