diff --git a/CHANGELOG.md b/CHANGELOG.md index 222de80c1a..b2a6c88e28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ For details about compatibility between different releases, see the **Commitment ### Added - Support fine-grained NbTrans controls while using Dynamic ADR mode in the Console. +- User bookmark listing now supports filtering bookmarks by entity type. + - This can be specified by setting `entity_types` field in `ListUserBookmarksRequest`. ### Changed diff --git a/api/ttn/lorawan/v3/api.md b/api/ttn/lorawan/v3/api.md index d96ccc39f7..24bd97bab3 100644 --- a/api/ttn/lorawan/v3/api.md +++ b/api/ttn/lorawan/v3/api.md @@ -10829,6 +10829,7 @@ Secret contains a secret value. It also contains the ID of the Encryption key us | `page` | [`uint32`](#uint32) | | Page number for pagination. 0 is interpreted as 1. | | `order` | [`string`](#string) | | Order the results by this field path. Default ordering is by ID. Prepend with a minus (-) to reverse the order. | | `deleted` | [`bool`](#bool) | | Only return recently deleted bookmarks. | +| `entity_types` | [`string`](#string) | repeated | Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not applied. | #### Field Rules @@ -10837,6 +10838,7 @@ Secret contains a secret value. It also contains the ID of the Encryption key us | `user_ids` |

`message.required`: `true`

| | `limit` |

`uint32.lte`: `1000`

| | `order` |

`string.in`: `[ user_id -user_id entity_type -entity_type entity_id -entity_id created_at -created_at]`

| +| `entity_types` |

`repeated.unique`: `true`

`repeated.items.string.in`: `[application client end device gateway organization user]`

