adminforth 2.50.0-next.1 → 2.50.0-next.10
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/commands/createApp/templates/.agents/skills/adminforth/SKILL.md.hbs +42 -0
- package/commands/createApp/templates/.agents/skills/adminforth-custom-vue/SKILL.md.hbs +340 -0
- package/commands/createApp/templates/.agents/skills/adminforth-hooks/SKILL.md.hbs +70 -0
- package/commands/createApp/templates/.agents/skills/adminforth-permissions/SKILL.md.hbs +59 -0
- package/commands/createApp/templates/AGENTS.md.hbs +71 -0
- package/commands/createApp/templates/CLAUDE.md.hbs +5 -0
- package/commands/createApp/templates/Dockerfile.hbs +5 -16
- package/commands/createApp/templates/package.json.hbs +6 -18
- package/commands/createApp/templates/readme.md.hbs +5 -24
- package/commands/createApp/utils.js +52 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +3 -2
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +9 -1
- package/dist/modules/restApi.js.map +1 -1
- package/dist/servers/express.d.ts +2 -0
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +34 -7
- package/dist/servers/express.js.map +1 -1
- package/dist/servers/openapi.d.ts.map +1 -1
- package/dist/servers/openapi.js +2 -0
- package/dist/servers/openapi.js.map +1 -1
- package/dist/spa/src/afcl/Link.vue +24 -4
- package/dist/spa/src/components/ResourceListTable.vue +1 -0
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/types/Back.ts +10 -1
- package/dist/spa/src/types/Common.ts +1 -1
- package/dist/spa/src/types/adapters/AudioAdapter.ts +73 -0
- package/dist/spa/src/types/adapters/index.ts +10 -0
- package/dist/spa/src/utils/listUtils.ts +3 -0
- package/dist/spa/src/views/ShowView.vue +1 -0
- package/dist/types/Back.d.ts +14 -1
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +1 -1
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/adapters/AudioAdapter.d.ts +52 -0
- package/dist/types/adapters/AudioAdapter.d.ts.map +1 -0
- package/dist/types/adapters/AudioAdapter.js +2 -0
- package/dist/types/adapters/AudioAdapter.js.map +1 -0
- package/dist/types/adapters/index.d.ts +1 -0
- package/dist/types/adapters/index.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adminforth
|
|
3
|
+
description: "Use for general AdminForth app work: resources, index.ts, api.ts, schema.prisma, custom UI, Data API, adapters, plugins, and overall project structure. Use specialized skills for permissions, hooks, and custom Vue work."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AdminForth App Workflow
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
- Editing `index.ts`, `api.ts`, `resources/*.ts`, `custom/**`, environment files, or deployment files.
|
|
12
|
+
- Adding a resource, menu item, custom page, component, custom API, plugin, or adapter.
|
|
13
|
+
- Deciding whether logic belongs in resource config, the Data API, a custom Express route, or frontend customization.
|
|
14
|
+
- Use `adminforth-permissions` when the task is mainly about access control.
|
|
15
|
+
- Use `adminforth-hooks` when the task is mainly about lifecycle hooks.
|
|
16
|
+
- Use `adminforth-custom-vue` when the task is mainly about components under `custom/`, field renderers, or page/login/global injections.
|
|
17
|
+
|
|
18
|
+
## Project Map
|
|
19
|
+
|
|
20
|
+
- `index.ts`: main AdminForth config, resource registration, menu, and server startup.
|
|
21
|
+
- `api.ts`: custom Express endpoints. Prefer schema-aware handlers so request and response shapes stay explicit.
|
|
22
|
+
- `resources/*.ts`: table or collection definitions, columns, labels, filters, and resource-level behavior.
|
|
23
|
+
- `custom/`: Vue components, custom pages, injections, and static assets.
|
|
24
|
+
{{#if prismaDbUrl}}- `schema.prisma`: optional schema and migration source when this app uses Prisma.{{/if}}
|
|
25
|
+
- `.env.local` and `.env`: local config and secrets.
|
|
26
|
+
|
|
27
|
+
## Recommended Workflow
|
|
28
|
+
|
|
29
|
+
1. Change the database schema with Prisma or your own migration tool. AdminForth itself does not change the database schema for you.
|
|
30
|
+
2. Add or update the matching resource in `resources/*.ts`.
|
|
31
|
+
3. Register the resource and menu entry in `index.ts`.
|
|
32
|
+
4. Prefer resource config for labels, field visibility, defaults, and validation before reaching for custom code.
|
|
33
|
+
5. Use AdminForth Data API for simple CRUD, filtering, sorting, and counts. Use your own ORM or query builder for joins, aggregations, or more complex queries.
|
|
34
|
+
6. Put custom business endpoints in `api.ts`.
|
|
35
|
+
7. Put custom UI in `custom/`.
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
- Install: `{{packageManager}} install`
|
|
40
|
+
- Start local development: `{{packageManagerRun}} dev`
|
|
41
|
+
{{#if prismaDbUrl}}- Apply local migrations: `{{packageManagerRun}} migrate:local`
|
|
42
|
+
- Create a migration: `{{packageManagerRun}} makemigration{{packageManagerScriptArgSeparator}}--name <change-name>`{{/if}}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adminforth-custom-vue
|
|
3
|
+
description: "Use when implementing AdminForth custom Vue UI: field components, page injections, login or global injections, meta-driven component declarations, and frontend packages inside custom/."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AdminForth Custom Vue Workflow
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
- Editing files under `custom/`.
|
|
12
|
+
- Adding custom field renderers or editors via `column.components`.
|
|
13
|
+
- Adding resource page injections, login injections, or global layout injections.
|
|
14
|
+
- Passing `meta` into reusable Vue components.
|
|
15
|
+
- Installing frontend packages used only by custom AdminForth Vue code.
|
|
16
|
+
|
|
17
|
+
## `custom/` Directory and `@@/`
|
|
18
|
+
|
|
19
|
+
- `custom/` is the frontend workspace for AdminForth custom Vue code.
|
|
20
|
+
- By default, `@@/Something.vue` means “resolve this path from `customComponentsDir` on the backend”, and `customComponentsDir` defaults to `./custom`.
|
|
21
|
+
- Example: `@@/reports/OrdersHeader.vue` maps to `./custom/reports/OrdersHeader.vue`.
|
|
22
|
+
- The same `@@/` prefix also works for assets and helper files that are bundled into the SPA.
|
|
23
|
+
|
|
24
|
+
## Frontend Packages in `custom/`
|
|
25
|
+
|
|
26
|
+
- Install frontend-only dependencies inside `custom/`, not in the app root.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd custom
|
|
30
|
+
{{packageManager}} install apexcharts vue3-apexcharts
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- If `custom/package.json` does not exist yet, initialize the folder first.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd custom
|
|
37
|
+
{{packageManager}} init
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Organizing Components
|
|
41
|
+
|
|
42
|
+
- You can freely create subfolders and split components into smaller children.
|
|
43
|
+
- Example structure:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
custom/
|
|
47
|
+
reports/
|
|
48
|
+
OrdersTopPanel.vue
|
|
49
|
+
parts/
|
|
50
|
+
OrdersTotals.vue
|
|
51
|
+
OrdersFilters.vue
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Example parent component using subcomponents:
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<template>
|
|
58
|
+
<section class="grid gap-4 md:grid-cols-2">
|
|
59
|
+
<OrdersTotals :record="record" />
|
|
60
|
+
<OrdersFilters :meta="meta" />
|
|
61
|
+
</section>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup lang="ts">
|
|
65
|
+
import OrdersTotals from './parts/OrdersTotals.vue';
|
|
66
|
+
import OrdersFilters from './parts/OrdersFilters.vue';
|
|
67
|
+
|
|
68
|
+
defineProps<{
|
|
69
|
+
record?: any;
|
|
70
|
+
meta?: any;
|
|
71
|
+
}>();
|
|
72
|
+
</script>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- In AdminForth config you still reference only the entry component:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
beforeBreadcrumbs: '@@/reports/OrdersTopPanel.vue'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Short vs Full Component Declaration
|
|
82
|
+
|
|
83
|
+
- Short declaration is just a file string:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
show: '@@/RoomsCell.vue'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- Full declaration lets you pass `meta`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
show: {
|
|
93
|
+
file: '@@/RoomsCell.vue',
|
|
94
|
+
meta: {
|
|
95
|
+
filler: '🟨',
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- Use full declaration when the same component should behave differently in different places.
|
|
101
|
+
- `meta` is passed into the Vue component as a prop.
|
|
102
|
+
- Common `meta` use cases are reusable display options, plugin settings, `thinEnoughToShrinkTable`, `afOrder`, or custom page layout flags.
|
|
103
|
+
|
|
104
|
+
## Field Component Spots
|
|
105
|
+
|
|
106
|
+
- `components.show`: custom value renderer on show page.
|
|
107
|
+
- `components.showRow`: replaces the full show-table row.
|
|
108
|
+
- `components.create`: custom editor on create page.
|
|
109
|
+
- `components.edit`: custom editor on edit page.
|
|
110
|
+
- `components.list`: custom value renderer in list cells.
|
|
111
|
+
- `components.filter`: custom filter input.
|
|
112
|
+
|
|
113
|
+
## Field Component Props and Emits
|
|
114
|
+
|
|
115
|
+
- `show` and `list` components should expect `column`, `record`, `resource`, `adminUser`, and optional `meta`.
|
|
116
|
+
- In practice, custom `edit` and `create` components receive `column`, `value`, `record`, `resource`, `adminUser`, `readonly`, and optional `meta`.
|
|
117
|
+
- Custom `edit` and `create` components can emit:
|
|
118
|
+
- `update:value` to change the current field value.
|
|
119
|
+
- `update:recordFieldValue` to change another field in the same record.
|
|
120
|
+
- `update:inValidity` to report custom validation state.
|
|
121
|
+
- `update:emptiness` to report custom emptiness rules.
|
|
122
|
+
- If you update hidden technical fields through `update:recordFieldValue`, the target column may need `allowModifyWhenNotShowInCreate` or `allowModifyWhenNotShowInEdit`.
|
|
123
|
+
|
|
124
|
+
## Custom Edit Component Example
|
|
125
|
+
|
|
126
|
+
```vue
|
|
127
|
+
<template>
|
|
128
|
+
<div class="grid gap-2">
|
|
129
|
+
<Input
|
|
130
|
+
:model-value="localValue"
|
|
131
|
+
:readonly="readonly"
|
|
132
|
+
:placeholder="meta?.placeholder || column.label"
|
|
133
|
+
@update:model-value="onInput"
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<p v-if="errorMessage" class="text-sm text-red-600">
|
|
137
|
+
{{ errorMessage }}
|
|
138
|
+
</p>
|
|
139
|
+
|
|
140
|
+
<p v-else-if="isEmpty" class="text-sm text-amber-600">
|
|
141
|
+
Value is currently empty
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</template>
|
|
145
|
+
|
|
146
|
+
<script setup lang="ts">
|
|
147
|
+
import { computed, onMounted, ref } from 'vue';
|
|
148
|
+
import Input from '@/afcl/Input.vue';
|
|
149
|
+
import type {
|
|
150
|
+
AdminForthResourceColumnCommon,
|
|
151
|
+
AdminForthResourceCommon,
|
|
152
|
+
AdminUser,
|
|
153
|
+
} from '@/types/Common';
|
|
154
|
+
|
|
155
|
+
const props = defineProps<{
|
|
156
|
+
column: AdminForthResourceColumnCommon;
|
|
157
|
+
value: string | null | undefined;
|
|
158
|
+
record: Record<string, any>;
|
|
159
|
+
meta?: { minLength?: number; placeholder?: string };
|
|
160
|
+
resource: AdminForthResourceCommon;
|
|
161
|
+
adminUser: AdminUser;
|
|
162
|
+
readonly: boolean;
|
|
163
|
+
}>();
|
|
164
|
+
|
|
165
|
+
const emit = defineEmits<{
|
|
166
|
+
(e: 'update:value', value: string): void;
|
|
167
|
+
(e: 'update:recordFieldValue', payload: { fieldName: string; fieldValue: any }): void;
|
|
168
|
+
(e: 'update:inValidity', value: string | false): void;
|
|
169
|
+
(e: 'update:emptiness', value: boolean): void;
|
|
170
|
+
}>();
|
|
171
|
+
|
|
172
|
+
const localValue = ref(props.value ?? '');
|
|
173
|
+
const WHITESPACE_RE = /\s+/g;
|
|
174
|
+
|
|
175
|
+
const isEmpty = computed(() => localValue.value.trim() === '');
|
|
176
|
+
const errorMessage = computed(() => {
|
|
177
|
+
const minLength = props.meta?.minLength ?? 3;
|
|
178
|
+
if (isEmpty.value) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return localValue.value.trim().length < minLength
|
|
182
|
+
? `Minimum length is ${minLength}`
|
|
183
|
+
: false;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
onMounted(syncState);
|
|
187
|
+
|
|
188
|
+
function onInput(nextValue: string) {
|
|
189
|
+
localValue.value = nextValue;
|
|
190
|
+
emit('update:value', nextValue);
|
|
191
|
+
emit('update:recordFieldValue', {
|
|
192
|
+
fieldName: 'slug',
|
|
193
|
+
fieldValue: nextValue.toLowerCase().trim().replace(WHITESPACE_RE, '-'),
|
|
194
|
+
});
|
|
195
|
+
syncState();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function syncState() {
|
|
199
|
+
// for edit/create components where emptiness is not obvious (e.g. image was not uploaded), always emit emptiness when it change
|
|
200
|
+
emit('update:emptiness', isEmpty.value);
|
|
201
|
+
|
|
202
|
+
// for custom validity (e.g. upload still in progress), emit error message or false for valid
|
|
203
|
+
emit('update:inValidity', errorMessage.value || false);
|
|
204
|
+
}
|
|
205
|
+
</script>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Custom List and Show Example with `meta`
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
{
|
|
212
|
+
name: 'number_of_rooms',
|
|
213
|
+
components: {
|
|
214
|
+
show: {
|
|
215
|
+
file: '@@/RoomsCell.vue',
|
|
216
|
+
meta: { filler: '🟨', suffix: 'rooms' },
|
|
217
|
+
},
|
|
218
|
+
list: {
|
|
219
|
+
file: '@@/RoomsCell.vue',
|
|
220
|
+
meta: { filler: '🟦', suffix: 'r' },
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```vue
|
|
227
|
+
<template>
|
|
228
|
+
<div class="flex items-center gap-2">
|
|
229
|
+
<span>
|
|
230
|
+
{{ meta?.filler?.repeat(record.number_of_rooms || 0) }}
|
|
231
|
+
</span>
|
|
232
|
+
<span>{{ record.number_of_rooms }} {{ meta?.suffix }}</span>
|
|
233
|
+
</div>
|
|
234
|
+
</template>
|
|
235
|
+
|
|
236
|
+
<script setup lang="ts">
|
|
237
|
+
import type {
|
|
238
|
+
AdminForthResourceColumnCommon,
|
|
239
|
+
AdminForthResourceCommon,
|
|
240
|
+
AdminUser,
|
|
241
|
+
} from '@/types/Common';
|
|
242
|
+
|
|
243
|
+
defineProps<{
|
|
244
|
+
column: AdminForthResourceColumnCommon;
|
|
245
|
+
record: Record<string, any>;
|
|
246
|
+
meta?: { filler?: string; suffix?: string };
|
|
247
|
+
resource: AdminForthResourceCommon;
|
|
248
|
+
adminUser: AdminUser;
|
|
249
|
+
}>();
|
|
250
|
+
</script>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Resource Page Injection Spots
|
|
254
|
+
|
|
255
|
+
- `pageInjections.list.beforeBreadcrumbs`
|
|
256
|
+
- `pageInjections.list.afterBreadcrumbs`
|
|
257
|
+
- `pageInjections.list.beforeActionButtons`
|
|
258
|
+
- `pageInjections.list.bottom`
|
|
259
|
+
- `pageInjections.list.threeDotsDropdownItems`
|
|
260
|
+
- `pageInjections.list.customActionIcons`
|
|
261
|
+
- `pageInjections.list.customActionIconsThreeDotsMenuItems`
|
|
262
|
+
- `pageInjections.list.tableBodyStart`
|
|
263
|
+
- `pageInjections.list.tableRowReplace`
|
|
264
|
+
- `pageInjections.show.beforeBreadcrumbs`
|
|
265
|
+
- `pageInjections.show.afterBreadcrumbs`
|
|
266
|
+
- `pageInjections.show.bottom`
|
|
267
|
+
- `pageInjections.show.threeDotsDropdownItems`
|
|
268
|
+
- `pageInjections.edit.beforeBreadcrumbs`
|
|
269
|
+
- `pageInjections.edit.afterBreadcrumbs`
|
|
270
|
+
- `pageInjections.edit.bottom`
|
|
271
|
+
- `pageInjections.edit.threeDotsDropdownItems`
|
|
272
|
+
- `pageInjections.create.beforeBreadcrumbs`
|
|
273
|
+
- `pageInjections.create.afterBreadcrumbs`
|
|
274
|
+
- `pageInjections.create.bottom`
|
|
275
|
+
- `pageInjections.create.threeDotsDropdownItems`
|
|
276
|
+
|
|
277
|
+
## Login and Global Injection Spots
|
|
278
|
+
|
|
279
|
+
- `customization.loginPageInjections.panelHeader`
|
|
280
|
+
- `customization.loginPageInjections.underInputs`
|
|
281
|
+
- `customization.loginPageInjections.underLoginButton`
|
|
282
|
+
- `customization.globalInjections.userMenu`
|
|
283
|
+
- `customization.globalInjections.header`
|
|
284
|
+
- `customization.globalInjections.sidebar`
|
|
285
|
+
- `customization.globalInjections.sidebarTop`
|
|
286
|
+
- `customization.globalInjections.everyPageBottom`
|
|
287
|
+
|
|
288
|
+
## Top Injection Example
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
options: {
|
|
292
|
+
pageInjections: {
|
|
293
|
+
list: {
|
|
294
|
+
beforeBreadcrumbs: {
|
|
295
|
+
file: '@@/reports/OrdersTopPanel.vue',
|
|
296
|
+
meta: {
|
|
297
|
+
title: 'Orders overview',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## List Injection Shrink Behavior
|
|
306
|
+
|
|
307
|
+
- On list pages, AdminForth tries to keep the table itself scrollable when there are no large top or bottom injections.
|
|
308
|
+
- That shrink behavior is affected by four list injection spots: `beforeBreadcrumbs`, `afterBreadcrumbs`, `beforeActionButtons`, and `bottom`.
|
|
309
|
+
- If none of those four spots are used, the table tries to shrink into the viewport and vertical scrolling happens inside the table area.
|
|
310
|
+
- If any of those spots are used, AdminForth assumes the page may now need extra vertical space, so the table stops shrinking and page scrolling moves to the document body instead.
|
|
311
|
+
- If your injected panel is actually compact, set `meta.thinEnoughToShrinkTable: true` on that injection.
|
|
312
|
+
- Important: every injection in those four spots must set `thinEnoughToShrinkTable: true`, otherwise the table will still stay in non-shrinking mode.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
options: {
|
|
316
|
+
pageInjections: {
|
|
317
|
+
list: {
|
|
318
|
+
bottom: {
|
|
319
|
+
file: '@@/reports/OrdersSummary.vue',
|
|
320
|
+
meta: {
|
|
321
|
+
thinEnoughToShrinkTable: true,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Row-Level List Injection Notes
|
|
330
|
+
|
|
331
|
+
- `tableBodyStart` renders extra `<tr>` rows near the start of the table body and receives `resource`, `adminUser`, and `meta`.
|
|
332
|
+
- `tableRowReplace` replaces the default `<tr>` for each record and receives `record`, `resource`, `adminUser`, and `meta`.
|
|
333
|
+
- `customActionIcons` and `customActionIconsThreeDotsMenuItems` are per-record spots and receive `record`, `resource`, `adminUser`, `meta`, and `updateRecords`.
|
|
334
|
+
|
|
335
|
+
## Practical Rules
|
|
336
|
+
|
|
337
|
+
- Prefer simple string declarations until you actually need `meta`.
|
|
338
|
+
- Reuse one component with multiple full declarations instead of cloning similar files.
|
|
339
|
+
- Keep page injections small unless the layout intentionally becomes page-scrolling.
|
|
340
|
+
- Keep custom edit and create components explicit about validity and emptiness if the default input heuristics are not enough.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adminforth-hooks
|
|
3
|
+
description: "Use when implementing or changing AdminForth hooks: beforeDatasourceRequest, afterDatasourceResponse, beforeSave, afterSave, and source-aware show or edit flows."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AdminForth Hooks Workflow
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
- Editing `resource.hooks` in `resources/*.ts`.
|
|
12
|
+
- Deciding which hook stage should own filtering, normalization, enrichment, validation, or side effects.
|
|
13
|
+
- Handling the difference between show-page requests and edit-page initial load.
|
|
14
|
+
|
|
15
|
+
## Hook Rules
|
|
16
|
+
|
|
17
|
+
- Hooks execute on the backend during AdminForth request handling.
|
|
18
|
+
- Keep hooks fast because every awaited operation delays the current request.
|
|
19
|
+
- Return `{ ok: true }` to continue or `{ ok: false, error: 'message' }` to stop the flow.
|
|
20
|
+
- A hook can be a single async function or an array of async functions.
|
|
21
|
+
|
|
22
|
+
## Available Hooks
|
|
23
|
+
|
|
24
|
+
- `show.beforeDatasourceRequest`
|
|
25
|
+
- `show.afterDatasourceResponse`
|
|
26
|
+
- `list.beforeDatasourceRequest`
|
|
27
|
+
- `list.afterDatasourceResponse`
|
|
28
|
+
- `create.beforeSave`
|
|
29
|
+
- `create.afterSave`
|
|
30
|
+
- `edit.beforeSave`
|
|
31
|
+
- `edit.afterSave`
|
|
32
|
+
- `delete.beforeSave`
|
|
33
|
+
- `delete.afterSave`
|
|
34
|
+
|
|
35
|
+
## Stage Selection
|
|
36
|
+
|
|
37
|
+
- Use `beforeDatasourceRequest` for read-time scoping, filter changes, or blocking access before the datasource runs.
|
|
38
|
+
- Use `afterDatasourceResponse` for reshaping or enriching records after the datasource returns them.
|
|
39
|
+
- Use `beforeSave` for checks or data mutations that must happen before create, edit, or delete reaches the database.
|
|
40
|
+
- Use `afterSave` only for post-write side effects such as logging, notifications, or sync work. Errors there do not roll back data that already reached the database.
|
|
41
|
+
|
|
42
|
+
## Show and Edit Behavior
|
|
43
|
+
|
|
44
|
+
- `show` hooks are also used when AdminForth loads initial data for the edit page.
|
|
45
|
+
- Inside `show` hooks, inspect `query.source` on the hook params, not `extra.query`.
|
|
46
|
+
- `query.source === 'show'` means the real show page.
|
|
47
|
+
- `query.source === 'edit'` means the edit form initial-load flow.
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
hooks: {
|
|
53
|
+
show: {
|
|
54
|
+
afterDatasourceResponse: async ({ query, response }) => {
|
|
55
|
+
if (query.source === 'edit') {
|
|
56
|
+
// runs only when AdminForth loads initial values for the edit page
|
|
57
|
+
} else if (query.source === 'show') {
|
|
58
|
+
// runs only on the real show page
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { ok: true };
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Choosing Hooks vs Permissions
|
|
68
|
+
|
|
69
|
+
- Prefer `adminforth-permissions` for straightforward role-based access control.
|
|
70
|
+
- Use hooks when the rule depends on specific record values, incoming filters, mutations, or request lifecycle timing.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adminforth-permissions
|
|
3
|
+
description: "Use when implementing or changing AdminForth access control: allowedActions, showIn callbacks, menu visibility, per-record restrictions, and permission-aware resource behavior."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AdminForth Permissions Workflow
|
|
8
|
+
|
|
9
|
+
## When to Use
|
|
10
|
+
|
|
11
|
+
- Editing `options.allowedActions`, column `showIn.*` callbacks, menu visibility, or other permission-sensitive resource behavior.
|
|
12
|
+
- Deciding whether a rule belongs in `allowedActions`, `showIn[x]`, or a hook that blocks access for specific records.
|
|
13
|
+
- Explaining why hiding a menu item is not the same as real authorization.
|
|
14
|
+
|
|
15
|
+
## Permission Model
|
|
16
|
+
|
|
17
|
+
- `allowedActions` is the real per-resource permission layer for `list`, `show`, `create`, `edit`, `delete`, and `filter`.
|
|
18
|
+
- `showIn[x]` is the real per-column and per-page-flow control for whether a field is accessible in a given UI flow.
|
|
19
|
+
- Menu visibility is UX only. Hiding a resource from the menu does not block direct URL access or API access.
|
|
20
|
+
- For per-record restrictions, use hooks that run before datasource access or before save and return `{ ok: false, error: 'message' }`.
|
|
21
|
+
|
|
22
|
+
## Allowed Actions Rules
|
|
23
|
+
|
|
24
|
+
- `allowedActions` can be static booleans or async functions.
|
|
25
|
+
- The callback receives `adminUser`, `resource`, `meta`, `source`, and `adminforth`.
|
|
26
|
+
- `source` tells you why the permission is being checked. Available values are `displayButtons`, `listRequest`, `showRequest`, `editLoadRequest`, `editRequest`, `createRequest`, `deleteRequest`, `bulkActionRequest`, and `customActionRequest`.
|
|
27
|
+
- `allowedActions` is evaluated for both UI rendering and real requests, so keep it fast and avoid unnecessary database work.
|
|
28
|
+
- Use `source === ActionCheckSource.DisplayButtons` only when you intentionally want different UI visibility from actual request authorization.
|
|
29
|
+
|
|
30
|
+
## Example
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { ActionCheckSource } from 'adminforth';
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
resourceId: 'orders',
|
|
37
|
+
options: {
|
|
38
|
+
allowedActions: {
|
|
39
|
+
list: ({ adminUser }) => adminUser.dbUser.role !== 'suspended',
|
|
40
|
+
delete: ({ adminUser, source }) => {
|
|
41
|
+
// `source` tells you why AdminForth is checking permission.
|
|
42
|
+
// This keeps the delete button visible, but blocks the real delete
|
|
43
|
+
// unless the request is made by a superadmin.
|
|
44
|
+
if (source === ActionCheckSource.DisplayButtons) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return adminUser.dbUser.role === 'superadmin';
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Choosing the Right Layer
|
|
56
|
+
|
|
57
|
+
- Use `allowedActions` for coarse resource-level permissions such as role-based create, edit, delete, or list access.
|
|
58
|
+
- Use `showIn[x]` when a field should be visible or editable only in specific flows or for specific admins.
|
|
59
|
+
- Use hooks when access depends on actual record values, request filters, or lifecycle context.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# AdminForth App Guide
|
|
2
|
+
|
|
3
|
+
## Package manager
|
|
4
|
+
|
|
5
|
+
This scaffold was generated with `{{packageManager}}`. Use `{{packageManager}}` commands in this project unless you intentionally migrate the tooling.
|
|
6
|
+
|
|
7
|
+
## Project map
|
|
8
|
+
|
|
9
|
+
- `index.ts` is the main AdminForth configuration and server entrypoint.
|
|
10
|
+
- `api.ts` is for custom Express APIs and request/response schemas.
|
|
11
|
+
- `resources/*.ts` defines resources, columns, labels, permissions, hooks, and display behavior.
|
|
12
|
+
- `custom/` is for custom Vue components, pages, injections, and static assets.
|
|
13
|
+
{{#if prismaDbUrl}}- `schema.prisma` is the schema and migration source if you use Prisma for this app.{{/if}}
|
|
14
|
+
- `.env.local` stores local non-sensitive defaults. `.env` stores secrets. In production prefer real environment variables or a secret manager.
|
|
15
|
+
|
|
16
|
+
## Docs sources for agents
|
|
17
|
+
|
|
18
|
+
- Use `https://adminforth.dev/llms.txt` for quick navigation across the AdminForth docs corpus.
|
|
19
|
+
- Use `https://adminforth.dev/llms-full.txt` (`llms full`) for the full single-file AdminForth reference.
|
|
20
|
+
- Prefer these sources before broad web search when the task is mainly about AdminForth APIs, plugins, adapters, or customization.
|
|
21
|
+
|
|
22
|
+
## Common commands
|
|
23
|
+
|
|
24
|
+
- Install dependencies: `{{packageManager}} install`
|
|
25
|
+
- Start the app locally: `{{packageManagerRun}} dev`
|
|
26
|
+
{{#if prismaDbUrl}}- Apply local migrations: `{{packageManagerRun}} migrate:local`
|
|
27
|
+
- Create a new migration and apply it locally: `{{packageManagerRun}} makemigration{{packageManagerScriptArgSeparator}}--name <change-name>`{{/if}}
|
|
28
|
+
|
|
29
|
+
## AdminForth workflow
|
|
30
|
+
|
|
31
|
+
- AdminForth connects to existing database and gives a back-office over the data (CRUD, filtering, sorting, etc).
|
|
32
|
+
- One table or collection maps to one resource.
|
|
33
|
+
- Resource config database type agnostic (same for all db types).
|
|
34
|
+
- Add new resources in `resources/`, register them in `index.ts`, and add menu entries if you want them visible in the sidebar.
|
|
35
|
+
- Resource config should mandatory list all column names which AdminFramework should be aware of, column name should match the name in the database table/collection.
|
|
36
|
+
- For relational schema-based dbs types of columns in resource are optional and used for overriding where possible, however AdminForth by default fetches physycal types and manual specification is not required.
|
|
37
|
+
- For NoSQL or schemaless dbs types of columns in resource are required for AdminForth to work.
|
|
38
|
+
- AdminForth does not manage database schema. By default project ships Prisma but developer can use own migration tool, and update only through `resources/*.ts` and `index.ts`.
|
|
39
|
+
- Always create `recordLabel` to change on UI how one record is represented across the app. Try to include the most identifying information but human-readable in the label but keep it short for readability (avoid IDs if possible).
|
|
40
|
+
- Use `fillOnCreate` to fill with default values on record creation on backend. Avoid showIn.create=true + fillOnCreate for the same field.
|
|
41
|
+
- Use `suggestOnCreate` to prefill input with a suggested value on the frontend.
|
|
42
|
+
- Use `enum`, `minLength`, `maxLength` in column config to enforce validation rules.
|
|
43
|
+
|
|
44
|
+
- Use AdminForth Data API for simple CRUD, filtering, sorting, and counts. For joins, aggregations, or heavier queries, use custom ORM or query builder.
|
|
45
|
+
- Use `api.ts` for custom business endpoints that do not fit standard resource CRUD.
|
|
46
|
+
- Use `custom/` for custom pages, field rendering, injections, and other Vue-side customization.
|
|
47
|
+
|
|
48
|
+
### Access control
|
|
49
|
+
|
|
50
|
+
- Keep access control explicit and backend-enforced. Menu visibility is only UX and is not a permission boundary.
|
|
51
|
+
- For permission-specific implementation details, examples, and routing between `allowedActions`, `showIn[x]`, and hooks, use the `adminforth-permissions` skill.
|
|
52
|
+
|
|
53
|
+
### Hooks
|
|
54
|
+
|
|
55
|
+
- Hooks run on the backend as part of AdminForth request flows, so keep them focused and fast.
|
|
56
|
+
- For hook stage selection, show-vs-edit behavior, and implementation examples, use the `adminforth-hooks` skill.
|
|
57
|
+
|
|
58
|
+
## Engineering conventions
|
|
59
|
+
|
|
60
|
+
- Trust typed internal contracts. Do not add duplicate validation for values already guaranteed by schema, types, or backend responses.
|
|
61
|
+
- Validate once at the boundary, not repeatedly in downstream consumers.
|
|
62
|
+
- Prefer small explicit helpers and named constants over speculative fallback logic.
|
|
63
|
+
- Extract reusable or non-trivial regexes into named constants instead of scattering inline regexes through business logic.
|
|
64
|
+
- Keep changes minimal, DRY, and YAGNI.
|
|
65
|
+
|
|
66
|
+
## Before you change the app
|
|
67
|
+
|
|
68
|
+
- If you add a new resource, register it in `index.ts` and add it to the menu if needed.
|
|
69
|
+
{{#if prismaDbUrl}}- If you change `schema.prisma`, create and apply a migration locally before testing the UI.{{/if}}
|
|
70
|
+
- If you add custom frontend code under `custom/`, install any extra frontend packages in `custom/package.json`.
|
|
71
|
+
- Default local login is `adminforth` / `adminforth` unless you changed the seeded admin user.
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
{{
|
|
3
|
-
FROM devforth/node20-pnpm:latest
|
|
2
|
+
FROM {{dockerBaseImage}}
|
|
4
3
|
WORKDIR /code/
|
|
5
|
-
ADD package.json
|
|
6
|
-
RUN
|
|
4
|
+
ADD package.json {{dockerAdditionalManifestFiles}} /code/
|
|
5
|
+
RUN {{packageManager}} {{dockerPackageInstallSubcommand}}
|
|
7
6
|
ADD . /code/
|
|
8
|
-
RUN
|
|
9
|
-
CMD ["sh", "-c", "
|
|
10
|
-
{{/unless}}
|
|
11
|
-
{{#if useNpm}}
|
|
12
|
-
FROM node:{{nodeMajor}}-slim
|
|
13
|
-
WORKDIR /code/
|
|
14
|
-
ADD package.json package-lock.json /code/
|
|
15
|
-
RUN npm ci
|
|
16
|
-
ADD . /code/
|
|
17
|
-
RUN npx adminforth bundle
|
|
18
|
-
CMD ["sh", "-c", "npm run migrate:prod && npm run prod"]
|
|
19
|
-
{{/if}}
|
|
7
|
+
RUN {{packageManagerExec}} adminforth bundle
|
|
8
|
+
CMD ["sh", "-c", "{{packageManagerRun}} migrate:prod && {{packageManagerRun}} prod"]
|
|
@@ -8,26 +8,14 @@
|
|
|
8
8
|
"license": "ISC",
|
|
9
9
|
"description": "",
|
|
10
10
|
"scripts": {
|
|
11
|
-
{{
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"migrate:
|
|
17
|
-
"migrate:prod": "pnpm _env:prod npx --yes prisma migrate deploy",
|
|
11
|
+
"dev": "{{packageManagerEnvDev}} tsx watch index.ts",
|
|
12
|
+
"prod": "{{packageManagerEnvProd}} tsx index.ts",
|
|
13
|
+
"start": "{{packageManagerRun}} dev",
|
|
14
|
+
"makemigration": "{{packageManagerEnvDev}} npx --yes prisma migrate dev --create-only",
|
|
15
|
+
"migrate:local": "{{packageManagerEnvDev}} npx --yes prisma migrate deploy",
|
|
16
|
+
"migrate:prod": "{{packageManagerEnvProd}} npx --yes prisma migrate deploy",
|
|
18
17
|
"_env:dev": "dotenvx run -f .env -f .env.local --",
|
|
19
18
|
"_env:prod": "dotenvx run -f .env.prod --"
|
|
20
|
-
{{/unless}}
|
|
21
|
-
{{#if useNpm}}
|
|
22
|
-
"dev": "npm run _env:dev -- tsx watch index.ts",
|
|
23
|
-
"prod": "npm run _env:prod -- tsx index.ts",
|
|
24
|
-
"start": "npm run dev",
|
|
25
|
-
"makemigration": "npm run _env:dev -- npx --yes prisma migrate dev --create-only",
|
|
26
|
-
"migrate:local": "npm run _env:dev -- npx --yes prisma migrate deploy",
|
|
27
|
-
"migrate:prod": "npm run _env:prod -- npx --yes prisma migrate deploy",
|
|
28
|
-
"_env:dev": "dotenvx run -f .env -f .env.local --",
|
|
29
|
-
"_env:prod": "dotenvx run -f .env.prod --"
|
|
30
|
-
{{/if}}
|
|
31
19
|
},
|
|
32
20
|
"engines": {
|
|
33
21
|
"node": ">=20"
|