Skip to content

Commit 66f9bdc

Browse files
committed
Merge branch 'next' of https://github.com/devforth/adminforth into fix-mongo-id-filter
2 parents 6c740e2 + 21e1ea1 commit 66f9bdc

File tree

150 files changed

+2857
-4536
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+2857
-4536
lines changed

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Follow SOLID principles
2+
Use DRY and avoid duplication
3+
Keep the code KISS unless complexity is required
4+
Avoid unnecessary abstractions.
5+
Cover edge cases and clear error handling

README.md

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,44 +61,50 @@ npx adminforth create-app
6161

6262
The most convenient way to add new features or fixes is using `dev-demo`. It imports the source code of the repository and plugins so you can edit them and see changes on the fly.
6363

64-
Fork repo, pull it and do next:
64+
To run dev demo:
65+
```sh
66+
cd dev-demo
6567

68+
npm run setup-dev-demo
69+
npm run migrate:all
6670

67-
```sh
68-
cd adminforth
69-
npm ci
70-
npm run build
71+
npm start
7172
```
7273

73-
To run dev demo:
74-
```sh
75-
cd dev-demo
76-
cp .env.sample .env
74+
## Adding columns to a database in dev-demo
7775

78-
# this will install all official plugins and link adminforth package, if plugin installed it will git pull and npm ci
79-
npm run install-plugins
76+
Open `./migrations` folder. There is prisma migration folder for the sqlite, postgres and mysql and `clickhouse_migrations` folder for the clickhouse:
8077

81-
# same for official adapters
82-
npm run install-adapters
78+
### Migrations for the MySQL, SQLite and Postgres
79+
To make migration add to the .prisma file in folder with database you need and add new tables or columns. Then run:
8380

84-
npm ci
8581

86-
./run_inventory.sh
82+
```
83+
npm run makemigration:sqlite -- --name init
84+
```
85+
86+
and
8787

88-
npm run migrate:local
89-
npm start
88+
```
89+
npm run migrate:sqlite
9090
```
9191

92-
## Adding columns to a database in dev-demo
92+
to apply migration
93+
94+
> use :sqlite, :mysql or :postgres for you case
95+
96+
### Migrations for the clickhouse
9397

94-
Open `.prisma` file, modify it, and run:
98+
In order to make migration for the clickhouse, go to the `./migrations/clickhouse_migrations` folder and add migration file to the folder.
9599

100+
Then run
96101
```
97-
npm run namemigration -- --name desctiption_of_changes
102+
npm run migrate:clickhouse
98103
```
99104

105+
to apply the migration.
100106

101-
### Testing CLI commands during development
107+
## Testing CLI commands during development
102108

103109

104110
Make sure you have not `adminforth` globally installed. If you have it, remove it:

adminforth/auth.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,8 @@ class AdminForthAuth implements IAdminForthAuth {
6565
headersLower['client-ip'] ||
6666
headersLower['client-address'] ||
6767
headersLower['client'] ||
68-
headersLower['x-host'] ||
69-
headersLower['host'] ||
70-
'unknown';
68+
headersLower['x-host'] ||
69+
null;
7170
}
7271
}
7372

adminforth/basePlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default class AdminForthPlugin implements IAdminForthPlugin {
3636
return 'non-uniquely-identified';
3737
}
3838

39-
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
39+
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource, allPluginInstances?: {pi: AdminForthPlugin, resource: AdminForthResource}[]) {
4040
this.resourceConfig = resourceConfig;
4141
const uniqueness = this.instanceUniqueRepresentation(this.pluginOptions);
4242

adminforth/dataConnectors/baseConnector.ts

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { suggestIfTypo } from "../modules/utils.js";
1010
import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections } from "../types/Common.js";
1111
import { randomUUID } from "crypto";
12+
import dayjs from "dayjs";
1213

1314

1415
export default class AdminForthBaseConnector implements IAdminForthDataSourceConnectorBase {
@@ -171,9 +172,9 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
171172
return { ok: false, error: `Value for operator '${filters.operator}' should not be empty array, in filter object: ${JSON.stringify(filters) }` };
172173
}
173174
}
174-
filters.value = filters.value.map((val: any) => this.setFieldValue(fieldObj, val));
175+
filters.value = filters.value.map((val: any) => this.validateAndSetFieldValue(fieldObj, val));
175176
} else {
176-
filtersAsSingle.value = this.setFieldValue(fieldObj, filtersAsSingle.value);
177+
filtersAsSingle.value = this.validateAndSetFieldValue(fieldObj, filtersAsSingle.value);
177178
}
178179
} else if (filtersAsSingle.insecureRawSQL || filtersAsSingle.insecureRawNoSQL) {
179180
// if "insecureRawSQL" filter is insecure sql string
@@ -226,6 +227,90 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
226227
throw new Error('Method not implemented.');
227228
}
228229

