Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 15 additions & 18 deletions adminforth/dataConnectors/baseConnector.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
AdminForthResource, IAdminForthDataSourceConnectorBase,
AdminForthResourceColumn,
IAdminForthSort, IAdminForthSingleFilter, IAdminForthAndOrFilter
IAdminForthSort, IAdminForthSingleFilter, IAdminForthAndOrFilter,
Filters
} from "../types/Back.js";


Expand Down Expand Up @@ -39,30 +40,30 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
limit: 1,
offset: 0,
sort: [],
filters: { operator: AdminForthFilterOperators.AND, subFilters: [{ field: this.getPrimaryKey(resource), operator: AdminForthFilterOperators.EQ, value: id }]},
filters: Filters.AND(Filters.EQ(this.getPrimaryKey(resource), id))
});
return data.length > 0 ? data[0] : null;
}

validateAndNormalizeInputFilters(filter: IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter> | undefined): IAdminForthAndOrFilter {
if (!filter) {
// if no filter, return empty "and" filter
return { operator: AdminForthFilterOperators.AND, subFilters: [] };
return Filters.AND();
}
if (typeof filter !== 'object') {
throw new Error(`Filter should be an array or an object`);
}
if (Array.isArray(filter)) {
// if filter is an array, combine them using "and" operator
return { operator: AdminForthFilterOperators.AND, subFilters: filter };
return Filters.AND(...filter);
}
if ((filter as IAdminForthAndOrFilter).subFilters) {
// if filter is already AndOr filter - return as is
return filter as IAdminForthAndOrFilter;
}

// by default, assume filter is Single filter, turn it into AndOr filter
return { operator: AdminForthFilterOperators.AND, subFilters: [filter] };
return Filters.AND(filter);
}

validateAndNormalizeFilters(filters: IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>, resource: AdminForthResource): { ok: boolean, error: string } {
Expand All @@ -82,12 +83,11 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
const column = resource.dataSourceColumns.find((col) => col.name == (f as IAdminForthSingleFilter).field);
// console.log(`\n~~~ column: ${JSON.stringify(column, null, 2)}\n~~~ resource.columns: ${JSON.stringify(resource.dataSourceColumns, null, 2)}\n~~~ filter: ${JSON.stringify(f, null, 2)}\n`);
if (column.isArray?.enabled && (column.enum || column.foreignResource)) {
filters[fIndex] = {
operator: AdminForthFilterOperators.OR,
subFilters: f.value.map((v: any) => {
return { field: column.name, operator: AdminForthFilterOperators.LIKE, value: v };
}),
};
filters[fIndex] = Filters.OR(
...f.value.map((v: any) => {
return Filters.LIKE(column.name, v);
})
);
}
}

Expand Down Expand Up @@ -318,13 +318,10 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
const primaryKeyField = this.getPrimaryKey(resource);
const existingRecord = await this.getData({
resource,
filters: {
operator: AdminForthFilterOperators.AND,
subFilters: [
{ field: column.name, operator: AdminForthFilterOperators.EQ, value },
...(record ? [{ field: primaryKeyField, operator: AdminForthFilterOperators.NE as AdminForthFilterOperators.NE, value: record[primaryKeyField] }] : [])
]
},
filters: Filters.AND(
Filters.EQ(column.name, value),
...(record ? [Filters.NEQ(primaryKeyField, record[primaryKeyField])] : [])
),
limit: 1,
sort: [],
offset: 0,
Expand Down
159 changes: 91 additions & 68 deletions adminforth/dataConnectors/mongo.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
import dayjs from 'dayjs';
import { MongoClient } from 'mongodb';
import { Decimal128, Double } from 'bson';
import { IAdminForthDataSourceConnector, IAdminForthSingleFilter, IAdminForthAndOrFilter, AdminForthResource } from '../types/Back.js';
import { MongoClient, BSON, ObjectId, Decimal128, Double, UUID } from 'mongodb';
import { IAdminForthDataSourceConnector, IAdminForthSingleFilter, IAdminForthAndOrFilter, AdminForthResource, Filters } from '../types/Back.js';
import AdminForthBaseConnector from './baseConnector.js';

import { AdminForthDataTypes, AdminForthFilterOperators, AdminForthSortDirections, } from '../types/Common.js';

function idToString(v: any) {
if (v == null) return null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

== is very implicit, better use !v maybe or

=== null || v === undefined return v

if (typeof v === "string" || typeof v === "number" || typeof v === "bigint") return String(v);

const s = BSON.EJSON.serialize(v);
if (s && typeof s === "object") {
if ("$oid" in s) {
return String(s.$oid);
}
if ("$uuid" in s) {
return String(s.$uuid);
}
return String(v);
}
Comment thread
NoOne7135 marked this conversation as resolved.
}

const extractSimplePkEq = (filters: IAdminForthAndOrFilter, pk: string): string | null => {
if (!filters?.subFilters?.length) return null;
if (filters.operator !== AdminForthFilterOperators.AND) return null;
if (filters.subFilters.length !== 1) return null;

const f: any = filters.subFilters[0];
if (!f?.field) return null;
if (f.field !== pk) return null;
if (f.operator !== AdminForthFilterOperators.EQ) return null;
if (typeof f.value !== "string") return null;

return f.value;
}

const escapeRegex = (value) => {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escapes special characters
};
Expand Down Expand Up @@ -183,72 +211,45 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
}, {});
}

