[AUDIT] Run knip over twenty-server (#21159)
# Introduction Run [knip](https://knip.dev/) over twenty-server Used config: ```json { "$schema": "https://unpkg.com/knip@5/schema.json", "workspaces": { "packages/twenty-server": { "entry": [ "src/main.ts", "src/command/command.ts", "src/queue-worker/queue-worker.ts", "src/database/scripts/setup-db.ts", "src/database/scripts/truncate-db.ts", "src/database/clickHouse/migrations/run-migrations.ts", "src/database/clickHouse/seeds/run-seeds.ts", "src/instrument.ts", "lingui.config.ts", "test/integration/graphql/codegen/index.ts", "test/integration/utils/setup-test.ts", "test/integration/utils/teardown-test.ts", "scripts/**/*.ts", "**/*.spec.ts", "**/*.integration-spec.ts" ], "project": ["src/**/*.ts", "test/**/*.ts", "scripts/**/*.ts"], "ignore": [ "src/database/typeorm/**/migrations/**", "src/database/typeorm/**/*.entity.ts", "**/*.workspace-entity.ts", "**/logic-function-resource/constants/seed-project/**" ], "ignoreDependencies": ["@types/psl", "@types/aws-lambda"], "ignoreBinaries": ["nest", "lingui", "typeorm"] } } } ```
This commit is contained in:
-209
@@ -1,209 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
type ClickHouseFilterResult = {
|
||||
whereClause: string;
|
||||
params: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type FilterOperator =
|
||||
| 'eq'
|
||||
| 'neq'
|
||||
| 'gt'
|
||||
| 'gte'
|
||||
| 'lt'
|
||||
| 'lte'
|
||||
| 'in'
|
||||
| 'is'
|
||||
| 'like'
|
||||
| 'ilike'
|
||||
| 'startsWith'
|
||||
| 'endsWith'
|
||||
| 'contains';
|
||||
|
||||
const getClickHouseType = (value: unknown): string => {
|
||||
if (typeof value === 'string') {
|
||||
return 'String';
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return Number.isInteger(value) ? 'Int64' : 'Float64';
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return 'Bool';
|
||||
}
|
||||
|
||||
return 'String';
|
||||
};
|
||||
|
||||
const buildOperatorCondition = (
|
||||
fieldName: string,
|
||||
operator: FilterOperator,
|
||||
paramName: string,
|
||||
paramType: string,
|
||||
): string => {
|
||||
switch (operator) {
|
||||
case 'eq':
|
||||
return `"${fieldName}" = {${paramName}:${paramType}}`;
|
||||
case 'neq':
|
||||
return `"${fieldName}" != {${paramName}:${paramType}}`;
|
||||
case 'gt':
|
||||
return `"${fieldName}" > {${paramName}:${paramType}}`;
|
||||
case 'gte':
|
||||
return `"${fieldName}" >= {${paramName}:${paramType}}`;
|
||||
case 'lt':
|
||||
return `"${fieldName}" < {${paramName}:${paramType}}`;
|
||||
case 'lte':
|
||||
return `"${fieldName}" <= {${paramName}:${paramType}}`;
|
||||
case 'in':
|
||||
return `"${fieldName}" IN {${paramName}:Array(${paramType})}`;
|
||||
case 'is':
|
||||
return `"${fieldName}" IS NULL`;
|
||||
case 'like':
|
||||
return `"${fieldName}" LIKE {${paramName}:${paramType}}`;
|
||||
case 'ilike':
|
||||
return `lower("${fieldName}") LIKE lower({${paramName}:${paramType}})`;
|
||||
case 'startsWith':
|
||||
return `"${fieldName}" LIKE concat({${paramName}:${paramType}}, '%')`;
|
||||
case 'endsWith':
|
||||
return `"${fieldName}" LIKE concat('%', {${paramName}:${paramType}})`;
|
||||
case 'contains':
|
||||
return `"${fieldName}" LIKE concat('%', {${paramName}:${paramType}}, '%')`;
|
||||
default:
|
||||
return `"${fieldName}" = {${paramName}:${paramType}}`;
|
||||
}
|
||||
};
|
||||
|
||||
const parseFilterValue = (
|
||||
fieldName: string,
|
||||
filterValue: unknown,
|
||||
paramIndex: number,
|
||||
): { conditions: string[]; params: Record<string, unknown> } => {
|
||||
const conditions: string[] = [];
|
||||
const params: Record<string, unknown> = {};
|
||||
|
||||
if (!isDefined(filterValue) || typeof filterValue !== 'object') {
|
||||
return { conditions, params };
|
||||
}
|
||||
|
||||
const filterObj = filterValue as Record<string, unknown>;
|
||||
|
||||
for (const [operator, value] of Object.entries(filterObj)) {
|
||||
if (!isDefined(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const paramName = `${fieldName}_${paramIndex}_${operator}`;
|
||||
|
||||
if (operator === 'is') {
|
||||
if (value === 'NULL') {
|
||||
conditions.push(`"${fieldName}" IS NULL`);
|
||||
} else if (value === 'NOT_NULL') {
|
||||
conditions.push(`"${fieldName}" IS NOT NULL`);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const paramType = getClickHouseType(value);
|
||||
|
||||
conditions.push(
|
||||
buildOperatorCondition(
|
||||
fieldName,
|
||||
operator as FilterOperator,
|
||||
paramName,
|
||||
paramType,
|
||||
),
|
||||
);
|
||||
params[paramName] = value;
|
||||
}
|
||||
|
||||
return { conditions, params };
|
||||
};
|
||||
|
||||
export const parseClickHouseFilter = (
|
||||
filter: ObjectRecordFilter | undefined,
|
||||
): ClickHouseFilterResult => {
|
||||
if (!isDefined(filter) || Object.keys(filter).length === 0) {
|
||||
return { whereClause: '', params: {} };
|
||||
}
|
||||
|
||||
const allConditions: string[] = [];
|
||||
const allParams: Record<string, unknown> = {};
|
||||
let paramIndex = 0;
|
||||
|
||||
// Handle 'and' operator
|
||||
if ('and' in filter && Array.isArray(filter.and)) {
|
||||
const andConditions: string[] = [];
|
||||
|
||||
for (const subFilter of filter.and) {
|
||||
const { whereClause, params } = parseClickHouseFilter(
|
||||
subFilter as ObjectRecordFilter,
|
||||
);
|
||||
|
||||
if (whereClause) {
|
||||
andConditions.push(`(${whereClause})`);
|
||||
Object.assign(allParams, params);
|
||||
}
|
||||
}
|
||||
|
||||
if (andConditions.length > 0) {
|
||||
allConditions.push(andConditions.join(' AND '));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 'or' operator
|
||||
if ('or' in filter && Array.isArray(filter.or)) {
|
||||
const orConditions: string[] = [];
|
||||
|
||||
for (const subFilter of filter.or) {
|
||||
const { whereClause, params } = parseClickHouseFilter(
|
||||
subFilter as ObjectRecordFilter,
|
||||
);
|
||||
|
||||
if (whereClause) {
|
||||
orConditions.push(`(${whereClause})`);
|
||||
Object.assign(allParams, params);
|
||||
}
|
||||
}
|
||||
|
||||
if (orConditions.length > 0) {
|
||||
allConditions.push(`(${orConditions.join(' OR ')})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 'not' operator
|
||||
if ('not' in filter && isDefined(filter.not)) {
|
||||
const { whereClause, params } = parseClickHouseFilter(
|
||||
filter.not as ObjectRecordFilter,
|
||||
);
|
||||
|
||||
if (whereClause) {
|
||||
allConditions.push(`NOT (${whereClause})`);
|
||||
Object.assign(allParams, params);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle field-level filters
|
||||
for (const [fieldName, filterValue] of Object.entries(filter)) {
|
||||
if (['and', 'or', 'not'].includes(fieldName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { conditions, params } = parseFilterValue(
|
||||
fieldName,
|
||||
filterValue,
|
||||
paramIndex++,
|
||||
);
|
||||
|
||||
allConditions.push(...conditions);
|
||||
Object.assign(allParams, params);
|
||||
}
|
||||
|
||||
return {
|
||||
whereClause: allConditions.join(' AND '),
|
||||
params: allParams,
|
||||
};
|
||||
};
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const parseClickHouseOrderBy = (
|
||||
orderBy: Array<Record<string, string>> | undefined,
|
||||
): string => {
|
||||
if (!isDefined(orderBy) || orderBy.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const orderClauses: string[] = [];
|
||||
|
||||
for (const orderItem of orderBy) {
|
||||
for (const [fieldName, direction] of Object.entries(orderItem)) {
|
||||
const normalizedDirection = direction
|
||||
.toUpperCase()
|
||||
.replace('NULLS_FIRST', 'NULLS FIRST')
|
||||
.replace('NULLS_LAST', 'NULLS LAST')
|
||||
.replace('ASC_NULLS_FIRST', 'ASC NULLS FIRST')
|
||||
.replace('ASC_NULLS_LAST', 'ASC NULLS LAST')
|
||||
.replace('DESC_NULLS_FIRST', 'DESC NULLS FIRST')
|
||||
.replace('DESC_NULLS_LAST', 'DESC NULLS LAST');
|
||||
|
||||
orderClauses.push(`"${fieldName}" ${normalizedDirection}`);
|
||||
}
|
||||
}
|
||||
|
||||
return orderClauses.length > 0 ? `ORDER BY ${orderClauses.join(', ')}` : '';
|
||||
};
|
||||
-1
@@ -1 +0,0 @@
|
||||
export type CompositeFieldGroupByDefinition = Record<string, boolean>;
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
import { type CompositeFieldGroupByDefinition } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/types/composite-field-group-by-definition.type';
|
||||
import { type DateFieldGroupByDefinition } from 'src/engine/api/common/common-args-processors/group-by-arg-processor/types/date-field-group-by-definition.type';
|
||||
|
||||
export type FieldGroupByDefinition =
|
||||
| boolean
|
||||
| CompositeFieldGroupByDefinition
|
||||
| DateFieldGroupByDefinition
|
||||
| undefined;
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MAX_DEPTH } from 'src/engine/api/rest/input-request-parsers/constants/max-depth.constant';
|
||||
import { type Depth } from 'src/engine/api/rest/input-request-parsers/types/depth.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export const getShouldRecurseIntoRelation = ({
|
||||
depth,
|
||||
flatField,
|
||||
}: {
|
||||
depth: Depth | undefined;
|
||||
flatField: FlatFieldMetadata;
|
||||
}): boolean => {
|
||||
const flatFieldIsJoinColumn =
|
||||
isDefined(flatField.settings) &&
|
||||
'junctionTargetFieldId' in flatField.settings;
|
||||
|
||||
// TODO: refactor this when we remove hard-coded activity relations
|
||||
const flatFieldIsActivityTarget =
|
||||
flatField.name === 'noteTargets' || flatField.name === 'taskTargets';
|
||||
|
||||
const shouldGoOneLevelDeeper =
|
||||
depth === MAX_DEPTH && isDefined(flatField.relationTargetObjectMetadataId);
|
||||
|
||||
const shouldRecurseIntoRelation =
|
||||
shouldGoOneLevelDeeper ||
|
||||
flatFieldIsActivityTarget ||
|
||||
flatFieldIsJoinColumn;
|
||||
|
||||
return shouldRecurseIntoRelation;
|
||||
};
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
import { AggregateOperations, FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
export const computeIsNumericReturningAggregate = (
|
||||
operation: AggregateOperations,
|
||||
fromFieldType: FieldMetadataType,
|
||||
): boolean => {
|
||||
if (
|
||||
operation === AggregateOperations.COUNT ||
|
||||
operation === AggregateOperations.COUNT_UNIQUE_VALUES ||
|
||||
operation === AggregateOperations.COUNT_EMPTY ||
|
||||
operation === AggregateOperations.COUNT_NOT_EMPTY ||
|
||||
operation === AggregateOperations.COUNT_TRUE ||
|
||||
operation === AggregateOperations.COUNT_FALSE ||
|
||||
operation === AggregateOperations.PERCENTAGE_EMPTY ||
|
||||
operation === AggregateOperations.PERCENTAGE_NOT_EMPTY
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
operation === AggregateOperations.MIN ||
|
||||
operation === AggregateOperations.MAX ||
|
||||
operation === AggregateOperations.AVG ||
|
||||
operation === AggregateOperations.SUM
|
||||
) {
|
||||
return [
|
||||
FieldMetadataType.NUMBER,
|
||||
FieldMetadataType.NUMERIC,
|
||||
FieldMetadataType.CURRENCY,
|
||||
].includes(fromFieldType);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
type GetFieldMetadataFromGraphQLFieldArgs = {
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
graphQLField: string;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
};
|
||||
|
||||
export function getFieldMetadataFromGraphQLField({
|
||||
flatObjectMetadata,
|
||||
graphQLField,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}: GetFieldMetadataFromGraphQLFieldArgs): FlatFieldMetadata | undefined {
|
||||
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
const sourceFieldMetadataId = fieldIdByName[graphQLField];
|
||||
let sourceFieldMetadata = sourceFieldMetadataId
|
||||
? findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: sourceFieldMetadataId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
// If empty, it could be a morph relation
|
||||
if (!isDefined(sourceFieldMetadata)) {
|
||||
const morphRelationsWithTargetObjectMetadata =
|
||||
getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
)
|
||||
.filter(
|
||||
(fieldMetadata) =>
|
||||
fieldMetadata.type === FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
.map((fieldMetadata) => {
|
||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||
fieldMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldMetadata,
|
||||
targetObjectMetadata,
|
||||
};
|
||||
});
|
||||
|
||||
const possibleGraphQLFieldNames: {
|
||||
graphQLField: string;
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
targetObjectMetadata: FlatObjectMetadata;
|
||||
}[] = [];
|
||||
|
||||
morphRelationsWithTargetObjectMetadata.map((morphRelation) => {
|
||||
if (
|
||||
!isFlatFieldMetadataOfType(
|
||||
morphRelation.fieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
) ||
|
||||
!morphRelation.fieldMetadata.settings?.relationType
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
possibleGraphQLFieldNames.push({
|
||||
graphQLField: morphRelation.fieldMetadata.name,
|
||||
fieldMetadata: morphRelation.fieldMetadata,
|
||||
targetObjectMetadata: morphRelation.targetObjectMetadata,
|
||||
});
|
||||
});
|
||||
|
||||
const fieldMetdata = possibleGraphQLFieldNames.find(
|
||||
(possibleGraphQLFieldName) =>
|
||||
possibleGraphQLFieldName.graphQLField === graphQLField,
|
||||
)?.fieldMetadata;
|
||||
|
||||
if (fieldMetdata) {
|
||||
sourceFieldMetadata = fieldMetdata;
|
||||
}
|
||||
}
|
||||
|
||||
return sourceFieldMetadata;
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
import {
|
||||
Kind,
|
||||
type FieldNode,
|
||||
type GraphQLResolveInfo,
|
||||
type InlineFragmentNode,
|
||||
type SelectionNode,
|
||||
type SelectionSetNode,
|
||||
type ValueNode,
|
||||
} from 'graphql';
|
||||
|
||||
const isFieldNode = (node: SelectionNode): node is FieldNode =>
|
||||
node.kind === Kind.FIELD;
|
||||
|
||||
const isInlineFragmentNode = (
|
||||
node: SelectionNode,
|
||||
): node is InlineFragmentNode => node.kind === Kind.INLINE_FRAGMENT;
|
||||
|
||||
const findFieldNode = (
|
||||
selectionSet: SelectionSetNode | undefined,
|
||||
key: string,
|
||||
): FieldNode | null => {
|
||||
if (!selectionSet) return null;
|
||||
|
||||
let field: FieldNode | null = null;
|
||||
|
||||
for (const selection of selectionSet.selections) {
|
||||
// We've found the field
|
||||
if (isFieldNode(selection) && selection.name.value === key) {
|
||||
return selection;
|
||||
}
|
||||
|
||||
// Recursively search for the field in nested selections
|
||||
if (
|
||||
(isFieldNode(selection) || isInlineFragmentNode(selection)) &&
|
||||
selection.selectionSet
|
||||
) {
|
||||
field = findFieldNode(selection.selectionSet, key);
|
||||
|
||||
// If we find the field in a nested selection, stop searching
|
||||
if (field) break;
|
||||
}
|
||||
}
|
||||
|
||||
return field;
|
||||
};
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
const parseValueNode = (
|
||||
valueNode: ValueNode,
|
||||
variables: GraphQLResolveInfo['variableValues'],
|
||||
) => {
|
||||
switch (valueNode.kind) {
|
||||
case Kind.VARIABLE:
|
||||
return variables[valueNode.name.value];
|
||||
case Kind.INT:
|
||||
case Kind.FLOAT:
|
||||
return Number(valueNode.value);
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
case Kind.ENUM:
|
||||
return valueNode.value;
|
||||
case Kind.LIST:
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
return valueNode.values.map((value) => parseValueNode(value, variables));
|
||||
case Kind.OBJECT:
|
||||
return valueNode.fields.reduce((obj, field) => {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
obj[field.name.value] = parseValueNode(field.value, variables);
|
||||
|
||||
return obj;
|
||||
}, {});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFieldArgumentsByKey = (
|
||||
info: GraphQLResolveInfo,
|
||||
fieldKey: string,
|
||||
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
|
||||
): Record<string, any> => {
|
||||
// Start from the first top-level field node and search recursively
|
||||
const targetField = findFieldNode(info.fieldNodes[0].selectionSet, fieldKey);
|
||||
|
||||
// If the field is not found, throw an error
|
||||
if (!targetField) {
|
||||
throw new Error(`Field "${fieldKey}" not found.`);
|
||||
}
|
||||
|
||||
// Extract the arguments from the field we've found
|
||||
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
|
||||
const args: Record<string, any> = {};
|
||||
|
||||
if (targetField.arguments && targetField.arguments.length) {
|
||||
for (const arg of targetField.arguments) {
|
||||
args[arg.name.value] = parseValueNode(arg.value, info.variableValues);
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
};
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
import {
|
||||
WorkspaceQueryRunnerException,
|
||||
WorkspaceQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
|
||||
export type PgGraphQLConfig = {
|
||||
atMost: number;
|
||||
};
|
||||
|
||||
interface PgGraphQLErrorMapping {
|
||||
[key: string]: (
|
||||
command: string,
|
||||
objectName: string,
|
||||
pgGraphqlConfig: PgGraphQLConfig,
|
||||
) => WorkspaceQueryRunnerException;
|
||||
}
|
||||
|
||||
const pgGraphQLCommandMapping = {
|
||||
insertInto: 'insert',
|
||||
update: 'update',
|
||||
deleteFrom: 'delete',
|
||||
};
|
||||
|
||||
const pgGraphQLErrorMapping: PgGraphQLErrorMapping = {
|
||||
'delete impacts too many records': (_, objectName, pgGraphqlConfig) =>
|
||||
new WorkspaceQueryRunnerException(
|
||||
`Cannot delete ${objectName} because it impacts too many records (more than ${pgGraphqlConfig?.atMost}).`,
|
||||
WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED,
|
||||
),
|
||||
'update impacts too many records': (_, objectName, pgGraphqlConfig) =>
|
||||
new WorkspaceQueryRunnerException(
|
||||
`Cannot update ${objectName} because it impacts too many records (more than ${pgGraphqlConfig?.atMost}).`,
|
||||
WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED,
|
||||
),
|
||||
'duplicate key value violates unique constraint': (command, objectName, _) =>
|
||||
new WorkspaceQueryRunnerException(
|
||||
`Cannot ${
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
pgGraphQLCommandMapping[command] ?? command
|
||||
} ${objectName} because it violates a uniqueness constraint.`,
|
||||
WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT,
|
||||
),
|
||||
'violates foreign key constraint': (command, objectName, _) =>
|
||||
new WorkspaceQueryRunnerException(
|
||||
`Cannot ${
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
pgGraphQLCommandMapping[command] ?? command
|
||||
} ${objectName} because it violates a foreign key constraint.`,
|
||||
WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT,
|
||||
),
|
||||
};
|
||||
|
||||
export const computePgGraphQLError = (
|
||||
command: string,
|
||||
objectName: string,
|
||||
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
|
||||
errors: any[],
|
||||
pgGraphqlConfig: PgGraphQLConfig,
|
||||
) => {
|
||||
const error = errors[0];
|
||||
const errorMessage = error?.message;
|
||||
|
||||
const mappedErrorKey = Object.keys(pgGraphQLErrorMapping).find((key) =>
|
||||
errorMessage?.includes(key),
|
||||
);
|
||||
|
||||
const mappedError = mappedErrorKey
|
||||
? pgGraphQLErrorMapping[mappedErrorKey]
|
||||
: null;
|
||||
|
||||
if (mappedError) {
|
||||
return mappedError(command, objectName, pgGraphqlConfig);
|
||||
}
|
||||
|
||||
return new WorkspaceQueryRunnerException(
|
||||
`GraphQL errors on ${command}${objectName}: ${JSON.stringify(error)}`,
|
||||
WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
};
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
export const withSoftDeleted = <T extends ObjectRecordFilter>(
|
||||
filter: T | undefined | null,
|
||||
): boolean => {
|
||||
if (!isDefined(filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(filter)) {
|
||||
return filter.some((item) => withSoftDeleted(item));
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (key === 'deletedAt') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (withSoftDeleted(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
|
||||
|
||||
export const CONNECTION_METHOD_NAMES = new Set<string>([
|
||||
RESOLVER_METHOD_NAMES.FIND_MANY,
|
||||
RESOLVER_METHOD_NAMES.FIND_DUPLICATES,
|
||||
RESOLVER_METHOD_NAMES.CREATE_MANY,
|
||||
RESOLVER_METHOD_NAMES.UPDATE_MANY,
|
||||
RESOLVER_METHOD_NAMES.DELETE_MANY,
|
||||
RESOLVER_METHOD_NAMES.DESTROY_MANY,
|
||||
RESOLVER_METHOD_NAMES.RESTORE_MANY,
|
||||
]);
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { GraphQLNamedType } from 'graphql';
|
||||
|
||||
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
|
||||
@Injectable()
|
||||
export class OrphanedTypesGenerator {
|
||||
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
|
||||
|
||||
fetchOrphanedTypes(): GraphQLNamedType[] {
|
||||
return this.gqlTypesStorage.getAllGqlTypesExcept([
|
||||
GqlOperation.Query,
|
||||
GqlOperation.Mutation,
|
||||
]);
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import { GraphQLISODateTime } from '@nestjs/graphql';
|
||||
|
||||
import { GraphQLInputObjectType, GraphQLList, GraphQLNonNull } from 'graphql';
|
||||
|
||||
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||
|
||||
export const DateTimeFilterType = new GraphQLInputObjectType({
|
||||
name: 'DateTimeFilter',
|
||||
fields: {
|
||||
eq: { type: GraphQLISODateTime },
|
||||
gt: { type: GraphQLISODateTime },
|
||||
gte: { type: GraphQLISODateTime },
|
||||
in: { type: new GraphQLList(new GraphQLNonNull(GraphQLISODateTime)) },
|
||||
lt: { type: GraphQLISODateTime },
|
||||
lte: { type: GraphQLISODateTime },
|
||||
neq: { type: GraphQLISODateTime },
|
||||
is: { type: FilterIs },
|
||||
},
|
||||
});
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import {
|
||||
type FieldMetadataType,
|
||||
type CompositeProperty,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
export const computeCompositePropertyTarget = (
|
||||
type: FieldMetadataType,
|
||||
compositeProperty: CompositeProperty,
|
||||
): string => {
|
||||
return `${type.toString()}->${compositeProperty.name}`;
|
||||
};
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
|
||||
export const isFieldMetadataRelationOrMorphRelation = (
|
||||
fieldMetadata: FieldMetadataEntity<FieldMetadataType>,
|
||||
): fieldMetadata is FieldMetadataEntity &
|
||||
(
|
||||
| FieldMetadataEntity<FieldMetadataType.RELATION>
|
||||
| FieldMetadataEntity<FieldMetadataType.MORPH_RELATION>
|
||||
) => {
|
||||
return (
|
||||
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.RELATION) ||
|
||||
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.MORPH_RELATION)
|
||||
);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
||||
import { type JSONSchema7 } from 'json-schema';
|
||||
|
||||
class ValidationSchemaManager {
|
||||
private static instance: ValidationSchemaManager;
|
||||
private schemas: Record<string, JSONSchema7> | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): ValidationSchemaManager {
|
||||
if (!ValidationSchemaManager.instance) {
|
||||
ValidationSchemaManager.instance = new ValidationSchemaManager();
|
||||
}
|
||||
|
||||
return ValidationSchemaManager.instance;
|
||||
}
|
||||
|
||||
public getSchemas(): Record<string, JSONSchema7> {
|
||||
if (!this.schemas) {
|
||||
this.schemas = validationMetadatasToSchemas() as Record<
|
||||
string,
|
||||
JSONSchema7
|
||||
>;
|
||||
}
|
||||
|
||||
return this.schemas;
|
||||
}
|
||||
}
|
||||
|
||||
export const validationSchemaManager = ValidationSchemaManager.getInstance();
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
|
||||
export const parseOmitNullValuesRestRequest = (
|
||||
request: AuthenticatedRequest,
|
||||
): boolean => {
|
||||
if (!isDefined(request.query.omit_null_values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return request.query.omit_null_values === 'true';
|
||||
};
|
||||
Reference in New Issue
Block a user