Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flexible-schema] Add findOne and fix findMany pagination + soft-delete for graphql-query-runner #6978

Merged
merged 6 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ export class GraphqlQueryRunnerException extends CustomException {
}

export enum GraphqlQueryRunnerExceptionCode {
INVALID_QUERY_INPUT = 'INVALID_QUERY_INPUT',
Copy link
Member

Choose a reason for hiding this comment

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

INVALID_INPUT

Copy link
Member

Choose a reason for hiding this comment

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

INVALID_FIRST / INVALID_LAST

MAX_DEPTH_REACHED = 'MAX_DEPTH_REACHED',
INVALID_CURSOR = 'INVALID_CURSOR',
INVALID_DIRECTION = 'INVALID_DIRECTION',
UNSUPPORTED_OPERATOR = 'UNSUPPORTED_OPERATOR',
ARGS_CONFLICT = 'ARGS_CONFLICT',
FIELD_NOT_FOUND = 'FIELD_NOT_FOUND',
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
RECORD_NOT_FOUND = 'RECORD_NOT_FOUND',
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@ export class GraphqlQueryFilterConditionParser {
}

const result: FindOptionsWhere<ObjectLiteral> = {};
let orCondition: FindOptionsWhere<ObjectLiteral>[] | null = null;
Comment on lines 28 to +29
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Consider initializing orCondition as an empty array instead of null for consistency.


for (const [key, value] of Object.entries(conditions)) {
switch (key) {
case 'and':
return this.parseAndCondition(value, isNegated);
Object.assign(result, this.parseAndCondition(value, isNegated));
break;
case 'or':
return this.parseOrCondition(value, isNegated);
orCondition = this.parseOrCondition(value, isNegated);
break;
case 'not':
return this.parse(value, !isNegated);
Object.assign(result, this.parse(value, !isNegated));
break;
default:
Object.assign(
result,
Expand All @@ -43,6 +47,10 @@ export class GraphqlQueryFilterConditionParser {
}
}

if (orCondition) {
return orCondition.map((condition) => ({ ...result, ...condition }));
}
Comment on lines +50 to +52
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: This logic might not correctly handle negated 'or' conditions. Consider how negation should be applied when combining with other conditions.


return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ export class GraphqlQueryFilterOperatorParser {
lt: (value: any) => LessThan(value),
lte: (value: any) => LessThanOrEqual(value),
in: (value: any) => In(value),
is: (value: any) => (value === 'NULL' ? IsNull() : value),
is: (value: any) => {
if (value === 'NULL') {
return IsNull();
} else if (value === 'NOT_NULL') {
return Not(IsNull());
} else {
return value;
}
},
like: (value: string) => Like(`%${value}%`),
ilike: (value: string) => ILike(`%${value}%`),
startsWith: (value: string) => ILike(`${value}%`),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export class GraphqlQueryOrderFieldParser {
this.fieldMetadataMap = fieldMetadataMap;
}

parse(orderBy: RecordOrderBy): Record<string, FindOptionsOrderValue> {
parse(
orderBy: RecordOrderBy,
isForwardPagination = true,
): Record<string, FindOptionsOrderValue> {
return orderBy.reduce(
(acc, item) => {
Object.entries(item).forEach(([key, value]) => {
Expand All @@ -40,11 +43,15 @@ export class GraphqlQueryOrderFieldParser {
const compositeOrder = this.parseCompositeFieldForOrder(
fieldMetadata,
value,
isForwardPagination,
);

Object.assign(acc, compositeOrder);
} else {
acc[key] = this.convertOrderByToFindOptionsOrder(value);
acc[key] = this.convertOrderByToFindOptionsOrder(
value,
isForwardPagination,
);
}
});

Expand All @@ -57,6 +64,7 @@ export class GraphqlQueryOrderFieldParser {
private parseCompositeFieldForOrder(
fieldMetadata: FieldMetadataInterface,
value: any,
isForwardPagination = true,
): Record<string, FindOptionsOrderValue> {
const compositeType = compositeTypeDefinitions.get(
fieldMetadata.type as CompositeFieldMetadataType,
Expand Down Expand Up @@ -87,8 +95,10 @@ export class GraphqlQueryOrderFieldParser {
`Sub field order by value must be of type OrderByDirection, but got: ${subFieldValue}`,
);
}
acc[fullFieldName] =
this.convertOrderByToFindOptionsOrder(subFieldValue);
acc[fullFieldName] = this.convertOrderByToFindOptionsOrder(
subFieldValue,
isForwardPagination,
);

return acc;
},
Expand All @@ -98,16 +108,29 @@ export class GraphqlQueryOrderFieldParser {

private convertOrderByToFindOptionsOrder(
direction: OrderByDirection,
isForwardPagination = true,
): FindOptionsOrderValue {
switch (direction) {
case OrderByDirection.AscNullsFirst:
return { direction: 'ASC', nulls: 'FIRST' };
return {
direction: isForwardPagination ? 'ASC' : 'DESC',
nulls: 'FIRST',
};
case OrderByDirection.AscNullsLast:
return { direction: 'ASC', nulls: 'LAST' };
return {
direction: isForwardPagination ? 'ASC' : 'DESC',
nulls: 'LAST',
};
case OrderByDirection.DescNullsFirst:
return { direction: 'DESC', nulls: 'FIRST' };
return {
direction: isForwardPagination ? 'DESC' : 'ASC',
nulls: 'FIRST',
};
case OrderByDirection.DescNullsLast:
return { direction: 'DESC', nulls: 'LAST' };
return {
direction: isForwardPagination ? 'DESC' : 'ASC',
nulls: 'LAST',
};
default:
throw new GraphqlQueryRunnerException(
`Invalid direction: ${direction}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
FindOptionsOrderValue,
FindOptionsWhere,
IsNull,
ObjectLiteral,
} from 'typeorm';

Expand Down Expand Up @@ -32,20 +33,55 @@ export class GraphqlQueryParser {

parseFilter(
recordFilter: RecordFilter,
shouldAddDefaultSoftDeleteCondition = false,
): FindOptionsWhere<ObjectLiteral> | FindOptionsWhere<ObjectLiteral>[] {
const graphqlQueryFilterParser = new GraphqlQueryFilterParser(
this.fieldMetadataMap,
);

return graphqlQueryFilterParser.parse(recordFilter);
const parsedFilter = graphqlQueryFilterParser.parse(recordFilter);

if (
!shouldAddDefaultSoftDeleteCondition ||
!('deletedAt' in this.fieldMetadataMap)
) {
return parsedFilter;
}

return this.addDefaultSoftDeleteCondition(parsedFilter);
}

private addDefaultSoftDeleteCondition(
filter: FindOptionsWhere<ObjectLiteral> | FindOptionsWhere<ObjectLiteral>[],
): FindOptionsWhere<ObjectLiteral> | FindOptionsWhere<ObjectLiteral>[] {
if (Array.isArray(filter)) {
return filter.map((condition) =>
this.addSoftDeleteToCondition(condition),
);
}

return this.addSoftDeleteToCondition(filter);
}

private addSoftDeleteToCondition(
condition: FindOptionsWhere<ObjectLiteral>,
): FindOptionsWhere<ObjectLiteral> {
if (!('deletedAt' in condition)) {
return { ...condition, deletedAt: IsNull() };
}

return condition;
}

parseOrder(orderBy: RecordOrderBy): Record<string, FindOptionsOrderValue> {
parseOrder(
orderBy: RecordOrderBy,
isForwardPagination = true,
): Record<string, FindOptionsOrderValue> {
const graphqlQueryOrderParser = new GraphqlQueryOrderParser(
this.fieldMetadataMap,
);

return graphqlQueryOrderParser.parse(orderBy);
return graphqlQueryOrderParser.parse(orderBy, isForwardPagination);
}

parseSelectedFields(
Expand Down
Loading
Loading