@appxdigital/appx-core-cli 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@appxdigital/appx-core-cli",
4
- "version": "1.0.6",
4
+ "version": "1.0.8",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "npm:publish": "npm publish --access public",
@@ -0,0 +1,25 @@
1
+ import { AdminConfigType } from '@appxdigital/appx-core';
2
+ import { ComponentLoader } from 'adminjs';
3
+
4
+ const componentLoader = new ComponentLoader();
5
+
6
+ export const AdminConfig: AdminConfigType = {
7
+ componentLoader,
8
+ resources: [
9
+ {
10
+ name: 'User',
11
+ },
12
+ ],
13
+ rootPath: '/admin',
14
+ branding: {
15
+ companyName: 'AppX Core',
16
+ logo: 'https://appx-website-assets.fra1.cdn.digitaloceanspaces.com/2024/04/logo_color.svg',
17
+ },
18
+ // As you can see below, you can customize the dashboard component, which is the first page you see when you access the AdminJS
19
+ dashboard: {
20
+ component: componentLoader.add(
21
+ 'Dashboard',
22
+ '../backoffice/components/dashboard',
23
+ ),
24
+ },
25
+ };
@@ -1,37 +1,10 @@
1
1
  import React from 'react';
2
2
 
3
3
  export const Dashboard = () => {
4
+ // Redirect to Users resource
5
+ window.location.href = window.location.href + '/resources/User';
4
6
 
5
- return (
6
- <div style={{
7
- backgroundColor: 'white',
8
- borderRadius: '15px',
9
- height: '100%',
10
- padding: '1rem',
11
- margin: '1rem',
12
- }}>
13
- <h1 style={{ fontSize: "1.5rem", textAlign: "center" }}>Dashboard</h1>
14
- <div style={{
15
- display: 'flex',
16
- gap: '1rem',
17
- flexDirection: 'column',
18
- textAlign: 'center',
19
- marginTop: '3rem',
20
- borderRadius: '15px',
21
- border: '2px solid gainsboro',
22
- maxWidth: '300px',
23
- margin: '3rem auto',
24
- padding: '1rem',
25
- }}>
26
- <h2>Customize it!</h2>
27
- <p>You can customize your dashboard however you want.</p>
28
- <p>Display any data you might find useful.</p>
29
- <p>See the number of users or check statistics on graphics</p>
30
- <p>Whatever you want to do! Edit it on components/dashboard.</p>
31
- </div>
32
- </div>
33
-
34
- );
7
+ return null;
35
8
  };
36
9
 
37
10
  export default Dashboard;
@@ -1,27 +1,39 @@
1
- import {
2
- MiddlewareConsumer, Module, NestModule, RequestMethod,
3
- } from '@nestjs/common';
4
- import { AppController } from './app.controller';
5
- import { AppService } from './app.service';
6
- import { ConfigModule } from '@nestjs/config';
7
- import {AuthModule, PrismaInterceptor, AppxCoreModule} from '@appxdigital/appx-core';
8
- import { APP_INTERCEPTOR } from '@nestjs/core';
9
- import {
10
- RequestContextModule, RequestContextMiddleware,
11
- } from 'nestjs-request-context'
12
- import { PermissionsConfig } from './config/permissions.config';
1
+ import {MiddlewareConsumer, Module, NestModule, RequestMethod} from '@nestjs/common';
2
+ import {AppController} from './app.controller';
3
+ import {AppService} from './app.service';
4
+ import {ConfigModule} from '@nestjs/config';
5
+ import {AuthModule, PrismaInterceptor, AppxCoreModule, AppxCoreAdminModule} from '@appxdigital/appx-core';
6
+ import {APP_INTERCEPTOR} from '@nestjs/core';
7
+ import {RequestContextModule, RequestContextMiddleware} from 'nestjs-request-context'
8
+ import {PermissionsConfig} from './config/permissions.config';
9
+ import {AdminConfig} from './config/admin.config';
13
10
 
