mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
SMQ-3338 - Add created at period filter to entities (#3339)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
grpcTokenV1 "github.com/absmach/supermq/api/grpc/token/v1"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
@@ -56,6 +57,7 @@ var (
|
||||
testReferer = "http://localhost"
|
||||
domainID = testsutil.GenerateUUID(&testing.T{})
|
||||
verifiedSession = smqauthn.Session{UserID: validID, DomainID: domainID, Verified: true}
|
||||
validTimeStamp = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
const contentType = "application/json"
|
||||
@@ -846,6 +848,125 @@ func TestListUsers(t *testing.T) {
|
||||
authnRes: verifiedSession,
|
||||
err: apiutil.ErrInvalidQueryParams,
|
||||
},
|
||||
{
|
||||
desc: "list users with created_from",
|
||||
token: validToken,
|
||||
query: "created_from=2024-01-01T00:00:00Z",
|
||||
pageMeta: users.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Dir: api.DefDir,
|
||||
Order: api.DefOrder,
|
||||
CreatedFrom: validTimeStamp,
|
||||
},
|
||||
listUsersResponse: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 1,
|
||||
},
|
||||
Users: []users.User{user},
|
||||
},
|
||||
status: http.StatusOK,
|
||||
authnRes: verifiedSession,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list users with created_to",
|
||||
token: validToken,
|
||||
query: "created_to=2024-01-01T00:00:00Z",
|
||||
pageMeta: users.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Order: api.DefOrder,
|
||||
Dir: api.DefDir,
|
||||
CreatedTo: validTimeStamp,
|
||||
},
|
||||
listUsersResponse: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 1,
|
||||
},
|
||||
Users: []users.User{user},
|
||||
},
|
||||
status: http.StatusOK,
|
||||
authnRes: verifiedSession,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list users with both created_from and created_to",
|
||||
token: validToken,
|
||||
query: "created_from=2024-01-01T00:00:00Z&created_to=2024-01-01T00:00:00Z",
|
||||
pageMeta: users.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Order: api.DefOrder,
|
||||
Dir: api.DefDir,
|
||||
CreatedFrom: validTimeStamp,
|
||||
CreatedTo: validTimeStamp,
|
||||
},
|
||||
listUsersResponse: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 1,
|
||||
},
|
||||
Users: []users.User{user},
|
||||
},
|
||||
status: http.StatusOK,
|
||||
authnRes: verifiedSession,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list users with invalid created_from format",
|
||||
token: validToken,
|
||||
query: "created_from=invalid-date",
|
||||
status: http.StatusBadRequest,
|
||||
authnRes: verifiedSession,
|
||||
err: apiutil.ErrInvalidQueryParams,
|
||||
},
|
||||
{
|
||||
desc: "list users with invalid created_to format",
|
||||
token: validToken,
|
||||
query: "created_to=invalid-date",
|
||||
status: http.StatusBadRequest,
|
||||
authnRes: verifiedSession,
|
||||
err: apiutil.ErrInvalidQueryParams,
|
||||
},
|
||||
{
|
||||
desc: "list users with duplicate created_from",
|
||||
token: validToken,
|
||||
query: "created_from=2024-01-01T00:00:00Z&created_from=2024-01-02T00:00:00Z",
|
||||
status: http.StatusBadRequest,
|
||||
authnRes: verifiedSession,
|
||||
err: apiutil.ErrInvalidQueryParams,
|
||||
},
|
||||
{
|
||||
desc: "list users with duplicate created_to",
|
||||
token: validToken,
|
||||
query: "created_to=2024-12-31T23:59:59Z&created_to=2024-12-30T23:59:59Z",
|
||||
status: http.StatusBadRequest,
|
||||
authnRes: verifiedSession,
|
||||
err: apiutil.ErrInvalidQueryParams,
|
||||
},
|
||||
{
|
||||
desc: "list users with created_from and others",
|
||||
token: validToken,
|
||||
query: "created_from=2024-01-01T00:00:00Z&status=enabled&limit=10",
|
||||
pageMeta: users.Page{
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Order: api.DefOrder,
|
||||
Dir: api.DefDir,
|
||||
Status: users.EnabledStatus,
|
||||
CreatedFrom: validTimeStamp,
|
||||
},
|
||||
listUsersResponse: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 1,
|
||||
Limit: 10,
|
||||
},
|
||||
Users: []users.User{user},
|
||||
},
|
||||
status: http.StatusOK,
|
||||
authnRes: verifiedSession,
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
+15
-13
@@ -122,19 +122,21 @@ func listUsersEndpoint(svc users.Service) endpoint.Endpoint {
|
||||
}
|
||||
|
||||
pm := users.Page{
|
||||
Status: req.status,
|
||||
Offset: req.offset,
|
||||
Limit: req.limit,
|
||||
OnlyTotal: req.onlyTotal,
|
||||
Username: req.userName,
|
||||
Tags: req.tags,
|
||||
Metadata: req.metadata,
|
||||
FirstName: req.firstName,
|
||||
LastName: req.lastName,
|
||||
Email: req.email,
|
||||
Order: req.order,
|
||||
Dir: req.dir,
|
||||
Id: req.id,
|
||||
Status: req.status,
|
||||
Offset: req.offset,
|
||||
Limit: req.limit,
|
||||
OnlyTotal: req.onlyTotal,
|
||||
Username: req.userName,
|
||||
Tags: req.tags,
|
||||
Metadata: req.metadata,
|
||||
FirstName: req.firstName,
|
||||
LastName: req.lastName,
|
||||
Email: req.email,
|
||||
Order: req.order,
|
||||
Dir: req.dir,
|
||||
Id: req.id,
|
||||
CreatedFrom: req.createdFrom,
|
||||
CreatedTo: req.createdTo,
|
||||
}
|
||||
|
||||
page, err := svc.ListUsers(ctx, session, pm)
|
||||
|
||||
+16
-13
@@ -5,6 +5,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
@@ -94,19 +95,21 @@ func (req viewUserReq) validate() error {
|
||||
}
|
||||
|
||||
type listUsersReq struct {
|
||||
status users.Status
|
||||
offset uint64
|
||||
limit uint64
|
||||
onlyTotal bool
|
||||
userName string
|
||||
tags users.TagsQuery
|
||||
firstName string
|
||||
lastName string
|
||||
email string
|
||||
metadata users.Metadata
|
||||
order string
|
||||
dir string
|
||||
id string
|
||||
status users.Status
|
||||
offset uint64
|
||||
limit uint64
|
||||
onlyTotal bool
|
||||
userName string
|
||||
tags users.TagsQuery
|
||||
firstName string
|
||||
lastName string
|
||||
email string
|
||||
metadata users.Metadata
|
||||
order string
|
||||
dir string
|
||||
id string
|
||||
createdFrom time.Time
|
||||
createdTo time.Time
|
||||
}
|
||||
|
||||
func (req listUsersReq) validate() error {
|
||||
|
||||
+37
-13
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq"
|
||||
grpcTokenV1 "github.com/absmach/supermq/api/grpc/token/v1"
|
||||
@@ -317,20 +318,43 @@ func decodeListUsers(_ context.Context, r *http.Request) (any, error) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
cfrom, err := apiutil.ReadStringQuery(r, "created_from", "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
cto, err := apiutil.ReadStringQuery(r, "created_to", "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
var createdFrom, createdTo time.Time
|
||||
if cfrom != "" {
|
||||
if createdFrom, err = time.Parse(time.RFC3339, cfrom); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrInvalidQueryParams, err)
|
||||
}
|
||||
}
|
||||
if cto != "" {
|
||||
if createdTo, err = time.Parse(time.RFC3339, cto); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrInvalidQueryParams, err)
|
||||
}
|
||||
}
|
||||
|
||||
req := listUsersReq{
|
||||
status: st,
|
||||
offset: o,
|
||||
limit: l,
|
||||
onlyTotal: ot,
|
||||
metadata: m,
|
||||
userName: n,
|
||||
firstName: i,
|
||||
lastName: f,
|
||||
tags: tq,
|
||||
order: order,
|
||||
dir: dir,
|
||||
id: id,
|
||||
email: d,
|
||||
status: st,
|
||||
offset: o,
|
||||
limit: l,
|
||||
onlyTotal: ot,
|
||||
metadata: m,
|
||||
userName: n,
|
||||
firstName: i,
|
||||
lastName: f,
|
||||
tags: tq,
|
||||
order: order,
|
||||
dir: dir,
|
||||
id: id,
|
||||
email: d,
|
||||
createdFrom: createdFrom,
|
||||
createdTo: createdTo,
|
||||
}
|
||||
|
||||
return req, nil
|
||||
|
||||
+35
-26
@@ -621,19 +621,21 @@ func ToUser(dbu DBUser) (users.User, error) {
|
||||
}
|
||||
|
||||
type DBUsersPage struct {
|
||||
Total uint64 `db:"total"`
|
||||
Limit uint64 `db:"limit"`
|
||||
Offset uint64 `db:"offset"`
|
||||
FirstName string `db:"first_name"`
|
||||
LastName string `db:"last_name"`
|
||||
Username string `db:"username"`
|
||||
Id string `db:"id"`
|
||||
Email string `db:"email"`
|
||||
Metadata []byte `db:"metadata"`
|
||||
Tags pgtype.TextArray `db:"tags"`
|
||||
GroupID string `db:"group_id"`
|
||||
Role users.Role `db:"role"`
|
||||
Status users.Status `db:"status"`
|
||||
Total uint64 `db:"total"`
|
||||
Limit uint64 `db:"limit"`
|
||||
Offset uint64 `db:"offset"`
|
||||
FirstName string `db:"first_name"`
|
||||
LastName string `db:"last_name"`
|
||||
Username string `db:"username"`
|
||||
Id string `db:"id"`
|
||||
Email string `db:"email"`
|
||||
Metadata []byte `db:"metadata"`
|
||||
Tags pgtype.TextArray `db:"tags"`
|
||||
GroupID string `db:"group_id"`
|
||||
Role users.Role `db:"role"`
|
||||
Status users.Status `db:"status"`
|
||||
CreatedFrom time.Time `db:"created_from"`
|
||||
CreatedTo time.Time `db:"created_to"`
|
||||
}
|
||||
|
||||
func ToDBUsersPage(pm users.Page) (DBUsersPage, error) {
|
||||
@@ -648,18 +650,20 @@ func ToDBUsersPage(pm users.Page) (DBUsersPage, error) {
|
||||
}
|
||||
|
||||
return DBUsersPage{
|
||||
FirstName: pm.FirstName,
|
||||
LastName: pm.LastName,
|
||||
Username: pm.Username,
|
||||
Email: pm.Email,
|
||||
Id: pm.Id,
|
||||
Metadata: data,
|
||||
Total: pm.Total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Status: pm.Status,
|
||||
Tags: tags,
|
||||
Role: pm.Role,
|
||||
FirstName: pm.FirstName,
|
||||
LastName: pm.LastName,
|
||||
Username: pm.Username,
|
||||
Email: pm.Email,
|
||||
Id: pm.Id,
|
||||
Metadata: data,
|
||||
Total: pm.Total,
|
||||
Offset: pm.Offset,
|
||||
Limit: pm.Limit,
|
||||
Status: pm.Status,
|
||||
Tags: tags,
|
||||
Role: pm.Role,
|
||||
CreatedFrom: pm.CreatedFrom,
|
||||
CreatedTo: pm.CreatedTo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -694,13 +698,18 @@ func PageQuery(pm users.Page) (string, error) {
|
||||
if len(pm.Metadata) > 0 {
|
||||
query = append(query, "metadata @> :metadata")
|
||||
}
|
||||
|
||||
if len(pm.IDs) != 0 {
|
||||
query = append(query, fmt.Sprintf("id IN ('%s')", strings.Join(pm.IDs, "','")))
|
||||
}
|
||||
if pm.Status != users.AllStatus {
|
||||
query = append(query, "u.status = :status")
|
||||
}
|
||||
if !pm.CreatedFrom.IsZero() {
|
||||
query = append(query, "created_at >= :created_from")
|
||||
}
|
||||
if !pm.CreatedTo.IsZero() {
|
||||
query = append(query, "created_at <= :created_to")
|
||||
}
|
||||
|
||||
var emq string
|
||||
if len(query) > 0 {
|
||||
|
||||
@@ -1034,6 +1034,90 @@ func TestRetrieveAll(t *testing.T) {
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve users created from specific time",
|
||||
pageMeta: users.Page{
|
||||
CreatedFrom: baseTime.Add(50 * time.Millisecond),
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
Role: users.AllRole,
|
||||
Status: users.AllStatus,
|
||||
Order: "created_at",
|
||||
Dir: ascDir,
|
||||
},
|
||||
page: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 150,
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
},
|
||||
Users: items[50:200],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve users created to specific time",
|
||||
pageMeta: users.Page{
|
||||
CreatedTo: baseTime.Add(49 * time.Millisecond),
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
Role: users.AllRole,
|
||||
Status: users.AllStatus,
|
||||
Order: "created_at",
|
||||
Dir: ascDir,
|
||||
},
|
||||
page: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 50,
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
},
|
||||
Users: items[0:50],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve users created within time range",
|
||||
pageMeta: users.Page{
|
||||
CreatedFrom: baseTime.Add(50 * time.Millisecond),
|
||||
CreatedTo: baseTime.Add(99 * time.Millisecond),
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
Role: users.AllRole,
|
||||
Status: users.AllStatus,
|
||||
Order: "created_at",
|
||||
Dir: ascDir,
|
||||
},
|
||||
page: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 50,
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
},
|
||||
Users: items[50:100],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "retrieve users with time range outside of all records",
|
||||
pageMeta: users.Page{
|
||||
CreatedFrom: baseTime.Add(300 * time.Millisecond),
|
||||
CreatedTo: baseTime.Add(400 * time.Millisecond),
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
Role: users.AllRole,
|
||||
Status: users.AllStatus,
|
||||
},
|
||||
page: users.UsersPage{
|
||||
Page: users.Page{
|
||||
Total: 0,
|
||||
Offset: 0,
|
||||
Limit: 200,
|
||||
},
|
||||
Users: []users.User{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
+22
-20
@@ -176,26 +176,28 @@ func ToTagsQuery(s string) TagsQuery {
|
||||
|
||||
// Page contains page metadata that helps navigation.
|
||||
type Page struct {
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
OnlyTotal bool `json:"only_total"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Order string `json:"order,omitempty"`
|
||||
Dir string `json:"dir,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Tags TagsQuery `json:"tag,omitempty"`
|
||||
Permission string `json:"permission,omitempty"`
|
||||
Status Status `json:"status,omitempty"`
|
||||
IDs []string `json:"ids,omitempty"`
|
||||
Role Role `json:"-"`
|
||||
ListPerms bool `json:"-"`
|
||||
Username string `json:"username,omitempty"`
|
||||
FirstName string `json:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
Total uint64 `json:"total"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Limit uint64 `json:"limit"`
|
||||
OnlyTotal bool `json:"only_total"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Order string `json:"order,omitempty"`
|
||||
Dir string `json:"dir,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Tags TagsQuery `json:"tag,omitempty"`
|
||||
Permission string `json:"permission,omitempty"`
|
||||
Status Status `json:"status,omitempty"`
|
||||
IDs []string `json:"ids,omitempty"`
|
||||
Role Role `json:"-"`
|
||||
ListPerms bool `json:"-"`
|
||||
Username string `json:"username,omitempty"`
|
||||
FirstName string `json:"first_name,omitempty"`
|
||||
LastName string `json:"last_name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
CreatedFrom time.Time `json:"created_from,omitempty"`
|
||||
CreatedTo time.Time `json:"created_to,omitempty"`
|
||||
}
|
||||
|
||||
// Service specifies an API that must be fullfiled by the domain service
|
||||
|
||||
Reference in New Issue
Block a user