Skip to content

Commit d28f6d7

Browse files
authored
Merge branch 'next' into AdminForth/757
2 parents ecebb3d + 754e3d1 commit d28f6d7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1370
-287
lines changed

adminforth/commands/createApp/templates/index.ts.hbs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import AdminForth from 'adminforth';
33
import usersResource from "./resources/adminuser.js";
44
import { fileURLToPath } from 'url';
55
import path from 'path';
6+
import { Filters } from 'adminforth';
67

78
const ADMIN_BASE_URL = '';
89

@@ -16,7 +17,7 @@ export const admin = new AdminForth({
1617
loginBackgroundImage: 'https://images.unsplash.com/photo-1534239697798-120952b76f2b?q=80&w=3389&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
1718
loginBackgroundPosition: '1/2',
1819
loginPromptHTML: async () => {
19-
const adminforthUserExists = await admin.resource("users").count(Filters.EQ('email', 'adminforth')) > 0;
20+
const adminforthUserExists = await admin.resource("adminuser").count(Filters.EQ('email', 'adminforth')) > 0;
2021
if (adminforthUserExists) {
2122
return "Please use <b>adminforth</b> as username and <b>adminforth</b> as password"
2223
}

adminforth/documentation/docs/tutorial/03-Customization/06-customPages.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@ Open `index.ts` file and add the following code *BEFORE* `admin.express.serve(`
306306
307307
```ts title="/index.ts"
308308
309+
import type { IAdminUserExpressRequest } from 'adminforth';
310+
import express from 'express';
311+
309312
....
310313
311314
app.get(`${ADMIN_BASE_URL}/api/dashboard/`,

adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ Now create file `ApartsPie.vue` in the `custom` folder of your project:
9898
Also we have to add an Api to get percentages:
9999
100100
```ts title="./index.ts"
101+
import type { IAdminUserExpressRequest } from 'adminforth';
102+
import express from 'express';
103+
104+
....
105+
101106
app.get(`${ADMIN_BASE_URL}/api/aparts-by-room-percentages/`,
102107
admin.express.authorize(
103108
async (req: IAdminUserExpressRequest, res: express.Response) => {

adminforth/documentation/docs/tutorial/07-Plugins/01-AuditLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ Audit log is able to catch only standard actions like `create`, `update`, `delet
160160
If you have a custom, self coded actions in your API, you can log them by calling `logCustomAction` method of `AuditLogPlugin` instance:
161161
162162
```ts title="./resources/index.ts"
163+
import type { IAdminUserExpressRequest, ITranslateExpressRequest } from 'adminforth';
164+
import express from 'express';
165+
166+
....
163167

164168
app.get(`${ADMIN_BASE_URL}/api/dashboard/`,
165169
admin.express.authorize(

adminforth/documentation/docs/tutorial/07-Plugins/10-i18n.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,11 @@ onMounted(async () => {
425425
And on the backend side you can use tr function to translate the string:
426426
427427
```ts title="./index.ts"
428+
import type { IAdminUserExpressRequest, ITranslateExpressRequest } from 'adminforth';
429+
import express from 'express';
430+
431+
....
432+
428433
app.get(`${ADMIN_BASE_URL}/api/greeting`,
429434
admin.express.authorize(
430435
admin.express.translatable(

adminforth/modules/configValidator.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,6 @@ export default class ConfigValidator implements IConfigValidator {
227227

228228
bulkActions.push({
229229
label: `Delete checked`,
230-
state: 'danger',
231230
icon: 'flowbite:trash-bin-outline',
232231
confirm: 'Are you sure you want to delete selected items?',
233232
allowed: async ({ resource, adminUser, allowedActions }) => { return allowedActions.delete },

adminforth/modules/restApi.ts

Lines changed: 122 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
IAdminForthSort,
1212
HttpExtra,
1313
IAdminForthAndOrFilter,
14+
BackendOnlyInput,
1415
} from "../types/Back.js";
1516

1617
import { ADMINFORTH_VERSION, listify, md5hash, getLoginPromptHTML } from './utils.js';
@@ -23,6 +24,46 @@ import { ActionCheckSource, AdminForthConfigMenuItem, AdminForthDataTypes, Admin
2324
ShowInResolved} from "../types/Common.js";
2425
import { filtersTools } from "../modules/filtersTools.js";
2526

27+
async function resolveBoolOrFn(
28+
val: BackendOnlyInput | undefined,
29+
ctx: {
30+
adminUser: AdminUser;
31+
resource: AdminForthResource;
32+
meta: any;
33+
source: ActionCheckSource;
34+
adminforth: IAdminForth;
35+
}
36+
): Promise<boolean> {
37+
if (typeof val === 'function') {
38+
return !!(await (val)(ctx));
39+
}
40+
return !!val;
41+
}
42+
43+
async function isBackendOnly(
44+
col: AdminForthResource['columns'][number],
45+
ctx: {
46+
adminUser: AdminUser;
47+
resource: AdminForthResource;
48+
meta: any;
49+
source: ActionCheckSource;
50+
adminforth: IAdminForth;
51+
}
52+
): Promise<boolean> {
53+
return await resolveBoolOrFn(col.backendOnly, ctx);
54+
}
55+
56+
async function isShown(
57+
col: AdminForthResource['columns'][number],
58+
page: 'list' | 'show' | 'edit' | 'create' | 'filter',
59+
ctx: Parameters<typeof isBackendOnly>[1]
60+
): Promise<boolean> {
61+
const s = (col.showIn as any) || {};
62+
if (s[page] !== undefined) return await resolveBoolOrFn(s[page], ctx);
63+
if (s.all !== undefined) return await resolveBoolOrFn(s.all, ctx);
64+
return true;
65+
}
66+
2667
export async function interpretResource(
2768
adminUser: AdminUser,
2869
resource: AdminForthResource,
@@ -194,6 +235,18 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
194235
},
195236
})
196237

238+
server.endpoint({
239+
noAuth: true,
240+
method: 'GET',
241+
path: '/get_login_form_config',
242+
handler: async ({ tr }) => {
243+
const loginPromptHTML = await getLoginPromptHTML(this.adminforth.config.auth.loginPromptHTML);
244+
return {
245+
loginPromptHTML: await tr(loginPromptHTML, 'system.loginPromptHTML'),
246+
}
247+
}
248+
})
249+
197250
server.endpoint({
198251
noAuth: true,
199252
method: 'GET',
@@ -210,8 +263,6 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
210263
const resource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
211264
const usernameColumn = resource.columns.find((col) => col.name === usernameField);
212265

213-
const loginPromptHTML = await getLoginPromptHTML(this.adminforth.config.auth.loginPromptHTML);
214-
215266
return {
216267
brandName: this.adminforth.config.customization.brandName,
217268
usernameFieldName: usernameColumn.label,
@@ -220,7 +271,6 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
220271
removeBackgroundBlendMode: this.adminforth.config.auth.removeBackgroundBlendMode,
221272
title: this.adminforth.config.customization?.title,
222273
demoCredentials: this.adminforth.config.auth.demoCredentials,
223-
loginPromptHTML: await tr(loginPromptHTML, 'system.loginPromptHTML'),
224274
loginPageInjections: this.adminforth.config.customization.loginPageInjections,
225275
globalInjections: {
226276
everyPageBottom: this.adminforth.config.customization.globalInjections.everyPageBottom,
@@ -295,7 +345,6 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
295345

296346
const announcementBadge: AnnouncementBadgeResponse = this.adminforth.config.customization.announcementBadge?.(adminUser);
297347

298-
const loginPromptHTML = await getLoginPromptHTML(this.adminforth.config.auth.loginPromptHTML);
299348

300349

301350
const publicPart = {
@@ -306,7 +355,6 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
306355
removeBackgroundBlendMode: this.adminforth.config.auth.removeBackgroundBlendMode,
307356
title: this.adminforth.config.customization?.title,
308357
demoCredentials: this.adminforth.config.auth.demoCredentials,
309-
loginPromptHTML: await tr(loginPromptHTML, 'system.loginPromptHTML'),
310358
loginPageInjections: this.adminforth.config.customization.loginPageInjections,
311359
rememberMeDays: this.adminforth.config.auth.rememberMeDays,
312360
singleTheme: this.adminforth.config.customization.singleTheme,
@@ -356,12 +404,20 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
356404

357405
// strip all backendOnly fields or not described in adminForth fields from dbUser
358406
// (when user defines column and does not set backendOnly, we assume it is not backendOnly)
359-
Object.keys(adminUser.dbUser).forEach((key) => {
360-
const col = userResource.columns.find((col) => col.name === key);
361-
if (!col || col.backendOnly) {
407+
const ctx = {
408+
adminUser,
409+
resource: userResource,
410+
meta: {},
411+
source: ActionCheckSource.ShowRequest,
412+
adminforth: this.adminforth,
413+
};
414+
for (const key of Object.keys(adminUser.dbUser)) {
415+
const col = userResource.columns.find((c) => c.name === key);
416+
const bo = col ? await isBackendOnly(col, ctx) : true;
417+
if (!col || bo) {
362418
delete adminUser.dbUser[key];
363419
}
364-
})
420+
}
365421

366422
return {
367423
user: userData,
@@ -794,15 +850,32 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
794850
})
795851
);
796852

853+
const pkField = resource.columns.find((col) => col.primaryKey)?.name;
797854
// remove all columns which are not defined in resources, or defined but backendOnly
798-
data.data.forEach((item) => {
799-
Object.keys(item).forEach((key) => {
800-
if (!resource.columns.find((col) => col.name === key) || resource.columns.find((col) => col.name === key && col.backendOnly)) {
801-
delete item[key];
855+
{
856+
const ctx = {
857+
adminUser,
858+
resource,
859+
meta,
860+
source: {
861+
show: ActionCheckSource.ShowRequest,
862+
list: ActionCheckSource.ListRequest,
863+
edit: ActionCheckSource.EditLoadRequest,
864+
}[source],
865+
adminforth: this.adminforth,
866+
};
867+
868+
for (const item of data.data) {
869+
for (const key of Object.keys(item)) {
870+
const col = resource.columns.find((c) => c.name === key);
871+
const bo = col ? await isBackendOnly(col, ctx) : true;
872+
if (!col || bo) {
873+
delete item[key];
874+
}
802875
}
803-
})
804-
item._label = resource.recordLabel(item);
805-
});
876+
item._label = resource.recordLabel(item);
877+
}
878+
}
806879
if (source === 'list' && resource.options.listTableClickUrl) {
807880
await Promise.all(
808881
data.data.map(async (item) => {
@@ -1068,11 +1141,30 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
10681141
}
10691142
}
10701143

1144+
const ctxCreate = {
1145+
adminUser,
1146+
resource,
1147+
meta: { requestBody: body },
1148+
source: ActionCheckSource.CreateRequest,
1149+
adminforth: this.adminforth,
1150+
};
1151+
1152+
for (const column of resource.columns) {
1153+
if ((column.required as { create?: boolean })?.create) {
1154+
const shown = await isShown(column, 'create', ctxCreate);
1155+
if (shown && record[column.name] === undefined) {
1156+
return { error: `Column '${column.name}' is required`, ok: false };
1157+
}
1158+
}
1159+
}
1160+
10711161
for (const column of resource.columns) {
10721162
const fieldName = column.name;
10731163
if (fieldName in record) {
1074-
if (!column.showIn?.create || column.backendOnly) {
1075-
return { error: `Field "${fieldName}" cannot be modified as it is restricted from creation (showIn.create is false, please set it to true)`, ok: false };
1164+
const shown = await isShown(column, 'create', ctxCreate);
1165+
const bo = await isBackendOnly(column, ctxCreate);
1166+
if (!shown || bo) {
1167+
return { error: `Field "${fieldName}" cannot be modified as it is restricted from creation (backendOnly or showIn.create is false, please set it to true)`, ok: false };
10761168
}
10771169
}
10781170
}
@@ -1164,15 +1256,24 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
11641256
return { error: allowedError };
11651257
}
11661258

1259+
const ctxEdit = {
1260+
adminUser,
1261+
resource,
1262+
meta: { requestBody: body, newRecord: record, oldRecord, pk: recordId },
1263+
source: ActionCheckSource.EditRequest,
1264+
adminforth: this.adminforth,
1265+
};
1266+
11671267
for (const column of resource.columns) {
11681268
const fieldName = column.name;
11691269
if (fieldName in record) {
1170-
if (!column.showIn?.edit || column.editReadonly || column.backendOnly) {
1171-
return { error: `Field "${fieldName}" cannot be modified as it is restricted from editing (showIn.edit is false, please set it to true)`, ok: false };
1270+
const shown = await isShown(column, 'edit', ctxEdit);
1271+
const bo = await isBackendOnly(column, ctxEdit);
1272+
if (!shown || column.editReadonly || bo) {
1273+
return { error: `Field "${fieldName}" cannot be modified as it is restricted from editing (backendOnly or showIn.edit is false, please set it to true)`, ok: false };
11721274
}
11731275
}
11741276
}
1175-
11761277
// for polymorphic foreign resources, we need to find out the value for polymorphicOn column
11771278
for (const column of resource.columns) {
11781279
if (column.foreignResource?.polymorphicOn && record[column.name] === null) {

0 commit comments

Comments
 (0)