| ### Message `ListUserSessionsRequest` diff --git a/api/ttn/lorawan/v3/api.swagger.json b/api/ttn/lorawan/v3/api.swagger.json index 0b636eb1ee..f7bf028a2e 100644 --- a/api/ttn/lorawan/v3/api.swagger.json +++ b/api/ttn/lorawan/v3/api.swagger.json @@ -13944,6 +13944,17 @@ "in": "query", "required": false, "type": "boolean" + }, + { + "name": "entity_types", + "description": "Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not\napplied.", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" } ], "tags": [ diff --git a/api/ttn/lorawan/v3/user.proto b/api/ttn/lorawan/v3/user.proto index 7cb2090bdb..650386fcbc 100644 --- a/api/ttn/lorawan/v3/user.proto +++ b/api/ttn/lorawan/v3/user.proto @@ -553,6 +553,25 @@ message ListUserBookmarksRequest { // Only return recently deleted bookmarks. bool deleted = 5; + + // Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not + // applied. + repeated string entity_types = 6 [(validate.rules).repeated = { + unique: true, + items: { + string: { + // These values are the same as what is specified in the pkg/ttnpb/identifiers_polymorphism + in: [ + "application", + "client", + "end device", + "gateway", + "organization", + "user" + ] + } + } + }]; } message DeleteUserBookmarkRequest { diff --git a/pkg/identityserver/bunstore/user_bookmarks_store.go b/pkg/identityserver/bunstore/user_bookmarks_store.go index e709bffceb..9a30ee399c 100644 --- a/pkg/identityserver/bunstore/user_bookmarks_store.go +++ b/pkg/identityserver/bunstore/user_bookmarks_store.go @@ -97,6 +97,21 @@ func (*userBookmarkStore) selectWithUserID( } } +func (*userBookmarkStore) selectWithEntityType( + _ context.Context, ids ...string, +) func(*bun.SelectQuery) *bun.SelectQuery { + return func(q *bun.SelectQuery) *bun.SelectQuery { + switch len(ids) { + case 0: + return q + case 1: + return q.Where("?TableAlias.entity_type = ?", ids[0]) + default: + return q.Where("?TableAlias.entity_type IN (?)", bun.In(ids)) + } + } +} + func (*userBookmarkStore) selectWithEntityID( ctx context.Context, ids ...ttnpb.IDStringer, ) func(*bun.SelectQuery) *bun.SelectQuery { @@ -213,14 +228,21 @@ func (s *userBookmarkStore) CreateBookmark( } func (s *userBookmarkStore) FindBookmarks( - ctx context.Context, id *ttnpb.UserIdentifiers, + ctx context.Context, id *ttnpb.UserIdentifiers, entityTypes ...string, ) ([]*ttnpb.UserBookmark, error) { ctx, span := tracer.StartFromContext(ctx, "FindBookmarks", trace.WithAttributes( attribute.String("user_id", id.GetUserId()), )) defer span.End() - models, err := s.listUserBookmarksBy(ctx, s.selectWithUserID(ctx, id.GetUserId())) + models, err := s.listUserBookmarksBy( + ctx, + func(sq *bun.SelectQuery) *bun.SelectQuery { + sq = s.selectWithUserID(ctx, id.GetUserId())(sq) + sq = s.selectWithEntityType(ctx, entityTypes...)(sq) + return sq + }, + ) if err != nil { return nil, err } diff --git a/pkg/identityserver/store/store_interfaces.go b/pkg/identityserver/store/store_interfaces.go index e7ccddcaef..4cf6189ed7 100644 --- a/pkg/identityserver/store/store_interfaces.go +++ b/pkg/identityserver/store/store_interfaces.go @@ -189,7 +189,9 @@ type UserSessionStore interface { // UserBookmarkStore interface for storing user bookmarks. type UserBookmarkStore interface { CreateBookmark(context.Context, *ttnpb.UserBookmark) (*ttnpb.UserBookmark, error) - FindBookmarks(context.Context, *ttnpb.UserIdentifiers) ([]*ttnpb.UserBookmark, error) + FindBookmarks( + ctx context.Context, usrID *ttnpb.UserIdentifiers, entityTypes ...string, + ) ([]*ttnpb.UserBookmark, error) PurgeBookmark(context.Context, *ttnpb.UserBookmark) error BatchPurgeBookmarks( context.Context, *ttnpb.UserIdentifiers, []*ttnpb.EntityIdentifiers, diff --git a/pkg/identityserver/storetest/user_bookmarks_store.go b/pkg/identityserver/storetest/user_bookmarks_store.go index bf392879e9..b4dc4c6e97 100644 --- a/pkg/identityserver/storetest/user_bookmarks_store.go +++ b/pkg/identityserver/storetest/user_bookmarks_store.go @@ -88,6 +88,21 @@ func (st *StoreTest) TestBasicBookmarkOperations(t *testing.T) { // nolint:paral a.So(bookmarks, should.HaveLength, len(entityIDs)) }) + t.Run("FindBoomarks_FilterByEntityType", func(t *testing.T) { // nolint:paralleltest + a, ctx := test.New(t) + + bookmarks, err := s.FindBookmarks(ctx, usr1.GetIds(), "organization") + a.So(err, should.BeNil) + // There is one of each entity type, so by filtering the by 'organization' it should return one bookmark. + a.So(bookmarks, should.HaveLength, 1) + + bookmarks, err = s.FindBookmarks(ctx, usr1.GetIds(), "application", "end device") + a.So(err, should.BeNil) + // There is one of each entity type, so by filtering the by ['application','end device'] and it should return + // only two bookmarks. + a.So(bookmarks, should.HaveLength, 2) + }) + t.Run("PurgeBookmark", func(t *testing.T) { // nolint:paralleltest a, ctx := test.New(t) err := s.PurgeBookmark(ctx, &ttnpb.UserBookmark{ diff --git a/pkg/identityserver/user_bookmark_registry.go b/pkg/identityserver/user_bookmark_registry.go index 29019d3255..887d5ded02 100644 --- a/pkg/identityserver/user_bookmark_registry.go +++ b/pkg/identityserver/user_bookmark_registry.go @@ -62,7 +62,7 @@ func (is *IdentityServer) listUserBookmarks( } }() - bookmarks, err := is.store.FindBookmarks(ctx, req.UserIds) + bookmarks, err := is.store.FindBookmarks(ctx, req.UserIds, req.EntityTypes...) if err != nil { return nil, err } diff --git a/pkg/identityserver/user_bookmark_registry_test.go b/pkg/identityserver/user_bookmark_registry_test.go index 7476dfbf6f..0ae64b59cc 100644 --- a/pkg/identityserver/user_bookmark_registry_test.go +++ b/pkg/identityserver/user_bookmark_registry_test.go @@ -107,6 +107,42 @@ func TestUsersBookmarksOperations(t *testing.T) { a.So(got, should.BeNil) a.So(errors.IsPermissionDenied(err), should.BeTrue) }) + t.Run("FindBookmarks/HasEntityType", func(t *testing.T) { // nolint:paralleltest + a, ctx := test.New(t) + + // Invalid entity type. + got, err := reg.List(ctx, &ttnpb.ListUserBookmarksRequest{ + UserIds: usr1.Ids, + EntityTypes: []string{"invalid"}, + }, creds) + a.So(got, should.BeNil) + a.So(errors.IsInvalidArgument(err), should.BeTrue) + + // Entity type without bookmarks. + got, err = reg.List(ctx, &ttnpb.ListUserBookmarksRequest{ + UserIds: usr1.Ids, + EntityTypes: []string{"organization"}, + }, creds) + a.So(got, should.NotBeNil) + a.So(got.GetBookmarks(), should.HaveLength, 0) + a.So(err, should.BeNil) + + // Entity type with bookmarks. + got, err = reg.List(ctx, &ttnpb.ListUserBookmarksRequest{ + UserIds: usr1.Ids, + EntityTypes: []string{"application"}, + }, creds) + a.So(got.GetBookmarks(), should.HaveLength, 2) + a.So(err, should.BeNil) + + // Multiple entity types in request + got, err = reg.List(ctx, &ttnpb.ListUserBookmarksRequest{ + UserIds: usr1.Ids, + EntityTypes: []string{"application", "organization", "gateway"}, + }, creds) + a.So(got.GetBookmarks(), should.HaveLength, 2) + a.So(err, should.BeNil) + }) t.Run("FindBookmarks", func(t *testing.T) { // nolint:paralleltest a, ctx := test.New(t) got, err := reg.List(ctx, &ttnpb.ListUserBookmarksRequest{ diff --git a/pkg/ttnpb/user.pb.go b/pkg/ttnpb/user.pb.go index 8ad3b25b15..7b1e8013e4 100644 --- a/pkg/ttnpb/user.pb.go +++ b/pkg/ttnpb/user.pb.go @@ -2118,6 +2118,9 @@ type ListUserBookmarksRequest struct { Order string `protobuf:"bytes,4,opt,name=order,proto3" json:"order,omitempty"` // Only return recently deleted bookmarks. Deleted bool `protobuf:"varint,5,opt,name=deleted,proto3" json:"deleted,omitempty"` + // Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not + // applied. + EntityTypes []string `protobuf:"bytes,6,rep,name=entity_types,json=entityTypes,proto3" json:"entity_types,omitempty"` } func (x *ListUserBookmarksRequest) Reset() { @@ -2187,6 +2190,13 @@ func (x *ListUserBookmarksRequest) GetDeleted() bool { return false } +func (x *ListUserBookmarksRequest) GetEntityTypes() []string { + if x != nil { + return x.EntityTypes + } + return nil +} + type DeleteUserBookmarkRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3008,7 +3018,7 @@ var file_ttn_lorawan_v3_user_proto_rawDesc = []byte{ 0x21, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x22, 0xb5, 0x02, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, + 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x22, 0xa4, 0x03, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, @@ -3027,49 +3037,56 @@ var file_ttn_lorawan_v3_user_proto_rawDesc = []byte{ 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x52, 0x0b, 0x2d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x64, 0x3a, 0x08, 0xf2, 0xaa, 0x19, 0x04, 0x08, 0x00, 0x10, 0x01, 0x22, - 0xad, 0x01, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6f, - 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, - 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, - 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, - 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x73, 0x12, 0x4a, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, - 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, - 0x01, 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x22, - 0xb5, 0x01, 0x0a, 0x1f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, - 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, - 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x4c, 0x0a, 0x0a, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, - 0x42, 0x0a, 0xfa, 0x42, 0x07, 0x92, 0x01, 0x04, 0x08, 0x01, 0x10, 0x14, 0x52, 0x09, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x2a, 0x70, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x73, 0x6f, - 0x6c, 0x65, 0x54, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4e, 0x53, 0x4f, - 0x4c, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4d, 0x45, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x10, - 0x00, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x53, 0x4f, 0x4c, 0x45, 0x5f, 0x54, 0x48, 0x45, - 0x4d, 0x45, 0x5f, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f, - 0x4e, 0x53, 0x4f, 0x4c, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4d, 0x45, 0x5f, 0x44, 0x41, 0x52, 0x4b, - 0x10, 0x02, 0x1a, 0x15, 0xea, 0xaa, 0x19, 0x11, 0x18, 0x01, 0x2a, 0x0d, 0x43, 0x4f, 0x4e, 0x53, - 0x4f, 0x4c, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4d, 0x45, 0x2a, 0x7d, 0x0a, 0x0f, 0x44, 0x61, 0x73, - 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x16, - 0x44, 0x41, 0x53, 0x48, 0x42, 0x4f, 0x41, 0x52, 0x44, 0x5f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, - 0x5f, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x41, 0x53, 0x48, - 0x42, 0x4f, 0x41, 0x52, 0x44, 0x5f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x4c, 0x49, 0x53, - 0x54, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x41, 0x53, 0x48, 0x42, 0x4f, 0x41, 0x52, 0x44, - 0x5f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x47, 0x52, 0x49, 0x44, 0x10, 0x02, 0x1a, 0x18, - 0xea, 0xaa, 0x19, 0x14, 0x18, 0x01, 0x2a, 0x10, 0x44, 0x41, 0x53, 0x48, 0x42, 0x4f, 0x41, 0x52, - 0x44, 0x5f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x74, - 0x68, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x2f, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, - 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x74, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x6d, 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x42, 0x4a, 0xfa, 0x42, 0x47, + 0x92, 0x01, 0x44, 0x18, 0x01, 0x22, 0x40, 0x72, 0x3e, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x0a, + 0x65, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x52, 0x0b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x54, + 0x79, 0x70, 0x65, 0x73, 0x3a, 0x08, 0xf2, 0xaa, 0x19, 0x04, 0x08, 0x00, 0x10, 0x01, 0x22, 0xad, + 0x01, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x42, 0x6f, 0x6f, + 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, + 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x73, 0x12, 0x4a, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, + 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, + 0x02, 0x10, 0x01, 0x52, 0x09, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x22, 0xb5, + 0x01, 0x0a, 0x1f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, + 0x65, 0x72, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x44, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, + 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x4c, 0x0a, 0x0a, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, + 0x74, 0x6e, 0x2e, 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2e, 0x76, 0x33, 0x2e, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x42, + 0x0a, 0xfa, 0x42, 0x07, 0x92, 0x01, 0x04, 0x08, 0x01, 0x10, 0x14, 0x52, 0x09, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x49, 0x64, 0x73, 0x2a, 0x70, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, + 0x65, 0x54, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x43, 0x4f, 0x4e, 0x53, 0x4f, 0x4c, + 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4d, 0x45, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x10, 0x00, + 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x53, 0x4f, 0x4c, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4d, + 0x45, 0x5f, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x4f, 0x4e, + 0x53, 0x4f, 0x4c, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4d, 0x45, 0x5f, 0x44, 0x41, 0x52, 0x4b, 0x10, + 0x02, 0x1a, 0x15, 0xea, 0xaa, 0x19, 0x11, 0x18, 0x01, 0x2a, 0x0d, 0x43, 0x4f, 0x4e, 0x53, 0x4f, + 0x4c, 0x45, 0x5f, 0x54, 0x48, 0x45, 0x4d, 0x45, 0x2a, 0x7d, 0x0a, 0x0f, 0x44, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x16, 0x44, + 0x41, 0x53, 0x48, 0x42, 0x4f, 0x41, 0x52, 0x44, 0x5f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, + 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x41, 0x53, 0x48, 0x42, + 0x4f, 0x41, 0x52, 0x44, 0x5f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x4c, 0x49, 0x53, 0x54, + 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x41, 0x53, 0x48, 0x42, 0x4f, 0x41, 0x52, 0x44, 0x5f, + 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x5f, 0x47, 0x52, 0x49, 0x44, 0x10, 0x02, 0x1a, 0x18, 0xea, + 0xaa, 0x19, 0x14, 0x18, 0x01, 0x2a, 0x10, 0x44, 0x41, 0x53, 0x48, 0x42, 0x4f, 0x41, 0x52, 0x44, + 0x5f, 0x4c, 0x41, 0x59, 0x4f, 0x55, 0x54, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x6f, 0x2e, 0x74, 0x68, + 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, + 0x6c, 0x6f, 0x72, 0x61, 0x77, 0x61, 0x6e, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x76, 0x33, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x74, 0x74, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( diff --git a/pkg/ttnpb/user.pb.paths.fm.go b/pkg/ttnpb/user.pb.paths.fm.go index 9cfb778b91..0d89b5d779 100644 --- a/pkg/ttnpb/user.pb.paths.fm.go +++ b/pkg/ttnpb/user.pb.paths.fm.go @@ -549,6 +549,7 @@ var CreateUserBookmarkRequestFieldPathsTopLevel = []string{ } var ListUserBookmarksRequestFieldPathsNested = []string{ "deleted", + "entity_types", "limit", "order", "page", @@ -559,6 +560,7 @@ var ListUserBookmarksRequestFieldPathsNested = []string{ var ListUserBookmarksRequestFieldPathsTopLevel = []string{ "deleted", + "entity_types", "limit", "order", "page", diff --git a/pkg/ttnpb/user.pb.setters.fm.go b/pkg/ttnpb/user.pb.setters.fm.go index 2ac2010e61..92172ffea4 100644 --- a/pkg/ttnpb/user.pb.setters.fm.go +++ b/pkg/ttnpb/user.pb.setters.fm.go @@ -1709,6 +1709,15 @@ func (dst *ListUserBookmarksRequest) SetFields(src *ListUserBookmarksRequest, pa var zero bool dst.Deleted = zero } + case "entity_types": + if len(subs) > 0 { + return fmt.Errorf("'entity_types' has no subfields, but %s were specified", subs) + } + if src != nil { + dst.EntityTypes = src.EntityTypes + } else { + dst.EntityTypes = nil + } default: return fmt.Errorf("invalid field: '%s'", name) diff --git a/pkg/ttnpb/user.pb.validate.go b/pkg/ttnpb/user.pb.validate.go index 809d963124..4e02d4d74d 100644 --- a/pkg/ttnpb/user.pb.validate.go +++ b/pkg/ttnpb/user.pb.validate.go @@ -3797,6 +3797,31 @@ func (m *ListUserBookmarksRequest) ValidateFields(paths ...string) error { case "deleted": // no validation rules for Deleted + case "entity_types": + + _ListUserBookmarksRequest_EntityTypes_Unique := make(map[string]struct{}, len(m.GetEntityTypes())) + + for idx, item := range m.GetEntityTypes() { + _, _ = idx, item + + if _, exists := _ListUserBookmarksRequest_EntityTypes_Unique[item]; exists { + return ListUserBookmarksRequestValidationError{ + field: fmt.Sprintf("entity_types[%v]", idx), + reason: "repeated value must contain unique items", + } + } else { + _ListUserBookmarksRequest_EntityTypes_Unique[item] = struct{}{} + } + + if _, ok := _ListUserBookmarksRequest_EntityTypes_InLookup[item]; !ok { + return ListUserBookmarksRequestValidationError{ + field: fmt.Sprintf("entity_types[%v]", idx), + reason: "value must be in list [application client end device gateway organization user]", + } + } + + } + default: return ListUserBookmarksRequestValidationError{ field: name, @@ -3876,6 +3901,15 @@ var _ListUserBookmarksRequest_Order_InLookup = map[string]struct{}{ "-created_at": {}, } +var _ListUserBookmarksRequest_EntityTypes_InLookup = map[string]struct{}{ + "application": {}, + "client": {}, + "end device": {}, + "gateway": {}, + "organization": {}, + "user": {}, +} + // ValidateFields checks the field values on DeleteUserBookmarkRequest with the // rules defined in the proto definition for this message. If any rules are // violated, an error is returned. diff --git a/pkg/ttnpb/user_flags.pb.go b/pkg/ttnpb/user_flags.pb.go index c124da25e3..c1f35bb517 100644 --- a/pkg/ttnpb/user_flags.pb.go +++ b/pkg/ttnpb/user_flags.pb.go @@ -439,6 +439,7 @@ func AddSetFlagsForListUserBookmarksRequest(flags *pflag.FlagSet, prefix string, flags.AddFlag(flagsplugin.NewUint32Flag(flagsplugin.Prefix("page", prefix), "", flagsplugin.WithHidden(hidden))) flags.AddFlag(flagsplugin.NewStringFlag(flagsplugin.Prefix("order", prefix), "", flagsplugin.WithHidden(hidden))) flags.AddFlag(flagsplugin.NewBoolFlag(flagsplugin.Prefix("deleted", prefix), "", flagsplugin.WithHidden(hidden))) + flags.AddFlag(flagsplugin.NewStringSliceFlag(flagsplugin.Prefix("entity-types", prefix), "", flagsplugin.WithHidden(hidden))) } // SetFromFlags sets the ListUserBookmarksRequest message from flags. @@ -477,5 +478,11 @@ func (m *ListUserBookmarksRequest) SetFromFlags(flags *pflag.FlagSet, prefix str m.Deleted = val paths = append(paths, flagsplugin.Prefix("deleted", prefix)) } + if val, changed, err := flagsplugin.GetStringSlice(flags, flagsplugin.Prefix("entity_types", prefix)); err != nil { + return nil, err + } else if changed { + m.EntityTypes = val + paths = append(paths, flagsplugin.Prefix("entity_types", prefix)) + } return paths, nil } diff --git a/sdk/js/generated/api.json b/sdk/js/generated/api.json index edd1380274..0566e451ce 100644 --- a/sdk/js/generated/api.json +++ b/sdk/js/generated/api.json @@ -50451,6 +50451,37 @@ "isoneof": false, "oneofdecl": "", "defaultValue": "" + }, + { + "name": "entity_types", + "description": "Specifies that only bookmarks that reference the given entity type should be returned. If empty the filter is not\napplied.", + "label": "repeated", + "type": "string", + "longType": "string", + "fullType": "string", + "ismap": false, + "isoneof": false, + "oneofdecl": "", + "defaultValue": "", + "options": { + "validate.rules": [ + { + "name": "repeated.unique", + "value": true + }, + { + "name": "repeated.items.string.in", + "value": [ + "application", + "client", + "end device", + "gateway", + "organization", + "user" + ] + } + ] + } } ] },