[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:
Paul Rastoin
2026-06-04 12:05:22 +02:00
committed by GitHub
parent 4ad8d8e98e
commit 3d49642d12
140 changed files with 55 additions and 4643 deletions
+1 -31
View File
@@ -31,14 +31,10 @@
"@aws-sdk/credential-providers": "3.1001.0",
"@aws-sdk/s3-request-presigner": "3.1001.0",
"@azure/msal-node": "^3.8.4",
"@babel/preset-env": "7.26.9",
"@blocknote/server-util": "^0.47.3",
"@clickhouse/client": "^1.18.1",
"@dagrejs/dagre": "^1.1.2",
"@e2b/code-interpreter": "^1.0.4",
"@envelop/core": "4.0.3",
"@envelop/on-resolve": "4.1.0",
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@faker-js/faker": "9.8.0",
"@file-type/pdf": "^0.2.0",
"@graphql-tools/schema": "10.0.4",
@@ -49,7 +45,6 @@
"@lingui/conf": "5.1.2",
"@lingui/core": "^5.1.2",
"@lingui/format-po": "5.1.2",
"@lingui/react": "5.1.2",
"@microsoft/microsoft-graph-client": "3.0.7",
"@microsoft/microsoft-graph-types": "^2.40.0",
"@nestjs/axios": "3.1.2",
@@ -69,11 +64,9 @@
"@node-saml/node-saml": "5.1.0",
"@node-saml/passport-saml": "^5.1.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.60.0",
"@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
"@opentelemetry/exporter-prometheus": "^0.211.0",
"@opentelemetry/sdk-metrics": "^2.0.0",
"@opentelemetry/sdk-node": "^0.202.0",
"@ptc-org/nestjs-query-core": "4.4.0",
"@ptc-org/nestjs-query-graphql": "patch:@ptc-org/nestjs-query-graphql@4.2.0#./patches/@ptc-org+nestjs-query-graphql+4.2.0.patch",
"@ptc-org/nestjs-query-typeorm": "4.2.1-alpha.2",
@@ -88,7 +81,6 @@
"archiver": "7.0.1",
"axios": "^1.13.5",
"axios-retry": "^4.5.0",
"babel-plugin-module-resolver": "5.0.2",
"bcrypt": "^6.0.0",
"bullmq": "5.40.0",
"bytes": "3.1.2",
@@ -97,27 +89,22 @@
"chalk": "4.1.2",
"class-transformer": "0.5.1",
"class-validator": "0.14.0",
"class-validator-jsonschema": "^5.0.2",
"cloudflare": "^4.5.0",
"connect-redis": "^7.1.1",
"cron-parser": "5.1.1",
"dataloader": "2.2.2",
"date-fns": "2.30.0",
"deep-equal": "2.2.3",
"digest-fetch": "^3.1.1",
"dompurify": "3.3.3",
"dotenv": "16.4.5",
"exa-js": "^2.11.0",
"express": "4.22.1",
"express-session": "^1.18.2",
"file-type": "^21.3.1",
"fuse.js": "^7.1.0",
"gaxios": "5.1.3",
"google-auth-library": "8.9.0",
"googleapis": "105.0.0",
"graphql": "16.8.1",
"graphql-fields": "2.0.3",
"graphql-middleware": "^6.1.35",
"graphql-redis-subscriptions": "2.7.0",
"graphql-scalars": "1.23.0",
"graphql-subscriptions": "2.0.0",
@@ -143,12 +130,9 @@
"lodash.kebabcase": "4.1.1",
"lodash.merge": "^4.6.2",
"lodash.omit": "4.5.0",
"lodash.omitby": "^4.6.0",
"lodash.snakecase": "4.1.1",
"lodash.uniq": "^4.5.0",
"lodash.uniqby": "^4.7.0",
"lodash.upperfirst": "4.3.1",
"mailparser": "3.9.3",
"microdiff": "1.4.0",
"mrmime": "^2.0.1",
"ms": "2.1.3",
@@ -167,7 +151,6 @@
"planer": "1.2.0",
"pluralize": "8.0.0",
"postal-mime": "^2.6.1",
"protobufjs": "^7.5.5",
"psl": "^1.9.0",
"react": "18.3.1",
"react-dom": "18.3.1",
@@ -175,12 +158,10 @@
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
"semver": "7.6.3",
"sharp": "0.32.6",
"sns-payload-validator": "^2.1.0",
"stripe": "19.3.1",
"tar": "^7.5.9",
"temporal-polyfill": "^0.3.0",
"transliteration": "2.3.5",
"tsconfig-paths": "^4.2.0",
"tsdav": "^2.2.0",
"tslib": "2.8.1",
@@ -188,15 +169,11 @@
"typeorm": "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch",
"unzipper": "^0.12.3",
"uuid": "9.0.1",
"vite-tsconfig-paths": "4.3.2",
"zod": "^4.1.11"
},
"devDependencies": {
"@faker-js/faker": "^9.8.0",
"@lingui/cli": "^5.1.2",
"@lingui/vite-plugin": "5.1.2",
"@nestjs/cli": "^11.0.16",
"@nestjs/devtools-integration": "^0.2.1",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "11.1.16",
"@swc/cli": "^0.7.10",
@@ -204,10 +181,9 @@
"@swc/jest": "^0.2.39",
"@types/addressparser": "^1.0.3",
"@types/archiver": "^6.0.0",
"@types/babel__preset-env": "7.10.0",
"@types/aws-lambda": "^8.10.147",
"@types/bcrypt": "^5.0.0",
"@types/bytes": "^3.1.1",
"@types/dompurify": "^3.0.5",
"@types/express": "^4.17.13",
"@types/express-session": "^1.18.0",
"@types/graphql-fields": "^1.3.6",
@@ -223,16 +199,12 @@
"@types/lodash.isobject": "^3.0.7",
"@types/lodash.merge": "^4.6.9",
"@types/lodash.omit": "^4.5.9",
"@types/lodash.omitby": "^4.6.9",
"@types/lodash.snakecase": "^4.1.7",
"@types/lodash.uniq": "^4.5.9",
"@types/lodash.uniqby": "^4.7.9",
"@types/lodash.upperfirst": "^4.3.7",
"@types/mailparser": "^3.4.6",
"@types/ms": "^0.7.31",
"@types/node": "^24.0.0",
"@types/nodemailer": "^7.0.3",
"@types/openid-client": "^3.7.0",
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-jwt": "^3.0.8",
"@types/passport-microsoft": "^2.1.0",
@@ -240,10 +212,8 @@
"@types/psl": "^1.1.3",
"@types/react": "^18.2.39",
"@types/supertest": "^2.0.11",
"@types/tar": "^7.0.87",
"@types/unzipper": "^0",
"@types/uuid": "^9.0.2",
"@yarnpkg/types": "^4.0.0",
"jest": "29.7.0",
"jest-environment-node": "^29.4.1",
"prettier": "^3.1.1",
@@ -1,4 +0,0 @@
export const generateInsertStatement = (
insertPrefix: string,
formattedValues: string[],
): string => `${insertPrefix}(${formattedValues.join(', ')});\n`;
@@ -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,
};
};
@@ -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 +0,0 @@
export type CompositeFieldGroupByDefinition = Record<string, boolean>;
@@ -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;
@@ -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;
};
@@ -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;
};
@@ -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;
}
@@ -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;
};
@@ -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,
);
};
@@ -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;
};
@@ -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,
]);
@@ -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,
]);
}
}
@@ -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 },
},
});
@@ -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}`;
};
@@ -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();
@@ -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';
};
@@ -1,41 +0,0 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
import {
appendCommonExceptionCode,
CustomException,
} from 'src/utils/custom-exception';
export const CoreEntityCacheExceptionCode = appendCommonExceptionCode({
INVALID_PARAMETERS: 'INVALID_PARAMETERS',
} as const);
const getCoreEntityCacheExceptionUserFriendlyMessage = (
code: keyof typeof CoreEntityCacheExceptionCode,
) => {
switch (code) {
case CoreEntityCacheExceptionCode.INVALID_PARAMETERS:
return msg`Invalid parameters provided.`;
case CoreEntityCacheExceptionCode.INTERNAL_SERVER_ERROR:
return msg`An unexpected error occurred.`;
default:
assertUnreachable(code);
}
};
export class CoreEntityCacheException extends CustomException<
keyof typeof CoreEntityCacheExceptionCode
> {
constructor(
message: string,
code: keyof typeof CoreEntityCacheExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
getCoreEntityCacheExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,8 +0,0 @@
import { Field } from '@nestjs/graphql';
import { HealthIndicatorId } from 'src/engine/core-modules/admin-panel/enums/health-indicator-id.enum';
export class HealthIndicatorInput {
@Field(() => HealthIndicatorId)
indicatorId: HealthIndicatorId;
}
@@ -1,16 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class AccountSyncJobByStatusCounter {
@Field(() => Number, { nullable: true })
NOT_SYNCED?: number;
@Field(() => Number, { nullable: true })
ACTIVE?: number;
@Field(() => Number, { nullable: true })
FAILED_INSUFFICIENT_PERMISSIONS?: number;
@Field(() => Number, { nullable: true })
FAILED_UNKNOWN?: number;
}
@@ -1,8 +0,0 @@
// Refresh result returned by the app OAuth driver. Mirrors
// `ConnectedAccountTokens` from the central refresh manager but redeclared
// here so this engine-side driver has zero dependency on `modules/`.
export type AppOAuthTokens = {
accessToken: string;
refreshToken: string | null;
};
@@ -1,25 +0,0 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
export const APPLICATION_MANIFEST_METADATA_NAMES = [
'objectMetadata',
'fieldMetadata',
'logicFunction',
'frontComponent',
'role',
'skill',
'agent',
'view',
'viewField',
'viewFieldGroup',
'viewFilter',
'viewFilterGroup',
'viewGroup',
'navigationMenuItem',
'pageLayout',
'pageLayoutTab',
'pageLayoutWidget',
'commandMenuItem',
] as const satisfies AllMetadataName[];
export type ApplicationManifestMetadataName =
(typeof APPLICATION_MANIFEST_METADATA_NAMES)[number];
@@ -1,15 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
@ObjectType('ExchangeAuthCode')
export class ExchangeAuthCodeDTO {
@Field(() => AuthToken)
accessOrWorkspaceAgnosticToken: AuthToken;
@Field(() => AuthToken)
refreshToken: AuthToken;
@Field(() => AuthToken)
loginToken: AuthToken;
}
@@ -1,21 +0,0 @@
import { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
@ArgsType()
export class ExchangeAuthCodeInput {
@Field(() => String)
@IsNotEmpty()
@IsString()
authorizationCode: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
codeVerifier?: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
clientSecret?: string;
}
@@ -1,9 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
@ObjectType()
export class WorkspaceAgnosticToken {
@Field(() => AuthToken)
token: AuthToken;
}
@@ -1,10 +0,0 @@
import {
type PendingActivationUserWorkspaceAuthContext,
type WorkspaceAuthContext,
} from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
export const isPendingActivationUserAuthContext = (
context: WorkspaceAuthContext,
): context is PendingActivationUserWorkspaceAuthContext => {
return context.type === 'pendingActivationUser';
};
@@ -1,7 +0,0 @@
import { type UserEntity } from 'src/engine/core-modules/user/user.entity';
import { type AUTH_CONTEXT_USER_SELECT_FIELDS } from 'src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants';
export type AuthContextUser = Pick<
UserEntity,
(typeof AUTH_CONTEXT_USER_SELECT_FIELDS)[number]
>;
@@ -1,2 +0,0 @@
export const BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE =
'No remaining credits to execute workflow. Please check your subscription.';
@@ -1,20 +0,0 @@
/* @license Enterprise */
import { Field, ObjectType } from '@nestjs/graphql';
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
@ObjectType('BillingPrice')
export class BillingPriceDTO {
@Field(() => Number)
upTo: number;
@Field(() => Number)
amount: number;
@Field(() => String)
stripePriceId: string;
@Field(() => SubscriptionInterval)
recurringInterval: SubscriptionInterval;
}
@@ -1,15 +0,0 @@
/* @license Enterprise */
import { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsString } from 'class-validator';
import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum';
@ArgsType()
export class BillingProductInput {
@Field(() => String)
@IsString()
@IsNotEmpty()
product: AvailableProduct;
}
@@ -1,5 +0,0 @@
/* @license Enterprise */
export enum AvailableProduct {
BasePlan = 'base-plan',
}
@@ -1,14 +0,0 @@
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
import {
BillingException,
BillingExceptionCode,
} from 'src/engine/core-modules/billing/billing.exception';
export const getOppositeInterval = (interval: SubscriptionInterval) => {
if (interval === SubscriptionInterval.Month) return SubscriptionInterval.Year;
if (interval === SubscriptionInterval.Year) return SubscriptionInterval.Month;
throw new BillingException(
`Interval invalid`,
BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_INVALID,
);
};
@@ -1,14 +0,0 @@
import {
BillingException,
BillingExceptionCode,
} from 'src/engine/core-modules/billing/billing.exception';
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
export const getOppositePlan = (plan: BillingPlanKey) => {
if (plan === BillingPlanKey.PRO) return BillingPlanKey.ENTERPRISE;
if (plan === BillingPlanKey.ENTERPRISE) return BillingPlanKey.PRO;
throw new BillingException(
`Plan invalid`,
BillingExceptionCode.BILLING_PLAN_NOT_FOUND,
);
};
@@ -1,5 +0,0 @@
export const isSubscriptionIncompleteOnboardingStatus = (
hasAnySubscription: boolean,
) => {
return !hasAnySubscription;
};
@@ -1,5 +0,0 @@
/* @license Enterprise */
const DAYS_TO_MS = (days: number) => days * 24 * 60 * 60 * 1000;
export const ENTERPRISE_VALIDITY_TOKEN_DEFAULT_EXPIRATION_MS = DAYS_TO_MS(30);
@@ -1,13 +0,0 @@
/* @license Enterprise */
import { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsString } from 'class-validator';
@ArgsType()
export class SetEnterpriseKeyInput {
@Field(() => String)
@IsString()
@IsNotEmpty()
enterpriseKey: string;
}
@@ -1,11 +0,0 @@
import { type Readable } from 'stream';
export const readFileContent = async (stream: Readable): Promise<string> => {
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(Buffer.from(chunk));
}
return Buffer.concat(chunks).toString('utf8');
};
@@ -1,12 +0,0 @@
import { dirname } from 'path';
export const getLogicFunctionBaseFolderPath = (handlerPath: string): string => {
return dirname(dirname(handlerPath));
};
export const getRelativePathFromBase = (
handlerPath: string,
baseFolderPath: string,
): string => {
return handlerPath.replace(`${baseFolderPath}/`, '');
};
@@ -1,31 +0,0 @@
export type FindByPositionQueryArgs = {
positionValue: number | null;
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION;
};
export type FindMinPositionQueryArgs = {
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION;
};
export type FindMaxPositionQueryArgs = {
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION;
};
export type UpdatePositionQueryArgs = {
recordId: string;
positionValue: number;
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION;
};
export enum RecordPositionQueryType {
FIND_MIN_POSITION = 'FIND_MIN_POSITION',
FIND_MAX_POSITION = 'FIND_MAX_POSITION',
FIND_BY_POSITION = 'FIND_BY_POSITION',
UPDATE_POSITION = 'UPDATE_POSITION',
}
export type RecordPositionQueryArgs =
| FindByPositionQueryArgs
| FindMinPositionQueryArgs
| FindMaxPositionQueryArgs
| UpdatePositionQueryArgs;
@@ -1,7 +0,0 @@
export enum ToolType {
HTTP_REQUEST = 'HTTP_REQUEST',
SEND_EMAIL = 'SEND_EMAIL',
SEARCH_HELP_CENTER = 'SEARCH_HELP_CENTER',
CODE_INTERPRETER = 'CODE_INTERPRETER',
NAVIGATE_APP = 'NAVIGATE_APP',
}
@@ -1 +0,0 @@
export { MAX_EMAIL_RECIPIENTS } from 'twenty-shared/constants';
@@ -1,6 +0,0 @@
export type ToolContext = {
workspaceId: string;
userId?: string;
roleId?: string;
[key: string]: unknown;
};
@@ -1,2 +0,0 @@
export const CONFIG_VARIABLES_METADATA_DECORATOR_KEY =
'config-variables-metadata' as const;
@@ -1,2 +0,0 @@
export const CONFIG_VARIABLES_METADATA_DECORATOR_NAMES_KEY =
'config-variable-names' as const;
@@ -1,14 +0,0 @@
import {
TWENTY_ALL_VERSIONS,
TwentyAllVersion,
} from 'src/engine/core-modules/upgrade/constants/twenty-all-versions.constant';
import { TWENTY_CURRENT_VERSION } from 'src/engine/core-modules/upgrade/constants/twenty-current-version.constant';
import { IndexOf, IsGreaterOrEqual } from 'twenty-shared/types';
export type RemovedSinceVersion<RemoveAtVersion extends TwentyAllVersion, T> =
IsGreaterOrEqual<
IndexOf<typeof TWENTY_CURRENT_VERSION, typeof TWENTY_ALL_VERSIONS>,
IndexOf<RemoveAtVersion, typeof TWENTY_ALL_VERSIONS>
> extends true
? never
: T;
@@ -1,39 +0,0 @@
import { Logger } from '@nestjs/common';
import { isDefined } from 'class-validator';
/**
* A decorator function that logs the execution time of the decorated method.
*
* @returns The modified property descriptor with the execution time logging functionality.
*/
export function LogExecutionTime(label?: string | undefined) {
return function (
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const originalMethod = descriptor.value;
const logger = new Logger(`${target.constructor.name}:${propertyKey}`);
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
descriptor.value = async function (...args: any[]) {
const start = performance.now();
const result = await originalMethod.apply(this, args);
const end = performance.now();
const executionTime = end - start;
if (isDefined(label)) {
logger.log(`${label} execution time: ${executionTime.toFixed(2)}ms`);
} else {
logger.log(`Execution time: ${executionTime.toFixed(2)}ms`);
}
return result;
};
return descriptor;
};
}
@@ -1,29 +0,0 @@
import {
type CanActivate,
ForbiddenException,
Injectable,
} from '@nestjs/common';
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@Injectable()
export class DevelopmentGuard implements CanActivate {
constructor(private readonly twentyConfigService: TwentyConfigService) {}
canActivate(): boolean {
const nodeEnv = this.twentyConfigService.get('NODE_ENV');
if (
nodeEnv !== NodeEnvironment.DEVELOPMENT &&
nodeEnv !== NodeEnvironment.TEST
) {
throw new ForbiddenException(
'This endpoint is only available in development or test environments',
);
}
return true;
}
}
@@ -1,19 +0,0 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
// All workflow-related standard object IDs that should be filtered out from agent access
const WORKFLOW_STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = [
STANDARD_OBJECTS.workflow.universalIdentifier,
STANDARD_OBJECTS.workflowRun.universalIdentifier,
STANDARD_OBJECTS.workflowVersion.universalIdentifier,
STANDARD_OBJECTS.workflowAutomatedTrigger.universalIdentifier,
] as const;
export const isWorkflowRelatedObject = (
objectMetadata: ObjectMetadataEntity,
): boolean => {
return WORKFLOW_STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.includes(
objectMetadata.universalIdentifier as (typeof WORKFLOW_STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS)[number],
);
};
@@ -1,5 +0,0 @@
import { z } from 'zod';
import { aiProviderAuthTypeSchema } from 'src/engine/metadata-modules/ai/ai-models/types/ai-provider-auth-type.schema';
export type AiProviderAuthType = z.infer<typeof aiProviderAuthTypeSchema>;
@@ -1,60 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import {
CalendarChannelContactAutoCreationPolicy,
CalendarChannelSyncStage,
CalendarChannelVisibility,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class CreateCalendarChannelInput {
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsString()
@IsNotEmpty()
@Field()
handle: string;
@IsEnum(CalendarChannelVisibility)
@IsNotEmpty()
@Field(() => CalendarChannelVisibility)
visibility: CalendarChannelVisibility;
@IsEnum(CalendarChannelSyncStage)
@IsNotEmpty()
@Field(() => CalendarChannelSyncStage)
syncStage: CalendarChannelSyncStage;
@IsUUID()
@IsNotEmpty()
@Field(() => UUIDScalarType)
connectedAccountId: string;
@IsBoolean()
@IsNotEmpty()
@Field()
isContactAutoCreationEnabled: boolean;
@IsEnum(CalendarChannelContactAutoCreationPolicy)
@IsNotEmpty()
@Field(() => CalendarChannelContactAutoCreationPolicy)
contactAutoCreationPolicy: CalendarChannelContactAutoCreationPolicy;
@IsBoolean()
@IsNotEmpty()
@Field()
isSyncEnabled: boolean;
}
@@ -1,49 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsArray,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class CreateConnectedAccountInput {
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsString()
@IsNotEmpty()
@Field()
handle: string;
@IsString()
@IsNotEmpty()
@Field()
provider: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
accessToken?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
refreshToken?: string;
@IsArray()
@IsOptional()
@Field(() => [String], { nullable: true })
scopes?: string[];
@IsUUID()
@IsNotEmpty()
@Field(() => UUIDScalarType)
userWorkspaceId: string;
}
@@ -1,32 +0,0 @@
import { type ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
export const fromObjectMetadataEntityToObjectMetadataDto = (
objectMetadataEntity: ObjectMetadataEntity,
): ObjectMetadataDTO => {
const {
createdAt,
updatedAt,
description,
icon,
color,
standardOverrides,
shortcut,
duplicateCriteria,
applicationId,
...rest
} = objectMetadataEntity;
return {
...rest,
createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),
description: description ?? undefined,
icon: icon ?? undefined,
color: color ?? undefined,
standardOverrides: standardOverrides ?? undefined,
shortcut: shortcut ?? undefined,
duplicateCriteria: duplicateCriteria ?? undefined,
applicationId: applicationId ?? undefined,
};
};
@@ -1,10 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export const isFieldMetadataTypeRelation = (
fieldMetadata: FieldMetadataEntity,
): fieldMetadata is FieldMetadataEntity &
FieldMetadataEntity<FieldMetadataType.RELATION> => {
return fieldMetadata.type === FieldMetadataType.RELATION;
};
@@ -1,44 +0,0 @@
import { type FieldMetadataDefaultValueForAnyType } from 'twenty-shared/types';
export const unserializeDefaultValue = (
serializedDefaultValue: FieldMetadataDefaultValueForAnyType,
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
): any => {
if (serializedDefaultValue === undefined || serializedDefaultValue === null) {
return null;
}
if (typeof serializedDefaultValue === 'number') {
return serializedDefaultValue;
}
if (typeof serializedDefaultValue === 'boolean') {
return serializedDefaultValue;
}
if (typeof serializedDefaultValue === 'string') {
return serializedDefaultValue.replace(/'/g, '');
}
if (Array.isArray(serializedDefaultValue)) {
return serializedDefaultValue.map((value) =>
unserializeDefaultValue(value),
);
}
if (typeof serializedDefaultValue === 'object') {
return Object.entries(serializedDefaultValue).reduce(
(acc, [key, value]) => {
// @ts-expect-error legacy noImplicitAny
acc[key] = unserializeDefaultValue(value);
return acc;
},
{},
);
}
throw new Error(
`Invalid serialized default value "${serializedDefaultValue}"`,
);
};
@@ -1,79 +0,0 @@
import { plainToInstance } from 'class-transformer';
import { validateSync } from 'class-validator';
import {
FieldMetadataType,
type FieldMetadataOptions,
} from 'twenty-shared/types';
import {
FieldMetadataComplexOption,
type FieldMetadataDefaultOption,
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { isEnumFieldMetadataType } from './is-enum-field-metadata-type.util';
export const optionsValidatorsMap = {
// RATING doesn't need to be provided as it's the backend that will generate the options
[FieldMetadataType.SELECT]: [FieldMetadataComplexOption],
[FieldMetadataType.MULTI_SELECT]: [FieldMetadataComplexOption],
};
export const validateOptionsForType = (
type: FieldMetadataType,
options: FieldMetadataOptions,
): boolean => {
if (options === null) return true;
if (!Array.isArray(options)) {
throw new FieldMetadataException(
'Options must be an array',
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
if (!isEnumFieldMetadataType(type)) {
return true;
}
if (type === FieldMetadataType.RATING) {
return true;
}
const values = options.map(({ value }) => value);
// Check if all options are unique
if (new Set(values).size !== options.length) {
throw new FieldMetadataException(
'Options must be unique',
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
const validators = optionsValidatorsMap[type];
if (!validators) return false;
const isValid = options.every((option) => {
return validators.some((validator) => {
const optionsInstance = plainToInstance<
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
any,
FieldMetadataDefaultOption | FieldMetadataComplexOption
>(validator, option);
return (
validateSync(optionsInstance, {
whitelist: true,
forbidNonWhitelisted: true,
forbidUnknownValues: true,
}).length === 0
);
});
});
return isValid;
};
@@ -1,25 +0,0 @@
import {
type ValidationArguments,
type ValidationOptions,
registerDecorator,
} from 'class-validator';
export function IsQuotedString(validationOptions?: ValidationOptions) {
return function (object: object, propertyName: string) {
registerDecorator({
name: 'isQuotedString',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
validate(value: any) {
return typeof value === 'string' && /^'{1}.*'{1}$/.test(value);
},
defaultMessage(args: ValidationArguments) {
return `${args.property} must be a quoted string`;
},
},
});
};
}
@@ -1,7 +0,0 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_APPLICATION_VARIABLE_EDITABLE_PROPERTIES = [
'key',
'description',
'isSecret',
] as const satisfies MetadataEntityPropertyName<'applicationVariable'>[];
@@ -1,7 +0,0 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_CONNECTION_PROVIDER_EDITABLE_PROPERTIES = [
'displayName',
'type',
'oauthConfig',
] as const satisfies MetadataEntityPropertyName<'connectionProvider'>[];
@@ -1,4 +0,0 @@
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
export type AllFlatEntities =
AllFlatEntityTypesByMetadataName[keyof AllFlatEntityTypesByMetadataName]['flatEntity'];
@@ -1,8 +0,0 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
export type FlatEntityMapsKeyToMetadata<T extends string> =
T extends `flat${infer Name}Maps`
? Uncapitalize<Name> extends AllMetadataName
? Uncapitalize<Name>
: never
: never;
@@ -1,25 +0,0 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
type ExtractRelationIdProperties<T> = {
[K in keyof T]: K extends `${infer _}Id`
? K extends 'id' | 'workspaceId'
? never
: T[K] extends string | null | undefined
? K
: never
: never;
}[keyof T];
type PropertyNameToRelationName<T extends string> = T extends `${infer Name}Id`
? Name
: never;
type ExtractEntityRelations<TEntity> = {
[K in ExtractRelationIdProperties<TEntity> as PropertyNameToRelationName<K>]: K;
};
export type MetadataNameAndRelations = {
[T in AllMetadataName]: Partial<ExtractEntityRelations<MetadataEntity<T>>>;
};
@@ -1,70 +0,0 @@
import isEmpty from 'lodash.isempty';
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
export type DeleteFlatEntityFromFlatEntityMapsOrThrowArgs<
T extends SyncableFlatEntity,
> = {
entityToDeleteId: string;
flatEntityMaps: FlatEntityMaps<T>;
};
export const deleteFlatEntityFromFlatEntityMapsOrThrow = <
T extends SyncableFlatEntity,
>({
flatEntityMaps,
entityToDeleteId,
}: DeleteFlatEntityFromFlatEntityMapsOrThrowArgs<T>): FlatEntityMaps<T> => {
const universalIdentifierToDelete =
flatEntityMaps.universalIdentifierById[entityToDeleteId];
if (!isDefined(universalIdentifierToDelete)) {
throw new FlatEntityMapsException(
'deleteFlatEntityFromFlatEntityMapsOrThrow: entity to delete not found',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const updatedUniversalIdentifierByIdEntries = Object.entries(
flatEntityMaps.universalIdentifierById,
).filter(([id]) => id !== entityToDeleteId);
const updatedUniversalIdentifiersByApplicationIdEntries = Object.entries(
flatEntityMaps.universalIdentifiersByApplicationId,
)
.map(([applicationId, universalIdentifiers]) => {
const stillPresentUniversalIdentifiers = universalIdentifiers?.filter(
(universalIdentifier) =>
universalIdentifier !== universalIdentifierToDelete,
);
if (
!isDefined(stillPresentUniversalIdentifiers) ||
isEmpty(stillPresentUniversalIdentifiers)
) {
return undefined;
}
return [applicationId, stillPresentUniversalIdentifiers];
})
.filter(isDefined);
return {
byUniversalIdentifier: removePropertiesFromRecord(
flatEntityMaps.byUniversalIdentifier,
[universalIdentifierToDelete],
),
universalIdentifierById: Object.fromEntries(
updatedUniversalIdentifierByIdEntries,
),
universalIdentifiersByApplicationId: Object.fromEntries(
updatedUniversalIdentifiersByApplicationIdEntries,
),
};
};
@@ -1,12 +0,0 @@
import { uncapitalize } from 'twenty-shared/utils';
import { type FlatEntityMapsKeyToMetadata } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps-key-to-metadata';
export const getMetadataNameFromFlatEntityMapsKey = <T extends string>(
flatEntityMapsKey: T,
): FlatEntityMapsKeyToMetadata<T> => {
const withoutPrefix = flatEntityMapsKey.replace(/^flat/, '');
const withoutSuffix = withoutPrefix.replace(/Maps$/, '');
return uncapitalize(withoutSuffix) as FlatEntityMapsKeyToMetadata<T>;
};
@@ -1,28 +0,0 @@
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
export type ReplaceFlatEntityInFlatEntityMapsOrThrowArgs<
T extends SyncableFlatEntity,
> = {
flatEntity: T;
flatEntityMaps: FlatEntityMaps<T>;
};
export const replaceFlatEntityInFlatEntityMapsOrThrow = <
T extends SyncableFlatEntity,
>({
flatEntity,
flatEntityMaps,
}: ReplaceFlatEntityInFlatEntityMapsOrThrowArgs<T>): FlatEntityMaps<T> => {
const flatEntityMapsToReplace = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps,
entityToDeleteId: flatEntity.id,
});
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps: flatEntityMapsToReplace,
});
};
@@ -1,6 +0,0 @@
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export type FieldMetadataMinimalInformation = Pick<
FlatFieldMetadata,
'id' | 'objectMetadataId' | 'name'
>;
@@ -1,9 +0,0 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_FIELD_PERMISSION_EDITABLE_PROPERTIES = [
'roleId',
'objectMetadataId',
'fieldMetadataId',
'canReadFieldValue',
'canUpdateFieldValue',
] as const satisfies MetadataEntityPropertyName<'fieldPermission'>[];
@@ -1,31 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
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 FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
import {
FrontComponentException,
FrontComponentExceptionCode,
} from 'src/engine/metadata-modules/front-component/front-component.exception';
export const fromDeleteFrontComponentInputToFlatFrontComponentOrThrow = ({
flatFrontComponentMaps,
frontComponentId,
}: {
flatFrontComponentMaps: FlatEntityMaps<FlatFrontComponent>;
frontComponentId: string;
}): FlatFrontComponent => {
const existingFlatFrontComponent = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: frontComponentId,
flatEntityMaps: flatFrontComponentMaps,
});
if (!isDefined(existingFlatFrontComponent)) {
throw new FrontComponentException(
'Front component not found',
FrontComponentExceptionCode.FRONT_COMPONENT_NOT_FOUND,
);
}
return existingFlatFrontComponent;
};
@@ -1,10 +0,0 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_OBJECT_PERMISSION_EDITABLE_PROPERTIES = [
'roleId',
'objectMetadataId',
'canReadObjectRecords',
'canUpdateObjectRecords',
'canSoftDeleteObjectRecords',
'canDestroyObjectRecords',
] as const satisfies MetadataEntityPropertyName<'objectPermission'>[];
@@ -1,10 +0,0 @@
import { type AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/aggregate-chart-configuration.dto';
import { type BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto';
import { type LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto';
import { type PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/pie-chart-configuration.dto';
export type GraphConfiguration =
| BarChartConfigurationDTO
| LineChartConfigurationDTO
| PieChartConfigurationDTO
| AggregateChartConfigurationDTO;
@@ -1,6 +0,0 @@
import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
export type IframeConfiguration = {
configurationType?: WidgetConfigurationType;
url?: string;
};
@@ -1,6 +0,0 @@
export * from './types/flat-page-layout.type';
export * from './types/flat-page-layout-maps.type';
export * from './constants/flat-page-layout-editable-properties.constant';
export * from './utils/transform-page-layout-entity-to-flat-page-layout.util';
export * from './services/workspace-flat-page-layout-map-cache.service';
export * from './flat-page-layout.module';
@@ -1,7 +0,0 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
import { ROLE_TARGET_FOREIGN_KEY_PROPERTIES } from 'src/engine/metadata-modules/flat-role-target/constants/role-target-foreign-key-properties.constant';
export const FLAT_ROLE_TARGET_EDITABLE_PROPERTIES = [
'roleId',
...ROLE_TARGET_FOREIGN_KEY_PROPERTIES,
] as const satisfies MetadataEntityPropertyName<'roleTarget'>[];
@@ -1,17 +0,0 @@
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_ROLE_EDITABLE_PROPERTIES: MetadataEntityPropertyName<'role'>[] =
[
'label',
'description',
'icon',
'canUpdateAllSettings',
'canAccessAllTools',
'canReadAllObjectRecords',
'canUpdateAllObjectRecords',
'canSoftDeleteAllObjectRecords',
'canDestroyAllObjectRecords',
'canBeAssignedToUsers',
'canBeAssignedToAgents',
'canBeAssignedToApiKeys',
];
@@ -1,14 +0,0 @@
/* @license Enterprise */
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_ROW_LEVEL_PERMISSION_PREDICATE_EDITABLE_PROPERTIES = [
'fieldMetadataId',
'operand',
'value',
'rowLevelPermissionPredicateGroupId',
'positionInRowLevelPermissionPredicateGroup',
'subFieldName',
'workspaceMemberFieldMetadataId',
'workspaceMemberSubFieldName',
] as const satisfies MetadataEntityPropertyName<'rowLevelPermissionPredicate'>[];
@@ -1,9 +0,0 @@
/* @license Enterprise */
import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
export const FLAT_ROW_LEVEL_PERMISSION_PREDICATE_GROUP_EDITABLE_PROPERTIES = [
'logicalOperator',
'positionInRowLevelPermissionPredicateGroup',
'parentRowLevelPermissionPredicateGroupId',
] as const satisfies MetadataEntityPropertyName<'rowLevelPermissionPredicateGroup'>[];
@@ -1,11 +0,0 @@
import { createHash } from 'crypto';
export const generateDeterministicIndexName = (columns: string[]): string => {
const hash = createHash('sha256');
columns.forEach((column) => {
hash.update(column);
});
return hash.digest('hex').slice(0, 27);
};
@@ -1,88 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import {
MessageChannelContactAutoCreationPolicy,
MessageChannelPendingGroupEmailsAction,
MessageChannelSyncStage,
MessageChannelType,
MessageChannelVisibility,
MessageFolderImportPolicy,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class CreateMessageChannelInput {
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsString()
@IsNotEmpty()
@Field()
handle: string;
@IsEnum(MessageChannelVisibility)
@IsNotEmpty()
@Field(() => MessageChannelVisibility)
visibility: MessageChannelVisibility;
@IsEnum(MessageChannelType)
@IsNotEmpty()
@Field(() => MessageChannelType)
type: MessageChannelType;
@IsEnum(MessageChannelSyncStage)
@IsNotEmpty()
@Field(() => MessageChannelSyncStage)
syncStage: MessageChannelSyncStage;
@IsUUID()
@IsNotEmpty()
@Field(() => UUIDScalarType)
connectedAccountId: string;
@IsBoolean()
@IsNotEmpty()
@Field()
isContactAutoCreationEnabled: boolean;
@IsEnum(MessageChannelContactAutoCreationPolicy)
@IsNotEmpty()
@Field(() => MessageChannelContactAutoCreationPolicy)
contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy;
@IsEnum(MessageFolderImportPolicy)
@IsNotEmpty()
@Field(() => MessageFolderImportPolicy)
messageFolderImportPolicy: MessageFolderImportPolicy;
@IsBoolean()
@IsNotEmpty()
@Field()
excludeNonProfessionalEmails: boolean;
@IsBoolean()
@IsNotEmpty()
@Field()
excludeGroupEmails: boolean;
@IsEnum(MessageChannelPendingGroupEmailsAction)
@IsNotEmpty()
@Field(() => MessageChannelPendingGroupEmailsAction)
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction;
@IsBoolean()
@IsNotEmpty()
@Field()
isSyncEnabled: boolean;
}
@@ -1,56 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class CreateMessageFolderInput {
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
name?: string;
@IsBoolean()
@IsNotEmpty()
@Field()
isSentFolder: boolean;
@IsBoolean()
@IsNotEmpty()
@Field()
isSynced: boolean;
@IsString()
@IsOptional()
@Field({ nullable: true })
externalId?: string;
@IsEnum(MessageFolderPendingSyncAction)
@IsNotEmpty()
@Field(() => MessageFolderPendingSyncAction)
pendingSyncAction: MessageFolderPendingSyncAction;
@IsUUID()
@IsNotEmpty()
@Field(() => UUIDScalarType)
messageChannelId: string;
@IsString()
@IsOptional()
@Field(() => String, { nullable: true })
parentFolderId?: string;
}
@@ -1,13 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsUUID } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class DeletePageLayoutWidgetInput {
@Field(() => UUIDScalarType)
@IsUUID()
@IsNotEmpty()
id: string;
}
@@ -1 +0,0 @@
export const ADMIN_ROLE_LABEL = 'Admin';
@@ -1,3 +0,0 @@
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export type FieldMetadataMap = Record<string, FieldMetadataEntity>; // TODO refactor Should be CachedFieldMetadataEntity or best FlatFieldMetadata
@@ -1,8 +0,0 @@
import { IDENTIFIER_MAX_CHAR_LENGTH } from 'twenty-shared/metadata';
import { IDENTIFIER_MIN_CHAR_LENGTH } from 'src/engine/metadata-modules/utils/constants/identifier-min-char-length.constants';
export const exceedsDatabaseIdentifierMaximumLength = (string: string) =>
string.length > IDENTIFIER_MAX_CHAR_LENGTH;
export const beneathDatabaseIdentifierMinimumLength = (string: string) =>
string.length < IDENTIFIER_MIN_CHAR_LENGTH;
@@ -1,44 +0,0 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import {
appendCommonExceptionCode,
CustomException,
} from 'src/utils/custom-exception';
export const FlatViewExceptionCode = appendCommonExceptionCode({
VIEW_NOT_FOUND: 'VIEW_NOT_FOUND',
VIEW_ALREADY_EXISTS: 'VIEW_ALREADY_EXISTS',
} as const);
const getFlatViewExceptionUserFriendlyMessage = (
code: keyof typeof FlatViewExceptionCode,
) => {
switch (code) {
case FlatViewExceptionCode.VIEW_NOT_FOUND:
return msg`View not found.`;
case FlatViewExceptionCode.VIEW_ALREADY_EXISTS:
return msg`View already exists.`;
case FlatViewExceptionCode.INTERNAL_SERVER_ERROR:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class FlatViewException extends CustomException<
keyof typeof FlatViewExceptionCode
> {
constructor(
message: string,
code: keyof typeof FlatViewExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? getFlatViewExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,4 +0,0 @@
export enum ViewOpenRecordInType {
SIDE_PANEL = 'SIDE_PANEL',
RECORD_PAGE = 'RECORD_PAGE',
}
@@ -1,4 +0,0 @@
export type WorkspaceSchemaEnumDefinition = {
name: string;
values: string[];
};
@@ -1,7 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
export const isMorphRelationFieldMetadataType = (
type: FieldMetadataType,
): type is FieldMetadataType.MORPH_RELATION => {
return type === FieldMetadataType.MORPH_RELATION;
};
@@ -1,3 +0,0 @@
export const isQueryTimeoutError = (error: Error) => {
return error.message.includes('Query read timeout');
};
@@ -1,34 +0,0 @@
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { assertUnreachable } from 'twenty-shared/utils';
import { CustomException } from 'src/utils/custom-exception';
export enum WorkspaceCleanerExceptionCode {
BILLING_SUBSCRIPTION_NOT_FOUND = 'BILLING_SUBSCRIPTION_NOT_FOUND',
}
const getWorkspaceCleanerExceptionUserFriendlyMessage = (
code: WorkspaceCleanerExceptionCode,
) => {
switch (code) {
case WorkspaceCleanerExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND:
return msg`Billing subscription not found.`;
default:
assertUnreachable(code);
}
};
export class WorkspaceCleanerException extends CustomException<WorkspaceCleanerExceptionCode> {
constructor(
message: string,
code: WorkspaceCleanerExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ??
getWorkspaceCleanerExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,43 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
type AddUniversalFlatEntityToUniversalFlatEntityMapsOrThrowArgs<
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
> = {
universalFlatEntity: T;
universalFlatEntityMaps: UniversalFlatEntityMaps<T>;
};
export const addUniversalFlatEntityToUniversalFlatEntityMapsOrThrow = <
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
>({
universalFlatEntity,
universalFlatEntityMaps,
}: AddUniversalFlatEntityToUniversalFlatEntityMapsOrThrowArgs<T>): UniversalFlatEntityMaps<T> => {
if (
isDefined(
universalFlatEntityMaps.byUniversalIdentifier[
universalFlatEntity.universalIdentifier
],
)
) {
throw new FlatEntityMapsException(
'addUniversalFlatEntityToUniversalFlatEntityMapsOrThrow: flat entity to add already exists',
FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS,
);
}
return {
byUniversalIdentifier: {
...universalFlatEntityMaps.byUniversalIdentifier,
[universalFlatEntity.universalIdentifier]: universalFlatEntity,
},
};
};
@@ -1,4 +0,0 @@
export type ConvertActionTypeToCamelCase<T extends string> =
T extends `${infer Before}_${infer After}`
? `${Before}${Capitalize<After>}`
: T;
@@ -1,12 +0,0 @@
import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
export const mapCalendarEventsByICalUID = (
existingCalendarEvents: CalendarEventWorkspaceEntity[],
): Map<string, string> => {
return new Map<string, string>(
existingCalendarEvents.map((calendarEvent) => [
calendarEvent.iCalUid ?? '',
calendarEvent.id,
]),
);
};
@@ -1,29 +0,0 @@
export const valuesStringForBatchRawQuery = (
values: {
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
[key: string]: any;
}[],
typesArray: string[] = [],
) => {
const castedValues = values.reduce((acc, _, rowIndex) => {
const numberOfColumns = typesArray.length;
const rowValues = Array.from(
{ length: numberOfColumns },
(_, columnIndex) => {
const placeholder = `$${rowIndex * numberOfColumns + columnIndex + 1}`;
const typeCast = typesArray[columnIndex]
? `::${typesArray[columnIndex]}`
: '';
return `${placeholder}${typeCast}`;
},
).join(', ');
acc.push(`(${rowValues})`);
return acc;
}, [] as string[]);
return castedValues.join(', ');
};
@@ -1,4 +0,0 @@
export type GmailApiError = {
code: number | string;
message: string;
};
@@ -1,6 +0,0 @@
export enum GmailDefaultMessageFolder {
CATEGORY_PROMOTIONS = 'CATEGORY_PROMOTIONS',
CATEGORY_SOCIAL = 'CATEGORY_SOCIAL',
CATEGORY_FORUMS = 'CATEGORY_FORUMS',
CATEGORY_UPDATES = 'CATEGORY_UPDATES',
}
@@ -1,4 +0,0 @@
export enum MessageFolderName {
INBOX = 'inbox',
SENT_ITEMS = 'sentItems',
}
@@ -1,3 +0,0 @@
export type MessageQuery = {
uri: string;
};
@@ -1,22 +0,0 @@
import { MessageParticipantRole } from 'twenty-shared/types';
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
import { isGroupEmail } from 'src/modules/messaging/message-import-manager/utils/is-group-email';
export const filterOutGroupEmails = (messages: MessageWithParticipants[]) => {
return messages.filter((message) => {
if (!message.participants) {
return true;
}
const fromParticipant = message.participants.find(
(participant) => participant.role === MessageParticipantRole.FROM,
);
if (!fromParticipant || !fromParticipant.handle) {
return true;
}
return !isGroupEmail(fromParticipant.handle);
});
};

Some files were not shown because too many files have changed in this diff Show More