Skip to content

Commit

Permalink
addPaginatedObjects
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelbrui committed Mar 18, 2024
1 parent a90eded commit 8a9ef8e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 44 deletions.
3 changes: 2 additions & 1 deletion packages/integrations/gei-crud/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "gei-crud",
"version": "0.9.2",
"version": "0.9.3",
"description": "GraphQL Editor integration for stucco. Allows basic crud operations and relations.",
"main": "lib/index.js",
"private": false,
"scripts": {
"start": "gecli dev",
"build": "tsc",
"lint:fix": "npm run lint -ws --if-present -- --fix",
"watch": "tsc --watch",
"update": "gecli schema pull && gecli typings",
"temp-db": "docker run -p 27017:27017 --rm mongo",
Expand Down
21 changes: 21 additions & 0 deletions packages/integrations/gei-crud/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ type Query{
dateFilter: DateFilterInput
sortByField: SortInput
): [Object!]
paginatedObjects(
data: DataInput
fieldFilter: FieldFilterInput
fieldRegexFilter: FieldFilterInput
dateFilter: DateFilterInput
sortByField: SortInput
paginate: PageOptions
): PaginatedObjects!
oneById(
data: DataInput
_id: String!
Expand Down Expand Up @@ -109,6 +117,19 @@ input Update{
owner: String
}

input PageOptions{
"""
default is 10
"""
limit: Int
cursorId: String
}

type PaginatedObjects{
cursorId: String
objects: [Object!]
}

schema{
query: Query
mutation: Mutation
Expand Down
44 changes: 1 addition & 43 deletions packages/integrations/gei-crud/src/Query/objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FieldResolveInput } from 'stucco-js';
import { prepareModel, prepareSourceParameters } from '../data.js';
import { DB } from '../db/orm.js';
import { DataInput } from '../integration.js';
import { QueryObject, checkStringFields, ifValueIsArray, snakeCaseToCamelCase, convertDateFilter, convertObjectToRegexFormat } from '../utils.js';

export const objects = async (input: FieldResolveInput & Partial<DataInput>) => {
return DB().then((db) => {
Expand Down Expand Up @@ -33,47 +34,4 @@ export const objects = async (input: FieldResolveInput & Partial<DataInput>) =>
.toArray();
});
};

interface QueryObject {
[key: string]: unknown;
}

// Function to convert the object to the desired format
function convertObjectToRegexFormat(obj: QueryObject): QueryObject | undefined {
for (const key in obj) {
if (Array.isArray(obj[key])) obj[key] = { $regex: { $in: obj[key] }, $options: 'i' };
if (obj[key] && typeof obj[key] === 'string') obj[key] = { $regex: obj[key], $options: 'i' };
}
return obj;
}

function ifValueIsArray(obj: QueryObject): QueryObject | undefined {
for (const key in obj) {
if (Array.isArray(obj[key])) obj[key] = { $in: obj[key] };
}
return obj;
}

function snakeCaseToCamelCase(input: string | null | undefined) {
return input?.toLowerCase().replace(/_([a-z])/g, (match, group1) => group1.toUpperCase());
}

function checkStringFields(obj: Record<string, unknown> | undefined): boolean {
for (const key in obj) {
if (typeof obj[key] === 'string' || Array.isArray(obj[key])) {
return true;
}
}
return false;
}

function convertDateFilter(obj: QueryObject): QueryObject | undefined {
if (!obj) return {};
return {
[obj.dateFieldName ? (obj.dateFieldName as string) : 'createdAt']: {
$gte: obj.from || '',
...(obj.to ? { $lte: obj.to } : {}),
},
};
}
export default objects;
44 changes: 44 additions & 0 deletions packages/integrations/gei-crud/src/Query/paginatedObjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { FieldResolveInput } from 'stucco-js';
import { prepareModel, prepareSourceParameters } from '../data.js';
import { DB } from '../db/orm.js';
import { DataInput } from '../integration.js';
import { ObjectId, Sort, SortDirection } from 'mongodb';
import { QueryObject, checkStringFields, getPaginationOpts, ifValueIsArray, paginateObjects, preparedSort, skipCursorForSortByField, convertObjectToRegexFormat, convertDateFilter } from '../utils.js';

export const objects = async (input: FieldResolveInput & Partial<DataInput>) => {
const paginate = getPaginationOpts(input.arguments?.paginate || input.data?.paginate)
const db = await DB()
const sortArg = input.arguments?.sortByField || input.arguments?.sort || input.data?.sort;

const fieldFilter = input.arguments?.fieldFilter;
const dateFilter = input.arguments?.dateFilter;
const fieldRegexFilter: any = input.arguments?.fieldRegexFilter
? input.arguments?.fieldRegexFilter
: fieldFilter
? {}
: checkStringFields(input.arguments)
? input.arguments
: {};
if (fieldRegexFilter?.sortByField) delete fieldRegexFilter?.sortByField;
if (fieldRegexFilter?.sort) delete fieldRegexFilter?.sort;
if (fieldRegexFilter?.dateFilter) delete fieldRegexFilter?.dateFilter;
const filterInput = {
...prepareSourceParameters(input),
...convertDateFilter(dateFilter as QueryObject),
...ifValueIsArray(fieldFilter as QueryObject),
...convertObjectToRegexFormat(ifValueIsArray(fieldRegexFilter) as QueryObject),
};

const objects = await db(input.data?.model || prepareModel(input))
.collection.find(filterInput,
{
sort: preparedSort(sortArg),
...(paginate?.limit && { limit: paginate?.limit }),
...((sortArg as [string, SortDirection])[0] !== '_id' && paginate?.cursorId && { skip: parseInt(paginate?.cursorId) })
}
)
.toArray();

return paginateObjects(objects, paginate.limit, 'objects', skipCursorForSortByField(input.data || input.arguments))
}

5 changes: 5 additions & 0 deletions packages/integrations/gei-crud/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export type DataInput = {
sourceParameters?: Array<string>;
related?: Array<{ model: string; field: string }>;
addFields?: Array<{ name: string; value: unknown }>;
sort?: { field: string; order?: boolean };
paginate?: {
limit?: number | null | undefined;
cursorId?: string | null | undefined;
} | null | undefined;
};
};

Expand Down
94 changes: 94 additions & 0 deletions packages/integrations/gei-crud/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ObjectId, Sort, SortDirection } from "mongodb";

