Skip to content

Commit 582a599

Browse files
authored
Merge pull request #616 from devforth/feature/AdminForth/1547/https-adminfo-need-a-handy-wat
Feature/admin forth/1547/https adminfo need a handy wat
2 parents df095f2 + 1b33b65 commit 582a599

2 files changed

Lines changed: 123 additions & 1 deletion

File tree

adminforth/documentation/docs/tutorial/03-Customization/09-Actions.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,46 @@ Backend handler: read the payload via `extra`.
360360
Notes:
361361
- If you don’t emit a payload, the default behavior is used by the UI (e.g., in lists the current row context is used). When you do provide a payload, it will be forwarded to the backend as `extra` for your action handler.
362362
- You can combine default context with your own payload by merging before emitting, for example: `emit('callAction', { ...row, asListed: true })` if your component has access to the row object.
363+
364+
## Start actions programmatically
365+
You can execute resource actions manually using adminforth.runAction(). This is useful inside hooks, plugins, cron jobs, custom endpoints, or any backend automation.
366+
367+
```ts title="./resources/apartments.ts"
368+
actions: [
369+
{
370+
//diff-add
371+
id: 'testToggle listedAction',
372+
name: 'Toggle listed',
373+
icon: 'flowbite:eye-solid',
374+
...
375+
}
376+
]
377+
```
378+
Then execute it from a hook for example:
379+
380+
```ts title="./resources/apartments.ts"
381+
hooks: {
382+
...
383+
afterSave: async ({ record, adminUser, resource, adminforth }: { record: any, adminUser: AdminUser, resource: AdminForthResource, adminforth: any }) => {
384+
385+
await adminforth.runAction({
386+
actionId: 'Toggle listed',
387+
resourceId: resource.resourceId,
388+
recordId: record.id,
389+
adminUser,
390+
});
391+
392+
return { ok: true };
393+
},
394+
},
395+
```
396+
397+
runAction() automatically:
398+
- finds the resource
399+
- finds the action
400+
- checks permissions via allowed
401+
- executes the action handler
402+
- passes full action context (recordId, adminUser, extra, etc.)
403+
404+
> ☝️ runAction() is not limited to hooks — you can call it anywhere you have access to the AdminForth instance.
405+

adminforth/index.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
import {
3030
AdminForthFilterOperators,
3131
AdminForthDataTypes,
32-
AdminUser,
32+
AdminUser, ActionCheckSource
3333
} from './types/Common.js';
3434

3535
import AdminForthPlugin from './basePlugin.js';
@@ -815,6 +815,85 @@ class AdminForth implements IAdminForth {
815815
return { error: null };
816816
}
817817

818+
async runAction({
819+
resourceId,
820+
actionId,
821+
recordId,
822+
adminUser,
823+
extra = {},
824+
response,
825+
tr,
826+
}: {
827+
resourceId: string,
828+
actionId: string,
829+
recordId: string | number,
830+
adminUser: AdminUser,
831+
extra,
832+
response?: any,
833+
tr?: any,
834+
}) {
835+
const resource = this.config.resources.find(
836+
(res) => res.resourceId === resourceId
837+
);
838+
839+
if (!resource) {
840+
return {
841+
ok: false,
842+
error: `Resource '${resourceId}' not found`,
843+
};
844+
}
845+
846+
const action = resource.options.actions?.find(
847+
(act) => act.id === actionId
848+
);
849+
850+
if (!action) {
851+
return {
852+
ok: false,
853+
error: `Action '${actionId}' not found`,
854+
};
855+
}
856+
857+
if (!action.action) {
858+
return {
859+
ok: false,
860+
error: `Action '${actionId}' has no action handler`,
861+
};
862+
}
863+
864+
if (typeof action.allowed === 'function') {
865+
const { allowedActions } = await interpretResource(
866+
adminUser,
867+
resource,
868+
{},
869+
ActionCheckSource.CustomActionRequest,
870+
this
871+
);
872+
873+
const execAllowed = await action.allowed({
874+
adminUser,
875+
standardAllowedActions: allowedActions,
876+
});
877+
878+
if (!execAllowed) {
879+
return {
880+
ok: false,
881+
error: `Action '${actionId}' not allowed`,
882+
};
883+
}
884+
}
885+
886+
return await action.action({
887+
recordId: String(recordId),
888+
adminUser,
889+
resource,
890+
adminforth: this,
891+
response: response as any,
892+
tr: tr as any,
893+
extra,
894+
});
895+
}
896+
818897
resource(resourceId: string): IOperationalResource {
819898
if (this.statuses.dbDiscover !== 'done') {
820899
if (this.statuses.dbDiscover === 'running') {

0 commit comments

Comments
 (0)