Add an user management page for admins
This commit is contained in:
41
internal/apitest/get_users_test.go
Normal file
41
internal/apitest/get_users_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package apitest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"git.artlef.fr/bibliomane/internal/dto"
|
||||
"git.artlef.fr/bibliomane/internal/testutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetUsersHandler_OK(t *testing.T) {
|
||||
status, result := testFetchUsers(t, "15", "0")
|
||||
assert.Equal(t, http.StatusOK, status)
|
||||
assert.Equal(t, int64(3), result.Count)
|
||||
assert.Equal(t, 3, len(result.Users))
|
||||
}
|
||||
|
||||
func TestGetUsersHandler_Limit(t *testing.T) {
|
||||
status, result := testFetchUsers(t, "2", "0")
|
||||
assert.Equal(t, http.StatusOK, status)
|
||||
assert.Equal(t, int64(3), result.Count)
|
||||
assert.Equal(t, 2, len(result.Users))
|
||||
}
|
||||
|
||||
func TestGetUsersHandler_Forbidden(t *testing.T) {
|
||||
status, _ := testutils.TestFetchModel[dto.UsersGet](t, "/ws/admin/users", "10", "0")
|
||||
assert.Equal(t, http.StatusForbidden, status)
|
||||
}
|
||||
|
||||
func TestGetUsersHandler_BookCountOK(t *testing.T) {
|
||||
status, result := testFetchUsers(t, "1", "1")
|
||||
assert.Equal(t, http.StatusOK, status)
|
||||
assert.Equal(t, "demo2", result.Users[0].Name)
|
||||
assert.Equal(t, int64(1), result.Users[0].AddedBooksCount)
|
||||
assert.Equal(t, int64(3), result.Users[0].UserBooksCount)
|
||||
}
|
||||
|
||||
func testFetchUsers(t *testing.T, limit string, offset string) (int, dto.UsersGet) {
|
||||
return testutils.TestFetchAdminModel[dto.UsersGet](t, "/ws/admin/users", limit, offset)
|
||||
}
|
||||
@@ -73,3 +73,16 @@ type CollectionListBookItemGet struct {
|
||||
Title string `json:"title"`
|
||||
CoverPath string `json:"coverPath"`
|
||||
}
|
||||
|
||||
type UsersGet struct {
|
||||
Count int64 `json:"count"`
|
||||
Users []UserGet `json:"users"`
|
||||
}
|
||||
|
||||
type UserGet struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Admin bool `json:"admin"`
|
||||
AddedBooksCount int64 `json:"addedbookscount"`
|
||||
UserBooksCount int64 `json:"userbookscount"`
|
||||
}
|
||||
|
||||
33
internal/query/queryusers.go
Normal file
33
internal/query/queryusers.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"git.artlef.fr/bibliomane/internal/dto"
|
||||
"git.artlef.fr/bibliomane/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func FetchAllUsers(db *gorm.DB, limit int, offset int) ([]dto.UserGet, error) {
|
||||
var books []dto.UserGet
|
||||
query := fetchAllUserQuery(db)
|
||||
query = query.Limit(limit)
|
||||
query = query.Offset(offset)
|
||||
query = query.Order("users.id DESC")
|
||||
res := query.Find(&books)
|
||||
return books, res.Error
|
||||
}
|
||||
|
||||
func FetchAllUsersCount(db *gorm.DB) (int64, error) {
|
||||
var count int64
|
||||
query := fetchAllUserQuery(db)
|
||||
res := query.Count(&count)
|
||||
return count, res.Error
|
||||
}
|
||||
|
||||
func fetchAllUserQuery(db *gorm.DB) *gorm.DB {
|
||||
query := db.Model(&model.User{})
|
||||
query = query.Select("users.id, users.name, users.admin, count(distinct books.id) as added_books_count, count(distinct user_books.id) as user_books_count")
|
||||
query = query.Joins("left join books on (books.added_by_id = users.id)")
|
||||
query = query.Joins("left join user_books on user_books.user_id = users.id")
|
||||
query = query.Group("users.id, users.name")
|
||||
return query
|
||||
}
|
||||
@@ -48,5 +48,5 @@ func PostLoginHandler(ac appcontext.AppContext) {
|
||||
gin.H{"error": fmt.Errorf("Error when generating JWT token: %w", err)})
|
||||
return
|
||||
}
|
||||
ac.C.JSON(http.StatusOK, gin.H{"message": i18nresource.GetTranslatedMessage(&ac, "AuthenticationSuccess"), "token": jwtToken})
|
||||
ac.C.JSON(http.StatusOK, gin.H{"message": i18nresource.GetTranslatedMessage(&ac, "AuthenticationSuccess"), "admin": admin, "token": jwtToken})
|
||||
}
|
||||
|
||||
34
internal/routes/usersget.go
Normal file
34
internal/routes/usersget.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.artlef.fr/bibliomane/internal/appcontext"
|
||||
"git.artlef.fr/bibliomane/internal/dto"
|
||||
"git.artlef.fr/bibliomane/internal/myvalidator"
|
||||
"git.artlef.fr/bibliomane/internal/query"
|
||||
)
|
||||
|
||||
func GetUsersHandler(ac appcontext.AppContext) {
|
||||
limit, err := ac.GetQueryLimit()
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
offset, err := ac.GetQueryOffset()
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
users, err := query.FetchAllUsers(ac.Db, limit, offset)
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
count, err := query.FetchAllUsersCount(ac.Db)
|
||||
if err != nil {
|
||||
myvalidator.ReturnErrorsAsJsonResponse(&ac, err)
|
||||
return
|
||||
}
|
||||
ac.C.JSON(http.StatusOK, dto.UsersGet{Count: count, Users: users})
|
||||
}
|
||||
@@ -108,6 +108,10 @@ func Setup(config *config.Config) *gin.Engine {
|
||||
ws.DELETE("/collection/item/:id", func(c *gin.Context) {
|
||||
routes.DeleteCollectionBookHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
|
||||
})
|
||||
ws.GET("/admin/users", func(c *gin.Context) {
|
||||
routes.GetUsersHandler(appcontext.AppContext{C: c, Db: db, I18n: bundle, Config: config})
|
||||
})
|
||||
|
||||
r.Static("/static/bookcover", config.ImageFolderPath)
|
||||
|
||||
folders := []string{"assets", "css", "image"}
|
||||
|
||||
@@ -44,6 +44,15 @@ func ConnectDemo2User(router *gin.Engine) string {
|
||||
return connectUser(router, loginJson)
|
||||
}
|
||||
|
||||
func ConnectAdminUser(router *gin.Engine) string {
|
||||
loginJson :=
|
||||
`{
|
||||
"username": "admin",
|
||||
"password":"demopw"
|
||||
}`
|
||||
return connectUser(router, loginJson)
|
||||
}
|
||||
|
||||
func connectUser(router *gin.Engine, loginJson string) string {
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/ws/auth/login", strings.NewReader(loginJson))
|
||||
@@ -87,6 +96,18 @@ func TestFetchOneModel[T any](t *testing.T, urlpath string, id string) (int, T)
|
||||
|
||||
func TestFetchModel[T any](t *testing.T, urlpath string, limit string, offset string) (int, T) {
|
||||
router := TestSetup()
|
||||
token := ConnectDemoUser(router)
|
||||
return testFetchModelWithUser[T](t, urlpath, limit, offset, token)
|
||||
}
|
||||
|
||||
func TestFetchAdminModel[T any](t *testing.T, urlpath string, limit string, offset string) (int, T) {
|
||||
router := TestSetup()
|
||||
token := ConnectAdminUser(router)
|
||||
return testFetchModelWithUser[T](t, urlpath, limit, offset, token)
|
||||
}
|
||||
|
||||
func testFetchModelWithUser[T any](t *testing.T, urlpath string, limit string, offset string, token string) (int, T) {
|
||||
router := TestSetup()
|
||||
|
||||
u, err := url.Parse(urlpath)
|
||||
if err != nil {
|
||||
@@ -107,7 +128,6 @@ func TestFetchModel[T any](t *testing.T, urlpath string, limit string, offset st
|
||||
q.Set("lang", "fr")
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
token := ConnectDemoUser(router)
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
Reference in New Issue
Block a user