[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:
@@ -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",
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
export const generateInsertStatement = (
|
||||
insertPrefix: string,
|
||||
formattedValues: string[],
|
||||
): string => `${insertPrefix}(${formattedValues.join(', ')});\n`;
|
||||
-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';
|
||||
};
|
||||
-41
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
-8
@@ -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;
|
||||
}
|
||||
-16
@@ -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;
|
||||
}
|
||||
-8
@@ -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;
|
||||
};
|
||||
-25
@@ -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;
|
||||
}
|
||||
-9
@@ -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;
|
||||
}
|
||||
-10
@@ -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]
|
||||
>;
|
||||
-2
@@ -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;
|
||||
}
|
||||
-15
@@ -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;
|
||||
}
|
||||
-5
@@ -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,
|
||||
);
|
||||
};
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
export const isSubscriptionIncompleteOnboardingStatus = (
|
||||
hasAnySubscription: boolean,
|
||||
) => {
|
||||
return !hasAnySubscription;
|
||||
};
|
||||
-5
@@ -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);
|
||||
-13
@@ -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;
|
||||
}
|
||||
-11
@@ -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');
|
||||
};
|
||||
-12
@@ -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}/`, '');
|
||||
};
|
||||
-31
@@ -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
@@ -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;
|
||||
};
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
export const CONFIG_VARIABLES_METADATA_DECORATOR_KEY =
|
||||
'config-variables-metadata' as const;
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
export const CONFIG_VARIABLES_METADATA_DECORATOR_NAMES_KEY =
|
||||
'config-variable-names' as const;
|
||||
-14
@@ -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;
|
||||
-39
@@ -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;
|
||||
}
|
||||
}
|
||||
-19
@@ -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],
|
||||
);
|
||||
};
|
||||
-5
@@ -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>;
|
||||
-60
@@ -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;
|
||||
}
|
||||
-49
@@ -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;
|
||||
}
|
||||
-32
@@ -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,
|
||||
};
|
||||
};
|
||||
-10
@@ -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;
|
||||
};
|
||||
-44
@@ -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}"`,
|
||||
);
|
||||
};
|
||||
-79
@@ -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;
|
||||
};
|
||||
-25
@@ -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`;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
-7
@@ -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'>[];
|
||||
-7
@@ -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'>[];
|
||||
-4
@@ -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'];
|
||||
-8
@@ -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;
|
||||
-25
@@ -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>>>;
|
||||
};
|
||||
-70
@@ -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,
|
||||
),
|
||||
};
|
||||
};
|
||||
-12
@@ -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>;
|
||||
};
|
||||
-28
@@ -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,
|
||||
});
|
||||
};
|
||||
-6
@@ -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'
|
||||
>;
|
||||
-9
@@ -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'>[];
|
||||
-31
@@ -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;
|
||||
};
|
||||
-10
@@ -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'>[];
|
||||
-10
@@ -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;
|
||||
-6
@@ -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';
|
||||
-7
@@ -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'>[];
|
||||
-17
@@ -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',
|
||||
];
|
||||
-14
@@ -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'>[];
|
||||
-9
@@ -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'>[];
|
||||
-11
@@ -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);
|
||||
};
|
||||
-88
@@ -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;
|
||||
}
|
||||
-56
@@ -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;
|
||||
}
|
||||
-13
@@ -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
@@ -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
|
||||
-8
@@ -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;
|
||||
-44
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
export enum ViewOpenRecordInType {
|
||||
SIDE_PANEL = 'SIDE_PANEL',
|
||||
RECORD_PAGE = 'RECORD_PAGE',
|
||||
}
|
||||
-4
@@ -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');
|
||||
};
|
||||
-34
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
-43
@@ -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,
|
||||
},
|
||||
};
|
||||
};
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
export type ConvertActionTypeToCamelCase<T extends string> =
|
||||
T extends `${infer Before}_${infer After}`
|
||||
? `${Before}${Capitalize<After>}`
|
||||
: T;
|
||||
-12
@@ -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,
|
||||
]),
|
||||
);
|
||||
};
|
||||
-29
@@ -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(', ');
|
||||
};
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
export type GmailApiError = {
|
||||
code: number | string;
|
||||
message: string;
|
||||
};
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
export enum GmailDefaultMessageFolder {
|
||||
CATEGORY_PROMOTIONS = 'CATEGORY_PROMOTIONS',
|
||||
CATEGORY_SOCIAL = 'CATEGORY_SOCIAL',
|
||||
CATEGORY_FORUMS = 'CATEGORY_FORUMS',
|
||||
CATEGORY_UPDATES = 'CATEGORY_UPDATES',
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
export enum MessageFolderName {
|
||||
INBOX = 'inbox',
|
||||
SENT_ITEMS = 'sentItems',
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
export type MessageQuery = {
|
||||
uri: string;
|
||||
};
|
||||
-22
@@ -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
Reference in New Issue
Block a user