1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-26 13:48:53 +08:00

NOISSUE - Add ListUsers, ViewUser and ViewProfile methods (#1262)

* NOISSUE - Add admin method in users service to return users list

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix loggings and metrics

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add email and metadata filters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Retrieve User infos by ID if Admin

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove admin checks and fix comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix missing query

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use generic funccs to create email and metadata queries

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add /users/profile endpoint

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Simplify db helpers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix View, List, Retrieve prefix methods naming

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix tracer endpoints naming

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add tests and remove TODO comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
This commit is contained in:
Manuel Imperiale 2020-10-26 10:17:08 +01:00 committed by GitHub
parent 420b598ac7
commit 1c298d8f27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 653 additions and 212 deletions

View File

@ -81,7 +81,7 @@ func viewUserEndpoint(svc users.Service) endpoint.Endpoint {
return nil, err
}
u, err := svc.User(ctx, req.token)
u, err := svc.ViewUser(ctx, req.token, req.userID)
if err != nil {
return nil, err
}
@ -93,6 +93,39 @@ func viewUserEndpoint(svc users.Service) endpoint.Endpoint {
}
}
func viewProfileEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(viewUserReq)
if err := req.validate(); err != nil {
return nil, err
}
u, err := svc.ViewProfile(ctx, req.token)
if err != nil {
return nil, err
}
return viewUserRes{
ID: u.ID,
Email: u.Email,
Metadata: u.Metadata,
}, nil
}
}
func listUsersEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listUsersReq)
if err := req.validate(); err != nil {
return users.UserPage{}, err
}
up, err := svc.ListUsers(ctx, req.token, req.offset, req.limit, req.email, req.metadata)
if err != nil {
return users.UserPage{}, err
}
return buildUsersResponse(up), nil
}
}
func updateUserEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateUserReq)
@ -194,13 +227,13 @@ func removeUserFromGroup(svc users.Service) endpoint.Endpoint {
}
}
func listUsersForGroupEndpoint(svc users.Service) endpoint.Endpoint {
func listMembersEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listUserGroupReq)
if err := req.validate(); err != nil {
return users.UserPage{}, err
}
up, err := svc.Members(ctx, req.token, req.groupID, req.offset, req.limit, req.metadata)
up, err := svc.ListMembers(ctx, req.token, req.groupID, req.offset, req.limit, req.metadata)
if err != nil {
return users.UserPage{}, err
}
@ -208,13 +241,13 @@ func listUsersForGroupEndpoint(svc users.Service) endpoint.Endpoint {
}
}
func listUserGroupsEndpoint(svc users.Service) endpoint.Endpoint {
func listMembershipsEndpoint(svc users.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listUserGroupReq)
if err := req.validate(); err != nil {
return users.UserPage{}, err
}
gp, err := svc.Memberships(ctx, req.token, req.userID, req.offset, req.limit, req.metadata)
gp, err := svc.ListMemberships(ctx, req.token, req.userID, req.offset, req.limit, req.metadata)
if err != nil {
return groupPageRes{}, err
}
@ -252,7 +285,7 @@ func viewGroupEndpoint(svc users.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return viewGroupRes{}, err
}
group, err := svc.Group(ctx, req.token, req.groupID)
group, err := svc.ViewGroup(ctx, req.token, req.groupID)
if err != nil {
return viewGroupRes{}, err
}
@ -271,7 +304,7 @@ func listGroupsEndpoint(svc users.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return groupPageRes{}, err
}
gp, err := svc.Groups(ctx, req.token, req.groupID, req.offset, req.limit, req.metadata)
gp, err := svc.ListGroups(ctx, req.token, req.groupID, req.offset, req.limit, req.metadata)
if err != nil {
return groupPageRes{}, err
}

View File

@ -190,7 +190,7 @@ func TestUser(t *testing.T) {
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
_, err := svc.Register(context.Background(), user)
userID, err := svc.Register(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("register user got unexpected error: %s", err))
auth := mocks.NewAuthService(map[string]string{user.Email: user.Email})
@ -210,7 +210,7 @@ func TestUser(t *testing.T) {
req := testRequest{
client: client,
method: http.MethodGet,
url: fmt.Sprintf("%s/users", ts.URL),
url: fmt.Sprintf("%s/users/%s", ts.URL, userID),
token: tc.token,
}
res, err := req.make()

View File

@ -51,7 +51,7 @@ func (lm *loggingMiddleware) Login(ctx context.Context, user users.User) (token
return lm.svc.Login(ctx, user)
}
func (lm *loggingMiddleware) User(ctx context.Context, token string) (u users.User, err error) {
func (lm *loggingMiddleware) ViewUser(ctx context.Context, token, id string) (u users.User, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method view_user for user %s took %s to complete", u.Email, time.Since(begin))
if err != nil {
@ -61,7 +61,33 @@ func (lm *loggingMiddleware) User(ctx context.Context, token string) (u users.Us
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.User(ctx, token)
return lm.svc.ViewUser(ctx, token, id)
}
func (lm *loggingMiddleware) ViewProfile(ctx context.Context, token string) (u users.User, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method view_profile for usser %s took %s to complete", u.Email, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ViewProfile(ctx, token)
}
func (lm *loggingMiddleware) ListUsers(ctx context.Context, token string, offset, limit uint64, email string, um users.Metadata) (e users.UserPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_users for token %s took %s to complete", token, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListUsers(ctx, token, offset, limit, email, um)
}
func (lm *loggingMiddleware) UpdateUser(ctx context.Context, token string, u users.User) (err error) {
@ -142,9 +168,9 @@ func (lm *loggingMiddleware) CreateGroup(ctx context.Context, token string, grou
return lm.svc.CreateGroup(ctx, token, group)
}
func (lm *loggingMiddleware) Groups(ctx context.Context, token, id string, offset, limit uint64, meta users.Metadata) (e users.GroupPage, err error) {
func (lm *loggingMiddleware) ListGroups(ctx context.Context, token, id string, offset, limit uint64, um users.Metadata) (e users.GroupPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method groups for parent %s took %s to complete", id, time.Since(begin))
message := fmt.Sprintf("Method list_groups for parent %s took %s to complete", id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -152,12 +178,12 @@ func (lm *loggingMiddleware) Groups(ctx context.Context, token, id string, offse
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Groups(ctx, token, id, offset, limit, meta)
return lm.svc.ListGroups(ctx, token, id, offset, limit, um)
}
func (lm *loggingMiddleware) Members(ctx context.Context, token, id string, offset, limit uint64, meta users.Metadata) (e users.UserPage, err error) {
func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, id string, offset, limit uint64, um users.Metadata) (e users.UserPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method members for parent %s took %s to complete", id, time.Since(begin))
message := fmt.Sprintf("Method list_members for parent %s took %s to complete", id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -165,7 +191,7 @@ func (lm *loggingMiddleware) Members(ctx context.Context, token, id string, offs
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Members(ctx, token, id, offset, limit, meta)
return lm.svc.ListMembers(ctx, token, id, offset, limit, um)
}
func (lm *loggingMiddleware) RemoveGroup(ctx context.Context, token, id string) (err error) {
@ -194,9 +220,9 @@ func (lm *loggingMiddleware) UpdateGroup(ctx context.Context, token string, grou
return lm.svc.UpdateGroup(ctx, token, group)
}
func (lm *loggingMiddleware) Group(ctx context.Context, token, id string) (u users.Group, err error) {
func (lm *loggingMiddleware) ViewGroup(ctx context.Context, token, id string) (u users.Group, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method group with id %s took %s to complete", id, time.Since(begin))
message := fmt.Sprintf("Method view_group with id %s took %s to complete", id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -204,7 +230,7 @@ func (lm *loggingMiddleware) Group(ctx context.Context, token, id string) (u use
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Group(ctx, token, id)
return lm.svc.ViewGroup(ctx, token, id)
}
func (lm *loggingMiddleware) Assign(ctx context.Context, token, userID, groupID string) (err error) {
@ -233,9 +259,9 @@ func (lm *loggingMiddleware) Unassign(ctx context.Context, token, userID, groupI
return lm.svc.Unassign(ctx, token, userID, groupID)
}
func (lm *loggingMiddleware) Memberships(ctx context.Context, token, id string, offset, limit uint64, meta users.Metadata) (e users.GroupPage, err error) {
func (lm *loggingMiddleware) ListMemberships(ctx context.Context, token, id string, offset, limit uint64, um users.Metadata) (e users.GroupPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method memberships for user %s took %s to complete", id, time.Since(begin))
message := fmt.Sprintf("Method list_memberships for user %s took %s to complete", id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@ -243,5 +269,5 @@ func (lm *loggingMiddleware) Memberships(ctx context.Context, token, id string,
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Memberships(ctx, token, id, offset, limit, meta)
return lm.svc.ListMemberships(ctx, token, id, offset, limit, um)
}

View File

@ -47,13 +47,31 @@ func (ms *metricsMiddleware) Login(ctx context.Context, user users.User) (string
return ms.svc.Login(ctx, user)
}
func (ms *metricsMiddleware) User(ctx context.Context, token string) (users.User, error) {
func (ms *metricsMiddleware) ViewUser(ctx context.Context, token, id string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "view_user").Add(1)
ms.latency.With("method", "view_user").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.User(ctx, token)
return ms.svc.ViewUser(ctx, token, id)
}
func (ms *metricsMiddleware) ViewProfile(ctx context.Context, token string) (users.User, error) {
defer func(begin time.Time) {
ms.counter.With("method", "view_profile").Add(1)
ms.latency.With("method", "view_profile").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ViewProfile(ctx, token)
}
func (ms *metricsMiddleware) ListUsers(ctx context.Context, token string, offset, limit uint64, email string, um users.Metadata) (users.UserPage, error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_users").Add(1)
ms.latency.With("method", "list_users").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListUsers(ctx, token, offset, limit, email, um)
}
func (ms *metricsMiddleware) UpdateUser(ctx context.Context, token string, u users.User) (err error) {
@ -110,22 +128,22 @@ func (ms *metricsMiddleware) CreateGroup(ctx context.Context, token string, grou
return ms.svc.CreateGroup(ctx, token, group)
}
func (ms *metricsMiddleware) Groups(ctx context.Context, token, id string, offset, limit uint64, meta users.Metadata) (users.GroupPage, error) {
func (ms *metricsMiddleware) ListGroups(ctx context.Context, token, id string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
defer func(begin time.Time) {
ms.counter.With("method", "groups").Add(1)
ms.latency.With("method", "groups").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "list_groups").Add(1)
ms.latency.With("method", "list_groups").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Groups(ctx, token, id, offset, limit, meta)
return ms.svc.ListGroups(ctx, token, id, offset, limit, um)
}
func (ms *metricsMiddleware) Members(ctx context.Context, token, id string, offset, limit uint64, meta users.Metadata) (users.UserPage, error) {
func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, id string, offset, limit uint64, um users.Metadata) (users.UserPage, error) {
defer func(begin time.Time) {
ms.counter.With("method", "members").Add(1)
ms.latency.With("method", "members").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "list_members").Add(1)
ms.latency.With("method", "list_members").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Members(ctx, token, id, offset, limit, meta)
return ms.svc.ListMembers(ctx, token, id, offset, limit, um)
}
func (ms *metricsMiddleware) RemoveGroup(ctx context.Context, token, id string) error {
@ -146,14 +164,14 @@ func (ms *metricsMiddleware) UpdateGroup(ctx context.Context, token string, grou
return ms.svc.UpdateGroup(ctx, token, group)
}
func (ms *metricsMiddleware) Group(ctx context.Context, token, name string) (users.Group, error) {
func (ms *metricsMiddleware) ViewGroup(ctx context.Context, token, name string) (users.Group, error) {
defer func(begin time.Time) {
ms.counter.With("method", "group").Add(1)
ms.latency.With("method", "group").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "view_group").Add(1)
ms.latency.With("method", "view_group").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Group(ctx, token, name)
return ms.svc.ViewGroup(ctx, token, name)
}
func (ms *metricsMiddleware) Assign(ctx context.Context, token, userID, groupID string) error {
@ -174,11 +192,11 @@ func (ms *metricsMiddleware) Unassign(ctx context.Context, token, userID, groupI
return ms.svc.Unassign(ctx, token, userID, groupID)
}
func (ms *metricsMiddleware) Memberships(ctx context.Context, token, id string, offset, limit uint64, meta users.Metadata) (users.GroupPage, error) {
func (ms *metricsMiddleware) ListMemberships(ctx context.Context, token, id string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
defer func(begin time.Time) {
ms.counter.With("method", "memberships").Add(1)
ms.latency.With("method", "memberships").Observe(time.Since(begin).Seconds())
ms.counter.With("method", "list_memberships").Add(1)
ms.latency.With("method", "list_memberships").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Memberships(ctx, token, id, offset, limit, meta)
return ms.svc.ListMemberships(ctx, token, id, offset, limit, um)
}

View File

@ -21,7 +21,8 @@ func (req userReq) validate() error {
}
type viewUserReq struct {
token string
token string
userID string
}
func (req viewUserReq) validate() error {
@ -31,6 +32,21 @@ func (req viewUserReq) validate() error {
return nil
}
type listUsersReq struct {
token string
offset uint64
limit uint64
email string
metadata users.Metadata
}
func (req listUsersReq) validate() error {
if req.token == "" {
return users.ErrUnauthorizedAccess
}
return nil
}
type updateUserReq struct {
token string
Metadata map[string]interface{} `json:"metadata,omitempty"`

View File

@ -28,6 +28,7 @@ const (
offsetKey = "offset"
limitKey = "limit"
nameKey = "name"
emailKey = "email"
metadataKey = "metadata"
defOffset = 0
@ -59,13 +60,27 @@ func MakeHandler(svc users.Service, tracer opentracing.Tracer) http.Handler {
opts...,
))
mux.Get("/users", kithttp.NewServer(
mux.Get("/users/profile", kithttp.NewServer(
kitot.TraceServer(tracer, "view_profile")(viewProfileEndpoint(svc)),
decodeViewProfile,
encodeResponse,
opts...,
))
mux.Get("/users/:userID", kithttp.NewServer(
kitot.TraceServer(tracer, "view_user")(viewUserEndpoint(svc)),
decodeViewUser,
encodeResponse,
opts...,
))
mux.Get("/users", kithttp.NewServer(
kitot.TraceServer(tracer, "list_users")(listUsersEndpoint(svc)),
decodeListUsers,
encodeResponse,
opts...,
))
mux.Put("/users", kithttp.NewServer(
kitot.TraceServer(tracer, "update_user")(updateUserEndpoint(svc)),
decodeUpdateUser,
@ -74,7 +89,7 @@ func MakeHandler(svc users.Service, tracer opentracing.Tracer) http.Handler {
))
mux.Get("/users/:userID/groups", kithttp.NewServer(
kitot.TraceServer(tracer, "memberships")(listUserGroupsEndpoint(svc)),
kitot.TraceServer(tracer, "list_memberships")(listMembershipsEndpoint(svc)),
decodeListUserGroupsRequest,
encodeResponse,
opts...,
@ -109,7 +124,7 @@ func MakeHandler(svc users.Service, tracer opentracing.Tracer) http.Handler {
))
mux.Get("/groups", kithttp.NewServer(
kitot.TraceServer(tracer, "groups")(listGroupsEndpoint(svc)),
kitot.TraceServer(tracer, "list_groups")(listGroupsEndpoint(svc)),
decodeListUserGroupsRequest,
encodeResponse,
opts...,
@ -137,7 +152,7 @@ func MakeHandler(svc users.Service, tracer opentracing.Tracer) http.Handler {
))
mux.Get("/groups/:groupID/users", kithttp.NewServer(
kitot.TraceServer(tracer, "members")(listUsersForGroupEndpoint(svc)),
kitot.TraceServer(tracer, "list_members")(listMembersEndpoint(svc)),
decodeListUserGroupsRequest,
encodeResponse,
opts...,
@ -178,12 +193,51 @@ func MakeHandler(svc users.Service, tracer opentracing.Tracer) http.Handler {
}
func decodeViewUser(_ context.Context, r *http.Request) (interface{}, error) {
req := viewUserReq{
token: r.Header.Get("Authorization"),
userID: bone.GetValue(r, "userID"),
}
return req, nil
}
func decodeViewProfile(_ context.Context, r *http.Request) (interface{}, error) {
req := viewUserReq{
token: r.Header.Get("Authorization"),
}
return req, nil
}
func decodeListUsers(_ context.Context, r *http.Request) (interface{}, error) {
o, err := readUintQuery(r, offsetKey, defOffset)
if err != nil {
return nil, err
}
l, err := readUintQuery(r, limitKey, defLimit)
if err != nil {
return nil, err
}
e, err := readStringQuery(r, emailKey)
if err != nil {
return nil, err
}
m, err := readMetadataQuery(r, metadataKey)
if err != nil {
return nil, err
}
req := listUsersReq{
token: r.Header.Get("Authorization"),
offset: o,
limit: l,
email: e,
metadata: m,
}
return req, nil
}
func decodeUpdateUser(_ context.Context, r *http.Request) (interface{}, error) {
var req updateUserReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {

View File

@ -35,10 +35,10 @@ type GroupRepository interface {
RetrieveByName(ctx context.Context, name string) (Group, error)
// RetrieveAllWithAncestors retrieves all groups if groupID == "", if groupID is specified returns children groups
RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, gm Metadata) (GroupPage, error)
RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, m Metadata) (GroupPage, error)
// Memberships retrieves all groups that user belongs to
Memberships(ctx context.Context, userID string, offset, limit uint64, gm Metadata) (GroupPage, error)
// RetrieveMemberships retrieves all groups that user belongs to
RetrieveMemberships(ctx context.Context, userID string, offset, limit uint64, m Metadata) (GroupPage, error)
// Assign adds user to group.
Assign(ctx context.Context, userID, groupID string) error

View File

@ -166,7 +166,7 @@ func (grm *groupRepositoryMock) Assign(ctx context.Context, userID, groupID stri
}
func (grm *groupRepositoryMock) Memberships(ctx context.Context, userID string, offset, limit uint64, gm users.Metadata) (users.GroupPage, error) {
func (grm *groupRepositoryMock) RetrieveMemberships(ctx context.Context, userID string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
var items []users.Group
@ -187,7 +187,7 @@ func (grm *groupRepositoryMock) Memberships(ctx context.Context, userID string,
}, nil
}
func (grm *groupRepositoryMock) RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, gm users.Metadata) (users.GroupPage, error) {
func (grm *groupRepositoryMock) RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
var items []users.Group

View File

@ -89,7 +89,28 @@ func (urm *userRepositoryMock) RetrieveByID(ctx context.Context, id string) (use
return val, nil
}
func (urm *userRepositoryMock) Members(ctx context.Context, groupID string, offset, limit uint64, gm users.Metadata) (users.UserPage, error) {
func (urm *userRepositoryMock) RetrieveAll(ctx context.Context, offset, limit uint64, email string, um users.Metadata) (users.UserPage, error) {
urm.mu.Lock()
defer urm.mu.Unlock()
up := users.UserPage{}
i := uint64(0)
for _, u := range urm.users {
if i >= offset && i < (limit+offset) {
up.Users = append(up.Users, u)
}
i++
}
up.Offset = offset
up.Limit = limit
up.Total = uint64(i)
return up, nil
}
func (urm *userRepositoryMock) RetrieveMembers(ctx context.Context, groupID string, offset, limit uint64, um users.Metadata) (users.UserPage, error) {
urm.mu.Lock()
defer urm.mu.Unlock()

View File

@ -148,8 +148,8 @@ func (gr groupRepository) RetrieveByName(ctx context.Context, name string) (user
return group, nil
}
func (gr groupRepository) RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, gm users.Metadata) (users.GroupPage, error) {
_, mq, err := getGroupsMetadataQuery(gm)
func (gr groupRepository) RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
_, mq, err := getGroupsMetadataQuery(um)
if err != nil {
return users.GroupPage{}, errors.Wrap(errRetrieveDB, err)
}
@ -166,17 +166,17 @@ func (gr groupRepository) RetrieveAllWithAncestors(ctx context.Context, groupID
`WITH RECURSIVE subordinates AS (
SELECT id, owner_id, parent_id, name, description, metadata
FROM groups
WHERE id = :id
WHERE id = :id
UNION
SELECT groups.id, groups.owner_id, groups.parent_id, groups.name, groups.description, groups.metadata
FROM groups
FROM groups
INNER JOIN subordinates s ON s.id = groups.parent_id %s
)`, mq)
q = fmt.Sprintf("%s SELECT * FROM subordinates ORDER BY id LIMIT :limit OFFSET :offset", sq)
cq = fmt.Sprintf("%s SELECT COUNT(*) FROM subordinates", sq)
}
dbPage, err := toDBGroupPage("", groupID, offset, limit, gm)
dbPage, err := toDBGroupPage("", groupID, offset, limit, um)
if err != nil {
return users.GroupPage{}, errors.Wrap(errSelectDb, err)
}
@ -217,8 +217,8 @@ func (gr groupRepository) RetrieveAllWithAncestors(ctx context.Context, groupID
return page, nil
}
func (gr groupRepository) Memberships(ctx context.Context, userID string, offset, limit uint64, gm users.Metadata) (users.GroupPage, error) {
m, mq, err := getGroupsMetadataQuery(gm)
func (gr groupRepository) RetrieveMemberships(ctx context.Context, userID string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
m, mq, err := getGroupsMetadataQuery(um)
if err != nil {
return users.GroupPage{}, errors.Wrap(errRetrieveDB, err)
}
@ -226,9 +226,9 @@ func (gr groupRepository) Memberships(ctx context.Context, userID string, offset
if mq != "" {
mq = fmt.Sprintf("AND %s", mq)
}
q := fmt.Sprintf(`SELECT g.id, g.owner_id, g.parent_id, g.name, g.description, g.metadata
q := fmt.Sprintf(`SELECT g.id, g.owner_id, g.parent_id, g.name, g.description, g.metadata
FROM group_relations gr, groups g
WHERE gr.group_id = g.id and gr.user_id = :userID
WHERE gr.group_id = g.id and gr.user_id = :userID
%s ORDER BY id LIMIT :limit OFFSET :offset;`, mq)
params := map[string]interface{}{
@ -257,7 +257,7 @@ func (gr groupRepository) Memberships(ctx context.Context, userID string, offset
items = append(items, gr)
}
cq := fmt.Sprintf(`SELECT COUNT(*)
cq := fmt.Sprintf(`SELECT COUNT(*)
FROM group_relations gr, groups g
WHERE gr.group_id = g.id and gr.user_id = :userID %s;`, mq)
@ -372,7 +372,7 @@ func toDBGroup(g users.Group) (dbGroup, error) {
}, nil
}
func toDBGroupPage(ownerID, groupID string, offset, limit uint64, metadata users.Metadata) (dbGroupPage, error) {
func toDBGroupPage(ownerID, groupID string, offset, limit uint64, um users.Metadata) (dbGroupPage, error) {
owner, err := toUUID(ownerID)
if err != nil {
return dbGroupPage{}, err
@ -386,7 +386,7 @@ func toDBGroupPage(ownerID, groupID string, offset, limit uint64, metadata users
}
return dbGroupPage{
ID: group,
Metadata: dbMetadata(metadata),
Metadata: dbMetadata(um),
OwnerID: owner,
Offset: offset,
Limit: limit,
@ -424,13 +424,13 @@ func toDBGroupRelation(userID, groupID string) (dbGroupRelation, error) {
}, nil
}
func getGroupsMetadataQuery(m users.Metadata) ([]byte, string, error) {
func getGroupsMetadataQuery(um users.Metadata) ([]byte, string, error) {
mq := ""
mb := []byte("{}")
if len(m) > 0 {
if len(um) > 0 {
mq = `groups.metadata @> :metadata`
b, err := json.Marshal(m)
b, err := json.Marshal(um)
if err != nil {
return nil, "", err
}

View File

@ -33,7 +33,7 @@ type userRepository struct {
db Database
}
// New instantiates a PostgreSQL implementation of user
// NewUserRepo instantiates a PostgreSQL implementation of user
// repository.
func NewUserRepo(db Database) users.UserRepository {
return &userRepository{
@ -124,7 +124,7 @@ func (ur userRepository) RetrieveByEmail(ctx context.Context, email string) (use
}
func (ur userRepository) RetrieveByID(ctx context.Context, id string) (users.User, error) {
q := `SELECT id, password, metadata FROM users WHERE id = $1`
q := `SELECT email, password, metadata FROM users WHERE id = $1`
dbu := dbUser{
ID: id,
@ -141,6 +141,77 @@ func (ur userRepository) RetrieveByID(ctx context.Context, id string) (users.Use
return toUser(dbu)
}
func (ur userRepository) RetrieveAll(ctx context.Context, offset, limit uint64, email string, um users.Metadata) (users.UserPage, error) {
eq, ep, err := createEmailQuery("", email)
if err != nil {
return users.UserPage{}, errors.Wrap(errRetrieveDB, err)
}
mq, mp, err := createMetadataQuery("", um)
if err != nil {
return users.UserPage{}, errors.Wrap(errRetrieveDB, err)
}
emq := ""
if eq != "" && mq == "" {
emq = fmt.Sprintf("WHERE %s", eq)
}
if eq == "" && mq != "" {
emq = fmt.Sprintf("WHERE %s", mq)
}
if eq != "" && mq != "" {
emq = fmt.Sprintf("WHERE %s AND %s", eq, mq)
}
q := fmt.Sprintf(`SELECT id, email, metadata FROM users %s ORDER BY email LIMIT :limit OFFSET :offset;`, emq)
params := map[string]interface{}{
"limit": limit,
"offset": offset,
"email": ep,
"metadata": mp,
}
rows, err := ur.db.NamedQueryContext(ctx, q, params)
if err != nil {
return users.UserPage{}, errors.Wrap(errSelectDb, err)
}
defer rows.Close()
var items []users.User
for rows.Next() {
dbusr := dbUser{}
if err := rows.StructScan(&dbusr); err != nil {
return users.UserPage{}, errors.Wrap(errSelectDb, err)
}
user, err := toUser(dbusr)
if err != nil {
return users.UserPage{}, err
}
items = append(items, user)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM users %s;`, emq)
total, err := total(ctx, ur.db, cq, params)
if err != nil {
return users.UserPage{}, errors.Wrap(errSelectDb, err)
}
page := users.UserPage{
Users: items,
PageMetadata: users.PageMetadata{
Total: total,
Offset: offset,
Limit: limit,
},
}
return page, nil
}
func (ur userRepository) UpdatePassword(ctx context.Context, email, password string) error {
q := `UPDATE users SET password = :password WHERE email = :email`
@ -156,21 +227,25 @@ func (ur userRepository) UpdatePassword(ctx context.Context, email, password str
return nil
}
func (ur userRepository) Members(ctx context.Context, groupID string, offset, limit uint64, gm users.Metadata) (users.UserPage, error) {
m, mq, err := getUsersMetadataQuery(gm)
func (ur userRepository) RetrieveMembers(ctx context.Context, groupID string, offset, limit uint64, um users.Metadata) (users.UserPage, error) {
mq, mp, err := createMetadataQuery("users.", um)
if err != nil {
return users.UserPage{}, errors.Wrap(errRetrieveDB, err)
}
if mq != "" {
mq = fmt.Sprintf(" AND %s", mq)
}
q := fmt.Sprintf(`SELECT u.id, u.email, u.metadata FROM users u, group_relations g
WHERE u.id = g.user_id AND g.group_id = :group
WHERE u.id = g.user_id AND g.group_id = :group
%s ORDER BY id LIMIT :limit OFFSET :offset;`, mq)
params := map[string]interface{}{
"group": groupID,
"limit": limit,
"offset": offset,
"metadata": m,
"metadata": mp,
}
rows, err := ur.db.NamedQueryContext(ctx, q, params)
@ -249,11 +324,12 @@ func (m dbMetadata) Value() (driver.Value, error) {
}
type dbUser struct {
ID string `db:"id"`
Owner string `db:"owner"`
Email string `db:"email"`
Password string `db:"password"`
Metadata []byte `db:"metadata"`
ID string `db:"id"`
Owner string `db:"owner"`
Email string `db:"email"`
Password string `db:"password"`
Metadata []byte `db:"metadata"`
Groups []users.Group `db:"groups"`
}
func toDBUser(u users.User) (dbUser, error) {
@ -290,17 +366,28 @@ func toUser(dbu dbUser) (users.User, error) {
}, nil
}
func getUsersMetadataQuery(m users.Metadata) ([]byte, string, error) {
mq := ""
mb := []byte("{}")
if len(m) > 0 {
mq = ` AND users.metadata @> :metadata`
b, err := json.Marshal(m)
if err != nil {
return nil, "", err
}
mb = b
func createEmailQuery(entity string, email string) (string, string, error) {
if email == "" {
return "", "", nil
}
return mb, mq, nil
// Create LIKE operator to search Users with email containing a given string
param := fmt.Sprintf(`%%%s%%`, email)
query := fmt.Sprintf("%semail LIKE :email", entity)
return query, param, nil
}
func createMetadataQuery(entity string, um users.Metadata) (string, []byte, error) {
if len(um) == 0 {
return "", nil, nil
}
param, err := json.Marshal(um)
if err != nil {
return "", nil, err
}
query := fmt.Sprintf("%smetadata @> :metadata", entity)
return query, param, nil
}

View File

@ -20,7 +20,7 @@ func TestUserSave(t *testing.T) {
email := "user-save@example.com"
uid, err := uuid.New().ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
desc string
@ -63,7 +63,7 @@ func TestSingleUserRetrieval(t *testing.T) {
email := "user-retrieval@example.com"
uid, err := uuid.New().ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
user := users.User{
ID: uid,
@ -88,7 +88,7 @@ func TestSingleUserRetrieval(t *testing.T) {
}
}
func TestMembers(t *testing.T) {
func TestRetrieveMembers(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
userRepo := postgres.NewUserRepo(dbMiddleware)
@ -96,8 +96,8 @@ func TestMembers(t *testing.T) {
var usrs []users.User
for i := uint64(0); i < nUsers; i++ {
uid, err := uuid.New().ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
email := fmt.Sprintf("retrieve-all-for-group%d@example.com", i)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
email := fmt.Sprintf("TestRetrieveMembers%d@example.com", i)
user := users.User{
ID: uid,
Email: email,
@ -141,10 +141,60 @@ func TestMembers(t *testing.T) {
}
for desc, tc := range cases {
page, err := userRepo.Members(context.Background(), tc.group, tc.offset, tc.limit, tc.metadata)
page, err := userRepo.RetrieveMembers(context.Background(), tc.group, tc.offset, tc.limit, tc.metadata)
size := uint64(len(usrs))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size))
assert.Equal(t, tc.total, page.Total, fmt.Sprintf("%s: expected total %d got %d\n", desc, tc.total, page.Total))
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
}
}
func TestRetrieveAll(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
userRepo := postgres.NewUserRepo(dbMiddleware)
var nUsers = uint64(10)
for i := uint64(0); i < nUsers; i++ {
uid, err := uuid.New().ID()
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
email := fmt.Sprintf("TestRetrieveAll%d@example.com", i)
user := users.User{
ID: uid,
Email: email,
Password: "pass",
}
_, err = userRepo.Save(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
}
cases := map[string]struct {
email string
offset uint64
limit uint64
size uint64
total uint64
metadata users.Metadata
}{
"retrieve all users filtered by email": {
email: "All",
offset: 0,
limit: nUsers,
size: nUsers,
total: nUsers,
},
"retrieve all users by email with limit and offset": {
email: "All",
offset: 2,
limit: 5,
size: 5,
total: nUsers,
},
}
for desc, tc := range cases {
page, err := userRepo.RetrieveAll(context.Background(), tc.offset, tc.limit, tc.email, tc.metadata)
size := uint64(len(page.Users))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size))
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
}
}

View File

@ -78,8 +78,14 @@ type Service interface {
// identified by the non-nil error values in the response.
Login(ctx context.Context, user User) (string, error)
// User authenticated user info for the given token.
User(ctx context.Context, token string) (User, error)
// ViewUser retrieves user info for a given user ID and an authorized token.
ViewUser(ctx context.Context, token, id string) (User, error)
// ViewProfile retrieves user info for a given token.
ViewProfile(ctx context.Context, token string) (User, error)
// ListUsers retrieves users list for a valid admin token.
ListUsers(ctx context.Context, token string, offset, limit uint64, email string, m Metadata) (UserPage, error)
// UpdateUser updates the user metadata.
UpdateUser(ctx context.Context, token string, user User) error
@ -104,18 +110,18 @@ type Service interface {
// UpdateGroup updates the group identified by the provided ID.
UpdateGroup(ctx context.Context, token string, group Group) error
// Group retrieves data about the group identified by ID.
Group(ctx context.Context, token, id string) (Group, error)
// ViewGroup retrieves data about the group identified by ID.
ViewGroup(ctx context.Context, token, id string) (Group, error)
// ListGroups retrieves groups that are children to group identified by parenID
// if parentID is empty all groups are listed.
Groups(ctx context.Context, token, parentID string, offset, limit uint64, meta Metadata) (GroupPage, error)
ListGroups(ctx context.Context, token, parentID string, offset, limit uint64, m Metadata) (GroupPage, error)
// Members retrieves users that are assigned to a group identified by groupID.
Members(ctx context.Context, token, groupID string, offset, limit uint64, meta Metadata) (UserPage, error)
ListMembers(ctx context.Context, token, groupID string, offset, limit uint64, m Metadata) (UserPage, error)
// Memberships retrieves groups that user identified with userID belongs to.
Memberships(ctx context.Context, token, groupID string, offset, limit uint64, meta Metadata) (GroupPage, error)
// ListMemberships retrieves groups that user identified with userID belongs to.
ListMemberships(ctx context.Context, token, groupID string, offset, limit uint64, m Metadata) (GroupPage, error)
// RemoveGroup removes the group identified with the provided ID.
RemoveGroup(ctx context.Context, token, id string) error
@ -135,11 +141,13 @@ type PageMetadata struct {
Name string
}
// GroupPage contains a page of groups.
type GroupPage struct {
PageMetadata
Groups []Group
}
// UserPage contains a page of users.
type UserPage struct {
PageMetadata
Users []User
@ -198,15 +206,36 @@ func (svc usersService) Login(ctx context.Context, user User) (string, error) {
return svc.issue(ctx, dbUser.Email, authn.UserKey)
}
func (svc usersService) User(ctx context.Context, token string) (User, error) {
func (svc usersService) ViewUser(ctx context.Context, token, id string) (User, error) {
_, err := svc.identify(ctx, token)
if err != nil {
return User{}, err
}
dbUser, err := svc.users.RetrieveByID(ctx, id)
if err != nil {
return User{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return User{
ID: id,
Email: dbUser.Email,
Password: "",
Metadata: dbUser.Metadata,
}, nil
}
func (svc usersService) ViewProfile(ctx context.Context, token string) (User, error) {
email, err := svc.identify(ctx, token)
if err != nil {
return User{}, err
}
dbUser, err := svc.users.RetrieveByEmail(ctx, email)
if err != nil {
return User{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return User{
ID: dbUser.ID,
Email: email,
@ -215,12 +244,13 @@ func (svc usersService) User(ctx context.Context, token string) (User, error) {
}, nil
}
func (svc usersService) ListUsers(ctx context.Context, token string, groupID string, offset, limit uint64, um Metadata) (UserPage, error) {
func (svc usersService) ListUsers(ctx context.Context, token string, offset, limit uint64, email string, m Metadata) (UserPage, error) {
_, err := svc.identify(ctx, token)
if err != nil {
return UserPage{}, err
}
return svc.users.Members(ctx, groupID, offset, limit, um)
return svc.users.RetrieveAll(ctx, offset, limit, email, m)
}
func (svc usersService) UpdateUser(ctx context.Context, token string, u User) error {
@ -287,12 +317,103 @@ func (svc usersService) ChangePassword(ctx context.Context, authToken, password,
return svc.users.UpdatePassword(ctx, email, password)
}
// SendPasswordReset sends password recovery link to user
func (svc usersService) SendPasswordReset(_ context.Context, host, email, token string) error {
to := []string{email}
return svc.email.SendPasswordReset(to, host, token)
}
func (svc usersService) CreateGroup(ctx context.Context, token string, group Group) (Group, error) {
if group.Name == "" || !groupRegexp.MatchString(group.Name) {
return Group{}, ErrMalformedEntity
}
email, err := svc.identify(ctx, token)
if err != nil {
return Group{}, err
}
user, err := svc.users.RetrieveByEmail(ctx, email)
if err != nil {
return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
uid, err := uuidProvider.New().ID()
if err != nil {
return Group{}, errors.Wrap(ErrCreateUser, err)
}
group.ID = uid
group.OwnerID = user.ID
return svc.groups.Save(ctx, group)
}
func (svc usersService) ListGroups(ctx context.Context, token string, parentID string, offset, limit uint64, m Metadata) (GroupPage, error) {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.RetrieveAllWithAncestors(ctx, parentID, offset, limit, m)
}
func (svc usersService) ListMembers(ctx context.Context, token, groupID string, offset, limit uint64, m Metadata) (UserPage, error) {
if _, err := svc.identify(ctx, token); err != nil {
return UserPage{}, err
}
return svc.users.RetrieveMembers(ctx, groupID, offset, limit, m)
}
func (svc usersService) RemoveGroup(ctx context.Context, token, id string) error {
if _, err := svc.identify(ctx, token); err != nil {
return err
}
return svc.groups.Delete(ctx, id)
}
func (svc usersService) Unassign(ctx context.Context, token, userID, groupID string) error {
if _, err := svc.identify(ctx, token); err != nil {
return err
}
return svc.groups.Unassign(ctx, userID, groupID)
}
func (svc usersService) UpdateGroup(ctx context.Context, token string, group Group) error {
if _, err := svc.identify(ctx, token); err != nil {
return err
}
return svc.groups.Update(ctx, group)
}
func (svc usersService) ViewGroup(ctx context.Context, token, id string) (Group, error) {
if _, err := svc.identify(ctx, token); err != nil {
return Group{}, err
}
return svc.groups.RetrieveByID(ctx, id)
}
func (svc usersService) Assign(ctx context.Context, token, userID, groupID string) error {
if _, err := svc.identify(ctx, token); err != nil {
return err
}
return svc.groups.Assign(ctx, userID, groupID)
}
func (svc usersService) ListMemberships(ctx context.Context, token, userID string, offset, limit uint64, m Metadata) (GroupPage, error) {
if _, err := svc.identify(ctx, token); err != nil {
return GroupPage{}, err
}
return svc.groups.RetrieveMemberships(ctx, userID, offset, limit, m)
}
// Auth helpers
func (svc usersService) issue(ctx context.Context, email string, keyType uint32) (string, error) {
key, err := svc.auth.Issue(ctx, &mainflux.IssueReq{Issuer: email, Type: keyType})
if err != nil {
return "", errors.Wrap(ErrUserNotFound, err)
}
return key.GetValue(), nil
}
func (svc usersService) identify(ctx context.Context, token string) (string, error) {
email, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
@ -300,96 +421,3 @@ func (svc usersService) identify(ctx context.Context, token string) (string, err
}
return email.GetValue(), nil
}
func (svc usersService) CreateGroup(ctx context.Context, token string, group Group) (Group, error) {
if group.Name == "" || !groupRegexp.MatchString(group.Name) {
return Group{}, ErrMalformedEntity
}
userID, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
user, err := svc.users.RetrieveByEmail(ctx, userID.Value)
if err != nil {
return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
uid, err := uuidProvider.New().ID()
if err != nil {
return Group{}, errors.Wrap(ErrCreateUser, err)
}
group.ID = uid
group.OwnerID = user.ID
return svc.groups.Save(ctx, group)
}
func (svc usersService) Groups(ctx context.Context, token string, parentID string, offset, limit uint64, meta Metadata) (GroupPage, error) {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.RetrieveAllWithAncestors(ctx, parentID, offset, limit, meta)
}
func (svc usersService) Members(ctx context.Context, token, groupID string, offset, limit uint64, meta Metadata) (UserPage, error) {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return UserPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.users.Members(ctx, groupID, offset, limit, meta)
}
func (svc usersService) RemoveGroup(ctx context.Context, token, id string) error {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Delete(ctx, id)
}
func (svc usersService) Unassign(ctx context.Context, token, userID, groupID string) error {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Unassign(ctx, userID, groupID)
}
func (svc usersService) UpdateGroup(ctx context.Context, token string, group Group) error {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Update(ctx, group)
}
func (svc usersService) Group(ctx context.Context, token, id string) (Group, error) {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.RetrieveByID(ctx, id)
}
func (svc usersService) Assign(ctx context.Context, token, userID, groupID string) error {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Assign(ctx, userID, groupID)
}
func (svc usersService) issue(ctx context.Context, email string, keyType uint32) (string, error) {
key, err := svc.auth.Issue(ctx, &mainflux.IssueReq{Issuer: email, Type: keyType})
if err != nil {
return "", errors.Wrap(ErrUserNotFound, err)
}
return key.GetValue(), nil
}
func (svc usersService) Memberships(ctx context.Context, token, userID string, offset, limit uint64, meta Metadata) (GroupPage, error) {
_, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Memberships(ctx, userID, offset, limit, meta)
}

View File

@ -115,7 +115,50 @@ func TestLogin(t *testing.T) {
}
}
func TestUser(t *testing.T) {
func TestViewUser(t *testing.T) {
svc := newService()
id, err := svc.Register(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
token, err := svc.Login(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
u := user
u.Password = ""
cases := map[string]struct {
user users.User
token string
userID string
err error
}{
"view user with authorized token": {
user: u,
token: token,
userID: id,
err: nil,
},
"view user with unauthorized token": {
user: users.User{},
token: "",
userID: id,
err: users.ErrUnauthorizedAccess,
},
"view user with authorized token and invalid user id": {
user: users.User{},
token: token,
userID: "",
err: users.ErrUnauthorizedAccess,
},
}
for desc, tc := range cases {
_, err := svc.ViewUser(context.Background(), tc.token, tc.userID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestViewProfile(t *testing.T) {
svc := newService()
_, err := svc.Register(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
@ -144,7 +187,61 @@ func TestUser(t *testing.T) {
}
for desc, tc := range cases {
_, err := svc.User(context.Background(), tc.token)
_, err := svc.ViewProfile(context.Background(), tc.token)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestListUsers(t *testing.T) {
svc := newService()
_, err := svc.Register(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
token, err := svc.Login(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
var nUsers = uint64(10)
for i := uint64(1); i < nUsers; i++ {
email := fmt.Sprintf("TestListUsers%d@example.com", i)
user := users.User{
Email: email,
Password: "passpass",
}
_, err := svc.Register(context.Background(), user)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
}
cases := map[string]struct {
token string
offset uint64
limit uint64
email string
size uint64
err error
}{
"list users with authorized token": {
token: token,
size: 0,
err: nil,
},
"list user with unauthorized token": {
token: "",
size: 0,
err: users.ErrUnauthorizedAccess,
},
"list users with offset and limit": {
token: token,
offset: 6,
limit: nUsers,
size: nUsers - 6,
},
}
for desc, tc := range cases {
page, err := svc.ListUsers(context.Background(), tc.token, tc.offset, tc.limit, tc.email, nil)
size := uint64(len(page.Users))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
@ -344,7 +441,7 @@ func TestUpdateGroup(t *testing.T) {
for _, tc := range cases {
err := svc.UpdateGroup(context.Background(), token, tc.group)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
g, err := svc.Group(context.Background(), token, saved.ID)
g, err := svc.ViewGroup(context.Background(), token, saved.ID)
assert.Nil(t, err, fmt.Sprintf("retrieve group failed: %s", err))
assert.Equal(t, tc.group.Description, g.Description, tc.desc, tc.err)
assert.Equal(t, tc.group.Name, g.Name, tc.desc, tc.err)

View File

@ -77,20 +77,20 @@ func (grm groupRepositoryMiddleware) RetrieveByName(ctx context.Context, name st
return grm.repo.RetrieveByName(ctx, name)
}
func (grm groupRepositoryMiddleware) RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, gm users.Metadata) (users.GroupPage, error) {
func (grm groupRepositoryMiddleware) RetrieveAllWithAncestors(ctx context.Context, groupID string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
span := createSpan(ctx, grm.tracer, retrieveAll)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.RetrieveAllWithAncestors(ctx, groupID, offset, limit, gm)
return grm.repo.RetrieveAllWithAncestors(ctx, groupID, offset, limit, um)
}
func (grm groupRepositoryMiddleware) Memberships(ctx context.Context, userID string, offset, limit uint64, gm users.Metadata) (users.GroupPage, error) {
func (grm groupRepositoryMiddleware) RetrieveMemberships(ctx context.Context, userID string, offset, limit uint64, um users.Metadata) (users.GroupPage, error) {
span := createSpan(ctx, grm.tracer, memberships)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Memberships(ctx, userID, offset, limit, gm)
return grm.repo.RetrieveMemberships(ctx, userID, offset, limit, um)
}
func (grm groupRepositoryMiddleware) Unassign(ctx context.Context, userID, groupID string) error {

View File

@ -75,12 +75,20 @@ func (urm userRepositoryMiddleware) UpdatePassword(ctx context.Context, email, p
return urm.repo.UpdatePassword(ctx, email, password)
}
func (urm userRepositoryMiddleware) Members(ctx context.Context, groupID string, offset, limit uint64, gm users.Metadata) (users.UserPage, error) {
func (urm userRepositoryMiddleware) RetrieveAll(ctx context.Context, offset, limit uint64, email string, um users.Metadata) (users.UserPage, error) {
span := createSpan(ctx, urm.tracer, members)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return urm.repo.Members(ctx, groupID, offset, limit, gm)
return urm.repo.RetrieveAll(ctx, offset, limit, email, um)
}
func (urm userRepositoryMiddleware) RetrieveMembers(ctx context.Context, groupID string, offset, limit uint64, um users.Metadata) (users.UserPage, error) {
span := createSpan(ctx, urm.tracer, members)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return urm.repo.RetrieveMembers(ctx, groupID, offset, limit, um)
}
func createSpan(ctx context.Context, tracer opentracing.Tracer, opName string) opentracing.Span {

View File

@ -72,11 +72,14 @@ type UserRepository interface {
// RetrieveByID retrieves user by its unique identifier ID.
RetrieveByID(ctx context.Context, id string) (User, error)
// RetrieveAll retrieves all users
RetrieveAll(ctx context.Context, offset, limit uint64, email string, m Metadata) (UserPage, error)
// UpdatePassword updates password for user with given email
UpdatePassword(ctx context.Context, email, password string) error
// Members retrieves all users that belong to a group
Members(ctx context.Context, groupID string, offset, limit uint64, um Metadata) (UserPage, error)
// RetrieveMembers retrieves all users that belong to a group
RetrieveMembers(ctx context.Context, groupID string, offset, limit uint64, m Metadata) (UserPage, error)
}
func isEmail(email string) bool {