export type PaginatedResult<T> = {
[key: string]: T[] | string | undefined;
cursorId: string | undefined;
};

export function paginateObjects<T>(
objects: T[],
limit: number | undefined,
propertyName: string,
cursor?: number,
): PaginatedResult<T> {
let cursorId = undefined;
if (limit === objects.length) {
objects.pop();
cursorId =
cursor?.toString() ||
(objects[limit > 2 ? limit - 2 : 0] as { id?: string | undefined })?.id ||
(objects[limit > 2 ? limit - 2 : 0] as { _id?: ObjectId | undefined })?._id?.toHexString();
}
const result: PaginatedResult<T> = {
cursorId,
};

result[propertyName] = objects;
return result;
}


export const getPaginationOpts = (
pageOptions?: { cursorId?: string | null; limit?: number | null } | null | undefined,
): { cursorId: string | undefined; limit: number } => ({
cursorId: pageOptions?.cursorId || undefined,
limit: (pageOptions?.limit || 10) + 1, // 10 orders default
});

export const preparedSort = (sort?: { field?: string | null; order?: string | null } | null): Sort => {
const direction = (sort?.order?.toLowerCase() || 'desc') as SortDirection;
const sortById = ['_id', direction];
return (
sort?.field && sort.field !== 'CREATED_AT'
? [[snakeCaseToCamelCase(sort.field), direction], sortById]
: sortById
) as Sort;
};

export const skipCursorForSortByField = (filter: any) =>
filter?.sort?.field && filter.sort.field !== 'CREATED_AT'
? parseInt(filter.paginate?.cursorId || '0') + (filter.paginate?.limit || 0)
: undefined;

export interface QueryObject {
[key: string]: unknown;
}

// Function to convert the object to the desired format
export function convertObjectToRegexFormat(obj: QueryObject): QueryObject | undefined {
for (const key in obj) {
if (Array.isArray(obj[key])) obj[key] = { $regex: { $in: obj[key] }, $options: 'i' };
if (obj[key] && typeof obj[key] === 'string') obj[key] = { $regex: obj[key], $options: 'i' };
}
return obj;
}

export function ifValueIsArray(obj: QueryObject): QueryObject | undefined {
for (const key in obj) {
if (Array.isArray(obj[key])) obj[key] = { $in: obj[key] };
}
return obj;
}

export function snakeCaseToCamelCase(input: string | null | undefined) {
return input?.toLowerCase().replace(/_([a-z])/g, (match, group1) => group1.toUpperCase());
}

export function checkStringFields(obj: Record<string, unknown> | undefined): boolean {
for (const key in obj) {
if (typeof obj[key] === 'string' || Array.isArray(obj[key])) {
return true;
}
}
return false;
}

export function convertDateFilter(obj: QueryObject): QueryObject | undefined {
if (!obj) return {};
return {
[obj.dateFieldName ? (obj.dateFieldName as string) : 'createdAt']: {
$gte: obj.from || '',
...(obj.to ? { $lte: obj.to } : {}),
},
};
}

0 comments on commit 8a9ef8e

Please sign in to comment.