14
11
  @Module({
15
- imports: [RequestContextModule, ConfigModule.forRoot({
16
- isGlobal: true, expandVariables: true, envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
17
- }), AppxCoreModule.forRoot(PermissionsConfig), RequestContextModule, AuthModule,], controllers: [AppController], providers: [AppService, {
18
- provide: APP_INTERCEPTOR, useClass: PrismaInterceptor,
19
- },],
12
+ imports: [
13
+ RequestContextModule,
14
+ ConfigModule.forRoot({
15
+ isGlobal: true,
16
+ expandVariables: true,
17
+ envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
18
+ }),
19
+ AppxCoreModule.forRoot(PermissionsConfig),
20
+ AppxCoreAdminModule.forRoot(AdminConfig, PermissionsConfig),
21
+ RequestContextModule,
22
+ AuthModule,
23
+ ],
24
+ controllers: [AppController],
25
+ providers: [
26
+ AppService,
27
+ {
28
+ provide: APP_INTERCEPTOR,
29
+ useClass: PrismaInterceptor,
30
+ },
31
+ ],
20
32
  })
21
33
  export class AppModule implements NestModule {
22
- configure(consumer: MiddlewareConsumer) {
34
+ configure (consumer: MiddlewareConsumer) {
23
35
  consumer
24
36
  .apply(RequestContextMiddleware)
25
- .forRoutes({ path: '*', method: RequestMethod.ALL });
37
+ .forRoutes({path: '*', method: RequestMethod.ALL});
26
38
  }
27
39
  }
@@ -1,17 +1,20 @@
1
- import {PermissionsConfigType} from '@appxdigital/appx-core';
1
+ import {
2
+ PermissionsConfigType,
3
+ PermissionPlaceholder,
4
+ } from '@appxdigital/appx-core';
2
5
 
3
6
  export const PermissionsConfig: PermissionsConfigType = {
4
7
  User: {
5
8
  ADMIN: {
6
- findUnique: 'ALL',
9
+ findFirst: 'ALL',
7
10
  findMany: 'ALL',
8
11
  create: 'ALL',
9
- update: 'ALL',
10
- delete: 'ALL',
12
+ updateMany: 'ALL',
13
+ deleteMany: 'ALL',
11
14
  },
12
15
  CLIENT: {
13
- findUnique: {
14
- conditions: { id: '$USER_ID' }
16
+ findFirst: {
17
+ conditions: { id: PermissionPlaceholder.USER_ID },
15
18
  },
16
19
  }
17
20
  },
package/wizard.js CHANGED
@@ -24,7 +24,6 @@ const phaseDurations = {
24
24
  installDependencies: 80,
25
25
  installCore: 120,
26
26
  prismaSetup: 10,
27
- adminSetup: 40,
28
27
  setupConcluded: 5,
29
28
  end: 5
30
29
  };
@@ -37,9 +36,6 @@ const calculateTotalETA = (includeBackoffice) => {
37
36
  phaseDurations.prismaSetup +
38
37
  phaseDurations.setupConcluded
39
38
  + phaseDurations.end;
40
- if (includeBackoffice) {
41
- totalTime += phaseDurations.adminSetup;
42
- }
43
39
  return totalTime;
44
40
  };
45
41
 
@@ -225,12 +221,19 @@ APP_PORT=3000
225
221
 
226
222
  #Default behaviour for use of transactions
227
223
  USE_TRANSACTION=true
224
+
228
225
  #Session secret
229
- SESSION_SECRET="DEFAULT_SECRET"
226
+ SESSION_SECRET="${require('crypto').randomBytes(32).toString('hex')}"
227
+
230
228
  #Cookie name for the session token
231
229
  SESSION_COOKIE_NAME="APPXCORE"
230
+
232
231
  #Expiration time for the session token in seconds
233
232
  SESSION_TTL=86400
233
+
234
+ # JWT
235
+ JWT_SECRET="${require('crypto').randomBytes(32).toString('hex')}"
236
+ JWT_REFRESH_SECRET="${require('crypto').randomBytes(32).toString('hex')}"
234
237
  `;
235
238
 
236
239
  fs.writeFileSync(`${projectPath}/.env`, envContent);
@@ -262,9 +265,6 @@ SESSION_TTL=86400
262
265
  }
263
266
 
264
267
  function setupProjectStructure(projectPath, answers) {
265
-
266
- const includeBackoffice = answers?.backoffice;
267
-
268
268
  ensureAndRunNestCli(projectPath, answers?.showOutput);
269
269
  incrementProgress(answers?.showOutput, "nestjs");
270
270
 
@@ -288,7 +288,7 @@ function setupProjectStructure(projectPath, answers) {
288
288
  console.warn('Could not find generated tsconfig.json to modify.');
289
289
  }
290
290
 
291
- const appModuleContent = includeBackoffice ? getAdminAppModuleTemplate() : getAppModuleTemplate();
291
+ const appModuleContent = getAppModuleTemplate();
292
292
  fs.writeFileSync(`${projectPath}/src/app.module.ts`, appModuleContent);
293
293
  incrementProgress(answers?.showOutput, "appmodule");
294
294
  installDependenciesFromManifest(answers?.showOutput);
@@ -311,13 +311,8 @@ function setupProjectStructure(projectPath, answers) {
311
311
  incrementProgress(answers?.showOutput, "prismaSetup");
312
312
  createPrismaModule(projectPath);
313
313
  createPermissionsConfig(projectPath);
314
+ createAdminConfig(projectPath);
314
315
  addScriptsToPackageJson(projectPath);
315
-
316
- if (includeBackoffice) {
317
- executeCommand('npm install adminjs @adminjs/express @adminjs/nestjs @adminjs/prisma @prisma/sdk react express-formidable @adminjs/passwords', answers?.showOutput);
318
- setupAdminJS(projectPath);
319
- incrementProgress(answers?.showOutput, "adminSetup");
320
- }
321
316
  }
322
317
 
323
318
  function ensureAndRunNestCli(projectPath, showOutput = false) {
@@ -351,65 +346,24 @@ function getAppModuleTemplate() {
351
346
  return fs.readFileSync(appModuleTemplatePath, 'utf8');
352
347
  }
353
348
 
354
- function setupAdminJS(projectPath) {
355
- const backoffice = path.join(projectPath, 'src/backoffice');
356
- fs.ensureDirSync(backoffice);
357
-
358
- const backofficeComponents = path.join(backoffice, 'components');
359
- fs.ensureDirSync(backofficeComponents);
360
-
361
- const componentLoaderContent = getComponentLoaderTemplate();
362
- fs.writeFileSync(path.join(backoffice, 'component-loader.ts'), componentLoaderContent);
363
-
364
- const dashboardContent = getDashboardTemplate();
365
- fs.writeFileSync(path.join(backofficeComponents, 'dashboard.tsx'), dashboardContent);
366
-
367
- const utilsContent = getUtilsTemplate();
368
- fs.writeFileSync(path.join(backoffice, 'utils.ts'), utilsContent);
369
-
370
- const adminContent = getAdminTemplate();
371
- fs.writeFileSync(path.join(backoffice, 'admin.ts'), adminContent);
372
-
373
- const tsConfigPath = path.join(projectPath, 'tsconfig.json');
374
- const tsConfig = fs.readJsonSync(tsConfigPath);
375
-
376
- tsConfig.compilerOptions.jsx = 'react';
377
- fs.writeJsonSync(tsConfigPath, tsConfig, { spaces: 2 });
378
- }
379
-
380
- function getComponentLoaderTemplate() {
381
- const template = path.join(__dirname, 'templates', 'adminjs', 'component-loader.template.js');
382
- return fs.readFileSync(template, 'utf8');
383
- }
384
-
385
- function getDashboardTemplate() {
386
- const template = path.join(__dirname, 'templates', 'adminjs', 'dashboard.template.js');
387
- return fs.readFileSync(template, 'utf8');
388
- }
389
-
390
- function getUtilsTemplate() {
391
- const template = path.join(__dirname, 'templates', 'adminjs', 'utils.template.js');
392
- return fs.readFileSync(template, 'utf8');
393
- }
349
+ function createPermissionsConfig(projectPath) {
350
+ const configDir = path.join(projectPath, 'src/config');
351
+ fs.ensureDirSync(configDir);
394
352
 
395
- function getAdminTemplate() {
396
- const template = path.join(__dirname, 'templates', 'adminjs', 'admin.template.js');
397
- return fs.readFileSync(template, 'utf8');
398
- }
353
+ const templatePath = path.join(__dirname, 'templates', 'permissions.config.template.js');
354
+ const permissionsConfigContent = fs.readFileSync(templatePath, 'utf-8');
399
355
 
400
- function getAdminAppModuleTemplate() {
401
- const appModuleTemplatePath = path.join(__dirname, 'templates', 'adminjs', 'adminjs-app.module.template.js');
402
- return fs.readFileSync(appModuleTemplatePath, 'utf8');
356
+ fs.writeFileSync(path.join(configDir, 'permissions.config.ts'), permissionsConfigContent);
403
357
  }
404
358
 
405
- function createPermissionsConfig(projectPath) {
359
+ function createAdminConfig(projectPath) {
406
360
  const configDir = path.join(projectPath, 'src/config');
407
361
  fs.ensureDirSync(configDir);
408
362
 
409
- const templatePath = path.join(__dirname, 'templates', 'permissions.config.template.js');
363
+ const templatePath = path.join(__dirname, 'templates', 'admin.config.template.js');
410
364
  const permissionsConfigContent = fs.readFileSync(templatePath, 'utf-8');
411
365
 
412
- fs.writeFileSync(path.join(configDir, 'permissions.config.ts'), permissionsConfigContent);
366
+ fs.writeFileSync(path.join(configDir, 'admin.config.ts'), permissionsConfigContent);
413
367
  }
414
368
 
415
369
  function customizePrismaSchema(projectPath, answers) {
@@ -1,143 +0,0 @@
1
- import { DynamicModule } from '@nestjs/common';
2
- import { addBasicFilters, createActions, createPermissionHandler, dynamicImport, getAdminJSResources } from './utils';
3
- import { initializeComponents } from './component-loader';
4
- import { readFileSync } from 'fs';
5
- import { getDMMF } from '@prisma/sdk';
6
- import {PrismaService} from '@appxdigital/appx-core';
7
- import { PrismaModule } from "../prisma/prisma.module";
8
-
9
- const DEFAULT_ADMIN = {
10
- email: 'joao.duvido@appx.pt',
11
- password: 'password',
12
- };
13
-
14
- const authenticate = async (email: string, password: string) => {
15
- if (email === DEFAULT_ADMIN.email && password === DEFAULT_ADMIN.password) {
16
- return Promise.resolve(DEFAULT_ADMIN);
17
- }
18
- return null;
19
- };
20
-
21
- export async function createAdminJsModule(): Promise<DynamicModule> {
22
- // Due to AdminJS only allowing ESM now, we need to use dynamic imports to load the modules, this function can be found within src/backoffice/utils.ts
23
- const { default: AdminJS } = await dynamicImport('adminjs');
24
- const { Database, Resource } = await dynamicImport('@adminjs/prisma');
25
- const { AdminModule } = await dynamicImport('@adminjs/nestjs');
26
- const { default: importExportFeature } = await dynamicImport('@adminjs/import-export');
27
- const { default: passwordFeature } = await dynamicImport('@adminjs/passwords');
28
- const argon2 = await dynamicImport('argon2');
29
-
30
- // Below, this function in src/backoffice/utils.ts is used to get the resources you want to expose to AdminJS
31
- const resources = getAdminJSResources();
32
-
33
- // Once you create customized components, you can load them on the componentLoader just like the Dashboard component example
34
-
35
- const { componentLoader, Components } = await initializeComponents();
36
-
37
- // This gets the models from the Prisma schema, and then creates the AdminJS resources
38
- const schemaPath = './prisma/schema.prisma';
39
- const schema = readFileSync(schemaPath, 'utf-8');
40
- const dmmf = await getDMMF({ datamodel: schema });
41
-
42
- const models = [];
43
-
44
- for (const resource of resources) {
45
- const model = dmmf.datamodel.models.find(
46
- (model) => model.name === resource.name,
47
- );
48
-
49
- models.push({
50
- model,
51
- options: resource.options,
52
- features: model.name === 'User' ? [
53
- passwordFeature({
54
- properties: {
55
- encryptedPassword: 'password',
56
- password: 'plainPassword',
57
- },
58
- hash: argon2.hash,
59
- componentLoader,
60
- }),
61
- ] : [],
62
- });
63
- }
64
-
65
- AdminJS.registerAdapter({ Database, Resource });
66
-
67
-
68
- return AdminModule.createAdminAsync({
69
- imports: [PrismaModule],
70
- inject: [PrismaService],
71
- useFactory: async (prisma: PrismaService) => {
72
-
73
- // If you comment out this function below, you will be able to access the AdminJS with the DEFAULT_ADMIN credentials at the top of this file
74
- // With that, you can create a new user, and then undo the comment on this function to disable the default admin, using the new user you created
75
- const authenticate = async (email: string, password: string) => {
76
- const user = await prisma.user.findUnique({
77
- where: {
78
- email
79
- }
80
- });
81
-
82
- if (!user || user.role !== 'ADMIN') {
83
- return null;
84
- }
85
-
86
- const isPasswordValid = await argon2.verify(user.password, password);
87
-
88
- return isPasswordValid ? Promise.resolve({ email: user.email, role: user.role, id: user.id }) : null;
89
- }
90
-
91
- return {
92
- adminJsOptions: {
93
- rootPath: '/admin',
94
- // As you can see below, you can customize the dashboard component, which is the first page you see when you access the AdminJS
95
- dashboard: {
96
- component: Components.Dashboard,
97
- handler: async () => {
98
- return { some: 'output' };
99
- },
100
- },
101
- branding: {
102
- // Here you can change the company name, which appears on the browser's tab
103
- companyName: 'AppX Core Wizard',
104
- // This removes the "Made with love (...)" from the footer
105
- withMadeWithLove: false,
106
- // Here you can change the logo, currently using AppX's as an example
107
- logo: 'https://i.ibb.co/XZNRS5m/appxdigitalcom-logo.jpg',
108
- },
109
- resources: models.map((m) => {
110
- return {
111
- resource: { model: m.model, client: prisma },
112
- options: {
113
- ...m.options,
114
- actions: createActions(),
115
- },
116
- features: [...(m.features || []), importExportFeature({
117
- componentLoader
118
- })],
119
- };
120
- }),
121
-
122
- componentLoader,
123
- },
124
- auth: {
125
- authenticate,
126
- cookieName: process.env.SESSION_COOKIE_NAME,
127
- cookiePassword: process.env.SESSION_SECRET,
128
- },
129
- sessionOptions: {
130
- resave: false,
131
- saveUninitialized: true,
132
- secret: process.env.SESSION_SECRET,
133
- cookie: {
134
- httpOnly: process.env.NODE_ENV === 'production',
135
- //secure: process.env.NODE_ENV === 'production',
136
- secure: false,
137
- },
138
- name: process.env.SESSION_COOKIE_NAME,
139
- },
140
- };
141
- },
142
- });
143
- }
@@ -1,48 +0,0 @@
1
- import {
2
- MiddlewareConsumer,
3
- Module,
4
- NestModule,
5
- RequestMethod,
6
- } from '@nestjs/common';
7
- import { ConfigModule } from '@nestjs/config';
8
- import { APP_INTERCEPTOR } from '@nestjs/core';
9
- import { AppController } from './app.controller';
10
- import { AppService } from './app.service';
11
- import {AppxCoreModule, AuthModule, PrismaInterceptor} from '@appxdigital/appx-core';
12
- import {
13
- RequestContextModule,
14
- RequestContextMiddleware,
15
- } from 'nestjs-request-context';
16
- import { UserModule } from './modules/user/user.module';
17
- import { PermissionsConfig } from "./config/permissions.config";
18
- import { createAdminJsModule } from "./backoffice/admin";
19
-
20
- @Module({
21
- imports: [
22
- RequestContextModule,
23
- ConfigModule.forRoot({
24
- isGlobal: true,
25
- expandVariables: true,
26
- envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
27
- }),
28
- AppxCoreModule.forRoot(PermissionsConfig),
29
- AuthModule,
30
- UserModule,
31
- createAdminJsModule().then((AdminJsModule) => AdminJsModule),
32
- ],
33
- controllers: [AppController],
34
- providers: [
35
- AppService,
36
- {
37
- provide: APP_INTERCEPTOR,
38
- useClass: PrismaInterceptor,
39
- },
40
- ],
41
- })
42
- export class AppModule implements NestModule {
43
- configure(consumer: MiddlewareConsumer) {
44
- consumer
45
- .apply(RequestContextMiddleware)
46
- .forRoutes({ path: '*', method: RequestMethod.ALL });
47
- }
48
- }
@@ -1,14 +0,0 @@
1
- import { dynamicImport } from "./utils";
2
-
3
- async function loadComponents() {
4
- const { ComponentLoader } = await dynamicImport('adminjs');
5
- const componentLoader = new ComponentLoader();
6
-
7
- const Components = {
8
- Dashboard: componentLoader.add('dashboard', './components/dashboard'),
9
- };
10
-
11
- return { componentLoader, Components };
12
- }
13
-
14
- export const initializeComponents = loadComponents;
@@ -1,131 +0,0 @@
1
- import { PermissionsConfig } from "../config/permissions.config";
2
-
3
- export const dynamicImport = async (packageName: string) =>
4
- new Function(`return import('${packageName}')`)();
5
-
6
- export const getAdminJSResources = (specific = null) => {
7
- const resources = [
8
- {
9
- name: 'User',
10
- options: {},
11
- },
12
- ];
13
-
14
- return specific ? resources.filter((r) => r.name === specific) : resources;
15
- };
16
-
17
- export function createPermissionHandler(role: string, resource: string, action: string) {
18
- const rolePermissions = PermissionsConfig[resource]?.[role];
19
-
20
- if (!rolePermissions) {
21
- return () => false;
22
- }
23
-
24
- const actionMapping = {
25
- list: 'findMany',
26
- show: 'findUnique',
27
- edit: 'update',
28
- delete: 'delete',
29
- new: 'create',
30
- };
31
-
32
- const mappedAction = actionMapping[action];
33
-
34
- if (!mappedAction || !rolePermissions[mappedAction]) {
35
- return () => false;
36
- }
37
-
38
- if (rolePermissions[mappedAction] === 'ALL') {
39
- return () => true;
40
- }
41
-
42
- if (typeof rolePermissions[mappedAction] === 'object') {
43
-
44
- return (requestContext) => {
45
- // @ts-ignore
46
- const clauses = rolePermissions[mappedAction]?.clauses;
47
- if (!clauses) return () => false;
48
- return parseClauses(clauses)(requestContext);
49
- };
50
- }
51
-
52
- return () => false;
53
- }
54
-
55
- const clauseMapping = {
56
- '$USER_ID': "id",
57
- '$USER_EMAIL': "email",
58
- };
59
-
60
- const parseClauses = (clauses) => {
61
- return (requestContext) => {
62
- if (!clauses || !Array.isArray(clauses)) return true;
63
-
64
- for (const clause of clauses) {
65
- if (clause.type === 'field') {
66
- for (const [field, value] of Object.entries(clause.conditions)) {
67
- let actualValue = value;
68
-
69
- if (typeof value === 'string' && value.startsWith('$')) {
70
- actualValue = requestContext?.currentAdmin[clauseMapping[value]] ?? null;
71
- }
72
-
73
- if (requestContext.record?.param(field) !== actualValue) {
74
- return false;
75
- }
76
- }
77
- }
78
- }
79
- return true;
80
- };
81
- };
82
-
83
-
84
- export const addBasicFilters = (filters) => {
85
- return async (request, context) => {
86
- for (const [field, value] of Object.entries(filters)) {
87
- request.query[`filters.${field}`] = value;
88
- }
89
- return request;
90
- };
91
- };
92
-
93
- export const createActions = () => {
94
- return {
95
- list: {
96
- isAccessible: createIsAccessible('list'),
97
- },
98
- show: {
99
- isAccessible: createIsAccessible('show'),
100
- },
101
- edit: {
102
- isAccessible: createIsAccessible('edit'),
103
- },
104
- delete: {
105
- isAccessible: createIsAccessible('delete'),
106
- },
107
- new: {
108
- isAccessible: createIsAccessible('new'),
109
- },
110
- };
111
- };
112
-
113
- const createIsAccessible = (action) => {
114
- return (context) => {
115
- if (!context.currentAdmin) return false;
116
- const { role } = context.currentAdmin;
117
- return createPermissionHandler(role, context.resource.model.name, action)(context);
118
- };
119
- };
120
-
121
- const createBeforeHook = (filters) => {
122
- return (request, context) => {
123
- if (context.currentAdmin) {
124
- request.query.filters = {
125
- ...request.query.filters,
126
- ...filters,
127
- };
128
- }
129
- return request;
130
- };
131
- };