Skip to content

Commit d9f7cf4

Browse files
committed
Merge branch 'new-dev-demo' of https://github.com/devforth/adminforth into new-dev-demo
2 parents 504a898 + 61a4ce0 commit d9f7cf4

File tree

10 files changed

+83
-38
lines changed

10 files changed

+83
-38
lines changed

adminforth/documentation/docs/tutorial/03-Customization/04-hooks.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,21 +189,29 @@ Let's limit it:
189189
```ts title='./resources/apartments.ts'
190190
{
191191
...
192-
hooks: {
193-
dropdownList: {
194-
beforeDatasourceRequest: async ({ adminUser, query }: { adminUser: AdminUser, query: any }) => {
195-
if (adminUser.dbUser.role !== "superadmin") {
196-
query.filtersTools.replaceOrAddTopFilter(Filters.EQ("id", adminUser.dbUser.id));
197-
};
198-
return {
199-
"ok": true,
200-
};
201-
}
192+
193+
foreignResource: {
194+
195+
...
196+
197+
hooks: {
198+
dropdownList: {
199+
beforeDatasourceRequest: async ({ adminUser, query }: { adminUser: AdminUser, query: any }) => {
200+
if (adminUser.dbUser.role !== "superadmin") {
201+
query.filtersTools.replaceOrAddTopFilter(Filters.EQ("id", adminUser.dbUser.id));
202+
};
203+
return {
204+
"ok": true,
205+
};
206+
}
207+
},
202208
},
203-
},
209+
}
204210
}
205211
```
206212
213+
> ☝️☝️☝️ This hooks should be written only inside column. If you'll add it in resource hooks - it won't work
214+
207215
In our case we limit the dropdown list to show only the current user, however you can use same sample to list only objects who are related to the current user in case if you will have relation configurations which require to show related objects which belongs to the current user.
208216
209217
Flow diagram for dropdown list:

adminforth/documentation/docs/tutorial/07-Plugins/05-upload.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ To do this add avatar column to the user resource:
524524
role String
525525
created_at DateTime
526526
//diff-add
527-
avatar ?String
527+
avatar String?
528528
}
529529
```
530530
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Working without direct database connection
2+
3+
Out of the box, AdminForth connects directly to your database using one of the supported drivers (PostgreSQL, MySQL, ClickHouse, MongoDB) and executes queries against it.
4+
5+
In some cases, you may not want to expose a direct database connection to AdminForth. Instead, you may prefer to allow AdminForth to access and modify data through your own APIs (for example, REST, GraphQL, or JSON-RPC).
6+
7+
With this approach, AdminForth never connects to the database and never even knows its URL. All read and write operations go through your API layer.
8+
9+
Why do this?
10+
11+
- Your API may enforce additional constraints or validation rules.
12+
13+
- You can precisely log all operations using your own logging or audit systems. (The built-in AuditLog tracks data modifications only and does not log read operations.)
14+
15+
- Your API may contain custom logic, such as distributed workflows or complex data-modification rules.
16+
17+
To implement this, you need to extend the data connector class and implement a small set of methods responsible for data access and mutations.
18+
19+
This example demonstrates how to do this using GraphQL, but the same approach can be adapted to REST or any other protocol. The code comments include detailed guidance for these cases.
20+
21+
Another reason to create a custom data source adapter is to support a database that AdminForth does not yet support. In that case, you are welcome to submit a pull request to AdminForth to add native support for that database.

adminforth/spa/src/components/ResourceForm.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ async function searchOptions(columnName: string, searchTerm: string) {
322322
323323
324324
const editableColumns = computed(() => {
325-
return props.resource?.columns?.filter(column => column.showIn?.[mode.value] && (currentValues.value ? checkShowIf(column, currentValues.value) : true));
325+
return props.resource?.columns?.filter(column => column.showIn?.[mode.value] && (currentValues.value ? checkShowIf(column, currentValues.value, props.resource.columns) : true));
326326
});
327327
328328
const isValid = computed(() => {

adminforth/spa/src/components/ShowTable.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424
dark:bg-darkShowTablesBodyBackground dark:border-darkShowTableBodyBorder block md:table-row"
2525
>
2626
<component
27-
v-if="column.components?.showRow && checkShowIf(column, record)"
27+
v-if="column.components?.showRow && checkShowIf(column, record, resource?.columns || [])"
2828
:is="getCustomComponent(column.components.showRow)"
2929
:meta="column.components.showRow.meta"
3030
:column="column"
3131
:resource="coreStore.resource"
3232
:record="coreStore.record"
3333
/>
34-
<template v-else-if="checkShowIf(column, record)">
34+
<template v-else-if="checkShowIf(column, record, resource?.columns || [])">
3535
<td class="px-6 py-4 relative block md:table-cell font-bold md:font-normal pb-0 md:pb-4">
3636
{{ column.label }}
3737
</td>

adminforth/spa/src/renderers/RichText.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,19 @@ const htmlContent = protectAgainstXSS(props.record[props.column.name])
2222
/* You can add default styles here if needed */
2323
word-break: break-word;
2424
}
25+
.rich-text :deep(table) {
26+
border-collapse: collapse;
27+
border: 1px solid #ddd;
28+
}
29+
30+
.rich-text :deep(table th),
31+
.rich-text :deep(table td) {
32+
border: 1px solid #ddd;
33+
padding: 8px;
34+
}
35+
36+
.rich-text :deep(table th) {
37+
background-color: #f5f5f5;
38+
font-weight: 600;
39+
}
2540
</style>

adminforth/spa/src/stores/filters.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const useFiltersStore = defineStore('filters', () => {
1515
}
1616
const setFilter = (filter: any) => {
1717
const index = filters.value.findIndex(f => f.field === filter.field);
18-
if (filters.value[index]) {
18+
if (filters.value[index] && filters.value[index].operator === filter.value.operator) {
1919
filters.value[index] = filter;
2020
return;
2121
}

adminforth/spa/src/utils.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export async function callApi({path, method, body, headers, silentError = false}
5151
} else {
5252
await router.push({ name: 'login' });
5353
}
54-
return null;alert
54+
return null;
5555
}
5656
return await r.json();
5757
} catch(e) {
@@ -446,9 +446,14 @@ export function createSearchInputHandlers(
446446
}, {} as Record<string, (searchTerm: string) => void>);
447447
}
448448

449-
export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any>) {
449+
export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Record<string, any>, allColumns: AdminForthResourceColumnInputCommon[]) {
450450
if (!c.showIf) return true;
451-
451+
const recordCopy = { ...record };
452+
for (const col of allColumns) {
453+
if (!recordCopy[col.name]) {
454+
recordCopy[col.name] = null;
455+
}
456+
}
452457
const evaluatePredicate = (predicate: Predicate): boolean => {
453458
const results: boolean[] = [];
454459

@@ -463,7 +468,7 @@ export function checkShowIf(c: AdminForthResourceColumnInputCommon, record: Reco
463468
const fieldEntries = Object.entries(predicate).filter(([key]) => !key.startsWith('$'));
464469
if (fieldEntries.length > 0) {
465470
const fieldResult = fieldEntries.every(([field, condition]) => {
466-
const recordValue = record[field];
471+
const recordValue = recordCopy[field];
467472

468473
if (condition === undefined) {
469474
return true;

adminforth/spa/src/views/CreateView.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ async function saveRecord(opts?: { confirmationResult?: any }) {
192192
validating.value = false;
193193
}
194194
const requiredColumns = coreStore.resource?.columns.filter(c => c.required?.create === true) || [];
195-
const requiredColumnsToSkip = requiredColumns.filter(c => checkShowIf(c, record.value) === false);
195+
const requiredColumnsToSkip = requiredColumns.filter(c => checkShowIf(c, record.value, coreStore.resource?.columns || []) === false);
196196
saving.value = true;
197197
const response = await callAdminForthApi({
198198
method: 'POST',

live-demo/app/index.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,16 @@ if (import.meta.url === `file://${process.argv[1]}`) {
148148
const db = admin.resource('aparts').dataConnector.client;
149149
const days = req.body.days || 7;
150150
const apartsByDays = await db.prepare(
151-
`SELECT
151+
`SELECT
152152
strftime('%Y-%m-%d', created_at) as day,
153153
COUNT(*) as count
154154
FROM apartments
155155
GROUP BY day
156-
ORDER BY day DESC
157-
LIMIT ?;
158-
`
156+
ORDER BY day ASC
157+
LIMIT ?;`
159158
).all(days);
160159

