From 1e39772ca33f23a1d94a4ecfa7e537cd9d60f5c1 Mon Sep 17 00:00:00 2001 From: azagaya <46932830+azagaya@users.noreply.github.com> Date: Wed, 8 May 2024 15:35:56 -0300 Subject: [PATCH 01/19] Fix example for other protocols --- .../search-users-by-credentials/index.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/2/api/controllers/security/search-users-by-credentials/index.md b/doc/2/api/controllers/security/search-users-by-credentials/index.md index ee54d04ccd..56ecf0cfee 100644 --- a/doc/2/api/controllers/security/search-users-by-credentials/index.md +++ b/doc/2/api/controllers/security/search-users-by-credentials/index.md @@ -54,15 +54,17 @@ Body: "action": "searchUsersByCredentials", "strategy": "", "body": { - "bool": { - "must": [ - { - "match": { - // example with the "local" authentication strategy - "username": "test@example.com" + "query":{ + "bool": { + "must": [ + { + "match": { + // example with the "local" authentication strategy + "username": "test@example.com" + } } - } - ] + ] + } } }, } From 425b65619adf2a57829ca095e55feaa9ff32c419 Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Fri, 17 May 2024 10:42:40 +0200 Subject: [PATCH 02/19] chore: expose hmset bug --- test/api/controllers/memoryStorageController.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/api/controllers/memoryStorageController.test.js b/test/api/controllers/memoryStorageController.test.js index c77dfe7865..082ed9a64e 100644 --- a/test/api/controllers/memoryStorageController.test.js +++ b/test/api/controllers/memoryStorageController.test.js @@ -1022,6 +1022,7 @@ describe("MemoryStorageController", () => { entries: [ { field: "foo", value: "bar" }, { field: "baz", value: "qux" }, + { field: "bar", value: 0 } ], }, }); From d973c4fe3f0b1d51d8389c606e5f3e1b31b47b86 Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Fri, 17 May 2024 10:43:27 +0200 Subject: [PATCH 03/19] fix: hmset accepts value: 0 --- lib/api/controllers/memoryStorageController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/controllers/memoryStorageController.js b/lib/api/controllers/memoryStorageController.js index 7cc2ab3408..6e913a7b7c 100644 --- a/lib/api/controllers/memoryStorageController.js +++ b/lib/api/controllers/memoryStorageController.js @@ -267,11 +267,11 @@ function initMapping() { kassert.assertBodyAttributeType(request, "entries", "array"); val.forEach((v) => { - if (typeof v !== "object" || !v.field || !v.value) { + if (typeof v !== "object" || !v.field || (!v.value && v.value !== 0)) { throw kerror.get( "invalid_argument", "entries", - "", + "", ); } From 2d9285d37569afd80e9a6d25c7c8c4c532957c6b Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Fri, 17 May 2024 10:43:54 +0200 Subject: [PATCH 04/19] chore: expose mset bug --- test/api/controllers/memoryStorageController.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/api/controllers/memoryStorageController.test.js b/test/api/controllers/memoryStorageController.test.js index 082ed9a64e..c55e63fa60 100644 --- a/test/api/controllers/memoryStorageController.test.js +++ b/test/api/controllers/memoryStorageController.test.js @@ -1097,6 +1097,7 @@ describe("MemoryStorageController", () => { { key: "key1", value: "value1" }, { key: "key2", value: "value2" }, { key: "key3", value: "value3" }, + { key: "key4", value: 0 } ], }, }); From d8168a8c5158c4ebcf7a51dddcaa9b2fa5fa1e65 Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Fri, 17 May 2024 10:44:42 +0200 Subject: [PATCH 05/19] fix: mset accepts value: 0 --- lib/api/controllers/memoryStorageController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/controllers/memoryStorageController.js b/lib/api/controllers/memoryStorageController.js index 6e913a7b7c..d72f4f2d28 100644 --- a/lib/api/controllers/memoryStorageController.js +++ b/lib/api/controllers/memoryStorageController.js @@ -348,7 +348,7 @@ function initMapping() { kassert.assertBodyHasAttribute(request, "entries"); kassert.assertBodyAttributeType(request, "entries", "array"); val.forEach((entry) => { - if (typeof entry !== "object" || !entry.key || !entry.value) { + if (typeof entry !== "object" || !entry.key || (!entry.value && entry.value !== 0)) { throw kerror.get( "invalid_argument", "entries", From 02c829cd3a339c6af48a97bd8b65f98ff3b6b2eb Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Fri, 17 May 2024 10:55:29 +0200 Subject: [PATCH 06/19] chore: lint --- lib/api/controllers/memoryStorageController.js | 12 ++++++++++-- test/api/controllers/memoryStorageController.test.js | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/api/controllers/memoryStorageController.js b/lib/api/controllers/memoryStorageController.js index d72f4f2d28..9569b298c3 100644 --- a/lib/api/controllers/memoryStorageController.js +++ b/lib/api/controllers/memoryStorageController.js @@ -267,7 +267,11 @@ function initMapping() { kassert.assertBodyAttributeType(request, "entries", "array"); val.forEach((v) => { - if (typeof v !== "object" || !v.field || (!v.value && v.value !== 0)) { + if ( + typeof v !== "object" || + !v.field || + (!v.value && v.value !== 0) + ) { throw kerror.get( "invalid_argument", "entries", @@ -348,7 +352,11 @@ function initMapping() { kassert.assertBodyHasAttribute(request, "entries"); kassert.assertBodyAttributeType(request, "entries", "array"); val.forEach((entry) => { - if (typeof entry !== "object" || !entry.key || (!entry.value && entry.value !== 0)) { + if ( + typeof entry !== "object" || + !entry.key || + (!entry.value && entry.value !== 0) + ) { throw kerror.get( "invalid_argument", "entries", diff --git a/test/api/controllers/memoryStorageController.test.js b/test/api/controllers/memoryStorageController.test.js index c55e63fa60..510e719ee1 100644 --- a/test/api/controllers/memoryStorageController.test.js +++ b/test/api/controllers/memoryStorageController.test.js @@ -1022,7 +1022,7 @@ describe("MemoryStorageController", () => { entries: [ { field: "foo", value: "bar" }, { field: "baz", value: "qux" }, - { field: "bar", value: 0 } + { field: "bar", value: 0 }, ], }, }); @@ -1097,7 +1097,7 @@ describe("MemoryStorageController", () => { { key: "key1", value: "value1" }, { key: "key2", value: "value2" }, { key: "key3", value: "value3" }, - { key: "key4", value: 0 } + { key: "key4", value: 0 }, ], }, }); From cb685f9fe36d95e0a03b9170270cc5fabd5f8b2f Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Fri, 17 May 2024 12:13:09 +0200 Subject: [PATCH 07/19] fixup! chore: expose mset bug --- test/api/controllers/memoryStorageController.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/api/controllers/memoryStorageController.test.js b/test/api/controllers/memoryStorageController.test.js index 510e719ee1..ad2f312ac5 100644 --- a/test/api/controllers/memoryStorageController.test.js +++ b/test/api/controllers/memoryStorageController.test.js @@ -1111,6 +1111,8 @@ describe("MemoryStorageController", () => { "value2", "key3", "value3", + "key4", + 0, ]); }); From 35256f0299c01424af397707c02062128ebc98b2 Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Wed, 22 May 2024 08:11:04 +0200 Subject: [PATCH 08/19] fix(doc): fix a typo in documentation --- doc/2/framework/events/api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/2/framework/events/api/index.md b/doc/2/framework/events/api/index.md index 16f76ef980..261e01f028 100644 --- a/doc/2/framework/events/api/index.md +++ b/doc/2/framework/events/api/index.md @@ -36,7 +36,7 @@ The `before` event name is built using the following template: #### Example -| API action | After event name | +| API action | Before event name | | -------------------------------------------------------------------------------------------- | -------------------------------- | | [auth:login](/core/2/api/controllers/auth/login) | auth:beforeLogin` | | [document:createOrReplace](/core/2/api/controllers/document/create-or-replace) | `document:beforeCreateOrReplace` | From f9d5603eaea4cbebc3b5d96baa9064c5d6d7b29f Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Thu, 23 May 2024 07:04:22 +0200 Subject: [PATCH 09/19] doc: fix typo in document:export --- doc/2/api/controllers/document/export/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/2/api/controllers/document/export/index.md b/doc/2/api/controllers/document/export/index.md index b906c94844..434299c8e6 100644 --- a/doc/2/api/controllers/document/export/index.md +++ b/doc/2/api/controllers/document/export/index.md @@ -84,7 +84,7 @@ Following arguments are available: `query`, `fields` and `fieldsName`. "index": "", "collection": "", "controller": "document", - "action": "search", + "action": "export", "body": { "query": { // ... From 017c63646fe5cd1934f8e1a9a57ce2643b85cbb7 Mon Sep 17 00:00:00 2001 From: Juiced66 Date: Thu, 23 May 2024 07:10:39 +0200 Subject: [PATCH 10/19] doc: fix typo in document:deleteFields --- doc/2/api/controllers/document/delete-fields/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/2/api/controllers/document/delete-fields/index.md b/doc/2/api/controllers/document/delete-fields/index.md index 96c9545a42..a30af89398 100644 --- a/doc/2/api/controllers/document/delete-fields/index.md +++ b/doc/2/api/controllers/document/delete-fields/index.md @@ -91,7 +91,7 @@ Returns an object containing updated document information, with the following pr "index": "", "collection": "", "controller": "document", - "action": "replace", + "action": "deleteFields", "requestId": "", "result": { "_id": "", From e87f5de5101b8987d2e38e3e34cd689df0112970 Mon Sep 17 00:00:00 2001 From: Florian Maunier Date: Fri, 31 May 2024 11:47:52 +0200 Subject: [PATCH 11/19] Enable role and profile caching (#2533) Enable caching of roles and profiles in the security module. --- .gitignore | 6 +- lib/core/security/profileRepository.ts | 83 +++++--------- lib/core/security/roleRepository.js | 2 +- lib/core/shared/ObjectRepository.ts | 2 +- test/core/security/profileRepository.test.js | 112 +++---------------- 5 files changed, 51 insertions(+), 154 deletions(-) diff --git a/.gitignore b/.gitignore index 541da47582..b027c245d7 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ lib/core/backend/backendSubscription.js lib/core/backend/backendVault.js lib/core/backend/index.js lib/core/backend/internalLogger.js +lib/core/cache/cacheDbEnum.js lib/core/debug/kuzzleDebugger.js lib/core/plugin/pluginContext.js lib/core/realtime/channel.js @@ -135,10 +136,12 @@ lib/core/realtime/subscription.js lib/core/security/profileRepository.js lib/core/security/tokenRepository.js lib/core/shared/KoncordeWrapper.js -lib/core/shared/repository.js +lib/core/shared/ObjectRepository.js lib/core/shared/sdk/embeddedSdk.js lib/core/shared/sdk/funnelProtocol.js +lib/core/shared/store.js lib/core/storage/indexCache.js +lib/core/storage/storeScopeEnum.js lib/kerror/errors/*.js lib/kerror/errors/badRequestError.js lib/kerror/errors/externalServiceError.js @@ -208,6 +211,7 @@ lib/types/realtime/RoomList.js lib/types/RequestPayload.js lib/types/ResponsePayload.js lib/types/RoleDefinition.js +lib/types/shared/StoreCollectionsDefinition.js lib/types/StrategyDefinition.js lib/types/Target.js lib/types/Token.js diff --git a/lib/core/security/profileRepository.ts b/lib/core/security/profileRepository.ts index fd2032b26a..630aa77af9 100644 --- a/lib/core/security/profileRepository.ts +++ b/lib/core/security/profileRepository.ts @@ -26,7 +26,6 @@ import { JSONObject } from "kuzzle-sdk"; import { OptimizedPolicy, Policy } from "../../../index"; import * as kerror from "../../kerror"; import { Profile } from "../../model/security/profile"; -import { cacheDbEnum } from "../cache/cacheDbEnum"; import { ObjectRepository } from "../shared/ObjectRepository"; /** @internal */ @@ -59,19 +58,14 @@ type UpdateOptions = { */ export class ProfileRepository extends ObjectRepository { private module: any; - private profiles: Map; /** * @constructor */ constructor(securityModule) { - super({ - cache: cacheDbEnum.NONE, - store: global.kuzzle.internalIndex, - }); + super({ store: global.kuzzle.internalIndex }); this.module = securityModule; - this.profiles = new Map(); this.collection = "profiles"; this.ObjectConstructor = Profile; @@ -129,7 +123,7 @@ export class ProfileRepository extends ObjectRepository { * @param {String} [id] - profile identifier */ global.kuzzle.onAsk("core:security:profile:invalidate", (id) => - this.invalidate(id), + this.deleteFromCache(id), ); /** @@ -191,15 +185,29 @@ export class ProfileRepository extends ObjectRepository { * @returns {Promise.} * @throws {NotFoundError} If the corresponding profile doesn't exist */ - async load(id: string): Promise { - if (this.profiles.has(id)) { - return this.profiles.get(id); - } + override async load( + id: string, + options: { key?: string } = {}, + ): Promise { + const profile = await this.loadFromCache(id, options); + + if (profile === null) { + const profileFromDatabase = await this.loadOneFromDatabase(id); + + if (profileFromDatabase !== null) { + await this.persistToCache(profileFromDatabase); + + profileFromDatabase.optimizedPolicies = this.optimizePolicies( + profileFromDatabase.policies, + ); + } - const profile = await super.load(id); + return profileFromDatabase; + } profile.optimizedPolicies = this.optimizePolicies(profile.policies); - this.profiles.set(id, profile); + + await this.refreshCacheTTL(profile); return profile; } @@ -232,16 +240,7 @@ export class ProfileRepository extends ObjectRepository { } for (const id of profileIds) { - let profile: Profile | Promise = this.profiles.get(id); - - if (!profile) { - profile = this.loadOneFromDatabase(id).then((p) => { - p.optimizedPolicies = this.optimizePolicies(p.policies); - this.profiles.set(id, p); - return p; - }); - } - + const profile: Profile | Promise = this.load(id); profiles.push(profile); } @@ -443,7 +442,7 @@ export class ProfileRepository extends ObjectRepository { await this.deleteFromDatabase(profile._id, { refresh }); - this.profiles.delete(profile._id); + await this.deleteFromCache(profile._id); } /** @@ -502,12 +501,12 @@ export class ProfileRepository extends ObjectRepository { }); const updatedProfile = await this.loadOneFromDatabase(profile._id); + await this.persistToCache(updatedProfile); + // Recompute optimized policies based on new policies updatedProfile.optimizedPolicies = this.optimizePolicies( updatedProfile.policies, ); - - this.profiles.set(profile._id, updatedProfile); return updatedProfile; } @@ -538,32 +537,6 @@ export class ProfileRepository extends ObjectRepository { return profile; } - /** - * @override - */ - async truncate(opts: JSONObject) { - try { - await super.truncate(opts); - } finally { - // always clear the RAM cache: even if truncate fails in the middle of it, - // some of the cached profiles might not be valid anymore - this.invalidate(); - } - } - - /** - * Invalidate the cache entries for the given profile. If none is provided, - * the entire cache is emptied. - * @param {string} [profileId] - */ - invalidate(profileId?: string) { - if (!profileId) { - this.profiles.clear(); - } else { - this.profiles.delete(profileId); - } - } - /** * Optimize each policy to get a O(1) index access time * and a O(log(n)) collection search time. @@ -636,11 +609,11 @@ export class ProfileRepository extends ObjectRepository { // Otherwise we cannot stub them // ============================================ - async toDTO(dto: Profile): Promise { + toDTO(dto: Profile): JSONObject { return super.toDTO(dto); } - async deleteFromDatabase(id: string, options: JSONObject) { + deleteFromDatabase(id: string, options: JSONObject) { return super.deleteFromDatabase(id, options); } diff --git a/lib/core/security/roleRepository.js b/lib/core/security/roleRepository.js index 272b645977..023a1cbcb1 100644 --- a/lib/core/security/roleRepository.js +++ b/lib/core/security/roleRepository.js @@ -43,7 +43,7 @@ class RoleRepository extends ObjectRepository { */ constructor(securityModule) { super({ - cache: cacheDbEnum.NONE, + cache: cacheDbEnum.INTERNAL, store: global.kuzzle.internalIndex, }); diff --git a/lib/core/shared/ObjectRepository.ts b/lib/core/shared/ObjectRepository.ts index 1f30a0d281..641528fa50 100644 --- a/lib/core/shared/ObjectRepository.ts +++ b/lib/core/shared/ObjectRepository.ts @@ -138,7 +138,7 @@ export class ObjectRepository { try { response = await global.kuzzle.ask(`core:cache:${this.cacheDb}:get`, key); - if (response === null) { + if (response === null || response === undefined) { return null; } diff --git a/test/core/security/profileRepository.test.js b/test/core/security/profileRepository.test.js index 01fe5f0f5d..2be90e3fba 100644 --- a/test/core/security/profileRepository.test.js +++ b/test/core/security/profileRepository.test.js @@ -64,14 +64,6 @@ describe("Test: security/profileRepository", () => { should(profileRepository.load).calledWith("foo"); }); - it("should return a profile from memory cache", async () => { - profileRepository.profiles.set("foo", testProfile); - - const profile = await profileRepository.load("foo"); - - should(profile).be.exactly(testProfile); - }); - it("should reject if the profile does not exist", () => { kuzzle.ask .withArgs( @@ -94,14 +86,15 @@ describe("Test: security/profileRepository", () => { let profile; try { - sinon.stub(ObjectRepository.prototype, "load").resolves(testProfile); + sinon + .stub(ObjectRepository.prototype, "loadOneFromDatabase") + .resolves(testProfile); profile = await profileRepository.load("foo"); } finally { - ObjectRepository.prototype.load.restore(); + ObjectRepository.prototype.loadOneFromDatabase.restore(); } should(profile).be.exactly(testProfile); - should(profileRepository.profiles).have.value("foo", testProfile); }); }); @@ -141,43 +134,17 @@ describe("Test: security/profileRepository", () => { const p3 = { _id: "p3", baz: "foo", constructor: { _hash: () => false } }; profileRepository.loadOneFromDatabase.withArgs("p1").resolves(p1); + profileRepository.loadOneFromDatabase.withArgs("p2").resolves(p2); profileRepository.loadOneFromDatabase.withArgs("p3").resolves(p3); roleRepositoryMock.loadRoles.resolves([{ _id: "default" }]); - profileRepository.profiles.set("p2", p2); - const result = await profileRepository.loadProfiles(["p1", "p2", "p3"]); should(result).eql([p1, p2, p3]); - // should not load p2 from the database since it has been cached should(profileRepository.loadOneFromDatabase).calledWith("p1"); - should(profileRepository.loadOneFromDatabase).neverCalledWith("p2"); + should(profileRepository.loadOneFromDatabase).calledWith("p2"); should(profileRepository.loadOneFromDatabase).calledWith("p3"); - should(profileRepository.profiles).have.value("p1", p1); - should(profileRepository.profiles).have.value("p2", p2); - should(profileRepository.profiles).have.value("p3", p3); - }); - - it("should use only the cache if all profiles are known", async () => { - const p1 = { _id: "p1", foo: "bar", constructor: { _hash: () => false } }; - const p2 = { _id: "p2", bar: "baz", constructor: { _hash: () => false } }; - const p3 = { _id: "p3", baz: "foo", constructor: { _hash: () => false } }; - - roleRepositoryMock.loadRoles.resolves([{ _id: "default" }]); - - profileRepository.profiles.set("p1", p1); - profileRepository.profiles.set("p2", p2); - profileRepository.profiles.set("p3", p3); - - const result = await profileRepository.loadProfiles(["p1", "p2", "p3"]); - - should(result).eql([p1, p2, p3]); - // should not load p2 from the database since it has been cached - should(profileRepository.loadOneFromDatabase).not.called(); - should(profileRepository.profiles).have.value("p1", p1); - should(profileRepository.profiles).have.value("p2", p2); - should(profileRepository.profiles).have.value("p3", p3); }); }); @@ -260,16 +227,16 @@ describe("Test: security/profileRepository", () => { } }); - it("should call deleteFromDatabase and remove the profile from memory", async () => { - profileRepository.profiles.set(testProfile._id, true); - + it("should call deleteFromDatabase and deleteFromCache", async () => { await profileRepository.deleteById(testProfile._id); should(profileRepository.deleteFromDatabase) .be.calledOnce() .be.calledWithMatch(testProfile._id, { refresh: "false" }); - should(profileRepository.profiles).not.have.key(testProfile._id); + should(profileRepository.deleteFromCache) + .be.calledOnce() + .be.calledWithMatch(testProfile._id); }); it("should be able to handle the refresh option", async () => { @@ -294,8 +261,6 @@ describe("Test: security/profileRepository", () => { total: 1, }); - profileRepository.profiles.set(testProfile._id, true); - await profileRepository.deleteById(testProfile._id, { onAssignedUsers: "remove", }); @@ -381,7 +346,6 @@ describe("Test: security/profileRepository", () => { await profileRepository.validateAndSaveProfile(testProfile); should(result).be.exactly(testProfile); - should(profileRepository.profiles).have.value("foo", testProfile); }); it("should compute the optimized policies", async () => { @@ -469,11 +433,13 @@ describe("Test: security/profileRepository", () => { describe("#load", () => { afterEach(() => { - ObjectRepository.prototype.load.restore(); + ObjectRepository.prototype.loadOneFromDatabase.restore(); }); it("should compute the optimized policies", async () => { - sinon.stub(ObjectRepository.prototype, "load").resolves(testProfile); + sinon + .stub(ObjectRepository.prototype, "loadOneFromDatabase") + .resolves(testProfile); profileRepository.optimizePolicies = sinon.stub().resolves([]); @@ -694,38 +660,11 @@ describe("Test: security/profileRepository", () => { }); it('should register a "truncate" event', async () => { - sinon.stub(profileRepository, "truncate"); - kuzzle.ask.restore(); await kuzzle.ask("core:security:profile:truncate", "foo"); should(profileRepository.truncate).calledWith("foo"); }); - - it("should clear the RAM cache once the truncate succeeds", async () => { - const opts = { foo: "bar" }; - - profileRepository.profiles.set("foo", "bar"); - profileRepository.profiles.set("baz", "qux"); - - await profileRepository.truncate(opts); - - should(ObjectRepository.prototype.truncate).calledWith(opts); - should(profileRepository.profiles).be.empty(); - }); - - it("should clear the RAM cache even if the truncate fails", async () => { - const error = new Error("foo"); - - ObjectRepository.prototype.truncate.rejects(error); - - profileRepository.profiles.set("foo", "bar"); - profileRepository.profiles.set("baz", "qux"); - - await should(profileRepository.truncate()).rejectedWith(error); - - should(profileRepository.profiles).be.empty(); - }); }); describe("#optimizePolicy", () => { @@ -782,31 +721,12 @@ describe("Test: security/profileRepository", () => { describe("#invalidate", () => { it('should register an "invalidate" event', async () => { - sinon.stub(profileRepository, "invalidate"); + sinon.stub(profileRepository, "deleteFromCache"); kuzzle.ask.restore(); await kuzzle.ask("core:security:profile:invalidate", "foo"); - should(profileRepository.invalidate).calledWith("foo"); - }); - - it("should invalidate only the provided profile", async () => { - profileRepository.profiles.set("foo", "bar"); - profileRepository.profiles.set("baz", "qux"); - - await profileRepository.invalidate("baz"); - - should(profileRepository.profiles).has.key("foo"); - should(profileRepository.profiles).not.has.key("baz"); - }); - - it("should invalidate the entire cache with no argument", async () => { - profileRepository.profiles.set("foo", "bar"); - profileRepository.profiles.set("baz", "qux"); - - await profileRepository.invalidate(); - - should(profileRepository.profiles).be.empty(); + should(profileRepository.deleteFromCache).calledWith("foo"); }); }); }); From 5c1943ea3ce97a9bbaad6af94399178db4cd4636 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 3 Jun 2024 08:07:58 +0000 Subject: [PATCH 12/19] chore(release): 2.30.1-beta.1 [skip ci] ## [2.30.1-beta.1](https://github.com/kuzzleio/kuzzle/compare/v2.30.0...v2.30.1-beta.1) (2024-06-03) ### Bug Fixes * **doc:** fix a typo in documentation ([35256f0](https://github.com/kuzzleio/kuzzle/commit/35256f0299c01424af397707c02062128ebc98b2)) * hmset accepts value: 0 ([d973c4f](https://github.com/kuzzleio/kuzzle/commit/d973c4fe3f0b1d51d8389c606e5f3e1b31b47b86)) * mset accepts value: 0 ([d8168a8](https://github.com/kuzzleio/kuzzle/commit/d8168a8c5158c4ebcf7a51dddcaa9b2fa5fa1e65)) --- CHANGELOG.md | 9 +++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e03357e979..71daadc419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [2.30.1-beta.1](https://github.com/kuzzleio/kuzzle/compare/v2.30.0...v2.30.1-beta.1) (2024-06-03) + + +### Bug Fixes + +* **doc:** fix a typo in documentation ([35256f0](https://github.com/kuzzleio/kuzzle/commit/35256f0299c01424af397707c02062128ebc98b2)) +* hmset accepts value: 0 ([d973c4f](https://github.com/kuzzleio/kuzzle/commit/d973c4fe3f0b1d51d8389c606e5f3e1b31b47b86)) +* mset accepts value: 0 ([d8168a8](https://github.com/kuzzleio/kuzzle/commit/d8168a8c5158c4ebcf7a51dddcaa9b2fa5fa1e65)) + # [2.30.0](https://github.com/kuzzleio/kuzzle/compare/v2.29.1...v2.30.0) (2024-05-07) diff --git a/package-lock.json b/package-lock.json index bb37b9b654..bfca615308 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kuzzle", - "version": "2.30.0", + "version": "2.30.1-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kuzzle", - "version": "2.30.0", + "version": "2.30.1-beta.1", "license": "Apache-2.0", "dependencies": { "@elastic/elasticsearch": "https://github.com/elastic/elasticsearch-js/archive/refs/tags/v7.13.0.tar.gz", diff --git a/package.json b/package.json index 24acd50d51..59e638f288 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kuzzle", "author": "The Kuzzle Team ", - "version": "2.30.0", + "version": "2.30.1-beta.1", "description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.", "bin": "bin/start-kuzzle-server", "scripts": { From 87be8bde384dfddfec66e6ad98aa0791e381957a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rodriguez?= Date: Mon, 10 Jun 2024 15:10:39 +0200 Subject: [PATCH 13/19] build: no strict version for kuzzle-sdk (#2537) Change kuzzle-sdk version restriction to allow npm to dedup the package when it's required in other package. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb37b9b654..5b9dae1c7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "koncorde": "4.3.0", "kuzzle-plugin-auth-passport-local": "6.4.0", "kuzzle-plugin-logger": "3.0.3", - "kuzzle-sdk": "7.11.1", + "kuzzle-sdk": "^7.11.3", "kuzzle-vault": "2.0.4", "lodash": "4.17.21", "long": "5.2.3", @@ -10325,9 +10325,9 @@ } }, "node_modules/kuzzle-sdk": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/kuzzle-sdk/-/kuzzle-sdk-7.11.1.tgz", - "integrity": "sha512-1nPZmekFE2Q1gNhPisn9kg1/LsDRzDnl4GIYGdjIHISK5BhiAICMEsmMMyTiYqja42/mdMqm+M5bFx4p3wMJdQ==", + "version": "7.11.3", + "resolved": "https://registry.npmjs.org/kuzzle-sdk/-/kuzzle-sdk-7.11.3.tgz", + "integrity": "sha512-YHPSE99GRg1GT9UD5hOuJnt5vc/Gqmp705id4sCjyRJNRaq9uf3j3OzM6bvD+4YPZB7LTim53z66CFQP7IV7Qg==", "dependencies": { "min-req-promise": "^1.0.1", "ws": "^8.13.0" diff --git a/package.json b/package.json index 24acd50d51..b3ad3ee619 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "koncorde": "4.3.0", "kuzzle-plugin-auth-passport-local": "6.4.0", "kuzzle-plugin-logger": "3.0.3", - "kuzzle-sdk": "7.11.1", + "kuzzle-sdk": "^7.11.3", "kuzzle-vault": "2.0.4", "lodash": "4.17.21", "long": "5.2.3", From 508ac72a25b690ac452ff32dbe80e0833c00290d Mon Sep 17 00:00:00 2001 From: Quentin Date: Mon, 15 Jul 2024 11:24:11 +0200 Subject: [PATCH 14/19] feat(funnel): add optional parameter to request to trigger pipes --- lib/api/funnel.js | 3 +++ lib/api/request/requestInput.ts | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/api/funnel.js b/lib/api/funnel.js index f0539ab6be..cb8802db85 100644 --- a/lib/api/funnel.js +++ b/lib/api/funnel.js @@ -753,6 +753,9 @@ class Funnel { */ async executePluginRequest(request) { try { + if (request.input.allowTriggerEvents) { + return await this.processRequest(request); + } return await doAction(this.getController(request), request); } catch (e) { this.handleErrorDump(e); diff --git a/lib/api/request/requestInput.ts b/lib/api/request/requestInput.ts index 2f4da700d1..ca1f86a17c 100644 --- a/lib/api/request/requestInput.ts +++ b/lib/api/request/requestInput.ts @@ -32,6 +32,7 @@ const _body = "body\u200b"; const _headers = "headers\u200b"; const _controller = "controller\u200b"; const _action = "action\u200b"; +const _allowTriggerEvents = "allowTriggerEvents\u200b"; // any property not listed here will be copied into // RequestInput.args @@ -155,6 +156,7 @@ export class RequestInput { this[_body] = null; this[_controller] = null; this[_action] = null; + this[_allowTriggerEvents] = null; // default value to null for former "resources" to avoid breaking this.args = {}; @@ -181,6 +183,7 @@ export class RequestInput { this.body = data.body; this.controller = data.controller; this.action = data.action; + this.allowTriggerEvents = data.allowTriggerEvents; } /** @@ -263,7 +266,12 @@ export class RequestInput { this[_action] = assert.assertString("action", str); } } - + get allowTriggerEvents(): boolean | undefined { + return this[_allowTriggerEvents]; + } + set allowTriggerEvents(bool: boolean) { + this[_allowTriggerEvents] = bool === true ? true : undefined; + } /** * Request body. * In Http it's the request body parsed. From 6c3f1cbd918c0e1e14de727cb84b09a2167f35c5 Mon Sep 17 00:00:00 2001 From: Quentin Date: Mon, 15 Jul 2024 14:28:49 +0200 Subject: [PATCH 15/19] Add unit tests for the activation of pipes --- test/api/funnel/executePluginRequest.test.js | 28 +++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/test/api/funnel/executePluginRequest.test.js b/test/api/funnel/executePluginRequest.test.js index 25df418605..f344927e45 100644 --- a/test/api/funnel/executePluginRequest.test.js +++ b/test/api/funnel/executePluginRequest.test.js @@ -2,7 +2,6 @@ const should = require("should"); const sinon = require("sinon"); - const KuzzleMock = require("../../mocks/kuzzle.mock"); const { MockNativeController } = require("../../mocks/controller.mock"); const FunnelController = require("../../../lib/api/funnel"); @@ -102,4 +101,31 @@ describe("funnel.executePluginRequest", () => { done(e); }); }); + + it("should trigger pipes if allowTriggerEvent is enabled", async () => { + const request = new Request({ + controller: "testme", + action: "succeed", + allowTriggerEvents: true, + }); + + return funnel.executePluginRequest(request).then((response) => { + should(response).be.exactly(request); + should(kuzzle.pipe).calledWith("testme:beforeSucceed"); + should(kuzzle.pipe).calledWith("testme:afterSucceed"); + }); + }); + + it("should not trigger pipes if allowTriggerEvent is disabled", async () => { + const request = new Request({ + controller: "testme", + action: "succeed", + }); + + return funnel.executePluginRequest(request).then((response) => { + should(response).be.exactly(request); + should(kuzzle.pipe).not.calledWith("testme:beforeSucceed"); + should(kuzzle.pipe).not.calledWith("testme:afterSucceed"); + }); + }); }); From 4913389e4be38f3cb23d85ca2bc769fc979dd64e Mon Sep 17 00:00:00 2001 From: Quentin Date: Tue, 16 Jul 2024 11:04:44 +0200 Subject: [PATCH 16/19] feat(doc): add documentation in the event-system guide --- .../develop-on-kuzzle/event-system/index.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/2/guides/develop-on-kuzzle/event-system/index.md b/doc/2/guides/develop-on-kuzzle/event-system/index.md index b19b1f7b76..5c4d6321ba 100644 --- a/doc/2/guides/develop-on-kuzzle/event-system/index.md +++ b/doc/2/guides/develop-on-kuzzle/event-system/index.md @@ -187,3 +187,25 @@ await app.trigger('app-name/file-available', fileUrl); ::: warning If an internal event is triggered, the payload must be the same as the original event. ::: + +**Events can be triggered** with SDK and controllers actions. + +::: warning +By default, actions executed through the embedded SDK or controllers won't trigger any events and thus no pipe or hooks will be called. +::: + +This behaviour is set in order to prevent an infinite loop in which a pipe calls a controller generating an event calling this same pipe again and again. + +It is nonetheless possible to pass the flag `allowTrigggerEvents` to the options parameters of the controller to allow events firing : + +```ts +await this.sdk.document.create( + "index", + 'collection', + { + contentOfDocument: 'CREATED VIA CONTROLLER', + }, + _idOfDocument, + { allowTriggerEvents: true }, + ); +``` From 928e21ff3b4edc3fef77bf359abf41b046bed849 Mon Sep 17 00:00:00 2001 From: Quentin Date: Tue, 16 Jul 2024 16:52:08 +0200 Subject: [PATCH 17/19] chore(funnel): rename flag for firing events --- .../guides/develop-on-kuzzle/event-system/index.md | 6 +++--- lib/api/funnel.js | 2 +- lib/api/request/requestInput.ts | 14 +++++++------- test/api/funnel/executePluginRequest.test.js | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/2/guides/develop-on-kuzzle/event-system/index.md b/doc/2/guides/develop-on-kuzzle/event-system/index.md index 5c4d6321ba..9452a31437 100644 --- a/doc/2/guides/develop-on-kuzzle/event-system/index.md +++ b/doc/2/guides/develop-on-kuzzle/event-system/index.md @@ -188,7 +188,7 @@ await app.trigger('app-name/file-available', fileUrl); If an internal event is triggered, the payload must be the same as the original event. ::: -**Events can be triggered** with SDK and controllers actions. +**Events are not triggered** with SDK and controllers actions. ::: warning By default, actions executed through the embedded SDK or controllers won't trigger any events and thus no pipe or hooks will be called. @@ -196,7 +196,7 @@ By default, actions executed through the embedded SDK or controllers won't trigg This behaviour is set in order to prevent an infinite loop in which a pipe calls a controller generating an event calling this same pipe again and again. -It is nonetheless possible to pass the flag `allowTrigggerEvents` to the options parameters of the controller to allow events firing : +It is nonetheless possible to pass the flag `triggerEvents` to the options parameters of the controller to fire events : ```ts await this.sdk.document.create( @@ -206,6 +206,6 @@ await this.sdk.document.create( contentOfDocument: 'CREATED VIA CONTROLLER', }, _idOfDocument, - { allowTriggerEvents: true }, + { triggerEvents: true }, ); ``` diff --git a/lib/api/funnel.js b/lib/api/funnel.js index cb8802db85..e8c4bad0a4 100644 --- a/lib/api/funnel.js +++ b/lib/api/funnel.js @@ -753,7 +753,7 @@ class Funnel { */ async executePluginRequest(request) { try { - if (request.input.allowTriggerEvents) { + if (request.input.triggerEvents) { return await this.processRequest(request); } return await doAction(this.getController(request), request); diff --git a/lib/api/request/requestInput.ts b/lib/api/request/requestInput.ts index ca1f86a17c..a0b741db15 100644 --- a/lib/api/request/requestInput.ts +++ b/lib/api/request/requestInput.ts @@ -32,7 +32,7 @@ const _body = "body\u200b"; const _headers = "headers\u200b"; const _controller = "controller\u200b"; const _action = "action\u200b"; -const _allowTriggerEvents = "allowTriggerEvents\u200b"; +const _triggerEvents = "triggerEvents\u200b"; // any property not listed here will be copied into // RequestInput.args @@ -156,7 +156,7 @@ export class RequestInput { this[_body] = null; this[_controller] = null; this[_action] = null; - this[_allowTriggerEvents] = null; + this[_triggerEvents] = null; // default value to null for former "resources" to avoid breaking this.args = {}; @@ -183,7 +183,7 @@ export class RequestInput { this.body = data.body; this.controller = data.controller; this.action = data.action; - this.allowTriggerEvents = data.allowTriggerEvents; + this.triggerEvents = data.triggerEvents; } /** @@ -266,11 +266,11 @@ export class RequestInput { this[_action] = assert.assertString("action", str); } } - get allowTriggerEvents(): boolean | undefined { - return this[_allowTriggerEvents]; + get triggerEvents(): boolean | undefined { + return this[_triggerEvents]; } - set allowTriggerEvents(bool: boolean) { - this[_allowTriggerEvents] = bool === true ? true : undefined; + set triggerEvents(bool: boolean) { + this[_triggerEvents] = bool === true ? true : undefined; } /** * Request body. diff --git a/test/api/funnel/executePluginRequest.test.js b/test/api/funnel/executePluginRequest.test.js index f344927e45..7c50241f2e 100644 --- a/test/api/funnel/executePluginRequest.test.js +++ b/test/api/funnel/executePluginRequest.test.js @@ -102,11 +102,11 @@ describe("funnel.executePluginRequest", () => { }); }); - it("should trigger pipes if allowTriggerEvent is enabled", async () => { + it("should trigger pipes if triggerEvent is enabled", async () => { const request = new Request({ controller: "testme", action: "succeed", - allowTriggerEvents: true, + triggerEvents: true, }); return funnel.executePluginRequest(request).then((response) => { @@ -116,7 +116,7 @@ describe("funnel.executePluginRequest", () => { }); }); - it("should not trigger pipes if allowTriggerEvent is disabled", async () => { + it("should not trigger pipes if triggerEvent is disabled", async () => { const request = new Request({ controller: "testme", action: "succeed", From 7e08169c308d89e72f6a9afc951b8bf48651d492 Mon Sep 17 00:00:00 2001 From: Quentin Date: Wed, 17 Jul 2024 10:22:40 +0200 Subject: [PATCH 18/19] chore(doc events): clarify wording on events firing --- doc/2/guides/develop-on-kuzzle/event-system/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/2/guides/develop-on-kuzzle/event-system/index.md b/doc/2/guides/develop-on-kuzzle/event-system/index.md index 9452a31437..d259d1fe51 100644 --- a/doc/2/guides/develop-on-kuzzle/event-system/index.md +++ b/doc/2/guides/develop-on-kuzzle/event-system/index.md @@ -188,10 +188,10 @@ await app.trigger('app-name/file-available', fileUrl); If an internal event is triggered, the payload must be the same as the original event. ::: -**Events are not triggered** with SDK and controllers actions. +**Events are not triggered** with the embedded SDK and controllers actions. ::: warning -By default, actions executed through the embedded SDK or controllers won't trigger any events and thus no pipe or hooks will be called. +By default, actions executed through the embedded SDK or controllers won't trigger any events and thus no pipe or hooks will be called. On the contrary, controllers accessed by the external SDK through HTTP or Websocket requests will always fire events. ::: This behaviour is set in order to prevent an infinite loop in which a pipe calls a controller generating an event calling this same pipe again and again. From 782e2ca967b7b5864654665cf96b36308098a803 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 22 Jul 2024 08:42:58 +0000 Subject: [PATCH 19/19] chore(release): 2.31.0-beta.1 [skip ci] # [2.31.0-beta.1](https://github.com/kuzzleio/kuzzle/compare/v2.30.1-beta.1...v2.31.0-beta.1) (2024-07-22) ### Features * **doc:** add documentation in the event-system guide ([4913389](https://github.com/kuzzleio/kuzzle/commit/4913389e4be38f3cb23d85ca2bc769fc979dd64e)) * **funnel:** add optional parameter to request to trigger pipes ([508ac72](https://github.com/kuzzleio/kuzzle/commit/508ac72a25b690ac452ff32dbe80e0833c00290d)) --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71daadc419..3e10b42dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# [2.31.0-beta.1](https://github.com/kuzzleio/kuzzle/compare/v2.30.1-beta.1...v2.31.0-beta.1) (2024-07-22) + + +### Features + +* **doc:** add documentation in the event-system guide ([4913389](https://github.com/kuzzleio/kuzzle/commit/4913389e4be38f3cb23d85ca2bc769fc979dd64e)) +* **funnel:** add optional parameter to request to trigger pipes ([508ac72](https://github.com/kuzzleio/kuzzle/commit/508ac72a25b690ac452ff32dbe80e0833c00290d)) + ## [2.30.1-beta.1](https://github.com/kuzzleio/kuzzle/compare/v2.30.0...v2.30.1-beta.1) (2024-06-03) diff --git a/package-lock.json b/package-lock.json index 3d1641ee06..58cb162594 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "kuzzle", - "version": "2.30.1-beta.1", + "version": "2.31.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kuzzle", - "version": "2.30.1-beta.1", + "version": "2.31.0-beta.1", "license": "Apache-2.0", "dependencies": { "@elastic/elasticsearch": "https://github.com/elastic/elasticsearch-js/archive/refs/tags/v7.13.0.tar.gz", diff --git a/package.json b/package.json index 09a8fadb17..1d0f02d96a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kuzzle", "author": "The Kuzzle Team ", - "version": "2.30.1-beta.1", + "version": "2.31.0-beta.1", "description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.", "bin": "bin/start-kuzzle-server", "scripts": {