Skip to content

Commit

Permalink
Merge pull request #522 from teamhanko/feat-add-user-admin-endpoints
Browse files Browse the repository at this point in the history
feat: add query params to search users
  • Loading branch information
FreddyDevelop authored Jan 26, 2023
2 parents fd138e7 + 1a4f2bf commit 9a1e42c
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 36 deletions.
28 changes: 28 additions & 0 deletions backend/dto/admin/email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package admin

import (
"github.com/gofrs/uuid"
"github.com/teamhanko/hanko/backend/persistence/models"
"time"
)

type Email struct {
ID uuid.UUID `json:"id"`
Address string `json:"address"`
IsVerified bool `json:"is_verified"`
IsPrimary bool `json:"is_primary"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// FromEmailModel Converts the DB model to a DTO object
func FromEmailModel(email *models.Email) *Email {
return &Email{
ID: email.ID,
Address: email.Address,
IsVerified: email.Verified,
IsPrimary: email.IsPrimary(),
CreatedAt: email.CreatedAt,
UpdatedAt: email.UpdatedAt,
}
}
35 changes: 35 additions & 0 deletions backend/dto/admin/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package admin

import (
"github.com/gofrs/uuid"
"github.com/teamhanko/hanko/backend/dto"
"github.com/teamhanko/hanko/backend/persistence/models"
"time"
)

type User struct {
ID uuid.UUID `json:"id"`
WebauthnCredentials []dto.WebauthnCredentialResponse `json:"webauthn_credentials,omitempty"`
Emails []Email `json:"emails,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// FromUserModel Converts the DB model to a DTO object
func FromUserModel(model models.User) User {
credentials := make([]dto.WebauthnCredentialResponse, len(model.WebauthnCredentials))
for i := range model.WebauthnCredentials {
credentials[i] = *dto.FromWebauthnCredentialModel(&model.WebauthnCredentials[i])
}
emails := make([]Email, len(model.Emails))
for i := range model.Emails {
emails[i] = *FromEmailModel(&model.Emails[i])
}
return User{
ID: model.ID,
WebauthnCredentials: credentials,
Emails: emails,
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
56 changes: 44 additions & 12 deletions backend/handler/user_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"github.com/gofrs/uuid"
"github.com/labstack/echo/v4"
"github.com/teamhanko/hanko/backend/dto"
"github.com/teamhanko/hanko/backend/dto/admin"
"github.com/teamhanko/hanko/backend/pagination"
"github.com/teamhanko/hanko/backend/persistence"
"net/http"
"net/url"
"strconv"
"strings"
)

type UserHandlerAdmin struct {
Expand Down Expand Up @@ -41,18 +43,14 @@ func (h *UserHandlerAdmin) Delete(c echo.Context) error {
return fmt.Errorf("failed to delete user: %w", err)
}

return c.JSON(http.StatusNoContent, nil)
}

type UserPatchRequest struct {
UserId string `param:"id" validate:"required,uuid4"`
Email string `json:"email" validate:"omitempty,email"`
Verified *bool `json:"verified"`
return c.NoContent(http.StatusNoContent)
}

type UserListRequest struct {
PerPage int `query:"per_page"`
Page int `query:"page"`
PerPage int `query:"per_page"`
Page int `query:"page"`
Email string `query:"email"`
UserId string `query:"user_id"`
}

func (h *UserHandlerAdmin) List(c echo.Context) error {
Expand All @@ -70,12 +68,22 @@ func (h *UserHandlerAdmin) List(c echo.Context) error {
request.PerPage = 20
}

users, err := h.persister.GetUserPersister().List(request.Page, request.PerPage)
userId := uuid.Nil
if request.UserId != "" {
userId, err = uuid.FromString(request.UserId)
if err != nil {
return dto.NewHTTPError(http.StatusBadRequest, "failed to parse user_id as uuid").SetInternal(err)
}
}

email := strings.ToLower(request.Email)

users, err := h.persister.GetUserPersister().List(request.Page, request.PerPage, userId, email)
if err != nil {
return fmt.Errorf("failed to get list of users: %w", err)
}

userCount, err := h.persister.GetUserPersister().Count()
userCount, err := h.persister.GetUserPersister().Count(userId, email)
if err != nil {
return fmt.Errorf("failed to get total count of users: %w", err)
}
Expand All @@ -85,5 +93,29 @@ func (h *UserHandlerAdmin) List(c echo.Context) error {
c.Response().Header().Set("Link", pagination.CreateHeader(u, userCount, request.Page, request.PerPage))
c.Response().Header().Set("X-Total-Count", strconv.FormatInt(int64(userCount), 10))

return c.JSON(http.StatusOK, users)
l := make([]admin.User, len(users))
for i := range users {
l[i] = admin.FromUserModel(users[i])
}

return c.JSON(http.StatusOK, l)
}

func (h *UserHandlerAdmin) Get(c echo.Context) error {
userId, err := uuid.FromString(c.Param("id"))
if err != nil {
return dto.NewHTTPError(http.StatusBadRequest, "failed to parse userId as uuid").SetInternal(err)
}

p := h.persister.GetUserPersister()
user, err := p.Get(userId)
if err != nil {
return fmt.Errorf("failed to get user: %w", err)
}

if user == nil {
return dto.NewHTTPError(http.StatusNotFound, "user not found")
}

return c.JSON(http.StatusOK, admin.FromUserModel(*user))
}
36 changes: 30 additions & 6 deletions backend/persistence/user_persister.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ type UserPersister interface {
Create(models.User) error
Update(models.User) error
Delete(models.User) error
List(page int, perPage int) ([]models.User, error)
Count() (int, error)
List(page int, perPage int, userId uuid.UUID, email string) ([]models.User, error)
Count(userId uuid.UUID, email string) (int, error)
}

type userPersister struct {
Expand Down Expand Up @@ -87,10 +87,18 @@ func (p *userPersister) Delete(user models.User) error {
return nil
}

func (p *userPersister) List(page int, perPage int) ([]models.User, error) {
func (p *userPersister) List(page int, perPage int, userId uuid.UUID, email string) ([]models.User, error) {
users := []models.User{}

err := p.db.Q().Paginate(page, perPage).All(&users)
query := p.db.
Q().
EagerPreload("Emails", "Emails.PrimaryEmail", "WebauthnCredentials").
LeftJoin("emails", "emails.user_id = users.id")
query = p.addQueryParamsToSqlQuery(query, userId, email)
err := query.GroupBy("users.id").
Paginate(page, perPage).
All(&users)

if err != nil && errors.Is(err, sql.ErrNoRows) {
return users, nil
}
Expand All @@ -101,11 +109,27 @@ func (p *userPersister) List(page int, perPage int) ([]models.User, error) {
return users, nil
}

func (p *userPersister) Count() (int, error) {
count, err := p.db.Count(&models.User{})
func (p *userPersister) Count(userId uuid.UUID, email string) (int, error) {
query := p.db.
Q().
LeftJoin("emails", "emails.user_id = users.id")
query = p.addQueryParamsToSqlQuery(query, userId, email)
count, err := query.GroupBy("users.id").
Count(&models.User{})
if err != nil {
return 0, fmt.Errorf("failed to get user count: %w", err)
}

return count, nil
}

func (p *userPersister) addQueryParamsToSqlQuery(query *pop.Query, userId uuid.UUID, email string) *pop.Query {
if email != "" {
query = query.Where("emails.address LIKE ?", "%"+email+"%")
}
if !userId.IsNil() {
query = query.Where("users.id = ?", userId)
}

return query
}
4 changes: 3 additions & 1 deletion backend/server/admin_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func NewAdminRouter(persister persistence.Persister) *echo.Echo {
e := echo.New()
e.HideBanner = true

e.HTTPErrorHandler = dto.NewHTTPErrorHandler(dto.HTTPErrorHandlerConfig{Debug: false, Logger: e.Logger})
e.Use(middleware.RequestID())
e.Use(hankoMiddleware.GetLoggerMiddleware())

Expand All @@ -27,8 +28,9 @@ func NewAdminRouter(persister persistence.Persister) *echo.Echo {
userHandler := handler.NewUserHandlerAdmin(persister)

user := e.Group("/users")
user.DELETE("/:id", userHandler.Delete)
user.GET("", userHandler.List)
user.GET("/:id", userHandler.Get)
user.DELETE("/:id", userHandler.Delete)

auditLogHandler := handler.NewAuditLogHandler(persister)

Expand Down
4 changes: 2 additions & 2 deletions backend/test/user_persister.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (p *userPersister) Delete(user models.User) error {
return nil
}

func (p *userPersister) List(page int, perPage int) ([]models.User, error) {
func (p *userPersister) List(page int, perPage int, userId uuid.UUID, email string) ([]models.User, error) {
if len(p.users) == 0 {
return p.users, nil
}
Expand Down Expand Up @@ -81,6 +81,6 @@ func (p *userPersister) List(page int, perPage int) ([]models.User, error) {
return result[page-1], nil
}

func (p *userPersister) Count() (int, error) {
func (p *userPersister) Count(userId uuid.UUID, email string) (int, error) {
return len(p.users), nil
}
Loading

0 comments on commit 9a1e42c

Please sign in to comment.