getPrimaryKey(resource) {
for (const col of resource.dataSourceColumns) {
if (col.primaryKey) {
return col.name;
}
}
}

getFieldValue(field, value) {
if (field.type == AdminForthDataTypes.DATETIME) {
if (!value) {
return null;
}
return dayjs(Date.parse(value)).toISOString();

} else if (field.type == AdminForthDataTypes.DATE) {
if (!value) {
return null;
}
return dayjs(Date.parse(value)).toISOString().split('T')[0];

} else if (field.type == AdminForthDataTypes.BOOLEAN) {
return value === null ? null : !!value;
} else if (field.type == AdminForthDataTypes.DECIMAL) {
if (value === null || value === undefined) {
return null;
}
return value?.toString();
if (field.type === AdminForthDataTypes.DATETIME) {
return value ? dayjs(Date.parse(value)).toISOString() : null;
}
if (field.type === AdminForthDataTypes.DATE) {
return value ? dayjs(Date.parse(value)).toISOString().split("T")[0] : null;
}
if (field.type === AdminForthDataTypes.BOOLEAN) {
return value === null ? null : !!value;
}
if (field.type === AdminForthDataTypes.DECIMAL) {
return value === null || value === undefined ? null : value.toString();
}
if (field.name === '_id') {
return idToString(value);
}

return value;
}


setFieldValue(field, value) {
if (value === undefined) return undefined;
if (value === null) return null;

if (value === undefined) {
return undefined;
}
if (value === null || value === '') {
return null;
}
if (field.type === AdminForthDataTypes.DATETIME) {
if (value === "" || value === null) {
return null;
}
return dayjs(value).isValid() ? dayjs(value).toDate() : null;
}

if (field.type === AdminForthDataTypes.INTEGER) {
if (value === "" || value === null) {
return null;
}
return Number.isFinite(value) ? Math.trunc(value) : null;
}

if (field.type === AdminForthDataTypes.FLOAT) {
if (value === "" || value === null) {
return null;
}
return Number.isFinite(value) ? value : null;
}

if (field.type === AdminForthDataTypes.DECIMAL) {
if (value === "" || value === null) {
return null;
}
return value.toString();
}

return value;
}

Expand Down Expand Up @@ -299,34 +300,53 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
.map((f) => this.getFilterQuery(resource, f)));
}

async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }:
{
resource: AdminForthResource,
limit: number,
offset: number,
sort: { field: string, direction: AdminForthSortDirections }[],
async getDataWithOriginalTypes({ resource, limit, offset, sort, filters }:
{
resource: AdminForthResource,
limit: number,
offset: number,
sort: { field: string, direction: AdminForthSortDirections }[],
filters: IAdminForthAndOrFilter,
}
): Promise<any[]> {

// const columns = resource.dataSourceColumns.filter(c=> !c.virtual).map((col) => col.name).join(', ');
const tableName = resource.table;
const collection = this.client.db().collection(tableName);


const collection = this.client.db().collection(tableName);
const pk = this.getPrimaryKey(resource);
const pkValue = extractSimplePkEq(filters, pk);

if (pkValue !== null) {
let res = await collection.find({ [pk]: pkValue }).limit(1).toArray();
if (res.length) {
return res;
}
try {
res = await collection.find({ [pk]: new UUID(pkValue) }).limit(1).toArray();
if (res.length) {
return res;
}
} catch {}
try {
res = await collection.find({ [pk]: new ObjectId(pkValue) }).limit(1).toArray();
if (res.length) {
return res;
}
} catch {}
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
return [];
}

const query = filters.subFilters.length ? this.getFilterQuery(resource, filters) : {};

const sortArray: any[] = sort.map((s) => {
return [s.field, this.SortDirectionsMap[s.direction]];
});
const sortArray: any[] = sort.map((s) => [s.field, this.SortDirectionsMap[s.direction]]);

const result = await collection.find(query)
return await collection.find(query)
.sort(sortArray)
.skip(offset)
.limit(limit)
.toArray();

return result
}

async getCount({ resource, filters }: {
Expand Down Expand Up @@ -379,14 +399,17 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
}

async updateRecordOriginalValues({ resource, recordId, newValues }) {
const collection = this.client.db().collection(resource.table);
await collection.updateOne({ [this.getPrimaryKey(resource)]: recordId }, { $set: newValues });
const collection = this.client.db().collection(resource.table);
const pk = this.getPrimaryKey(resource);
const rows = await this.getDataWithOriginalTypes({resource, limit: 1, offset: 0, sort: [], filters: Filters.AND(Filters.EQ(pk, recordId))});
await collection.updateOne({ [pk]: rows[0][pk] || recordId }, { $set: newValues });
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
}

async deleteRecord({ resource, recordId }): Promise<boolean> {
const primaryKey = this.getPrimaryKey(resource);
const collection = this.client.db().collection(resource.table);
const res = await collection.deleteOne({ [primaryKey]: recordId });
const pk = this.getPrimaryKey(resource);
const rows = await this.getDataWithOriginalTypes({resource, limit: 1, offset: 0, sort: [], filters: Filters.AND(Filters.EQ(pk, recordId))});
const res = await collection.deleteOne({ [pk]: rows[0][pk] });
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
Comment thread
NoOne7135 marked this conversation as resolved.
Outdated
return res.deletedCount > 0;
}

Expand Down