230+
validateAndSetFieldValue(field: AdminForthResourceColumn, value: any): any {
231+
// Int
232+
if (field.type === AdminForthDataTypes.INTEGER) {
233+
if (value === "" || value === null) {
234+
return this.setFieldValue(field, null);
235+
}
236+
if (!Number.isFinite(value)) {
237+
throw new Error(`Value is not an integer. Field ${field.name} with type is ${field.type}, but got value: ${value} with type ${typeof value}`);
238+
}
239+
return this.setFieldValue(field, value);
240+
}
241+
242+
// Float
243+
if (field.type === AdminForthDataTypes.FLOAT) {
244+
if (value === "" || value === null) {
245+
return this.setFieldValue(field, null);
246+
}
247+
248+
if (typeof value !== "number" || !Number.isFinite(value)) {
249+
throw new Error(
250+
`Value is not a float. Field ${field.name} with type is ${field.type}, but got value: ${String(value)} with type ${typeof value}`
251+
);
252+
}
253+
254+
return this.setFieldValue(field, value);
255+
}
256+
257+
// Decimal
258+
if (field.type === AdminForthDataTypes.DECIMAL) {
259+
if (value === "" || value === null) {
260+
return this.setFieldValue(field, null);
261+
}
262+
if (typeof value === "string") {
263+
const string = value.trim();
264+
if (!string) {
265+
return this.setFieldValue(field, null);
266+
}
267+
if (Number.isFinite(Number(string))) {
268+
return this.setFieldValue(field, string);
269+
}
270+
throw new Error(`Value is not a decimal. Field ${field.name} with type is ${field.type}, but got value: ${value} with type ${typeof value}`);
271+
}
272+
273+
throw new Error(`Value is not a decimal. Field ${field.name} with type is ${field.type}, but got value: ${String(value)} with type ${typeof value}`);
274+
}
275+
276+
// Date
277+
278+
279+
// DateTime
280+
if (field.type === AdminForthDataTypes.DATETIME) {
281+
if (value === "" || value === null) {
282+
return this.setFieldValue(field, null);
283+
}
284+
if (!dayjs(value).isValid()) {
285+
throw new Error(`Value is not a valid datetime. Field ${field.name} with type is ${field.type}, but got value: ${value} with type ${typeof value}`);
286+
}
287+
return this.setFieldValue(field, value);
288+
}
289+
290+
// Time
291+
292+
// Boolean
293+
if (field.type === AdminForthDataTypes.BOOLEAN) {
294+
if (value === "" || value === null) {
295+
return this.setFieldValue(field, null);
296+
}
297+
if (typeof value !== 'boolean') {
298+
throw new Error(`Value is not a boolean. Field ${field.name} with type is ${field.type}, but got value: ${value} with type ${typeof value}`);
299+
}
300+
return this.setFieldValue(field, value);
301+
}
302+
303+
// JSON
304+
305+
// String
306+
if (field.type === AdminForthDataTypes.STRING) {
307+
if (value === "" || value === null){
308+
return this.setFieldValue(field, null);
309+
}
310+
}
311+
return this.setFieldValue(field, value);
312+
}
313+
229314
getMinMaxForColumnsWithOriginalTypes({ resource, columns }: { resource: AdminForthResource; columns: AdminForthResourceColumn[]; }): Promise<{ [key: string]: { min: any; max: any; }; }> {
230315
throw new Error('Method not implemented.');
231316
}
@@ -275,7 +360,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
275360
}
276361
if (filledRecord[col.name] !== undefined) {
277362
// no sense to set value if it is not defined
278-
recordWithOriginalValues[col.name] = this.setFieldValue(col, filledRecord[col.name]);
363+
recordWithOriginalValues[col.name] = this.validateAndSetFieldValue(col, filledRecord[col.name]);
279364
}
280365
}
281366

@@ -332,7 +417,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
332417
Update record received field '${field}' (with value ${newValues[field]}), but such column not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}
333418
`);
334419
}
335-
recordWithOriginalValues[col.name] = this.setFieldValue(col, newValues[col.name]);
420+
recordWithOriginalValues[col.name] = this.validateAndSetFieldValue(col, newValues[col.name]);
336421
}
337422
const record = await this.getRecordByPrimaryKey(resource, recordId);
338423
let error: string | null = null;

0 commit comments

Comments
 (0)