Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[4/n]: migrate the RESTAPI GET /rest/* to use TwentyORM directly #10372

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/twenty-server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const MIGRATED_REST_METHODS = [
RequestMethod.POST,
RequestMethod.PATCH,
RequestMethod.PUT,
// RequestMethod.GET,
];

@Module({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ export class RestApiCoreController {
}

@Get()
@UseFilters(RestApiExceptionFilter)
async handleApiGet(@Req() request: Request, @Res() res: Response) {
const result = await this.restApiCoreService.get(request);
const result = await this.restApiCoreServiceV2.get(request);

res.status(200).send(cleanGraphQLResponse(result.data.data));
res.status(200).send(result);
}

@Delete()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { parseCoreBatchPath } from 'src/engine/api/rest/core/query-builder/utils
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
import { Query } from 'src/engine/api/rest/core/types/query.type';
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';

@Injectable()
export class CoreQueryBuilderFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common';

import { Request } from 'express';

import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { QueryVariables } from 'src/engine/api/rest/core/types/query-variables.type';
import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';

@Injectable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,29 @@ import { BadRequestException, Injectable } from '@nestjs/common';

import { Request } from 'express';
import { capitalize } from 'twenty-shared';
import { FindOptionsWhere, ObjectLiteral } from 'typeorm';

import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
import { FieldValue } from 'src/engine/api/rest/core/types/field-value.type';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';

interface FormatResultParams<T> {
operation: 'delete' | 'create' | 'update' | 'findOne' | 'findMany';
objectNameSingular?: string;
objectNamePlural?: string;
data: T;
meta?: any;
}
@Injectable()
export class RestApiCoreServiceV2 {
constructor(
private readonly coreQueryBuilderFactory: CoreQueryBuilderFactory,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly limitInputFactory: LimitInputFactory,
private readonly filterInputFactory: FilterInputFactory,
) {}

async delete(request: Request) {
Expand All @@ -29,8 +42,12 @@ export class RestApiCoreServiceV2 {

await repository.delete(recordId);

return this.formatResult('delete', objectMetadataNameSingular, {
id: recordToDelete.id,
return this.formatResult({
operation: 'delete',
objectNameSingular: objectMetadataNameSingular,
data: {
id: recordToDelete.id,
},
});
}

Expand All @@ -41,11 +58,11 @@ export class RestApiCoreServiceV2 {
await this.getRepositoryAndMetadataOrFail(request);
const createdRecord = await repository.save(body);

return this.formatResult(
'create',
objectMetadataNameSingular,
createdRecord,
);
return this.formatResult({
operation: 'create',
objectNameSingular: objectMetadataNameSingular,
data: createdRecord,
});
}

async update(request: Request) {
Expand All @@ -67,21 +84,67 @@ export class RestApiCoreServiceV2 {
...request.body,
});

return this.formatResult(
'update',
return this.formatResult({
operation: 'update',
objectNameSingular: objectMetadataNameSingular,
data: updatedRecord,
});
}

async get(request: Request) {
const { id: recordId } = parseCorePath(request);
const {
objectMetadataNameSingular,
updatedRecord,
);
objectMetadataNamePlural,
repository,
objectMetadata,
} = await this.getRepositoryAndMetadataOrFail(request);

if (recordId) {
const record = await repository.findOne({
where: { id: recordId },
});

return this.formatResult({
operation: 'findOne',
objectNameSingular: objectMetadataNameSingular,
data: record,
});
} else {
const limit = this.limitInputFactory.create(request);
const filter = this.filterInputFactory.create(request, objectMetadata);

const records = await repository.find({
take: limit,
// where: this.getWhereFilter(filter), // to use the computed filter
});

return this.formatResult({
objectNamePlural: objectMetadataNamePlural,
operation: 'findMany',
data: records,
});
}
}

private formatResult<T>(
operation: 'delete' | 'create' | 'update' | 'find',
objectNameSingular: string,
data: T,
) {
private formatResult<T>({
operation,
objectNameSingular,
objectNamePlural,
data,
}: FormatResultParams<T>) {
let prefix;

if (operation === 'findOne') {
prefix = objectNameSingular || '';
} else if (operation === 'findMany') {
prefix = objectNamePlural || '';
} else {
prefix = operation + capitalize(objectNameSingular || '');
}
const result = {
data: {
[operation + capitalize(objectNameSingular)]: data,
[prefix]: data,
},
};

Expand All @@ -107,12 +170,73 @@ export class RestApiCoreServiceV2 {

const objectMetadataNameSingular =
objectMetadata.objectMetadataItem.nameSingular;
const objectMetadataNamePlural =
objectMetadata.objectMetadataItem.namePlural;
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspace.id,
objectMetadataNameSingular,
);

return { objectMetadataNameSingular, repository };
return {
objectMetadataNameSingular,
objectMetadataNamePlural,
objectMetadata,
repository,
};
}

private getWhereFilter(
filterObject: Record<string, FieldValue>,
): FindOptionsWhere<ObjectLiteral> {
if (!filterObject) return {};

const processCondition = (
condition: Record<string, FieldValue>,
): FindOptionsWhere<ObjectLiteral> => {
let result: FindOptionsWhere<ObjectLiteral> = {};

for (const key in condition) {
const value = condition[key];

if (key === 'and' && Array.isArray(value)) {
// Merge "and" conditions into the same object
result = value.reduce(
(acc, subCondition) => ({
...acc,
...processCondition(subCondition as Record<string, FieldValue>),
}),
result,
);
} else if (key === 'or' && Array.isArray(value)) {
// Keep OR as an array inside the "or" key
result['or'] = value.map((subCondition) =>
processCondition(subCondition as Record<string, FieldValue>),
);
} else if (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
) {
// Recursively process nested objects
const subCondition = processCondition(
value as Record<string, FieldValue>,
);

if ('eq' in subCondition) {
result[key] = (subCondition as any).eq; // Flatten { eq: value } → value
} else {
result[key] = subCondition;
}
} else {
// Directly assign primitive values (string, number, boolean)
result[key] = value;
}
}

return result;
};

return processCondition(filterObject);
}
}
2 changes: 2 additions & 0 deletions packages/twenty-server/src/engine/api/rest/rest-api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/c
import { RestApiCoreServiceV2 } from 'src/engine/api/rest/core/rest-api-core-v2.service';
import { RestApiCoreService } from 'src/engine/api/rest/core/rest-api-core.service';
import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory';
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
import { MetadataQueryBuilderModule } from 'src/engine/api/rest/metadata/query-builder/metadata-query-builder.module';
Expand Down Expand Up @@ -39,6 +40,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
StartingAfterInputFactory,
EndingBeforeInputFactory,
LimitInputFactory,
FilterInputFactory,
],
exports: [RestApiMetadataService],
})
Expand Down
Loading