Skip to content

Commit b266531

Browse files
authored
Merge pull request #308 from devforth/hide-columns-by-showIn
Hide columns by backend only
2 parents 4c02ad2 + 5636511 commit b266531

File tree

2 files changed

+123
-18
lines changed

2 files changed

+123
-18
lines changed

adminforth/modules/restApi.ts

Lines changed: 109 additions & 16 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,
@@ -363,12 +404,20 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
363404

364405
// strip all backendOnly fields or not described in adminForth fields from dbUser
365406
// (when user defines column and does not set backendOnly, we assume it is not backendOnly)
366-
Object.keys(adminUser.dbUser).forEach((key) => {
367-
const col = userResource.columns.find((col) => col.name === key);
368-
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) {
369418
delete adminUser.dbUser[key];
370419
}
371-
})
420+
}
372421

373422
return {
374423
user: userData,
@@ -803,14 +852,30 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
803852

804853
const pkField = resource.columns.find((col) => col.primaryKey)?.name;
805854
// remove all columns which are not defined in resources, or defined but backendOnly
806-
data.data.forEach((item) => {
807-
Object.keys(item).forEach((key) => {
808-
if (!resource.columns.find((col) => col.name === key) || resource.columns.find((col) => col.name === key && col.backendOnly)) {
809-
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+
}
810875
}
811-
})
812-
item._label = resource.recordLabel(item);
813-
});
876+
item._label = resource.recordLabel(item);
877+
}
878+
}
814879
if (source === 'list' && resource.options.listTableClickUrl) {
815880
await Promise.all(
816881
data.data.map(async (item) => {
@@ -1076,11 +1141,30 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
10761141
}
10771142
}
10781143

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+
10791161
for (const column of resource.columns) {
10801162
const fieldName = column.name;
10811163
if (fieldName in record) {
1082-
if (!column.showIn?.create || column.backendOnly) {
1083-
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 };
10841168
}
10851169
}
10861170
}
@@ -1172,15 +1256,24 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
11721256
return { error: allowedError };
11731257
}
11741258

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+
11751267
for (const column of resource.columns) {
11761268
const fieldName = column.name;
11771269
if (fieldName in record) {
1178-
if (!column.showIn?.edit || column.editReadonly || column.backendOnly) {
1179-
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 };
11801274
}
11811275
}
11821276
}
1183-
11841277
// for polymorphic foreign resources, we need to find out the value for polymorphicOn column
11851278
for (const column of resource.columns) {
11861279
if (column.foreignResource?.polymorphicOn && record[column.name] === null) {

adminforth/types/Back.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,15 +1520,27 @@ export type ShowInInput = ShowInModernInput | ShowInLegacyInput;
15201520
export type ShowIn = {
15211521
[key in AdminForthResourcePages]: AllowedActionValue
15221522
}
1523+
export type BackendOnlyInput =
1524+
| boolean
1525+
| ((p: {
1526+
adminUser: AdminUser;
1527+
resource: AdminForthResource;
1528+
meta: any;
1529+
source: ActionCheckSource;
1530+
adminforth: IAdminForth;
1531+
}) => boolean | Promise<boolean>);
1532+
15231533

1524-
export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn'> {
1534+
export interface AdminForthResourceColumnInput extends Omit<AdminForthResourceColumnInputCommon, 'showIn' | 'backendOnly'> {
15251535
showIn?: ShowInInput,
15261536
foreignResource?: AdminForthForeignResource,
1537+
backendOnly?: BackendOnlyInput;
15271538
}
15281539

1529-
export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn'> {
1540+
export interface AdminForthResourceColumn extends Omit<AdminForthResourceColumnCommon, 'showIn' | 'backendOnly'> {
15301541
showIn?: ShowIn,
15311542
foreignResource?: AdminForthForeignResource,
1543+
backendOnly?: BackendOnlyInput;
15321544
}
15331545

15341546
export interface IWebSocketClient {

0 commit comments

Comments
 (0)