3
0

Implement support for labels on system resources

This commit is contained in:
Denis Arh
2020-11-06 08:33:24 +01:00
parent b82ddad83a
commit 76e803cc3c
21 changed files with 859 additions and 28 deletions
+4
View File
@@ -12,6 +12,10 @@ func (s Store) convertApplicationFilter(f types.ApplicationFilter) (query squirr
query = filter.StateCondition(query, "app.deleted_at", f.Deleted)
if len(f.LabeledIDs) > 0 {
query = query.Where(squirrel.Eq{"app.id": f.LabeledIDs})
}
if f.Query != "" {
qs := f.Query + "%"
query = query.Where(squirrel.Or{
+4
View File
@@ -17,6 +17,10 @@ func (s Store) convertRoleFilter(f types.RoleFilter) (query squirrel.SelectBuild
query = query.Where(squirrel.Eq{"rl.ID": f.RoleID})
}
if len(f.LabeledIDs) > 0 {
query = query.Where(squirrel.Eq{"rl.id": f.LabeledIDs})
}
if f.MemberID > 0 {
query = query.Where(squirrel.Expr("rl.ID IN (SELECT rel_role FROM role_members AS m WHERE m.rel_user = ?)", f.MemberID))
}
+4
View File
@@ -18,6 +18,10 @@ func (s Store) convertUserFilter(f types.UserFilter) (query squirrel.SelectBuild
query = query.Where(squirrel.Eq{"usr.ID": f.UserID})
}
if len(f.LabeledIDs) > 0 {
query = query.Where(squirrel.Eq{"usr.id": f.LabeledIDs})
}
if len(f.RoleID) > 0 {
or := squirrel.Or{}
// Due to lack of support for more exotic expressions (slice of values inside subquery)
+40
View File
@@ -234,6 +234,8 @@ endpoints:
authentication:
- Client ID
- Session ID
imports:
- github.com/cortezaproject/corteza-server/pkg/label
apis:
- name: list
method: GET
@@ -253,6 +255,10 @@ endpoints:
required: false
title: Exclude (0, default), include (1) or return only (2) achived roles
type: uint
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- type: uint
name: limit
title: Limit
@@ -280,6 +286,10 @@ endpoints:
name: members
required: false
title: Role member IDs
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- name: update
method: PUT
title: Update role details
@@ -303,6 +313,10 @@ endpoints:
name: members
required: false
title: Role member IDs
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- name: read
method: GET
title: Read role details and memberships
@@ -443,6 +457,7 @@ endpoints:
- Client ID
- Session ID
imports:
- github.com/cortezaproject/corteza-server/pkg/label
- github.com/cortezaproject/corteza-server/system/types
apis:
- name: list
@@ -495,6 +510,10 @@ endpoints:
required: false
title: Exclude (0, default), include (1) or return only (2) suspended users
type: uint
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- type: uint
name: limit
title: Limit
@@ -526,6 +545,10 @@ endpoints:
type: types.UserKind
required: false
title: Kind (normal, bot)
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- name: update
method: PUT
title: Update user details
@@ -553,6 +576,10 @@ endpoints:
type: types.UserKind
required: false
title: Kind (normal, bot)
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- name: read
method: GET
title: Read user details
@@ -677,6 +704,7 @@ endpoints:
entrypoint: application
authentication: []
imports:
- github.com/cortezaproject/corteza-server/pkg/label
- sqlxTypes github.com/jmoiron/sqlx/types
apis:
- name: list
@@ -697,6 +725,10 @@ endpoints:
required: false
title: Exclude (0, default), include (1) or return only (2) deleted roles
type: uint
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- type: uint
name: limit
title: Limit
@@ -728,6 +760,10 @@ endpoints:
type: sqlxTypes.JSONText
required: false
title: Arbitrary JSON holding application configuration
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- name: update
method: PUT
title: Update user details
@@ -755,6 +791,10 @@ endpoints:
type: sqlxTypes.JSONText
required: false
title: Arbitrary JSON holding application configuration
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- name: read
method: GET
title: Read application details
+5 -2
View File
@@ -61,8 +61,9 @@ func (ctrl *Application) List(ctx context.Context, r *request.ApplicationList) (
var (
err error
f = types.ApplicationFilter{
Name: r.Name,
Query: r.Query,
Name: r.Name,
Query: r.Query,
Labels: r.Labels,
Deleted: filter.State(r.Deleted),
}
@@ -86,6 +87,7 @@ func (ctrl *Application) Create(ctx context.Context, r *request.ApplicationCreat
app = &types.Application{
Name: r.Name,
Enabled: r.Enabled,
Labels: r.Labels,
}
)
@@ -107,6 +109,7 @@ func (ctrl *Application) Update(ctx context.Context, r *request.ApplicationUpdat
ID: r.ApplicationID,
Name: r.Name,
Enabled: r.Enabled,
Labels: r.Labels,
}
)
+69
View File
@@ -11,6 +11,7 @@ package request
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/go-chi/chi"
sqlxTypes "github.com/jmoiron/sqlx/types"
@@ -46,6 +47,11 @@ type (
// Exclude (0, default), include (1) or return only (2) deleted roles
Deleted uint
// Labels GET parameter
//
// Labels
Labels map[string]string
// Limit GET parameter
//
// Limit
@@ -82,6 +88,11 @@ type (
//
// Arbitrary JSON holding application configuration
Config sqlxTypes.JSONText
// Labels POST parameter
//
// Labels
Labels map[string]string
}
ApplicationUpdate struct {
@@ -109,6 +120,11 @@ type (
//
// Arbitrary JSON holding application configuration
Config sqlxTypes.JSONText
// Labels POST parameter
//
// Labels
Labels map[string]string
}
ApplicationRead struct {
@@ -156,6 +172,7 @@ func (r ApplicationList) Auditable() map[string]interface{} {
"name": r.Name,
"query": r.Query,
"deleted": r.Deleted,
"labels": r.Labels,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
@@ -177,6 +194,11 @@ func (r ApplicationList) GetDeleted() uint {
return r.Deleted
}
// Auditable returns all auditable/loggable parameters
func (r ApplicationList) GetLabels() map[string]string {
return r.Labels
}
// Auditable returns all auditable/loggable parameters
func (r ApplicationList) GetLimit() uint {
return r.Limit
@@ -227,6 +249,17 @@ func (r *ApplicationList) Fill(req *http.Request) (err error) {
return err
}
}
if val, ok := tmp["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := tmp["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
@@ -262,6 +295,7 @@ func (r ApplicationCreate) Auditable() map[string]interface{} {
"enabled": r.Enabled,
"unify": r.Unify,
"config": r.Config,
"labels": r.Labels,
}
}
@@ -285,6 +319,11 @@ func (r ApplicationCreate) GetConfig() sqlxTypes.JSONText {
return r.Config
}
// Auditable returns all auditable/loggable parameters
func (r ApplicationCreate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *ApplicationCreate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
@@ -332,6 +371,18 @@ func (r *ApplicationCreate) Fill(req *http.Request) (err error) {
return err
}
}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
return err
@@ -350,6 +401,7 @@ func (r ApplicationUpdate) Auditable() map[string]interface{} {
"enabled": r.Enabled,
"unify": r.Unify,
"config": r.Config,
"labels": r.Labels,
}
}
@@ -378,6 +430,11 @@ func (r ApplicationUpdate) GetConfig() sqlxTypes.JSONText {
return r.Config
}
// Auditable returns all auditable/loggable parameters
func (r ApplicationUpdate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *ApplicationUpdate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
@@ -425,6 +482,18 @@ func (r *ApplicationUpdate) Fill(req *http.Request) (err error) {
return err
}
}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
{
+69
View File
@@ -11,6 +11,7 @@ package request
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/go-chi/chi"
"io"
@@ -45,6 +46,11 @@ type (
// Exclude (0, default), include (1) or return only (2) achived roles
Archived uint
// Labels GET parameter
//
// Labels
Labels map[string]string
// Limit GET parameter
//
// Limit
@@ -76,6 +82,11 @@ type (
//
// Role member IDs
Members []string
// Labels POST parameter
//
// Labels
Labels map[string]string
}
RoleUpdate struct {
@@ -98,6 +109,11 @@ type (
//
// Role member IDs
Members []string
// Labels POST parameter
//
// Labels
Labels map[string]string
}
RoleRead struct {
@@ -214,6 +230,7 @@ func (r RoleList) Auditable() map[string]interface{} {
"query": r.Query,
"deleted": r.Deleted,
"archived": r.Archived,
"labels": r.Labels,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
@@ -235,6 +252,11 @@ func (r RoleList) GetArchived() uint {
return r.Archived
}
// Auditable returns all auditable/loggable parameters
func (r RoleList) GetLabels() map[string]string {
return r.Labels
}
// Auditable returns all auditable/loggable parameters
func (r RoleList) GetLimit() uint {
return r.Limit
@@ -285,6 +307,17 @@ func (r *RoleList) Fill(req *http.Request) (err error) {
return err
}
}
if val, ok := tmp["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := tmp["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
@@ -319,6 +352,7 @@ func (r RoleCreate) Auditable() map[string]interface{} {
"name": r.Name,
"handle": r.Handle,
"members": r.Members,
"labels": r.Labels,
}
}
@@ -337,6 +371,11 @@ func (r RoleCreate) GetMembers() []string {
return r.Members
}
// Auditable returns all auditable/loggable parameters
func (r RoleCreate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *RoleCreate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
@@ -377,6 +416,18 @@ func (r *RoleCreate) Fill(req *http.Request) (err error) {
// return err
// }
//}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
return err
@@ -394,6 +445,7 @@ func (r RoleUpdate) Auditable() map[string]interface{} {
"name": r.Name,
"handle": r.Handle,
"members": r.Members,
"labels": r.Labels,
}
}
@@ -417,6 +469,11 @@ func (r RoleUpdate) GetMembers() []string {
return r.Members
}
// Auditable returns all auditable/loggable parameters
func (r RoleUpdate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *RoleUpdate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
@@ -457,6 +514,18 @@ func (r *RoleUpdate) Fill(req *http.Request) (err error) {
// return err
// }
//}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
{
+69
View File
@@ -11,6 +11,7 @@ package request
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/system/types"
"github.com/go-chi/chi"
@@ -86,6 +87,11 @@ type (
// Exclude (0, default), include (1) or return only (2) suspended users
Suspended uint
// Labels GET parameter
//
// Labels
Labels map[string]string
// Limit GET parameter
//
// Limit
@@ -122,6 +128,11 @@ type (
//
// Kind (normal, bot)
Kind types.UserKind
// Labels POST parameter
//
// Labels
Labels map[string]string
}
UserUpdate struct {
@@ -149,6 +160,11 @@ type (
//
// Kind (normal, bot)
Kind types.UserKind
// Labels POST parameter
//
// Labels
Labels map[string]string
}
UserRead struct {
@@ -261,6 +277,7 @@ func (r UserList) Auditable() map[string]interface{} {
"incSuspended": r.IncSuspended,
"deleted": r.Deleted,
"suspended": r.Suspended,
"labels": r.Labels,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
@@ -322,6 +339,11 @@ func (r UserList) GetSuspended() uint {
return r.Suspended
}
// Auditable returns all auditable/loggable parameters
func (r UserList) GetLabels() map[string]string {
return r.Labels
}
// Auditable returns all auditable/loggable parameters
func (r UserList) GetLimit() uint {
return r.Limit
@@ -430,6 +452,17 @@ func (r *UserList) Fill(req *http.Request) (err error) {
return err
}
}
if val, ok := tmp["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := tmp["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
@@ -465,6 +498,7 @@ func (r UserCreate) Auditable() map[string]interface{} {
"name": r.Name,
"handle": r.Handle,
"kind": r.Kind,
"labels": r.Labels,
}
}
@@ -488,6 +522,11 @@ func (r UserCreate) GetKind() types.UserKind {
return r.Kind
}
// Auditable returns all auditable/loggable parameters
func (r UserCreate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *UserCreate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
@@ -535,6 +574,18 @@ func (r *UserCreate) Fill(req *http.Request) (err error) {
return err
}
}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
return err
@@ -553,6 +604,7 @@ func (r UserUpdate) Auditable() map[string]interface{} {
"name": r.Name,
"handle": r.Handle,
"kind": r.Kind,
"labels": r.Labels,
}
}
@@ -581,6 +633,11 @@ func (r UserUpdate) GetKind() types.UserKind {
return r.Kind
}
// Auditable returns all auditable/loggable parameters
func (r UserUpdate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *UserUpdate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
@@ -628,6 +685,18 @@ func (r *UserUpdate) Fill(req *http.Request) (err error) {
return err
}
}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
{
+4 -1
View File
@@ -58,7 +58,8 @@ func (ctrl Role) List(ctx context.Context, r *request.RoleList) (interface{}, er
var (
err error
f = types.RoleFilter{
Query: r.Query,
Query: r.Query,
Labels: r.Labels,
Archived: filter.State(r.Archived),
Deleted: filter.State(r.Deleted),
@@ -83,6 +84,7 @@ func (ctrl Role) Create(ctx context.Context, r *request.RoleCreate) (interface{}
role = &types.Role{
Name: r.Name,
Handle: r.Handle,
Labels: r.Labels,
}
)
@@ -107,6 +109,7 @@ func (ctrl Role) Update(ctx context.Context, r *request.RoleUpdate) (interface{}
ID: r.RoleID,
Name: r.Name,
Handle: r.Handle,
Labels: r.Labels,
}
)
+3
View File
@@ -45,6 +45,7 @@ func (ctrl User) List(ctx context.Context, r *request.UserList) (interface{}, er
Username: r.Username,
Handle: r.Handle,
Kind: r.Kind,
Labels: r.Labels,
Suspended: filter.State(r.Suspended),
Deleted: filter.State(r.Deleted),
}
@@ -76,6 +77,7 @@ func (ctrl User) Create(ctx context.Context, r *request.UserCreate) (interface{}
Name: r.Name,
Handle: r.Handle,
Kind: r.Kind,
Labels: r.Labels,
}
return ctrl.user.With(ctx).Create(user)
@@ -88,6 +90,7 @@ func (ctrl User) Update(ctx context.Context, r *request.UserUpdate) (interface{}
Name: r.Name,
Handle: r.Handle,
Kind: r.Kind,
Labels: r.Labels,
}
return ctrl.user.With(ctx).Update(user)
+54 -5
View File
@@ -4,6 +4,7 @@ import (
"context"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/service/event"
"github.com/cortezaproject/corteza-server/system/types"
@@ -14,7 +15,7 @@ type (
ac applicationAccessController
eventbus eventDispatcher
actionlog actionlog.Recorder
store store.Applications
store store.Storer
}
applicationAccessController interface {
@@ -27,7 +28,7 @@ type (
)
// Application is a default application service initializer
func Application(s store.Applications, ac applicationAccessController, al actionlog.Recorder, eb eventDispatcher) *application {
func Application(s store.Storer, ac applicationAccessController, al actionlog.Recorder, eb eventDispatcher) *application {
return &application{store: s, ac: ac, actionlog: al, eventbus: eb}
}
@@ -83,8 +84,29 @@ func (svc *application) Search(ctx context.Context, af types.ApplicationFilter)
}
}
aa, f, err = store.SearchApplications(ctx, svc.store, af)
return err
if len(af.Labels) > 0 {
af.LabeledIDs, err = label.Search(
ctx,
svc.store,
types.Application{}.LabelResourceKind(),
af.Labels,
)
if err != nil {
return err
}
}
if aa, f, err = store.SearchApplications(ctx, svc.store, af); err != nil {
return err
}
if err = label.Load(ctx, svc.store, toLabeledApplications(aa)...); err != nil {
return err
}
return nil
}()
return aa, f, svc.recordAction(ctx, aaProps, ApplicationActionSearch, err)
@@ -116,7 +138,11 @@ func (svc *application) Create(ctx context.Context, new *types.Application) (app
return
}
aaProps.setApplication(app)
if err = label.Create(ctx, svc.store, new); err != nil {
return
}
app = new
_ = svc.eventbus.WaitFor(ctx, event.ApplicationAfterCreate(new, nil))
return nil
@@ -162,6 +188,13 @@ func (svc *application) Update(ctx context.Context, upd *types.Application) (app
return err
}
if label.Changed(app.Labels, upd.Labels) {
if err = label.Update(ctx, svc.store, upd); err != nil {
return
}
app.Labels = upd.Labels
}
_ = svc.eventbus.WaitFor(ctx, event.ApplicationAfterUpdate(upd, app))
return nil
}()
@@ -244,3 +277,19 @@ func (svc *application) Undelete(ctx context.Context, ID uint64) (err error) {
return svc.recordAction(ctx, aaProps, ApplicationActionUndelete, err)
}
// toLabeledApplications converts to []label.LabeledResource
//
// This function is auto-generated.
func toLabeledApplications(set []*types.Application) []label.LabeledResource {
if len(set) == 0 {
return nil
}
ll := make([]label.LabeledResource, len(set))
for i := range set {
ll[i] = set[i]
}
return ll
}
+87 -9
View File
@@ -5,6 +5,7 @@ import (
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/pkg/rbac"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/service/event"
@@ -112,8 +113,33 @@ func (svc role) Find(filter types.RoleFilter) (rr types.RoleSet, f types.RoleFil
}
}
rr, f, err = store.SearchRoles(svc.ctx, svc.store, filter)
return err
if len(filter.Labels) > 0 {
filter.LabeledIDs, err = label.Search(
svc.ctx,
svc.store,
types.Role{}.LabelResourceKind(),
filter.Labels,
)
if err != nil {
return err
}
// labels specified but no labeled resources found
if len(filter.LabeledIDs) == 0 {
return nil
}
}
if rr, f, err = store.SearchRoles(svc.ctx, svc.store, filter); err != nil {
return err
}
if err = label.Load(svc.ctx, svc.store, toLabeledRoles(rr)...); err != nil {
return err
}
return nil
}()
return rr, f, svc.recordAction(svc.ctx, raProps, RoleActionSearch, err)
@@ -125,9 +151,17 @@ func (svc role) FindByID(roleID uint64) (r *types.Role, err error) {
)
err = func() error {
r, err = svc.findByID(roleID)
if r, err = svc.findByID(roleID); err != nil {
return err
}
raProps.setRole(r)
return err
if err = label.Load(svc.ctx, svc.store, r); err != nil {
return err
}
return nil
}()
return r, svc.recordAction(svc.ctx, raProps, RoleActionLookup, err)
@@ -147,9 +181,17 @@ func (svc role) FindByName(name string) (r *types.Role, err error) {
)
err = func() error {
r, err = store.LookupRoleByName(svc.ctx, svc.store, name)
if r, err = store.LookupRoleByName(svc.ctx, svc.store, name); err != nil {
return err
}
raProps.setRole(r)
return err
if err = label.Load(svc.ctx, svc.store, r); err != nil {
return err
}
return nil
}()
return r, svc.recordAction(svc.ctx, raProps, RoleActionLookup, err)
@@ -161,9 +203,17 @@ func (svc role) FindByHandle(h string) (r *types.Role, err error) {
)
err = func() error {
r, err = store.LookupRoleByName(svc.ctx, svc.store, h)
if r, err = store.LookupRoleByHandle(svc.ctx, svc.store, h); err != nil {
return err
}
raProps.setRole(r)
return err
if err = label.Load(svc.ctx, svc.store, r); err != nil {
return err
}
return nil
}()
return r, svc.recordAction(svc.ctx, raProps, RoleActionLookup, err)
@@ -218,7 +268,11 @@ func (svc role) Create(new *types.Role) (r *types.Role, err error) {
return
}
raProps.setRole(r)
if err = label.Create(svc.ctx, svc.store, new); err != nil {
return
}
r = new
_ = svc.eventbus.WaitFor(svc.ctx, event.RoleAfterCreate(new, r))
return
@@ -269,6 +323,14 @@ func (svc role) Update(upd *types.Role) (r *types.Role, err error) {
return err
}
if label.Changed(r.Labels, upd.Labels) {
if err = label.Update(svc.ctx, svc.store, upd); err != nil {
return
}
r.Labels = upd.Labels
}
_ = svc.eventbus.WaitFor(svc.ctx, event.RoleAfterUpdate(upd, r))
return nil
@@ -547,3 +609,19 @@ func (svc role) MemberRemove(roleID, memberID uint64) (err error) {
return svc.recordAction(svc.ctx, raProps, RoleActionMemberRemove, err)
}
// toLabeledRoles converts to []label.LabeledResource
//
// This function is auto-generated.
func toLabeledRoles(set []*types.Role) []label.LabeledResource {
if len(set) == 0 {
return nil
}
ll := make([]label.LabeledResource, len(set))
for i := range set {
ll[i] = set[i]
}
return ll
}
+95 -8
View File
@@ -8,6 +8,7 @@ import (
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/service/event"
"github.com/cortezaproject/corteza-server/system/types"
@@ -142,8 +143,17 @@ func (svc user) FindByID(userID uint64) (u *types.User, err error) {
return nil
}
u, err = svc.proc(store.LookupUserByID(svc.ctx, svc.store, userID))
return err
if u, err = svc.proc(store.LookupUserByID(svc.ctx, svc.store, userID)); err != nil {
return err
}
uaProps.setUser(u)
if err = label.Load(svc.ctx, svc.store, u); err != nil {
return err
}
return nil
}()
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
@@ -155,8 +165,17 @@ func (svc user) FindByEmail(email string) (u *types.User, err error) {
)
err = func() error {
u, err = svc.proc(store.LookupUserByEmail(svc.ctx, svc.store, email))
return err
if u, err = svc.proc(store.LookupUserByEmail(svc.ctx, svc.store, email)); err != nil {
return err
}
uaProps.setUser(u)
if err = label.Load(svc.ctx, svc.store, u); err != nil {
return err
}
return nil
}()
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
@@ -168,8 +187,17 @@ func (svc user) FindByUsername(username string) (u *types.User, err error) {
)
err = func() error {
u, err = svc.proc(store.LookupUserByUsername(svc.ctx, svc.store, username))
return err
if u, err = svc.proc(store.LookupUserByUsername(svc.ctx, svc.store, username)); err != nil {
return err
}
uaProps.setUser(u)
if err = label.Load(svc.ctx, svc.store, u); err != nil {
return err
}
return nil
}()
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
@@ -181,8 +209,17 @@ func (svc user) FindByHandle(handle string) (u *types.User, err error) {
)
err = func() error {
u, err = svc.proc(store.LookupUserByHandle(svc.ctx, svc.store, handle))
return err
if u, err = svc.proc(store.LookupUserByHandle(svc.ctx, svc.store, handle)); err != nil {
return err
}
uaProps.setUser(u)
if err = label.Load(svc.ctx, svc.store, u); err != nil {
return err
}
return nil
}()
return u, svc.recordAction(svc.ctx, uaProps, UserActionLookup, err)
@@ -273,11 +310,33 @@ func (svc user) Find(filter types.UserFilter) (uu types.UserSet, f types.UserFil
}
}
if len(filter.Labels) > 0 {
filter.LabeledIDs, err = label.Search(
svc.ctx,
svc.store,
types.User{}.LabelResourceKind(),
filter.Labels,
)
if err != nil {
return err
}
// labels specified but no labeled resources found
if len(filter.LabeledIDs) == 0 {
return nil
}
}
uu, f, err = store.SearchUsers(svc.ctx, svc.store, filter)
if err != nil {
return err
}
if err = label.Load(svc.ctx, svc.store, toLabeledUsers(uu)...); err != nil {
return err
}
return uu.Walk(func(u *types.User) error {
svc.handlePrivateData(u)
return nil
@@ -343,6 +402,10 @@ func (svc user) Create(new *types.User) (u *types.User, err error) {
return
}
if err = label.Create(svc.ctx, svc.store, new); err != nil {
return
}
_ = svc.eventbus.WaitFor(svc.ctx, event.UserAfterCreate(new, u))
return
}()
@@ -405,6 +468,14 @@ func (svc user) Update(upd *types.User) (u *types.User, err error) {
return
}
if label.Changed(u.Labels, upd.Labels) {
if err = label.Update(svc.ctx, svc.store, upd); err != nil {
return
}
u.Labels = upd.Labels
}
_ = svc.eventbus.WaitFor(svc.ctx, event.UserAfterUpdate(upd, u))
return
}()
@@ -726,3 +797,19 @@ func createHandle(ctx context.Context, s store.Users, u *types.User) {
)
}
}
// toLabeledUsers converts to []label.LabeledResource
//
// This function is auto-generated.
func toLabeledUsers(set []*types.User) []label.LabeledResource {
if len(set) == 0 {
return nil
}
ll := make([]label.LabeledResource, len(set))
for i := range set {
ll[i] = set[i]
}
return ll
}
+5
View File
@@ -20,6 +20,8 @@ type (
Unify *ApplicationUnify `json:"unify,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
@@ -39,6 +41,9 @@ type (
Name string `json:"name"`
Query string `json:"query"`
LabeledIDs []uint64 `json:"-"`
Labels map[string]string `json:"labels,omitempty"`
Deleted filter.State `json:"deleted"`
// Check fn is called by store backend for each resource found function can
+9 -3
View File
@@ -10,9 +10,12 @@ import (
type (
// Role - An organisation may have many roles. Roles may have many channels available. Access to channels may be shared between roles.
Role struct {
ID uint64 `json:"roleID,string"`
Name string `json:"name"`
Handle string `json:"handle"`
ID uint64 `json:"roleID,string"`
Name string `json:"name"`
Handle string `json:"handle"`
Labels map[string]string `json:"labels,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
ArchivedAt *time.Time `json:"archivedAt,omitempty"`
@@ -31,6 +34,9 @@ type (
Deleted filter.State `json:"deleted"`
Archived filter.State `json:"archived"`
LabeledIDs []uint64 `json:"-"`
Labels map[string]string `json:"labels,omitempty"`
// Check fn is called by store backend for each resource found function can
// modify the resource and return false if store should not return it
//
+81
View File
@@ -0,0 +1,81 @@
package types
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// system/types/types.yaml
// SetLabel adds new label to label map
func (m *Application) SetLabel(key string, value string) {
if m.Labels == nil {
m.Labels = make(map[string]string)
}
m.Labels[key] = value
}
// GetLabels adds new label to label map
func (m Application) GetLabels() map[string]string {
return m.Labels
}
// GetLabels adds new label to label map
func (Application) LabelResourceKind() string {
return "application"
}
// GetLabels adds new label to label map
func (m Application) LabelResourceID() uint64 {
return m.ID
}
// SetLabel adds new label to label map
func (m *Role) SetLabel(key string, value string) {
if m.Labels == nil {
m.Labels = make(map[string]string)
}
m.Labels[key] = value
}
// GetLabels adds new label to label map
func (m Role) GetLabels() map[string]string {
return m.Labels
}
// GetLabels adds new label to label map
func (Role) LabelResourceKind() string {
return "role"
}
// GetLabels adds new label to label map
func (m Role) LabelResourceID() uint64 {
return m.ID
}
// SetLabel adds new label to label map
func (m *User) SetLabel(key string, value string) {
if m.Labels == nil {
m.Labels = make(map[string]string)
}
m.Labels[key] = value
}
// GetLabels adds new label to label map
func (m User) GetLabels() map[string]string {
return m.Labels
}
// GetLabels adds new label to label map
func (User) LabelResourceKind() string {
return "user"
}
// GetLabels adds new label to label map
func (m User) LabelResourceID() uint64 {
return m.ID
}
+3
View File
@@ -1,7 +1,10 @@
types:
User:
labelResourceType: user
Application:
labelResourceType: application
Role:
labelResourceType: role
RoleMember:
noIdField: true
Credentials:
+5
View File
@@ -25,6 +25,8 @@ type (
EmailConfirmed bool `json:"-"`
Labels map[string]string `json:"labels,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
SuspendedAt *time.Time `json:"suspendedAt,omitempty"`
@@ -49,6 +51,9 @@ type (
Handle string `json:"handle"`
Kind UserKind `json:"kind"`
LabeledIDs []uint64 `json:"-"`
Labels map[string]string `json:"labels,omitempty"`
Deleted filter.State `json:"deleted"`
Suspended filter.State `json:"suspended"`
+83
View File
@@ -9,7 +9,9 @@ import (
"github.com/cortezaproject/corteza-server/system/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
"github.com/steinfletcher/apitest-jsonpath"
"github.com/stretchr/testify/require"
"net/http"
"net/url"
"testing"
"time"
)
@@ -188,3 +190,84 @@ func TestApplicationDelete(t *testing.T) {
h.a.NotNil(res)
h.a.NotNil(res.DeletedAt)
}
func TestApplicationLabels(t *testing.T) {
h := newHelper(t)
h.clearApplications()
h.allow(types.SystemRBACResource, "application.create")
h.allow(types.ApplicationRBACResource.AppendWildcard(), "read")
h.allow(types.ApplicationRBACResource.AppendWildcard(), "update")
h.allow(types.ApplicationRBACResource.AppendWildcard(), "delete")
var (
ID uint64
)
t.Run("create", func(t *testing.T) {
var (
req = require.New(t)
payload = &types.Application{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
"/application/",
types.Application{Labels: map[string]string{"foo": "bar", "bar": "42"}},
payload,
)
req.NotZero(payload.ID)
h.a.Equal(payload.Labels["foo"], "bar",
"labels must contain foo with value bar")
h.a.Equal(payload.Labels["bar"], "42",
"labels must contain bar with value 42")
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
"response must match stored labels")
ID = payload.ID
})
t.Run("update", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
payload = &types.Application{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
fmt.Sprintf("PUT /application/%d", ID),
&types.Application{Labels: map[string]string{"foo": "baz", "baz": "123"}},
payload,
)
req.NotZero(payload.ID)
//req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
req.Equal(payload.Labels["foo"], "baz",
"labels must contain foo with value baz")
req.NotContains(payload.Labels, "bar",
"labels must not contain bar")
req.Equal(payload.Labels["baz"], "123",
"labels must contain baz with value 123")
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
"response must match stored labels")
})
t.Run("search", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
set = types.ApplicationSet{}
)
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/application/", &set, url.Values{"labels": []string{"baz=123"}})
req.NotEmpty(set)
req.NotNil(set.FindByID(ID))
req.NotNil(set.FindByID(ID).Labels)
})
}
+83
View File
@@ -9,7 +9,9 @@ import (
"github.com/cortezaproject/corteza-server/system/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
"github.com/steinfletcher/apitest-jsonpath"
"github.com/stretchr/testify/require"
"net/http"
"net/url"
"testing"
"time"
)
@@ -219,3 +221,84 @@ func TestRoleDelete(t *testing.T) {
h.a.NotNil(res)
h.a.NotNil(res.DeletedAt)
}
func TestRoleLabels(t *testing.T) {
h := newHelper(t)
h.clearRoles()
h.allow(types.SystemRBACResource, "role.create")
h.allow(types.RoleRBACResource.AppendWildcard(), "read")
h.allow(types.RoleRBACResource.AppendWildcard(), "update")
h.allow(types.RoleRBACResource.AppendWildcard(), "delete")
var (
ID uint64
)
t.Run("create", func(t *testing.T) {
var (
req = require.New(t)
payload = &types.Role{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
"/roles/",
types.Role{Labels: map[string]string{"foo": "bar", "bar": "42"}},
payload,
)
req.NotZero(payload.ID)
h.a.Equal(payload.Labels["foo"], "bar",
"labels must contain foo with value bar")
h.a.Equal(payload.Labels["bar"], "42",
"labels must contain bar with value 42")
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
"response must match stored labels")
ID = payload.ID
})
t.Run("update", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
payload = &types.Role{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
fmt.Sprintf("PUT /roles/%d", ID),
&types.Role{Labels: map[string]string{"foo": "baz", "baz": "123"}},
payload,
)
req.NotZero(payload.ID)
//req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
req.Equal(payload.Labels["foo"], "baz",
"labels must contain foo with value baz")
req.NotContains(payload.Labels, "bar",
"labels must not contain bar")
req.Equal(payload.Labels["baz"], "123",
"labels must contain baz with value 123")
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
"response must match stored labels")
})
t.Run("search", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
set = types.RoleSet{}
)
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/roles/", &set, url.Values{"labels": []string{"baz=123"}})
req.NotEmpty(set)
req.NotNil(set.FindByID(ID))
req.NotNil(set.FindByID(ID).Labels)
})
}
+83
View File
@@ -9,7 +9,9 @@ import (
"github.com/cortezaproject/corteza-server/system/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
"github.com/steinfletcher/apitest-jsonpath"
"github.com/stretchr/testify/require"
"net/http"
"net/url"
"testing"
"time"
)
@@ -380,3 +382,84 @@ func TestUserDelete(t *testing.T) {
Assert(helpers.AssertNoErrors).
End()
}
func TestUserLabels(t *testing.T) {
h := newHelper(t)
h.clearUsers()
h.allow(types.SystemRBACResource, "user.create")
h.allow(types.UserRBACResource.AppendWildcard(), "read")
h.allow(types.UserRBACResource.AppendWildcard(), "update")
h.allow(types.UserRBACResource.AppendWildcard(), "delete")
var (
ID uint64
)
t.Run("create", func(t *testing.T) {
var (
req = require.New(t)
payload = &types.User{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
"/users/",
types.User{Email: h.randEmail(), Labels: map[string]string{"foo": "bar", "bar": "42"}},
payload,
)
req.NotZero(payload.ID)
h.a.Equal(payload.Labels["foo"], "bar",
"labels must contain foo with value bar")
h.a.Equal(payload.Labels["bar"], "42",
"labels must contain bar with value 42")
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
"response must match stored labels")
ID = payload.ID
})
t.Run("update", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
payload = &types.User{}
)
helpers.SetLabelsViaAPI(h.apiInit(), t,
fmt.Sprintf("PUT /users/%d", ID),
&types.User{ID: ID, Email: h.randEmail(), Labels: map[string]string{"foo": "baz", "baz": "123"}},
payload,
)
req.NotZero(payload.ID)
//req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
req.Equal(payload.Labels["foo"], "baz",
"labels must contain foo with value baz")
req.NotContains(payload.Labels, "bar",
"labels must not contain bar")
req.Equal(payload.Labels["baz"], "123",
"labels must contain baz with value 123")
req.Equal(payload.Labels, helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID),
"response must match stored labels")
})
t.Run("search", func(t *testing.T) {
if ID == 0 {
t.Skip("label/create test not ran")
}
var (
req = require.New(t)
set = types.UserSet{}
)
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/users/", &set, url.Values{"labels": []string{"baz=123"}})
req.NotEmpty(set)
req.NotNil(set.FindByID(ID))
req.NotNil(set.FindByID(ID).Labels)
})
}