160+
161161
const totalAparts = apartsByDays.reduce((acc: number, { count }: { count:number }) => acc + count, 0);
162162

163163
// add listed, unlisted, listedPrice, unlistedPrice
@@ -170,19 +170,18 @@ if (import.meta.url === `file://${process.argv[1]}`) {
170170
SUM((1 - listed) * price) as unlistedPrice
171171
FROM apartments
172172
GROUP BY day
173-
ORDER BY day DESC
174-
LIMIT ?;
175-
`
173+
ORDER BY day ASC
174+
LIMIT ?;`
176175
).all(days);
177176

177+
178178
const apartsCountsByRooms = await db.prepare(
179179
`SELECT
180180
number_of_rooms,
181181
COUNT(*) as count
182182
FROM apartments
183183
GROUP BY number_of_rooms
184-
ORDER BY number_of_rooms;
185-
`
184+
ORDER BY number_of_rooms;`
186185
).all();
187186

188187
const topCountries = await db.prepare(
@@ -192,27 +191,24 @@ if (import.meta.url === `file://${process.argv[1]}`) {
192191
FROM apartments
193192
GROUP BY country
194193
ORDER BY count DESC
195-
LIMIT 4;
196-
`
194+
LIMIT 4;`
197195
).all();
198196

199197
const totalSquare = await db.prepare(
200198
`SELECT
201199
SUM(square_meter) as totalSquare
202-
FROM apartments;
203-
`
200+
FROM apartments;`
204201
).get();
205202

206203
const listedVsUnlistedPriceByDays = await db.prepare(
207-
`SELECT
204+
`SELECT
208205
strftime('%Y-%m-%d', created_at) as day,
209206
SUM(listed * price) as listedPrice,
210207
SUM((1 - listed) * price) as unlistedPrice
211208
FROM apartments
212209
GROUP BY day
213-
ORDER BY day DESC
214-
LIMIT ?;
215-
`
210+
ORDER BY day ASC
211+
LIMIT ?;`
216212
).all(days);
217213

218214
const totalListedPrice = Math.round(listedVsUnlistedByDays.reduce((

0 commit comments

Comments
 (0)