Skip to content

Commit 30fae5c

Browse files
committed
feat: implement removedInV4 utility and update related code to handle deprecated options
1 parent 7a47e04 commit 30fae5c

8 files changed

Lines changed: 49 additions & 34 deletions

File tree

‎adminforth/auth.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import crypto from 'crypto';
44
import AdminForth from './index.js';
55
import { AdminUserAuthorizationResult, AdminUserAuthorizeFunction, AfterSessionCreatedFunction, HttpExtra, IAdminForthAuth, IAdminForthHttpResponse } from './types/Back.js';
66
import { AdminUser } from './types/Common.js';
7-
import { listify } from './modules/utils.js';
7+
import { listify, removedInV4 } from './modules/utils.js';
88
import { afLogger } from './modules/logger.js';
99
import { Address4, Address6 } from 'ip-address';
1010

@@ -130,6 +130,9 @@ class AdminForthAuth implements IAdminForthAuth {
130130
setCustomCookie({ response, payload }: {
131131
response: any, payload: { name: string, value: string, expirySeconds?: number, httpOnly: boolean, sessionBased?: boolean }
132132
}) {
133+
if ('expiry' in payload) {
134+
throw new Error(removedInV4('setCustomCookie payload "expiry"', 'expirySeconds'));
135+
}
133136
const { name, value, httpOnly, expirySeconds = 24 * 60 * 60, sessionBased } = payload; // default 1 day
134137
const expiryMs = expirySeconds * 1000;
135138
const brandSlug = this.adminforth.config.customization.brandNameSlug;

‎adminforth/design/adoption.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ Which packages follow the [design concepts](./system-concepts.md): tokens instea
2020
| @adminforth/i18n | ✓ |
2121
| @adminforth/i18n-static | ✓ |
2222
| @adminforth/import-export | ✓ |
23-
| @adminforth/inline-create | |
2423
| @adminforth/json-form | |
2524
| @adminforth/list-in-place-edit | ✓ |
2625
| @adminforth/login-captcha | |
@@ -46,7 +45,7 @@ Which packages follow the [design concepts](./system-concepts.md): tokens instea
4645

4746
## Landing page previews
4847

49-
The cards of the landing gallery (`documentation/src/pages/index.tsx`) show either a PNG screenshot (`original`) or a static HTML page of `documentation/static/landing-previews` (`preview: '<file>'`) drawn in the design system. A plugin moves to `preview` once it is adapted.
48+
The cards of the landing gallery (`documentation/src/pages/index.tsx`) show a static HTML page of `documentation/static/landing-previews` (`preview: '<file>'`) drawn in the design system.
5049

5150
- Draw the screen of the plugin's core feature, not the whole page: keep one key element (an open menu, a form field, a dialog) and drop the columns, fields and buttons which don't explain the feature. A card is about 440px wide, so a screen with every column of the real page is unreadable.
5251
- Copy the closest page (a list, an edit form, a settings tab) and take the markup, texts, Lucide icons and tokens from the components of the plugin, the shared classes are in `previews.css`.
@@ -63,4 +62,4 @@ The cards of the landing gallery (`documentation/src/pages/index.tsx`) show eith
6362

6463
- If simple - get of flowbite, prefer pure AFCL + Tailwind, move features we luck to AFCL
6564
- Existing icon fonts apart lucide - try to get rid of them if not complex
66-
- Core uses only Lucide icons and `flag-icons` flags (`afcl/CountryFlag.vue`). `vue-flowbite` and `vue-heroicons` stay in `spa/package.json` while plugins import them (flowbite: foreign-inline-list, inline-create, user-soft-delete, and dashboard only for `flowbite:chart-pie-solid`, the old default menu icon which stored dashboard configs may name; no plugin imports heroicons any more). After plugins rework it they should disappear.
65+
- Core uses only Lucide icons and `flag-icons` flags (`afcl/CountryFlag.vue`). `vue-flowbite` and `vue-heroicons` stay in `spa/package.json` while plugins import them (flowbite: foreign-inline-list, user-soft-delete, and dashboard only for `flowbite:chart-pie-solid`, the old default menu icon which stored dashboard configs may name; no plugin imports heroicons any more). After plugins rework it they should disappear.

‎adminforth/index.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import CodeInjector from './modules/codeInjector.js';
44
import ExpressServer from './servers/express.js';
55
import OpenApiRegistry from './servers/openapi.js';
66
// import FastifyServer from './servers/fastify.js';
7-
import { ADMINFORTH_VERSION, listify, suggestIfTypo, RateLimiter, RAMLock, isProbablyUUIDColumn, convertPeriodToSeconds, hookResponseError, md5hash, applyRegexValidation, formatHugePluginError } from './modules/utils.js';
7+
import { ADMINFORTH_VERSION, listify, suggestIfTypo, RateLimiter, RAMLock, isProbablyUUIDColumn, convertPeriodToSeconds, hookResponseError, md5hash, applyRegexValidation, formatHugePluginError, removedInV4 } from './modules/utils.js';
88
import {
99
type AdminForthConfig,
1010
type IAdminForth,
@@ -825,6 +825,9 @@ class AdminForth implements IAdminForth {
825825
`Note: Return { ok: false, error: null, redirectToRecordId } to stop creation and redirect to an existing record.`
826826
);
827827
}
828+
if ('newRecordId' in resp) {
829+
throw new Error(removedInV4(`Resource "${resource.resourceId}" hooks.create.beforeSave result "newRecordId"`, 'redirectToRecordId'));
830+
}
828831
if (resp.ok === false && !resp.error) {
829832
const { error, redirectToRecordId } = resp;
830833
return {
@@ -886,6 +889,9 @@ class AdminForth implements IAdminForth {
886889
async updateResourceRecord(
887890
params: UpdateResourceRecordParams,
888891
): Promise<UpdateResourceRecordResult> {
892+
if ('record' in params) {
893+
throw new Error(removedInV4('updateResourceRecord param "record"', 'updates'));
894+
}
889895
const { resource, recordId, oldRecord, adminUser, extra, updates } = params;
890896
normalizeRecordValues(resource, updates);
891897
const err = this.validateRecordValues(resource, updates, 'edit');

‎adminforth/modules/configValidator.ts‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { compositePkValues, isCompositePrimaryKey } from './recordId.js';
1818

1919
import fs from 'fs';
2020
import path from 'path';
21-
import { guessLabelFromName, md5hash, RateLimiter, suggestIfTypo, slugifyString } from './utils.js';
21+
import { guessLabelFromName, md5hash, RateLimiter, suggestIfTypo, slugifyString, removedInV4 } from './utils.js';
2222
import {
2323
AdminForthSortDirections,
2424
type AdminForthComponentDeclarationFull,
@@ -179,6 +179,9 @@ export default class ConfigValidator implements IConfigValidator {
179179
customization.customPages.forEach((page) => {
180180
page.component = this.validateComponent(page.component, errors);
181181
const meta = page.component.meta || {};
182+
if ('customLayout' in meta) {
183+
errors.push(removedInV4(`Custom page "${page.path}" meta.customLayout`, `meta.sidebarAndHeader: 'none'`));
184+
}
182185
if (meta.sidebarAndHeader === undefined) {
183186
meta.sidebarAndHeader = 'default';
184187
}
@@ -337,7 +340,9 @@ export default class ConfigValidator implements IConfigValidator {
337340
// an invalid showIn is reported with the other config errors, the column is normalized as if it had none
338341
// so the checks after this one do not crash on an undefined showIn before the errors are thrown
339342
const showInIsObject = column.showIn && !Array.isArray(column.showIn) && typeof column.showIn === 'object';
340-
if (column.showIn && !showInIsObject) {
343+
if (Array.isArray(column.showIn)) {
344+
errors.push(removedInV4(`Resource "${resInput.resourceId || resInput.table}" column "${column.name}" showIn as an array`, 'an object, e.g. showIn: { list: true, edit: false }'));
345+
} else if (column.showIn && !showInIsObject) {
341346
errors.push(`Resource "${resInput.resourceId || resInput.table}" column "${column.name}" showIn must be an object`);
342347
}
343348

@@ -875,6 +880,9 @@ export default class ConfigValidator implements IConfigValidator {
875880
});
876881
}
877882

883+
if (resInput.options && 'bulkActions' in resInput.options) {
884+
errors.push(removedInV4(`Resource "${res.resourceId}" options.bulkActions`, 'options.actions with bulkHandler'));
885+
}
878886
const options: Partial<AdminForthResource['options']> = {...resInput.options, bulkActions: undefined, allowedActions: undefined};
879887

880888
options.allowedActions = this.validateAndNormalizeAllowedActions(resInput, errors);
@@ -1158,6 +1166,9 @@ export default class ConfigValidator implements IConfigValidator {
11581166
// AUTH checks
11591167

11601168
if (newConfig.auth) {
1169+
if ('resourceId' in newConfig.auth) {
1170+
throw new Error(removedInV4('auth.resourceId', 'auth.usersResourceId'));
1171+
}
11611172
if (!newConfig.auth.usersResourceId) {
11621173
throw new Error('No config.auth.usersResourceId defined');
11631174
}
@@ -1182,6 +1193,10 @@ export default class ConfigValidator implements IConfigValidator {
11821193
}
11831194
}
11841195

1196+
if ('rememberMeDays' in newConfig.auth) {
1197+
errors.push(removedInV4('auth.rememberMeDays', `auth.rememberMeDuration: "${newConfig.auth.rememberMeDays}d"`));
1198+
}
1199+
11851200
if (newConfig.auth.rememberMeDuration !== undefined) {
11861201
const duration = newConfig.auth.rememberMeDuration;
11871202
if (typeof duration !== 'string') {

‎adminforth/modules/utils.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ export function suggestIfTypo(names: string[], name: string): string {
9999
}
100100

101101

102+
/**
103+
* Message for an option or param removed in AdminForth 4, names what to use instead
104+
*/
105+
export function removedInV4(what: string, replacement: string): string {
106+
return `${what} is no longer supported in AdminForth 4+, use ${replacement} instead`;
107+
}
108+
102109
export function md5hash(str:string) {
103110
return crypto.createHash('md5').update(str).digest('hex');
104111
}

‎documentation/src/pages/index.module.css‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -673,15 +673,11 @@
673673
flex-direction: column;
674674
}
675675

676-
.gcard img,
676+
/* a drawn screen is a picture: no focus, clicks and scroll stay with the page */
677677
.gcard iframe {
678678
display: block;
679679
width: 100%;
680680
border-bottom: 1px solid var(--line);
681-
}
682-
683-
/* a drawn screen is a picture: no focus, clicks and scroll stay with the page */
684-
.gcard iframe {
685681
aspect-ratio: 16 / 10;
686682
border-top: 0;
687683
border-right: 0;

‎documentation/src/pages/index.tsx‎

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ const FEATURED_ON = [
6464

6565
type PluginItem = {name: string; slug: string; description: string; standard?: string};
6666
type PluginGroup = {title: string; items: PluginItem[]};
67-
// a card shows a screenshot (original) or a page of static/landing-previews drawn in the design system (preview)
68-
type GalleryItem = {title: string; link: string; description: string; original?: string; preview?: string};
67+
// a card shows a page of static/landing-previews drawn in the design system
68+
type GalleryItem = {title: string; link: string; description: string; preview: string};
6969

7070
const images: GalleryItem[] = [
7171
{
@@ -159,12 +159,6 @@ const images: GalleryItem[] = [
159159
link: '/docs/tutorial/Plugins/list-in-place-edit/',
160160
description: 'Edit some oftenly used fields in list directly without opening edit page. Decide which fields to be editable in list view'
161161
},
162-
{
163-
original: require('@site/static/img/previews/inplace-create.png').default,
164-
title: 'Inline create Plugin for quick create',
165-
link: '/docs/tutorial/Plugins/inline-create/',
166-
description: 'For tables with small fields number which should be created massively, use inline create plugin to create records directly in list view'
167-
},
168162
{
169163
preview: 'import-export',
170164
title: 'Import/Export CSV Plugin',
@@ -264,7 +258,6 @@ const PLUGIN_GROUPS: PluginGroup[] = [
264258
{name: 'Markdown', slug: 'markdown', description: 'Author and preview Markdown, stored as Markdown'},
265259
{name: 'JSON form', slug: 'json-form', description: 'A real form for a JSON column, generated from your schema', standard: 'JSON Schema'},
266260
{name: 'Uploads', slug: 'upload', description: 'S3, S3-compatible or local storage under your own keys', standard: 'S3 API'},
267-
{name: 'Inline create', slug: 'inline-create', description: 'Add a row without leaving the list'},
268261
{name: 'List in-place edit', slug: 'list-in-place-edit', description: 'Edit cells where you read them'},
269262
{name: 'Clone row', slug: 'clone-row', description: 'Duplicate a record into a prefilled form'},
270263
{name: 'Many-to-many', slug: 'many2many', description: 'Link tables managed as one ordinary field'},
@@ -544,16 +537,12 @@ function Gallery(): JSX.Element {
544537
<div className={styles.gallery}>
545538
{images.map((item, index) => (
546539
<div className={styles.gcard} key={`feature${index}`}>
547-
{item.preview ? (
548-
<iframe
549-
src={withBaseUrl(`/landing-previews/${item.preview}.html`)}
550-
title={item.title}
551-
loading="lazy"
552-
tabIndex={-1}
553-
/>
554-
) : (
555-
<img src={item.original} alt={item.title} title={item.title} loading="lazy" />
556-
)}
540+
<iframe
541+
src={withBaseUrl(`/landing-previews/${item.preview}.html`)}
542+
title={item.title}
543+
loading="lazy"
544+
tabIndex={-1}
545+
/>
557546
<div className={styles.gcardBody}>
558547
<Heading as="h3" className={styles.gcardTitle}>{item.title}</Heading>
559548
<p>{item.description}</p>

‎live-demo/app/resources/users.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ const usersResource: AdminForthResourceInput = {
9797
edit: {
9898
beforeSave: [
9999
blockDemoUsers,
100-
async ({ record, adminUser, resource}: any) => {
101-
if (record.password) {
102-
record.password_hash = await AdminForth.Utils.generatePasswordHash(record.password);
100+
async ({ updates }: any) => {
101+
if (updates.password) {
102+
updates.password_hash = await AdminForth.Utils.generatePasswordHash(updates.password);
103103
}
104104
return { ok: true }
105105
},

0 commit comments

Comments
 (0)