NOISSUE - Allow sorting (#3052)

Signed-off-by: Musilah <nataleigh.nk@gmail.com>
This commit is contained in:
Nataly Musilah
2025-08-18 15:54:26 +03:00
committed by GitHub
parent b942a42271
commit 2e3f52fdbf
16 changed files with 149 additions and 19 deletions
+6 -4
View File
@@ -28,10 +28,12 @@ const (
LimitKey = "limit"
OnlyTotal = "only_total"
NameOrder = "name"
IDOrder = "id"
AscDir = "asc"
DescDir = "desc"
NameOrder = "name"
IDOrder = "id"
AscDir = "asc"
DescDir = "desc"
UpdatedAtOrder = "updated_at"
CreatedAtOrder = "created_at"
MetadataKey = "metadata"
NameKey = "name"
+10
View File
@@ -93,6 +93,16 @@ func (req listChannelsReq) validate() error {
return apiutil.ErrNameSize
}
switch req.Order {
case "", api.NameOrder, api.CreatedAtOrder, api.UpdatedAtOrder:
default:
return apiutil.ErrInvalidOrder
}
if req.Dir != "" && (req.Dir != api.DescDir && req.Dir != api.AscDir) {
return apiutil.ErrInvalidDirection
}
return nil
}
+10
View File
@@ -85,6 +85,16 @@ func (req listClientsReq) validate() error {
return apiutil.ErrNameSize
}
switch req.Order {
case "", api.NameOrder, api.CreatedAtOrder, api.UpdatedAtOrder:
default:
return apiutil.ErrInvalidOrder
}
if req.Dir != "" && (req.Dir != api.DescDir && req.Dir != api.AscDir) {
return apiutil.ErrInvalidDirection
}
return nil
}
+10
View File
@@ -69,6 +69,16 @@ type listDomainsReq struct {
}
func (req listDomainsReq) validate() error {
switch req.Order {
case "", api.NameOrder, api.CreatedAtOrder, api.UpdatedAtOrder:
default:
return apiutil.ErrInvalidOrder
}
if req.Dir != "" && (req.Dir != api.DescDir && req.Dir != api.AscDir) {
return apiutil.ErrInvalidDirection
}
return nil
}
+11
View File
@@ -284,6 +284,15 @@ func decodePageMeta(r *http.Request) (groups.PageMeta, error) {
return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
}
order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder)
if err != nil {
return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
}
dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DescDir)
if err != nil {
return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
}
ret := groups.PageMeta{
Offset: offset,
Limit: limit,
@@ -297,6 +306,8 @@ func decodePageMeta(r *http.Request) (groups.PageMeta, error) {
AccessType: accessType,
RootGroup: rootGroup,
OnlyTotal: ot,
Order: order,
Dir: dir,
}
return ret, nil
}
+13 -1
View File
@@ -34,6 +34,8 @@ func TestDecodeListGroupsRequest(t *testing.T) {
PageMeta: groups.PageMeta{
Limit: 10,
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
},
err: nil,
@@ -54,6 +56,8 @@ func TestDecodeListGroupsRequest(t *testing.T) {
"test": "test",
},
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
},
err: nil,
@@ -166,13 +170,15 @@ func TestDecodeListChildrenRequest(t *testing.T) {
PageMeta: groups.PageMeta{
Limit: 10,
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
},
err: nil,
},
{
desc: "valid request with all parameters",
url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=-1&member_kind=random&permission=random&list_perms=true",
url: "http://localhost:8080?status=enabled&offset=10&limit=10&name=random&metadata={\"test\":\"test\"}&level=2&parent_id=random&tree=true&dir=desc&member_kind=random&permission=random&list_perms=true",
header: map[string][]string{
"Authorization": {"Bearer 123"},
},
@@ -188,6 +194,8 @@ func TestDecodeListChildrenRequest(t *testing.T) {
"test": "test",
},
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
},
err: nil,
@@ -227,6 +235,8 @@ func TestDecodePageMeta(t *testing.T) {
resp: groups.PageMeta{
Limit: 10,
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
err: nil,
},
@@ -242,6 +252,8 @@ func TestDecodePageMeta(t *testing.T) {
"test": "test",
},
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
err: nil,
},
+8
View File
@@ -2034,6 +2034,8 @@ func TestListChildrenGroupsEndpoint(t *testing.T) {
Limit: 1,
Offset: 0,
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
svcRes: groups.Page{
PageMeta: groups.PageMeta{
@@ -2056,6 +2058,8 @@ func TestListChildrenGroupsEndpoint(t *testing.T) {
Limit: 1,
Offset: 0,
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
authnErr: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
@@ -2072,6 +2076,8 @@ func TestListChildrenGroupsEndpoint(t *testing.T) {
Limit: 1,
Offset: 0,
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
status: http.StatusUnauthorized,
err: apiutil.ErrBearerToken,
@@ -2094,6 +2100,8 @@ func TestListChildrenGroupsEndpoint(t *testing.T) {
Limit: 1,
Offset: 0,
Actions: []string{},
Dir: "desc",
Order: "updated_at",
},
svcRes: groups.Page{},
svcErr: svcerr.ErrAuthorization,
+11
View File
@@ -66,6 +66,17 @@ func (req listGroupsReq) validate() error {
if req.userID != "" && req.groupID != "" {
return apiutil.ErrMultipleEntitiesFilter
}
switch req.Order {
case "", api.NameOrder, api.CreatedAtOrder, api.UpdatedAtOrder:
default:
return apiutil.ErrInvalidOrder
}
if req.Dir != "" && (req.Dir != api.DescDir && req.Dir != api.AscDir) {
return apiutil.ErrInvalidDirection
}
return nil
}
+2
View File
@@ -11,6 +11,8 @@ type PageMeta struct {
OnlyTotal bool `json:"only_total"`
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
Dir string `json:"dir,omitempty"`
Order string `json:"order,omitempty"`
Path string `json:"path,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Tag string `json:"tag,omitempty"`
+12 -3
View File
@@ -11,6 +11,7 @@ import (
"strings"
"time"
api "github.com/absmach/supermq/api/http"
groups "github.com/absmach/supermq/groups"
"github.com/absmach/supermq/internal/nullable"
"github.com/absmach/supermq/pkg/errors"
@@ -408,11 +409,19 @@ func (repo groupRepository) RetrieveByIDAndUser(ctx context.Context, domainID, u
}
func (repo groupRepository) RetrieveAll(ctx context.Context, pm groups.PageMeta) (groups.Page, error) {
var q string
query := buildQuery(pm)
q = fmt.Sprintf(`SELECT DISTINCT g.id, g.domain_id, tags, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description,
g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g %s ORDER BY g.created_at LIMIT :limit OFFSET :offset;`, query)
orderClause := ""
switch pm.Order {
case "name", "created_at", "updated_at":
orderClause = fmt.Sprintf("ORDER BY g.%s", pm.Order)
if pm.Dir == api.AscDir || pm.Dir == api.DescDir {
orderClause = fmt.Sprintf("%s %s", orderClause, pm.Dir)
}
}
q := fmt.Sprintf(`SELECT DISTINCT g.id, g.domain_id, tags, COALESCE(g.parent_id, '') AS parent_id, g.name, g.description,
g.metadata, g.created_at, g.updated_at, g.updated_by, g.status FROM groups g %s %s LIMIT :limit OFFSET :offset;`, query, orderClause)
dbPageMeta, err := toDBGroupPageMeta(pm)
if err != nil {
+13
View File
@@ -707,6 +707,8 @@ func TestRetrieveAll(t *testing.T) {
PageMeta: groups.PageMeta{
Offset: 0,
Limit: 10,
Order: "created_at",
Dir: "asc",
},
},
response: groups.Page{
@@ -725,6 +727,8 @@ func TestRetrieveAll(t *testing.T) {
PageMeta: groups.PageMeta{
Offset: 10,
Limit: 10,
Order: "created_at",
Dir: "asc",
},
},
response: groups.Page{
@@ -743,6 +747,9 @@ func TestRetrieveAll(t *testing.T) {
PageMeta: groups.PageMeta{
Offset: 0,
Limit: 50,
Order: "created_at",
Dir: "asc",
},
},
response: groups.Page{
@@ -761,6 +768,8 @@ func TestRetrieveAll(t *testing.T) {
PageMeta: groups.PageMeta{
Offset: 50,
Limit: 50,
Order: "created_at",
Dir: "asc",
},
},
response: groups.Page{
@@ -779,6 +788,8 @@ func TestRetrieveAll(t *testing.T) {
PageMeta: groups.PageMeta{
Offset: 1000,
Limit: 50,
Order: "created_at",
Dir: "asc",
},
},
response: groups.Page{
@@ -797,6 +808,8 @@ func TestRetrieveAll(t *testing.T) {
PageMeta: groups.PageMeta{
Offset: 170,
Limit: 50,
Order: "created_at",
Dir: "asc",
},
},
response: groups.Page{
+24 -8
View File
@@ -322,12 +322,16 @@ func TestListGroups(t *testing.T) {
domainID: domainID,
token: validToken,
pageMeta: sdk.PageMetadata{
Offset: offset,
Limit: 100,
Offset: offset,
Limit: 100,
Order: "created_at",
Direction: "asc",
},
svcReq: groups.PageMeta{
Offset: offset,
Limit: 100,
Order: "created_at",
Dir: "asc",
Actions: []string{},
},
svcRes: groups.Page{
@@ -381,12 +385,16 @@ func TestListGroups(t *testing.T) {
domainID: domainID,
token: validToken,
pageMeta: sdk.PageMetadata{
Offset: offset,
Limit: 0,
Offset: offset,
Limit: 0,
Order: "created_at",
Direction: "asc",
},
svcReq: groups.PageMeta{
Offset: offset,
Limit: 10,
Order: "created_at",
Dir: "asc",
Actions: []string{},
},
svcRes: groups.Page{
@@ -423,8 +431,10 @@ func TestListGroups(t *testing.T) {
domainID: domainID,
token: validToken,
pageMeta: sdk.PageMetadata{
Offset: 0,
Limit: 10,
Offset: 0,
Limit: 10,
Order: "created_at",
Direction: "asc",
Metadata: sdk.Metadata{
"name": "user_89",
},
@@ -432,6 +442,8 @@ func TestListGroups(t *testing.T) {
svcReq: groups.PageMeta{
Offset: 0,
Limit: 10,
Order: "created_at",
Dir: "asc",
Metadata: groups.Metadata{
"name": "user_89",
},
@@ -474,12 +486,16 @@ func TestListGroups(t *testing.T) {
domainID: domainID,
token: validToken,
pageMeta: sdk.PageMetadata{
Offset: offset,
Limit: limit,
Offset: offset,
Limit: limit,
Order: "created_at",
Direction: "asc",
},
svcReq: groups.PageMeta{
Offset: offset,
Limit: limit,
Order: "created_at",
Dir: "asc",
Actions: []string{},
},
svcRes: groups.Page{
+1 -1
View File
@@ -743,7 +743,7 @@ func TestListUsers(t *testing.T) {
},
},
token: validToken,
query: "order=name",
query: "order=username",
status: http.StatusOK,
authnRes: smqauthn.Session{UserID: validID, DomainID: domainID},
err: nil,
+7
View File
@@ -99,6 +99,13 @@ func (req listUsersReq) validate() error {
if req.limit > maxLimitSize || req.limit < 1 {
return apiutil.ErrLimitSize
}
switch req.order {
case "", api.CreatedAtOrder, api.UpdatedAtOrder, api.FirstNameKey, api.LastNameKey, api.UsernameKey, api.EmailKey:
default:
return apiutil.ErrInvalidOrder
}
if req.dir != "" && (req.dir != api.AscDir && req.dir != api.DescDir) {
return apiutil.ErrInvalidDirection
}
+1 -1
View File
@@ -113,7 +113,7 @@ func (res viewUserRes) Empty() bool {
type usersPageRes struct {
pageRes
Users []viewUserRes `json:"users,omitempty"`
Users []viewUserRes `json:"users"`
}
func (res usersPageRes) Code() int {
+10 -1
View File
@@ -117,9 +117,18 @@ func (repo *userRepo) RetrieveAll(ctx context.Context, pm users.Page) (users.Use
return users.UsersPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
}
orderClause := ""
switch pm.Order {
case "first_name", "last_name", "username", "email", "created_at", "updated_at":
orderClause = fmt.Sprintf("ORDER BY u.%s", pm.Order)
if pm.Dir == api.AscDir || pm.Dir == api.DescDir {
orderClause = fmt.Sprintf("%s %s", orderClause, pm.Dir)
}
}
q := fmt.Sprintf(`SELECT u.id, u.tags, u.email, u.metadata, u.status, u.role, u.first_name, u.last_name, u.username,
u.created_at, u.updated_at, u.profile_picture, COALESCE(u.updated_by, '') AS updated_by
FROM users u %s ORDER BY u.created_at LIMIT :limit OFFSET :offset;`, query)
FROM users u %s %s LIMIT :limit OFFSET :offset;`, query, orderClause)
dbPage, err := ToDBUsersPage(pm)
if err != nil {