diff --git a/lib/AbstractApiModule.js b/lib/AbstractApiModule.js index 0603aa8e..cd33a842 100644 --- a/lib/AbstractApiModule.js +++ b/lib/AbstractApiModule.js @@ -110,12 +110,22 @@ class AbstractApiModule extends AbstractModule { this.accessCheckHook = new Hook() /** * Hook invoked before a query runs, allowing observers to merge access-control clauses into - * `req.apiData.query`. Preferred over `accessCheckHook` for filtering: keeps pagination counts - * and the `Link` header accurate, and avoids any need to top up short pages. + * `req.apiData.query`. Preferred over `accessCheckHook` for access filtering: keeps pagination + * counts and the `Link` header accurate, and avoids any need to top up short pages. * Skipped for super users so they see unfiltered results, matching `checkAccess` behaviour. + * For user-driven query filters that must apply regardless of role, use `queryHook` instead. * @type {Hook} */ this.accessQueryHook = new Hook() + /** + * Hook invoked before a list query runs, allowing observers to merge user-driven filter clauses + * into `req.apiData.query` (e.g. dashboard filters sourced from another collection). Unlike + * `accessQueryHook` this runs for every user, including super users — it expresses what the user + * asked to see, not what they are permitted to see. Runs after `requestHook` and before + * pagination, so counts and the `Link` header stay accurate. + * @type {Hook} + */ + this.queryHook = new Hook({ mutable: true }) await this.setValues() this.validateValues() @@ -495,6 +505,7 @@ class AbstractApiModule extends AbstractModule { Object.keys(req.apiData.query).forEach(key => delete opts[key]) await this.requestHook.invoke(req) + await this.queryHook.invoke(req) if (!req.auth.isSuper) await this.accessQueryHook.invoke(req) await this.setUpPagination(req, res, mongoOpts)