@appxdigital/appx-core-cli 1.0.5 → 1.0.7
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 +28 -18
- 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
|
@@ -225,12 +225,19 @@ APP_PORT=3000
|
|
|
225
225
|
|
|
226
226
|
#Default behaviour for use of transactions
|
|
227
227
|
USE_TRANSACTION=true
|
|
228
|
+
|
|
228
229
|
#Session secret
|
|
229
|
-
SESSION_SECRET="
|
|
230
|
+
SESSION_SECRET="${require('crypto').randomBytes(32).toString('hex')}"
|
|
231
|
+
|
|
230
232
|
#Cookie name for the session token
|
|
231
233
|
SESSION_COOKIE_NAME="APPXCORE"
|
|
234
|
+
|
|
232
235
|
#Expiration time for the session token in seconds
|
|
233
236
|
SESSION_TTL=86400
|
|
237
|
+
|
|
238
|
+
# JWT
|
|
239
|
+
JWT_SECRET="${require('crypto').randomBytes(32).toString('hex')}"
|
|
240
|
+
JWT_REFRESH_SECRET="${require('crypto').randomBytes(32).toString('hex')}"
|
|
234
241
|
`;
|
|
235
242
|
|
|
236
243
|
fs.writeFileSync(`${projectPath}/.env`, envContent);
|
|
@@ -262,9 +269,6 @@ SESSION_TTL=86400
|
|
|
262
269
|
}
|
|
263
270
|
|
|
264
271
|
function setupProjectStructure(projectPath, answers) {
|
|
265
|
-
|
|
266
|
-
const includeBackoffice = answers?.backoffice;
|
|
267
|
-
|
|
268
272
|
ensureAndRunNestCli(projectPath, answers?.showOutput);
|
|
269
273
|
incrementProgress(answers?.showOutput, "nestjs");
|
|
270
274
|
|
|
@@ -288,7 +292,7 @@ function setupProjectStructure(projectPath, answers) {
|
|
|
288
292
|
console.warn('Could not find generated tsconfig.json to modify.');
|
|
289
293
|
}
|
|
290
294
|
|
|
291
|
-
const appModuleContent =
|
|
295
|
+
const appModuleContent = getAppModuleTemplate();
|
|
292
296
|
fs.writeFileSync(`${projectPath}/src/app.module.ts`, appModuleContent);
|
|
293
297
|
incrementProgress(answers?.showOutput, "appmodule");
|
|
294
298
|
installDependenciesFromManifest(answers?.showOutput);
|
|
@@ -314,7 +318,7 @@ function setupProjectStructure(projectPath, answers) {
|
|
|
314
318
|
addScriptsToPackageJson(projectPath);
|
|
315
319
|
|
|
316
320
|
if (includeBackoffice) {
|
|
317
|
-
executeCommand('npm install adminjs @adminjs/express @adminjs/nestjs @adminjs/prisma @prisma/sdk react express-formidable @adminjs/passwords', answers?.showOutput);
|
|
321
|
+
executeCommand('npm install adminjs @adminjs/express @adminjs/import-export @adminjs/nestjs @adminjs/prisma @prisma/sdk react express-formidable @adminjs/passwords', answers?.showOutput);
|
|
318
322
|
setupAdminJS(projectPath);
|
|
319
323
|
incrementProgress(answers?.showOutput, "adminSetup");
|
|
320
324
|
}
|
|
@@ -322,7 +326,7 @@ function setupProjectStructure(projectPath, answers) {
|
|
|
322
326
|
|
|
323
327
|
function ensureAndRunNestCli(projectPath, showOutput = false) {
|
|
324
328
|
const options = showOutput ? { stdio: 'inherit' } : { stdio: 'ignore' };
|
|
325
|
-
const command = 'npx @nestjs/cli new . --skip-install --package-manager npm';
|
|
329
|
+
const command = 'npx --yes @nestjs/cli new . --skip-install --package-manager npm';
|
|
326
330
|
try {
|
|
327
331
|
execSync('npx --no-install @nestjs/cli --version', options);
|
|
328
332
|
} catch {
|
|
@@ -397,11 +401,6 @@ function getAdminTemplate() {
|
|
|
397
401
|
return fs.readFileSync(template, 'utf8');
|
|
398
402
|
}
|
|
399
403
|
|
|
400
|
-
function getAdminAppModuleTemplate() {
|
|
401
|
-
const appModuleTemplatePath = path.join(__dirname, 'templates', 'adminjs', 'adminjs-app.module.template.js');
|
|
402
|
-
return fs.readFileSync(appModuleTemplatePath, 'utf8');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
404
|
function createPermissionsConfig(projectPath) {
|
|
406
405
|
const configDir = path.join(projectPath, 'src/config');
|
|
407
406
|
fs.ensureDirSync(configDir);
|
|
@@ -412,6 +411,16 @@ function createPermissionsConfig(projectPath) {
|
|
|
412
411
|
fs.writeFileSync(path.join(configDir, 'permissions.config.ts'), permissionsConfigContent);
|
|
413
412
|
}
|
|
414
413
|
|
|
414
|
+
function createAdminConfig(projectPath) {
|
|
415
|
+
const configDir = path.join(projectPath, 'src/config');
|
|
416
|
+
fs.ensureDirSync(configDir);
|
|
417
|
+
|
|
418
|
+
const templatePath = path.join(__dirname, 'templates', 'admin.config.template.js');
|
|
419
|
+
const permissionsConfigContent = fs.readFileSync(templatePath, 'utf-8');
|
|
420
|
+
|
|
421
|
+
fs.writeFileSync(path.join(configDir, 'admin.config.ts'), permissionsConfigContent);
|
|
422
|
+
}
|
|
423
|
+
|
|
415
424
|
function customizePrismaSchema(projectPath, answers) {
|
|
416
425
|
const prismaDir = `${projectPath}/prisma`;
|
|
417
426
|
const schemaFile = `${prismaDir}/schema.prisma`;
|
|
@@ -434,11 +443,12 @@ generator nestgraphql {
|
|
|
434
443
|
}
|
|
435
444
|
|
|
436
445
|
model User {
|
|
437
|
-
id
|
|
438
|
-
email
|
|
439
|
-
name
|
|
440
|
-
password
|
|
441
|
-
role
|
|
446
|
+
id Int @id @default(autoincrement())
|
|
447
|
+
email String @unique
|
|
448
|
+
name String?
|
|
449
|
+
password String? /// @Role(none)
|
|
450
|
+
role Role @default(GUEST)
|
|
451
|
+
refreshTokens UserRefreshToken[]
|
|
442
452
|
}
|
|
443
453
|
|
|
444
454
|
model Session {
|
|
@@ -451,7 +461,7 @@ model Session {
|
|
|
451
461
|
|
|
452
462
|
model UserRefreshToken {
|
|
453
463
|
id String @id @default(cuid())
|
|
454
|
-
token String @unique
|
|
464
|
+
token String @unique @db.VarChar(255)
|
|
455
465
|
userId Int
|
|
456
466
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
457
467
|
expiresAt DateTime
|
|
@@ -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
|
-
};
|