@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 +1 -1
- package/templates/admin.config.template.js +25 -0
- package/templates/adminjs/dashboard.template.js +3 -30
- package/templates/app.module.template.js +31 -19
- package/templates/permissions.config.template.js +9 -6
- package/wizard.js +19 -65
- package/templates/adminjs/admin.template.js +0 -143
- package/templates/adminjs/adminjs-app.module.template.js +0 -48
- package/templates/adminjs/component-loader.template.js +0 -14
- package/templates/adminjs/utils.template.js +0 -131
package/package.json
CHANGED
|
@@ -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
|
-
|
|
3
|
-
} from '
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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: [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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({
|
|
37
|
+
.forRoutes({path: '*', method: RequestMethod.ALL});
|
|
26
38
|
}
|
|
27
39
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
9
|
+
findFirst: 'ALL',
|
|
7
10
|
findMany: 'ALL',
|
|
8
11
|
create: 'ALL',
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
updateMany: 'ALL',
|
|
13
|
+
deleteMany: 'ALL',
|
|
11
14
|
},
|
|
12
15
|
CLIENT: {
|
|
13
|
-
|
|
14
|
-
conditions: { 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="
|
|
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 =
|
|
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
|
|
355
|
-
const
|
|
356
|
-
fs.ensureDirSync(
|
|
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
|
-
|
|
396
|
-
const
|
|
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
|
-
|
|
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
|
|
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', '
|
|
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, '
|
|
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
|
-
};
|