diff --git a/adminforth/documentation/docs/tutorial/03-Customization/04-hooks.md b/adminforth/documentation/docs/tutorial/03-Customization/04-hooks.md index a0478397..d63dbe78 100644 --- a/adminforth/documentation/docs/tutorial/03-Customization/04-hooks.md +++ b/adminforth/documentation/docs/tutorial/03-Customization/04-hooks.md @@ -135,16 +135,10 @@ For example we can prevent the user to see Apartments created by other users. Su if (adminUser.dbUser.role === "superadmin") { return { ok: true }; } - if (!query.filters || query.filters.length === 0) { - query.filters = []; - } - // skip existing realtor_id filter if it comes from UI Filters (right panel) - query.filters = query.filters.filter((filter: any) => filter.field !== "realtor_id"); - query.filters.push({ - field: "realtor_id", - value: adminUser.dbUser.id, - operator: "eq", - }); + + // this function will skip existing realtor_id filter if it supplied already from UI or previous hook, and will add new one for realtor_id + query.filterTools.replaceOrAddTopFilter(Filters.EQ('realtor_id', adminUser.dbUser.id). + return { ok: true }; }, }, @@ -199,7 +193,7 @@ Let's limit it: dropdownList: { beforeDatasourceRequest: async ({ adminUser, query }: { adminUser: AdminUser, query: any }) => { if (adminUser.dbUser.role !== "superadmin") { - query.filters = [{field: "id", value: adminUser.dbUser.id, operator: "eq"}]; + query.filtersTools.replaceOrAddTopFilter(Filters.EQ("id", adminUser.dbUser.id)); }; return { "ok": true, diff --git a/adminforth/modules/filtersTools.ts b/adminforth/modules/filtersTools.ts new file mode 100644 index 00000000..3a662874 --- /dev/null +++ b/adminforth/modules/filtersTools.ts @@ -0,0 +1,34 @@ +export const filtersTools = { + get(query: any) { + return { + checkTopFilterExists(field: string) { + return Array.isArray(query.filters) + ? query.filters.some((f: any) => f.field === field) + : false; + }, + + removeTopFilter(field: string) { + if (!Array.isArray(query.filters)) { + throw new Error('query.filters is not an array'); + } + + if (!this.checkTopFilterExists(field)) { + throw new Error(`Top-level filter for field "${field}" not found`); + } + + this.removeTopFilterIfExists(field); + }, + + removeTopFilterIfExists(field: string) { + if (!Array.isArray(query.filters)) return; + query.filters = query.filters.filter((f: any) => f.field !== field); + }, + + replaceOrAddTopFilter(filter: { field: string; value: any; operator: string }) { + if (!Array.isArray(query.filters)) query.filters = []; + this.removeTopFilterIfExists(filter.field); + query.filters.push(filter); + } + }; + } +}; \ No newline at end of file diff --git a/adminforth/modules/restApi.ts b/adminforth/modules/restApi.ts index ff746a1a..499ee606 100644 --- a/adminforth/modules/restApi.ts +++ b/adminforth/modules/restApi.ts @@ -21,6 +21,7 @@ import { ActionCheckSource, AdminForthConfigMenuItem, AdminForthDataTypes, Admin AnnouncementBadgeResponse, GetBaseConfigResponse, ShowInResolved} from "../types/Common.js"; +import { filtersTools } from "../modules/filtersTools.js"; export async function interpretResource( adminUser: AdminUser, @@ -614,10 +615,13 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { }[source]; for (const hook of listify(resource.hooks?.[hookSource]?.beforeDatasourceRequest)) { + const filterTools = filtersTools.get(body); + body.filtersTools = filterTools; const resp = await hook({ resource, query: body, adminUser, + filtersTools: filterTools, extra: { body, query, headers, cookies, requestUrl }, @@ -856,9 +860,12 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI { targetResources.map(async (targetResource) => { return new Promise(async (resolve) => { for (const hook of listify(columnConfig.foreignResource.hooks?.dropdownList?.beforeDatasourceRequest as BeforeDataSourceRequestFunction[])) { + const filterTools = filtersTools.get(body); + body.filtersTools = filterTools; const resp = await hook({ query: body, adminUser, + filtersTools: filterTools, resource: targetResource, extra: { body, query, headers, cookies, requestUrl diff --git a/dev-demo/resources/apartments.ts b/dev-demo/resources/apartments.ts index 2f53ea41..19711395 100644 --- a/dev-demo/resources/apartments.ts +++ b/dev-demo/resources/apartments.ts @@ -1,6 +1,7 @@ import { ActionCheckSource, AdminForthDataTypes, + AdminForthResource, AdminForthResourcePages, AdminUser, Filters, @@ -80,6 +81,20 @@ export default { }); return { ok: true, error: "" }; }, + beforeDatasourceRequest: async ({ + query, adminUser, resource, + }: { + query: any; adminUser: AdminUser; resource: AdminForthResource; + }) => { + if (adminUser.dbUser.role === "superadmin") { + return { ok: true }; + } + + // this function will skip existing realtor_id filter if it supplied already from UI or previous hook, and will add new one for realtor_id + query.filtersTools.replaceOrAddTopFilter(Filters.EQ('realtor_id', adminUser.dbUser.id)); + + return { ok: true }; + }, }, }, columns: [ @@ -275,6 +290,25 @@ export default { name: "listed", required: true, // will be required on create/edit }, + { + name: 'realtor_id', + showIn: {filter: true, show: true, edit: true, list: true, create: true}, + foreignResource: { + resourceId: 'users', + hooks: { + dropdownList: { + beforeDatasourceRequest: async ({ adminUser, query }: { adminUser: AdminUser, query: any }) => { + if (adminUser.dbUser.role !== "superadmin") { + query.filtersTools.replaceOrAddTopFilter(Filters.EQ("id", adminUser.dbUser.id)); + }; + return { + "ok": true, + }; + } + }, + } + } + }, { name: "user_id", showIn: {filter: true, show: true, edit: true, list: true, create: true},