Add exceptions for metadata modules (#6070)
Class exception for each metadata module + handler to map on graphql error TODO left : - find a way to call handler on auto-resolvers nestjs query (probably interceptors) - discuss what should be done for pre-hooks errors - discuss what should be done for Unauthorized exception
This commit is contained in:
@ -0,0 +1,11 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class DataSourceException extends CustomException {
|
||||||
|
constructor(message: string, code: DataSourceExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DataSourceExceptionCode {
|
||||||
|
DATA_SOURCE_NOT_FOUND = 'DATA_SOURCE_NOT_FOUND',
|
||||||
|
}
|
||||||
@ -3,6 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import { FindManyOptions, Repository } from 'typeorm';
|
import { FindManyOptions, Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DataSourceException,
|
||||||
|
DataSourceExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/data-source/data-source.exception';
|
||||||
|
|
||||||
import { DataSourceEntity } from './data-source.entity';
|
import { DataSourceEntity } from './data-source.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -58,10 +63,17 @@ export class DataSourceService {
|
|||||||
async getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
async getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<DataSourceEntity> {
|
): Promise<DataSourceEntity> {
|
||||||
return this.dataSourceMetadataRepository.findOneOrFail({
|
try {
|
||||||
where: { workspaceId },
|
return this.dataSourceMetadataRepository.findOneOrFail({
|
||||||
order: { createdAt: 'DESC' },
|
where: { workspaceId },
|
||||||
});
|
order: { createdAt: 'DESC' },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new DataSourceException(
|
||||||
|
`Data source not found for workspace ${workspaceId}: ${error}`,
|
||||||
|
DataSourceExceptionCode.DATA_SOURCE_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(workspaceId: string): Promise<void> {
|
async delete(workspaceId: string): Promise<void> {
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
|
|
||||||
export class InvalidStringException extends BadRequestException {
|
|
||||||
constructor(string: string) {
|
|
||||||
const message = `String "${string}" is not valid`;
|
|
||||||
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class FieldMetadataException extends CustomException {
|
||||||
|
code: FieldMetadataExceptionCode;
|
||||||
|
constructor(message: string, code: FieldMetadataExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FieldMetadataExceptionCode {
|
||||||
|
FIELD_METADATA_NOT_FOUND = 'FIELD_METADATA_NOT_FOUND',
|
||||||
|
INVALID_FIELD_INPUT = 'INVALID_FIELD_INPUT',
|
||||||
|
FIELD_MUTATION_NOT_ALLOWED = 'FIELD_MUTATION_NOT_ALLOWED',
|
||||||
|
FIELD_ALREADY_EXISTS = 'FIELD_ALREADY_EXISTS',
|
||||||
|
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
||||||
|
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ import { RelationDefinitionDTO } from 'src/engine/metadata-modules/field-metadat
|
|||||||
import { UpdateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
import { UpdateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||||
|
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Resolver(() => FieldMetadataDTO)
|
@Resolver(() => FieldMetadataDTO)
|
||||||
@ -34,10 +35,14 @@ export class FieldMetadataResolver {
|
|||||||
@Args('input') input: CreateOneFieldMetadataInput,
|
@Args('input') input: CreateOneFieldMetadataInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.fieldMetadataService.createOne({
|
try {
|
||||||
...input.field,
|
return this.fieldMetadataService.createOne({
|
||||||
workspaceId,
|
...input.field,
|
||||||
});
|
workspaceId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
fieldMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => FieldMetadataDTO)
|
@Mutation(() => FieldMetadataDTO)
|
||||||
@ -45,10 +50,14 @@ export class FieldMetadataResolver {
|
|||||||
@Args('input') input: UpdateOneFieldMetadataInput,
|
@Args('input') input: UpdateOneFieldMetadataInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.fieldMetadataService.updateOne(input.id, {
|
try {
|
||||||
...input.update,
|
return this.fieldMetadataService.updateOne(input.id, {
|
||||||
workspaceId,
|
...input.update,
|
||||||
});
|
workspaceId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
fieldMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => FieldMetadataDTO)
|
@Mutation(() => FieldMetadataDTO)
|
||||||
@ -85,27 +94,32 @@ export class FieldMetadataResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.fieldMetadataService.deleteOneField(input, workspaceId);
|
try {
|
||||||
|
return this.fieldMetadataService.deleteOneField(input, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
fieldMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField(() => RelationDefinitionDTO, { nullable: true })
|
@ResolveField(() => RelationDefinitionDTO, { nullable: true })
|
||||||
async relationDefinition(
|
async relationDefinition(
|
||||||
@Parent() fieldMetadata: FieldMetadataDTO,
|
@Parent() fieldMetadata: FieldMetadataDTO,
|
||||||
@Context() context: { loaders: IDataloaders },
|
@Context() context: { loaders: IDataloaders },
|
||||||
): Promise<RelationDefinitionDTO | null> {
|
): Promise<RelationDefinitionDTO | null | undefined> {
|
||||||
if (fieldMetadata.type !== FieldMetadataType.RELATION) {
|
if (fieldMetadata.type !== FieldMetadataType.RELATION) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationMetadataItem =
|
try {
|
||||||
await context.loaders.relationMetadataLoader.load(fieldMetadata.id);
|
const relationMetadataItem =
|
||||||
|
await context.loaders.relationMetadataLoader.load(fieldMetadata.id);
|
||||||
|
|
||||||
const relationDefinition =
|
return this.fieldMetadataService.getRelationDefinitionFromRelationMetadata(
|
||||||
await this.fieldMetadataService.getRelationDefinitionFromRelationMetadata(
|
|
||||||
fieldMetadata,
|
fieldMetadata,
|
||||||
relationMetadataItem,
|
relationMetadataItem,
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
return relationDefinition;
|
fieldMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
ConflictException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
@ -39,9 +34,15 @@ import {
|
|||||||
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
||||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
|
import {
|
||||||
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
validateMetadataName,
|
||||||
|
InvalidStringException,
|
||||||
|
} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||||
|
import {
|
||||||
|
FieldMetadataException,
|
||||||
|
FieldMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FieldMetadataEntity,
|
FieldMetadataEntity,
|
||||||
@ -94,7 +95,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!objectMetadata) {
|
if (!objectMetadata) {
|
||||||
throw new NotFoundException('Object does not exist');
|
throw new FieldMetadataException(
|
||||||
|
'Object metadata does not exist',
|
||||||
|
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fieldMetadataInput.isRemoteCreation) {
|
if (!fieldMetadataInput.isRemoteCreation) {
|
||||||
@ -107,7 +111,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
!fieldMetadataInput.options &&
|
!fieldMetadataInput.options &&
|
||||||
fieldMetadataInput.type !== FieldMetadataType.RATING
|
fieldMetadataInput.type !== FieldMetadataType.RATING
|
||||||
) {
|
) {
|
||||||
throw new BadRequestException('Options are required for enum fields');
|
throw new FieldMetadataException(
|
||||||
|
'Options are required for enum fields',
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +134,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (fieldAlreadyExists) {
|
if (fieldAlreadyExists) {
|
||||||
throw new ConflictException('Field already exists');
|
throw new FieldMetadataException(
|
||||||
|
'Field already exists',
|
||||||
|
FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdFieldMetadata = await fieldMetadataRepository.save({
|
const createdFieldMetadata = await fieldMetadataRepository.save({
|
||||||
@ -183,7 +193,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
const workspaceQueryRunner = workspaceDataSource?.createQueryRunner();
|
const workspaceQueryRunner = workspaceDataSource?.createQueryRunner();
|
||||||
|
|
||||||
if (!workspaceQueryRunner) {
|
if (!workspaceQueryRunner) {
|
||||||
throw new Error('Could not create workspace query runner');
|
throw new FieldMetadataException(
|
||||||
|
'Could not create workspace query runner',
|
||||||
|
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await workspaceQueryRunner.connect();
|
await workspaceQueryRunner.connect();
|
||||||
@ -263,7 +276,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!existingFieldMetadata) {
|
if (!existingFieldMetadata) {
|
||||||
throw new NotFoundException('Field does not exist');
|
throw new FieldMetadataException(
|
||||||
|
'Field does not exist',
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectMetadata =
|
const objectMetadata =
|
||||||
@ -277,7 +293,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!objectMetadata) {
|
if (!objectMetadata) {
|
||||||
throw new NotFoundException('Object does not exist');
|
throw new FieldMetadataException(
|
||||||
|
'Object metadata does not exist',
|
||||||
|
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertMutationNotOnRemoteObject(objectMetadata);
|
assertMutationNotOnRemoteObject(objectMetadata);
|
||||||
@ -287,15 +306,19 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
existingFieldMetadata.id &&
|
existingFieldMetadata.id &&
|
||||||
fieldMetadataInput.isActive === false
|
fieldMetadataInput.isActive === false
|
||||||
) {
|
) {
|
||||||
throw new BadRequestException(
|
throw new FieldMetadataException(
|
||||||
'Cannot deactivate label identifier field',
|
'Cannot deactivate label identifier field',
|
||||||
|
FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldMetadataInput.options) {
|
if (fieldMetadataInput.options) {
|
||||||
for (const option of fieldMetadataInput.options) {
|
for (const option of fieldMetadataInput.options) {
|
||||||
if (!option.id) {
|
if (!option.id) {
|
||||||
throw new BadRequestException('Option id is required');
|
throw new FieldMetadataException(
|
||||||
|
'Option id is required',
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,10 +348,18 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
? updatableFieldInput.defaultValue
|
? updatableFieldInput.defaultValue
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
const updatedFieldMetadata = await fieldMetadataRepository.findOneOrFail({
|
|
||||||
|
const updatedFieldMetadata = await fieldMetadataRepository.findOne({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!updatedFieldMetadata) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
'Field does not exist',
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fieldMetadataInput.name ||
|
fieldMetadataInput.name ||
|
||||||
updatableFieldInput.options ||
|
updatableFieldInput.options ||
|
||||||
@ -392,7 +423,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
throw new NotFoundException('Field does not exist');
|
throw new FieldMetadataException(
|
||||||
|
'Field does not exist',
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectMetadata =
|
const objectMetadata =
|
||||||
@ -403,7 +437,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!objectMetadata) {
|
if (!objectMetadata) {
|
||||||
throw new NotFoundException('Object does not exist');
|
throw new FieldMetadataException(
|
||||||
|
'Object metadata does not exist',
|
||||||
|
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await fieldMetadataRepository.delete(fieldMetadata.id);
|
await fieldMetadataRepository.delete(fieldMetadata.id);
|
||||||
@ -454,7 +491,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
throw new NotFoundException('Field does not exist');
|
throw new FieldMetadataException(
|
||||||
|
'Field does not exist',
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fieldMetadata;
|
return fieldMetadata;
|
||||||
@ -517,9 +557,12 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
relationMetadata.relationType === RelationMetadataType.MANY_TO_MANY ||
|
relationMetadata.relationType === RelationMetadataType.MANY_TO_MANY ||
|
||||||
relationMetadata.relationType === RelationMetadataType.MANY_TO_ONE
|
relationMetadata.relationType === RelationMetadataType.MANY_TO_ONE
|
||||||
) {
|
) {
|
||||||
throw new Error(`
|
throw new FieldMetadataException(
|
||||||
|
`
|
||||||
Relation type ${relationMetadata.relationType} not supported
|
Relation type ${relationMetadata.relationType} not supported
|
||||||
`);
|
`,
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRelationFromSource) {
|
if (isRelationFromSource) {
|
||||||
@ -561,8 +604,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
validateMetadataName(fieldMetadataInput.name);
|
validateMetadataName(fieldMetadataInput.name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InvalidStringException) {
|
if (error instanceof InvalidStringException) {
|
||||||
throw new BadRequestException(
|
throw new FieldMetadataException(
|
||||||
`Characters used in name "${fieldMetadataInput.name}" are not supported`,
|
`Characters used in name "${fieldMetadataInput.name}" are not supported`,
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { FieldMetadataException } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
|
||||||
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
|
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
|
||||||
|
|
||||||
describe('serializeDefaultValue', () => {
|
describe('serializeDefaultValue', () => {
|
||||||
@ -15,8 +14,10 @@ describe('serializeDefaultValue', () => {
|
|||||||
expect(serializeDefaultValue('now')).toBe('now()');
|
expect(serializeDefaultValue('now')).toBe('now()');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw BadRequestException for invalid dynamic default value type', () => {
|
it('should throw FieldMetadataException for invalid dynamic default value type', () => {
|
||||||
expect(() => serializeDefaultValue('invalid')).toThrow(BadRequestException);
|
expect(() => serializeDefaultValue('invalid')).toThrow(
|
||||||
|
FieldMetadataException,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle string static default value', () => {
|
it('should handle string static default value', () => {
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import { CompositeProperty } from 'src/engine/metadata-modules/field-metadata/in
|
|||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
import { pascalCase } from 'src/utils/pascal-case';
|
import { pascalCase } from 'src/utils/pascal-case';
|
||||||
|
import {
|
||||||
|
FieldMetadataException,
|
||||||
|
FieldMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
|
||||||
type ComputeColumnNameOptions = { isForeignKey?: boolean };
|
type ComputeColumnNameOptions = { isForeignKey?: boolean };
|
||||||
|
|
||||||
@ -29,8 +33,9 @@ export function computeColumnName<T extends FieldMetadataType | 'default'>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) {
|
if (isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) {
|
||||||
throw new Error(
|
throw new FieldMetadataException(
|
||||||
`Cannot compute column name for composite field metadata type: ${fieldMetadataOrFieldName.type}`,
|
`Cannot compute composite column name for non-composite field metadata type: ${fieldMetadataOrFieldName.type}`,
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,8 +66,9 @@ export function computeCompositeColumnName<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) {
|
if (!isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) {
|
||||||
throw new Error(
|
throw new FieldMetadataException(
|
||||||
`Cannot compute composite column name for non-composite field metadata type: ${fieldMetadataOrFieldName.type}`,
|
`Cannot compute composite column name for non-composite field metadata type: ${fieldMetadataOrFieldName.type}`,
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
FieldMetadataException,
|
||||||
|
FieldMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
import {
|
||||||
|
UserInputError,
|
||||||
|
ForbiddenError,
|
||||||
|
ConflictError,
|
||||||
|
InternalServerError,
|
||||||
|
NotFoundError,
|
||||||
|
} from 'src/engine/utils/graphql-errors.util';
|
||||||
|
|
||||||
|
export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||||
|
if (error instanceof FieldMetadataException) {
|
||||||
|
switch (error.code) {
|
||||||
|
case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND:
|
||||||
|
throw new NotFoundError(error.message);
|
||||||
|
case FieldMetadataExceptionCode.INVALID_FIELD_INPUT:
|
||||||
|
throw new UserInputError(error.message);
|
||||||
|
case FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED:
|
||||||
|
throw new ForbiddenError(error.message);
|
||||||
|
case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS:
|
||||||
|
throw new ConflictError(error.message);
|
||||||
|
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||||
|
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
|
||||||
|
default:
|
||||||
|
throw new InternalServerError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { FieldMetadataDefaultSerializableValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultSerializableValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FieldMetadataException,
|
||||||
|
FieldMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
import { isFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/is-function-default-value.util';
|
import { isFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/is-function-default-value.util';
|
||||||
import { serializeFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-function-default-value.util';
|
import { serializeFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-function-default-value.util';
|
||||||
|
|
||||||
@ -18,7 +20,10 @@ export const serializeDefaultValue = (
|
|||||||
serializeFunctionDefaultValue(defaultValue);
|
serializeFunctionDefaultValue(defaultValue);
|
||||||
|
|
||||||
if (!serializedTypeDefaultValue) {
|
if (!serializedTypeDefaultValue) {
|
||||||
throw new BadRequestException('Invalid default value');
|
throw new FieldMetadataException(
|
||||||
|
'Invalid default value',
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return serializedTypeDefaultValue;
|
return serializedTypeDefaultValue;
|
||||||
@ -51,5 +56,8 @@ export const serializeDefaultValue = (
|
|||||||
return `'${JSON.stringify(defaultValue)}'`;
|
return `'${JSON.stringify(defaultValue)}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadRequestException(`Invalid default value "${defaultValue}"`);
|
throw new FieldMetadataException(
|
||||||
|
`Invalid default value "${defaultValue}"`,
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,10 @@ import {
|
|||||||
FieldMetadataComplexOption,
|
FieldMetadataComplexOption,
|
||||||
FieldMetadataDefaultOption,
|
FieldMetadataDefaultOption,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
} 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';
|
import { isEnumFieldMetadataType } from './is-enum-field-metadata-type.util';
|
||||||
|
|
||||||
@ -24,7 +28,10 @@ export const validateOptionsForType = (
|
|||||||
if (options === null) return true;
|
if (options === null) return true;
|
||||||
|
|
||||||
if (!Array.isArray(options)) {
|
if (!Array.isArray(options)) {
|
||||||
throw new Error('Options must be an array');
|
throw new FieldMetadataException(
|
||||||
|
'Options must be an array',
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEnumFieldMetadataType(type)) {
|
if (!isEnumFieldMetadataType(type)) {
|
||||||
@ -39,7 +46,10 @@ export const validateOptionsForType = (
|
|||||||
|
|
||||||
// Check if all options are unique
|
// Check if all options are unique
|
||||||
if (new Set(values).size !== options.length) {
|
if (new Set(values).size !== options.length) {
|
||||||
throw new Error('Options must be unique');
|
throw new FieldMetadataException(
|
||||||
|
'Options must be unique',
|
||||||
|
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validators = optionsValidatorsMap[type];
|
const validators = optionsValidatorsMap[type];
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class ObjectMetadataException extends CustomException {
|
||||||
|
code: ObjectMetadataExceptionCode;
|
||||||
|
constructor(message: string, code: ObjectMetadataExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ObjectMetadataExceptionCode {
|
||||||
|
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
||||||
|
INVALID_OBJECT_INPUT = 'INVALID_OBJECT_INPUT',
|
||||||
|
OBJECT_MUTATION_NOT_ALLOWED = 'OBJECT_MUTATION_NOT_ALLOWED',
|
||||||
|
OBJECT_ALREADY_EXISTS = 'OBJECT_ALREADY_EXISTS',
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
UpdateOneObjectInput,
|
UpdateOneObjectInput,
|
||||||
} from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
} from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||||
import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook';
|
import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook';
|
||||||
|
import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Resolver(() => ObjectMetadataDTO)
|
@Resolver(() => ObjectMetadataDTO)
|
||||||
@ -26,7 +27,11 @@ export class ObjectMetadataResolver {
|
|||||||
@Args('input') input: DeleteOneObjectInput,
|
@Args('input') input: DeleteOneObjectInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.objectMetadataService.deleteOneObject(input, workspaceId);
|
try {
|
||||||
|
return this.objectMetadataService.deleteOneObject(input, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
objectMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => ObjectMetadataDTO)
|
@Mutation(() => ObjectMetadataDTO)
|
||||||
@ -34,8 +39,12 @@ export class ObjectMetadataResolver {
|
|||||||
@Args('input') input: UpdateOneObjectInput,
|
@Args('input') input: UpdateOneObjectInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
await this.beforeUpdateOneObject.run(input, workspaceId);
|
try {
|
||||||
|
await this.beforeUpdateOneObject.run(input, workspaceId);
|
||||||
|
|
||||||
return this.objectMetadataService.updateOneObject(input, workspaceId);
|
return this.objectMetadataService.updateOneObject(input, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
objectMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
ConflictException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import console from 'console';
|
import console from 'console';
|
||||||
@ -56,6 +51,10 @@ import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server
|
|||||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||||
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||||
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
|
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
|
||||||
|
import {
|
||||||
|
ObjectMetadataException,
|
||||||
|
ObjectMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||||
|
|
||||||
@ -121,7 +120,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!objectMetadata) {
|
if (!objectMetadata) {
|
||||||
throw new NotFoundException('Object does not exist');
|
throw new ObjectMetadataException(
|
||||||
|
'Object does not exist',
|
||||||
|
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE RELATIONS
|
// DELETE RELATIONS
|
||||||
@ -159,8 +161,9 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
objectMetadataInput.nameSingular.toLowerCase() ===
|
objectMetadataInput.nameSingular.toLowerCase() ===
|
||||||
objectMetadataInput.namePlural.toLowerCase()
|
objectMetadataInput.namePlural.toLowerCase()
|
||||||
) {
|
) {
|
||||||
throw new BadRequestException(
|
throw new ObjectMetadataException(
|
||||||
'The singular and plural name cannot be the same for an object',
|
'The singular and plural name cannot be the same for an object',
|
||||||
|
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +189,10 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (objectAlreadyExists) {
|
if (objectAlreadyExists) {
|
||||||
throw new ConflictException('Object already exists');
|
throw new ObjectMetadataException(
|
||||||
|
'Object already exists',
|
||||||
|
ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCustom = !objectMetadataInput.isRemote;
|
const isCustom = !objectMetadataInput.isRemote;
|
||||||
@ -372,18 +378,25 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
options: FindOneOptions<ObjectMetadataEntity>,
|
options: FindOneOptions<ObjectMetadataEntity>,
|
||||||
): Promise<ObjectMetadataEntity> {
|
): Promise<ObjectMetadataEntity> {
|
||||||
return this.objectMetadataRepository.findOneOrFail({
|
try {
|
||||||
relations: [
|
return this.objectMetadataRepository.findOneOrFail({
|
||||||
'fields',
|
relations: [
|
||||||
'fields.fromRelationMetadata',
|
'fields',
|
||||||
'fields.toRelationMetadata',
|
'fields.fromRelationMetadata',
|
||||||
],
|
'fields.toRelationMetadata',
|
||||||
...options,
|
],
|
||||||
where: {
|
...options,
|
||||||
...options.where,
|
where: {
|
||||||
workspaceId,
|
...options.where,
|
||||||
},
|
workspaceId,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new ObjectMetadataException(
|
||||||
|
'Object does not exist',
|
||||||
|
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findManyWithinWorkspace(
|
public async findManyWithinWorkspace(
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ObjectMetadataException,
|
||||||
|
ObjectMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||||
|
|
||||||
export const assertMutationNotOnRemoteObject = (
|
export const assertMutationNotOnRemoteObject = (
|
||||||
objectMetadataItem: ObjectMetadataInterface,
|
objectMetadataItem: ObjectMetadataInterface,
|
||||||
) => {
|
) => {
|
||||||
if (objectMetadataItem.isRemote) {
|
if (objectMetadataItem.isRemote) {
|
||||||
throw new BadRequestException('Remote objects are read-only');
|
throw new ObjectMetadataException(
|
||||||
|
'Remote objects are read-only',
|
||||||
|
ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import {
|
||||||
|
ObjectMetadataException,
|
||||||
|
ObjectMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||||
|
import {
|
||||||
|
UserInputError,
|
||||||
|
ForbiddenError,
|
||||||
|
ConflictError,
|
||||||
|
InternalServerError,
|
||||||
|
NotFoundError,
|
||||||
|
} from 'src/engine/utils/graphql-errors.util';
|
||||||
|
|
||||||
|
export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||||
|
if (error instanceof ObjectMetadataException) {
|
||||||
|
switch (error.code) {
|
||||||
|
case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||||
|
throw new NotFoundError(error.message);
|
||||||
|
case ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT:
|
||||||
|
throw new UserInputError(error.message);
|
||||||
|
case ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED:
|
||||||
|
throw new ForbiddenError(error.message);
|
||||||
|
case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS:
|
||||||
|
throw new ConflictError(error.message);
|
||||||
|
default:
|
||||||
|
throw new InternalServerError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
@ -1,9 +1,13 @@
|
|||||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
|
|
||||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||||
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||||
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
import {
|
||||||
|
ObjectMetadataException,
|
||||||
|
ObjectMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||||
|
import {
|
||||||
|
validateMetadataName,
|
||||||
|
InvalidStringException,
|
||||||
|
} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||||
import { camelCase } from 'src/utils/camel-case';
|
import { camelCase } from 'src/utils/camel-case';
|
||||||
|
|
||||||
const coreObjectNames = [
|
const coreObjectNames = [
|
||||||
@ -55,7 +59,10 @@ export const validateObjectMetadataInputOrThrow = <
|
|||||||
const validateNameIsNotReservedKeywordOrThrow = (name?: string) => {
|
const validateNameIsNotReservedKeywordOrThrow = (name?: string) => {
|
||||||
if (name) {
|
if (name) {
|
||||||
if (reservedKeywords.includes(name)) {
|
if (reservedKeywords.includes(name)) {
|
||||||
throw new ForbiddenException(`The name "${name}" is not available`);
|
throw new ObjectMetadataException(
|
||||||
|
`The name "${name}" is not available`,
|
||||||
|
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -63,7 +70,10 @@ const validateNameIsNotReservedKeywordOrThrow = (name?: string) => {
|
|||||||
const validateNameCamelCasedOrThrow = (name?: string) => {
|
const validateNameCamelCasedOrThrow = (name?: string) => {
|
||||||
if (name) {
|
if (name) {
|
||||||
if (name !== camelCase(name)) {
|
if (name !== camelCase(name)) {
|
||||||
throw new ForbiddenException(`Name should be in camelCase: ${name}`);
|
throw new ObjectMetadataException(
|
||||||
|
`Name should be in camelCase: ${name}`,
|
||||||
|
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -75,8 +85,9 @@ const validateNameCharactersOrThrow = (name?: string) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InvalidStringException) {
|
if (error instanceof InvalidStringException) {
|
||||||
throw new BadRequestException(
|
throw new ObjectMetadataException(
|
||||||
`Characters used in name "${name}" are not supported`,
|
`Characters used in name "${name}" are not supported`,
|
||||||
|
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class RelationMetadataException extends CustomException {
|
||||||
|
code: RelationMetadataExceptionCode;
|
||||||
|
constructor(message: string, code: RelationMetadataExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RelationMetadataExceptionCode {
|
||||||
|
RELATION_METADATA_NOT_FOUND = 'RELATION_METADATA_NOT_FOUND',
|
||||||
|
INVALID_RELATION_INPUT = 'INVALID_RELATION_INPUT',
|
||||||
|
RELATION_ALREADY_EXISTS = 'RELATION_ALREADY_EXISTS',
|
||||||
|
FOREIGN_KEY_NOT_FOUND = 'FOREIGN_KEY_NOT_FOUND',
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import { RelationMetadataService } from 'src/engine/metadata-modules/relation-me
|
|||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
|
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
|
||||||
import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input';
|
import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input';
|
||||||
|
import { relationMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/relation-metadata/utils/relation-metadata-graphql-api-exception-handler.util';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@ -20,9 +21,13 @@ export class RelationMetadataResolver {
|
|||||||
@Args('input') input: DeleteOneRelationInput,
|
@Args('input') input: DeleteOneRelationInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.relationMetadataService.deleteOneRelation(
|
try {
|
||||||
input.id,
|
return this.relationMetadataService.deleteOneRelation(
|
||||||
workspaceId,
|
input.id,
|
||||||
);
|
workspaceId,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
relationMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
ConflictException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
@ -28,9 +23,15 @@ import {
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||||
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
|
import {
|
||||||
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
validateMetadataName,
|
||||||
|
InvalidStringException,
|
||||||
|
} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||||
|
import {
|
||||||
|
RelationMetadataException,
|
||||||
|
RelationMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RelationMetadataEntity,
|
RelationMetadataEntity,
|
||||||
@ -66,8 +67,9 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
validateMetadataName(relationMetadataInput.toName);
|
validateMetadataName(relationMetadataInput.toName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InvalidStringException) {
|
if (error instanceof InvalidStringException) {
|
||||||
throw new BadRequestException(
|
throw new RelationMetadataException(
|
||||||
`Characters used in name "${relationMetadataInput.fromName}" or "${relationMetadataInput.toName}" are not supported`,
|
`Characters used in name "${relationMetadataInput.fromName}" or "${relationMetadataInput.toName}" are not supported`,
|
||||||
|
RelationMetadataExceptionCode.INVALID_RELATION_INPUT,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
@ -132,8 +134,9 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
if (
|
if (
|
||||||
relationMetadataInput.relationType === RelationMetadataType.MANY_TO_MANY
|
relationMetadataInput.relationType === RelationMetadataType.MANY_TO_MANY
|
||||||
) {
|
) {
|
||||||
throw new BadRequestException(
|
throw new RelationMetadataException(
|
||||||
'Many to many relations are not supported yet',
|
'Many to many relations are not supported yet',
|
||||||
|
RelationMetadataExceptionCode.INVALID_RELATION_INPUT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,8 +145,9 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
undefined ||
|
undefined ||
|
||||||
objectMetadataMap[relationMetadataInput.toObjectMetadataId] === undefined
|
objectMetadataMap[relationMetadataInput.toObjectMetadataId] === undefined
|
||||||
) {
|
) {
|
||||||
throw new NotFoundException(
|
throw new RelationMetadataException(
|
||||||
'Can\t find an existing object matching with fromObjectMetadataId or toObjectMetadataId',
|
'Can\t find an existing object matching with fromObjectMetadataId or toObjectMetadataId',
|
||||||
|
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,12 +181,13 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (fieldAlreadyExists) {
|
if (fieldAlreadyExists) {
|
||||||
throw new ConflictException(
|
throw new RelationMetadataException(
|
||||||
`Field on ${
|
`Field on ${
|
||||||
objectMetadataMap[
|
objectMetadataMap[
|
||||||
relationMetadataInput[`${relationDirection}ObjectMetadataId`]
|
relationMetadataInput[`${relationDirection}ObjectMetadataId`]
|
||||||
].nameSingular
|
].nameSingular
|
||||||
} already exists`,
|
} already exists`,
|
||||||
|
RelationMetadataExceptionCode.RELATION_ALREADY_EXISTS,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,7 +340,10 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!relationMetadata) {
|
if (!relationMetadata) {
|
||||||
throw new NotFoundException('Relation does not exist');
|
throw new RelationMetadataException(
|
||||||
|
'Relation does not exist',
|
||||||
|
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foreignKeyFieldMetadataName = `${camelCase(
|
const foreignKeyFieldMetadataName = `${camelCase(
|
||||||
@ -351,8 +359,9 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!foreignKeyFieldMetadata) {
|
if (!foreignKeyFieldMetadata) {
|
||||||
throw new NotFoundException(
|
throw new RelationMetadataException(
|
||||||
`Foreign key fieldMetadata not found (${foreignKeyFieldMetadataName}) for relation ${relationMetadata.id}`,
|
`Foreign key fieldMetadata not found (${foreignKeyFieldMetadataName}) for relation ${relationMetadata.id}`,
|
||||||
|
RelationMetadataExceptionCode.FOREIGN_KEY_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,6 +429,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
foundRelationMetadataItem ??
|
foundRelationMetadataItem ??
|
||||||
|
// TODO: return a relation metadata not found exception
|
||||||
new NotFoundException(
|
new NotFoundException(
|
||||||
`RelationMetadata with fieldMetadataId ${fieldMetadataId} not found`,
|
`RelationMetadata with fieldMetadataId ${fieldMetadataId} not found`,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
RelationMetadataException,
|
||||||
|
RelationMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception';
|
||||||
|
import {
|
||||||
|
UserInputError,
|
||||||
|
ConflictError,
|
||||||
|
InternalServerError,
|
||||||
|
NotFoundError,
|
||||||
|
} from 'src/engine/utils/graphql-errors.util';
|
||||||
|
|
||||||
|
export const relationMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||||
|
if (error instanceof RelationMetadataException) {
|
||||||
|
switch (error.code) {
|
||||||
|
case RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND:
|
||||||
|
throw new NotFoundError(error.message);
|
||||||
|
case RelationMetadataExceptionCode.INVALID_RELATION_INPUT:
|
||||||
|
throw new UserInputError(error.message);
|
||||||
|
case RelationMetadataExceptionCode.RELATION_ALREADY_EXISTS:
|
||||||
|
throw new ConflictError(error.message);
|
||||||
|
case RelationMetadataExceptionCode.FOREIGN_KEY_NOT_FOUND:
|
||||||
|
default:
|
||||||
|
throw new InternalServerError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class RemoteServerException extends CustomException {
|
||||||
|
code: RemoteServerExceptionCode;
|
||||||
|
constructor(message: string, code: RemoteServerExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RemoteServerExceptionCode {
|
||||||
|
REMOTE_SERVER_NOT_FOUND = 'REMOTE_SERVER_NOT_FOUND',
|
||||||
|
REMOTE_SERVER_ALREADY_EXISTS = 'REMOTE_SERVER_ALREADY_EXISTS',
|
||||||
|
REMOTE_SERVER_MUTATION_NOT_ALLOWED = 'REMOTE_SERVER_MUTATION_NOT_ALLOWED',
|
||||||
|
REMOTE_SERVER_CONNECTION_ERROR = 'REMOTE_SERVER_CONNECTION_ERROR',
|
||||||
|
INVALID_REMOTE_SERVER_INPUT = 'INVALID_REMOTE_SERVER_INPUT',
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ import { RemoteServerDTO } from 'src/engine/metadata-modules/remote-server/dtos/
|
|||||||
import { UpdateRemoteServerInput } from 'src/engine/metadata-modules/remote-server/dtos/update-remote-server.input';
|
import { UpdateRemoteServerInput } from 'src/engine/metadata-modules/remote-server/dtos/update-remote-server.input';
|
||||||
import { RemoteServerType } from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
import { RemoteServerType } from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
||||||
import { RemoteServerService } from 'src/engine/metadata-modules/remote-server/remote-server.service';
|
import { RemoteServerService } from 'src/engine/metadata-modules/remote-server/remote-server.service';
|
||||||
|
import { remoteServerGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/remote-server/utils/remote-server-graphql-api-exception-handler.util';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@ -24,7 +25,11 @@ export class RemoteServerResolver {
|
|||||||
@Args('input') input: CreateRemoteServerInput<RemoteServerType>,
|
@Args('input') input: CreateRemoteServerInput<RemoteServerType>,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteServerService.createOneRemoteServer(input, workspaceId);
|
try {
|
||||||
|
return this.remoteServerService.createOneRemoteServer(input, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
remoteServerGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => RemoteServerDTO)
|
@Mutation(() => RemoteServerDTO)
|
||||||
@ -32,7 +37,11 @@ export class RemoteServerResolver {
|
|||||||
@Args('input') input: UpdateRemoteServerInput<RemoteServerType>,
|
@Args('input') input: UpdateRemoteServerInput<RemoteServerType>,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteServerService.updateOneRemoteServer(input, workspaceId);
|
try {
|
||||||
|
return this.remoteServerService.updateOneRemoteServer(input, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
remoteServerGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => RemoteServerDTO)
|
@Mutation(() => RemoteServerDTO)
|
||||||
@ -40,7 +49,11 @@ export class RemoteServerResolver {
|
|||||||
@Args('input') { id }: RemoteServerIdInput,
|
@Args('input') { id }: RemoteServerIdInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteServerService.deleteOneRemoteServer(id, workspaceId);
|
try {
|
||||||
|
return this.remoteServerService.deleteOneRemoteServer(id, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
remoteServerGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => RemoteServerDTO)
|
@Query(() => RemoteServerDTO)
|
||||||
@ -48,7 +61,14 @@ export class RemoteServerResolver {
|
|||||||
@Args('input') { id }: RemoteServerIdInput,
|
@Args('input') { id }: RemoteServerIdInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteServerService.findOneByIdWithinWorkspace(id, workspaceId);
|
try {
|
||||||
|
return this.remoteServerService.findOneByIdWithinWorkspace(
|
||||||
|
id,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
remoteServerGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => [RemoteServerDTO])
|
@Query(() => [RemoteServerDTO])
|
||||||
@ -57,9 +77,13 @@ export class RemoteServerResolver {
|
|||||||
{ foreignDataWrapperType }: RemoteServerTypeInput<RemoteServerType>,
|
{ foreignDataWrapperType }: RemoteServerTypeInput<RemoteServerType>,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteServerService.findManyByTypeWithinWorkspace(
|
try {
|
||||||
foreignDataWrapperType,
|
return this.remoteServerService.findManyByTypeWithinWorkspace(
|
||||||
workspaceId,
|
foreignDataWrapperType,
|
||||||
);
|
workspaceId,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
remoteServerGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Injectable } from '@nestjs/common';
|
||||||
ForbiddenException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
@ -27,6 +23,10 @@ import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/work
|
|||||||
import { buildUpdateRemoteServerRawQuery } from 'src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils';
|
import { buildUpdateRemoteServerRawQuery } from 'src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils';
|
||||||
import { validateRemoteServerType } from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util';
|
import { validateRemoteServerType } from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util';
|
||||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
|
import {
|
||||||
|
RemoteServerException,
|
||||||
|
RemoteServerExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-server.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RemoteServerService<T extends RemoteServerType> {
|
export class RemoteServerService<T extends RemoteServerType> {
|
||||||
@ -122,7 +122,10 @@ export class RemoteServerService<T extends RemoteServerType> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!remoteServer) {
|
if (!remoteServer) {
|
||||||
throw new NotFoundException('Remote server does not exist');
|
throw new RemoteServerException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentRemoteTablesForServer =
|
const currentRemoteTablesForServer =
|
||||||
@ -132,8 +135,9 @@ export class RemoteServerService<T extends RemoteServerType> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (currentRemoteTablesForServer.length > 0) {
|
if (currentRemoteTablesForServer.length > 0) {
|
||||||
throw new ForbiddenException(
|
throw new RemoteServerException(
|
||||||
'Cannot update remote server with synchronized tables',
|
'Cannot update remote server with synchronized tables',
|
||||||
|
RemoteServerExceptionCode.REMOTE_SERVER_MUTATION_NOT_ALLOWED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +211,10 @@ export class RemoteServerService<T extends RemoteServerType> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!remoteServer) {
|
if (!remoteServer) {
|
||||||
throw new NotFoundException('Remote server does not exist');
|
throw new RemoteServerException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.remoteTableService.unsyncAll(workspaceId, remoteServer);
|
await this.remoteTableService.unsyncAll(workspaceId, remoteServer);
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class DistantTableException extends CustomException {
|
||||||
|
code: DistantTableExceptionCode;
|
||||||
|
constructor(message: string, code: DistantTableExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DistantTableExceptionCode {
|
||||||
|
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||||
|
TIMEOUT_ERROR = 'TIMEOUT_ERROR',
|
||||||
|
}
|
||||||
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
Injectable,
|
|
||||||
RequestTimeoutException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { EntityManager, Repository } from 'typeorm';
|
import { EntityManager, Repository } from 'typeorm';
|
||||||
@ -17,6 +13,10 @@ import { DistantTables } from 'src/engine/metadata-modules/remote-server/remote-
|
|||||||
import { STRIPE_DISTANT_TABLES } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/utils/stripe-distant-tables.util';
|
import { STRIPE_DISTANT_TABLES } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/utils/stripe-distant-tables.util';
|
||||||
import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column';
|
import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column';
|
||||||
import { isQueryTimeoutError } from 'src/engine/utils/query-timeout.util';
|
import { isQueryTimeoutError } from 'src/engine/utils/query-timeout.util';
|
||||||
|
import {
|
||||||
|
DistantTableException,
|
||||||
|
DistantTableExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DistantTableService {
|
export class DistantTableService {
|
||||||
@ -64,7 +64,10 @@ export class DistantTableService {
|
|||||||
tableName?: string,
|
tableName?: string,
|
||||||
): Promise<DistantTables> {
|
): Promise<DistantTables> {
|
||||||
if (!remoteServer.schema) {
|
if (!remoteServer.schema) {
|
||||||
throw new BadRequestException('Remote server schema is not defined');
|
throw new DistantTableException(
|
||||||
|
'Remote server schema is not defined',
|
||||||
|
DistantTableExceptionCode.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmpSchemaId = v4();
|
const tmpSchemaId = v4();
|
||||||
@ -116,8 +119,9 @@ export class DistantTableService {
|
|||||||
return distantTables;
|
return distantTables;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isQueryTimeoutError(error)) {
|
if (isQueryTimeoutError(error)) {
|
||||||
throw new RequestTimeoutException(
|
throw new DistantTableException(
|
||||||
`Could not find distant tables: ${error.message}`,
|
`Could not find distant tables: ${error.message}`,
|
||||||
|
DistantTableExceptionCode.TIMEOUT_ERROR,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,8 +136,9 @@ export class DistantTableService {
|
|||||||
case RemoteServerType.STRIPE_FDW:
|
case RemoteServerType.STRIPE_FDW:
|
||||||
return STRIPE_DISTANT_TABLES;
|
return STRIPE_DISTANT_TABLES;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException(
|
throw new DistantTableException(
|
||||||
`Type ${remoteServer.foreignDataWrapperType} does not have a static schema.`,
|
`Type ${remoteServer.foreignDataWrapperType} does not have a static schema.`,
|
||||||
|
DistantTableExceptionCode.INTERNAL_SERVER_ERROR,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class ForeignTableException extends CustomException {
|
||||||
|
code: ForeignTableExceptionCode;
|
||||||
|
constructor(message: string, code: ForeignTableExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ForeignTableExceptionCode {
|
||||||
|
FOREIGN_TABLE_MUTATION_NOT_ALLOWED = 'FOREIGN_TABLE_MUTATION_NOT_ALLOWED',
|
||||||
|
INVALID_FOREIGN_TABLE_INPUT = 'INVALID_FOREIGN_TABLE_INPUT',
|
||||||
|
}
|
||||||
@ -1,10 +1,14 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RemoteServerEntity,
|
RemoteServerEntity,
|
||||||
RemoteServerType,
|
RemoteServerType,
|
||||||
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
||||||
import { RemoteTableStatus } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto';
|
import { RemoteTableStatus } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto';
|
||||||
|
import {
|
||||||
|
ForeignTableException,
|
||||||
|
ForeignTableExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception';
|
||||||
import { getForeignTableColumnName } from 'src/engine/metadata-modules/remote-server/remote-table/foreign-table/utils/get-foreign-table-column-name.util';
|
import { getForeignTableColumnName } from 'src/engine/metadata-modules/remote-server/remote-table/foreign-table/utils/get-foreign-table-column-name.util';
|
||||||
import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column';
|
import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column';
|
||||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||||
@ -90,8 +94,9 @@ export class ForeignTableService {
|
|||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
this.workspaceMigrationService.deleteById(workspaceMigration.id);
|
this.workspaceMigrationService.deleteById(workspaceMigration.id);
|
||||||
|
|
||||||
throw new BadRequestException(
|
throw new ForeignTableException(
|
||||||
'Could not create foreign table. The table may already exists or a column type may not be supported.',
|
'Could not create foreign table. The table may already exists or a column type may not be supported.',
|
||||||
|
ForeignTableExceptionCode.INVALID_FOREIGN_TABLE_INPUT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,7 +135,10 @@ export class ForeignTableService {
|
|||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
this.workspaceMigrationService.deleteById(workspaceMigration.id);
|
this.workspaceMigrationService.deleteById(workspaceMigration.id);
|
||||||
|
|
||||||
throw new BadRequestException('Could not alter foreign table.');
|
throw new ForeignTableException(
|
||||||
|
'Could not alter foreign table.',
|
||||||
|
ForeignTableExceptionCode.FOREIGN_TABLE_MUTATION_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +175,10 @@ export class ForeignTableService {
|
|||||||
case RemoteServerType.STRIPE_FDW:
|
case RemoteServerType.STRIPE_FDW:
|
||||||
return { object: distantTableName };
|
return { object: distantTableName };
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException('Foreign data wrapper not supported');
|
throw new ForeignTableException(
|
||||||
|
'Foreign data wrapper not supported',
|
||||||
|
ForeignTableExceptionCode.INVALID_FOREIGN_TABLE_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class RemoteTableException extends CustomException {
|
||||||
|
code: RemoteTableExceptionCode;
|
||||||
|
constructor(message: string, code: RemoteTableExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RemoteTableExceptionCode {
|
||||||
|
REMOTE_TABLE_NOT_FOUND = 'REMOTE_TABLE_NOT_FOUND',
|
||||||
|
INVALID_REMOTE_TABLE_INPUT = 'INVALID_REMOTE_TABLE_INPUT',
|
||||||
|
REMOTE_TABLE_ALREADY_EXISTS = 'REMOTE_TABLE_ALREADY_EXISTS',
|
||||||
|
NO_FOREIGN_TABLES_FOUND = 'NO_FOREIGN_TABLES_FOUND',
|
||||||
|
NO_OBJECT_METADATA_FOUND = 'NO_OBJECT_METADATA_FOUND',
|
||||||
|
NO_FIELD_METADATA_FOUND = 'NO_FIELD_METADATA_FOUND',
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import { FindManyRemoteTablesInput } from 'src/engine/metadata-modules/remote-se
|
|||||||
import { RemoteTableInput } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input';
|
import { RemoteTableInput } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input';
|
||||||
import { RemoteTableDTO } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto';
|
import { RemoteTableDTO } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto';
|
||||||
import { RemoteTableService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.service';
|
import { RemoteTableService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.service';
|
||||||
|
import { remoteTableGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/remote-server/remote-table/utils/remote-table-graphql-api-exception-handler.util';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@ -19,11 +20,15 @@ export class RemoteTableResolver {
|
|||||||
@Args('input') input: FindManyRemoteTablesInput,
|
@Args('input') input: FindManyRemoteTablesInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteTableService.findDistantTablesWithStatus(
|
try {
|
||||||
input.id,
|
return this.remoteTableService.findDistantTablesWithStatus(
|
||||||
workspaceId,
|
input.id,
|
||||||
input.shouldFetchPendingSchemaUpdates,
|
workspaceId,
|
||||||
);
|
input.shouldFetchPendingSchemaUpdates,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
remoteTableGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => RemoteTableDTO)
|
@Mutation(() => RemoteTableDTO)
|
||||||
@ -31,7 +36,11 @@ export class RemoteTableResolver {
|
|||||||
@Args('input') input: RemoteTableInput,
|
@Args('input') input: RemoteTableInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteTableService.syncRemoteTable(input, workspaceId);
|
try {
|
||||||
|
return this.remoteTableService.syncRemoteTable(input, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
remoteTableGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => RemoteTableDTO)
|
@Mutation(() => RemoteTableDTO)
|
||||||
@ -39,7 +48,11 @@ export class RemoteTableResolver {
|
|||||||
@Args('input') input: RemoteTableInput,
|
@Args('input') input: RemoteTableInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteTableService.unsyncRemoteTable(input, workspaceId);
|
try {
|
||||||
|
return this.remoteTableService.unsyncRemoteTable(input, workspaceId);
|
||||||
|
} catch (error) {
|
||||||
|
remoteTableGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => RemoteTableDTO)
|
@Mutation(() => RemoteTableDTO)
|
||||||
@ -47,9 +60,13 @@ export class RemoteTableResolver {
|
|||||||
@Args('input') input: RemoteTableInput,
|
@Args('input') input: RemoteTableInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.remoteTableService.syncRemoteTableSchemaChanges(
|
try {
|
||||||
input,
|
return this.remoteTableService.syncRemoteTableSchemaChanges(
|
||||||
workspaceId,
|
input,
|
||||||
);
|
workspaceId,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
remoteTableGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Logger, NotFoundException } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
@ -40,6 +40,10 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||||
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import {
|
||||||
|
RemoteTableException,
|
||||||
|
RemoteTableExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.exception';
|
||||||
|
|
||||||
export class RemoteTableService {
|
export class RemoteTableService {
|
||||||
private readonly logger = new Logger(RemoteTableService.name);
|
private readonly logger = new Logger(RemoteTableService.name);
|
||||||
@ -74,7 +78,10 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!remoteServer) {
|
if (!remoteServer) {
|
||||||
throw new NotFoundException('Remote server does not exist');
|
throw new RemoteTableException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentRemoteTables = await this.findRemoteTablesByServerId({
|
const currentRemoteTables = await this.findRemoteTablesByServerId({
|
||||||
@ -148,7 +155,10 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!remoteServer) {
|
if (!remoteServer) {
|
||||||
throw new NotFoundException('Remote server does not exist');
|
throw new RemoteTableException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentRemoteTableWithSameDistantName =
|
const currentRemoteTableWithSameDistantName =
|
||||||
@ -161,7 +171,10 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (currentRemoteTableWithSameDistantName) {
|
if (currentRemoteTableWithSameDistantName) {
|
||||||
throw new BadRequestException('Remote table already exists');
|
throw new RemoteTableException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataSourceMetatada =
|
const dataSourceMetatada =
|
||||||
@ -200,7 +213,10 @@ export class RemoteTableService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!distantTableColumns) {
|
if (!distantTableColumns) {
|
||||||
throw new BadRequestException('Table not found');
|
throw new RemoteTableException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only support remote tables with an id column for now.
|
// We only support remote tables with an id column for now.
|
||||||
@ -209,7 +225,10 @@ export class RemoteTableService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!distantTableIdColumn) {
|
if (!distantTableIdColumn) {
|
||||||
throw new BadRequestException('Remote table must have an id column');
|
throw new RemoteTableException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.foreignTableService.createForeignTable(
|
await this.foreignTableService.createForeignTable(
|
||||||
@ -250,7 +269,10 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!remoteServer) {
|
if (!remoteServer) {
|
||||||
throw new NotFoundException('Remote server does not exist');
|
throw new RemoteTableException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteTable = await this.remoteTableRepository.findOne({
|
const remoteTable = await this.remoteTableRepository.findOne({
|
||||||
@ -262,7 +284,10 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!remoteTable) {
|
if (!remoteTable) {
|
||||||
throw new NotFoundException('Remote table does not exist');
|
throw new RemoteTableException(
|
||||||
|
'Remote table does not exist',
|
||||||
|
RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.unsyncOne(workspaceId, remoteTable, remoteServer);
|
await this.unsyncOne(workspaceId, remoteTable, remoteServer);
|
||||||
@ -302,7 +327,10 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!remoteServer) {
|
if (!remoteServer) {
|
||||||
throw new NotFoundException('Remote server does not exist');
|
throw new RemoteTableException(
|
||||||
|
'Remote server does not exist',
|
||||||
|
RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteTable = await this.remoteTableRepository.findOne({
|
const remoteTable = await this.remoteTableRepository.findOne({
|
||||||
@ -314,7 +342,10 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!remoteTable) {
|
if (!remoteTable) {
|
||||||
throw new NotFoundException('Remote table does not exist');
|
throw new RemoteTableException(
|
||||||
|
'Remote table does not exist',
|
||||||
|
RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const distantTableColumns =
|
const distantTableColumns =
|
||||||
@ -379,7 +410,10 @@ export class RemoteTableService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!currentForeignTableNames.includes(remoteTable.localTableName)) {
|
if (!currentForeignTableNames.includes(remoteTable.localTableName)) {
|
||||||
throw new NotFoundException('Foreign table does not exist');
|
throw new RemoteTableException(
|
||||||
|
'Foreign table does not exist',
|
||||||
|
RemoteTableExceptionCode.NO_FOREIGN_TABLES_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectMetadata =
|
const objectMetadata =
|
||||||
@ -507,8 +541,9 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!objectMetadata) {
|
if (!objectMetadata) {
|
||||||
throw new NotFoundException(
|
throw new RemoteTableException(
|
||||||
`Cannot find associated object for table ${foreignTableName}`,
|
`Cannot find associated object for table ${foreignTableName}`,
|
||||||
|
RemoteTableExceptionCode.NO_OBJECT_METADATA_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const columnUpdate of columnsUpdates) {
|
for (const columnUpdate of columnsUpdates) {
|
||||||
@ -547,8 +582,9 @@ export class RemoteTableService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!fieldMetadataToDelete) {
|
if (!fieldMetadataToDelete) {
|
||||||
throw new NotFoundException(
|
throw new RemoteTableException(
|
||||||
`Cannot find associated field metadata for column ${columnName}`,
|
`Cannot find associated field metadata for column ${columnName}`,
|
||||||
|
RemoteTableExceptionCode.NO_FIELD_METADATA_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { BadRequestException } from '@nestjs/common/exceptions';
|
|
||||||
|
|
||||||
import { singular } from 'pluralize';
|
import { singular } from 'pluralize';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
|
|
||||||
import { camelCase } from 'src/utils/camel-case';
|
import { camelCase } from 'src/utils/camel-case';
|
||||||
|
import {
|
||||||
|
RemoteTableException,
|
||||||
|
RemoteTableExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.exception';
|
||||||
|
|
||||||
const MAX_SUFFIX = 10;
|
const MAX_SUFFIX = 10;
|
||||||
|
|
||||||
@ -55,5 +57,8 @@ export const getRemoteTableLocalName = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadRequestException('Table name is already taken.');
|
throw new RemoteTableException(
|
||||||
|
'Table name is already taken',
|
||||||
|
RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import {
|
||||||
|
RemoteTableException,
|
||||||
|
RemoteTableExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.exception';
|
||||||
|
import {
|
||||||
|
UserInputError,
|
||||||
|
ConflictError,
|
||||||
|
InternalServerError,
|
||||||
|
NotFoundError,
|
||||||
|
} from 'src/engine/utils/graphql-errors.util';
|
||||||
|
|
||||||
|
export const remoteTableGraphqlApiExceptionHandler = (error: Error) => {
|
||||||
|
if (error instanceof RemoteTableException) {
|
||||||
|
switch (error.code) {
|
||||||
|
case RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND:
|
||||||
|
case RemoteTableExceptionCode.NO_OBJECT_METADATA_FOUND:
|
||||||
|
case RemoteTableExceptionCode.NO_FOREIGN_TABLES_FOUND:
|
||||||
|
case RemoteTableExceptionCode.NO_FIELD_METADATA_FOUND:
|
||||||
|
throw new NotFoundError(error.message);
|
||||||
|
case RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT:
|
||||||
|
throw new UserInputError(error.message);
|
||||||
|
case RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS:
|
||||||
|
throw new ConflictError(error.message);
|
||||||
|
default:
|
||||||
|
throw new InternalServerError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
@ -1,10 +1,12 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ForeignDataWrapperOptions,
|
ForeignDataWrapperOptions,
|
||||||
RemoteServerEntity,
|
RemoteServerEntity,
|
||||||
RemoteServerType,
|
RemoteServerType,
|
||||||
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
||||||
|
import {
|
||||||
|
RemoteServerException,
|
||||||
|
RemoteServerExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-server.exception';
|
||||||
import { UserMappingOptions } from 'src/engine/metadata-modules/remote-server/types/user-mapping-options';
|
import { UserMappingOptions } from 'src/engine/metadata-modules/remote-server/types/user-mapping-options';
|
||||||
|
|
||||||
export type DeepPartial<T> = {
|
export type DeepPartial<T> = {
|
||||||
@ -49,7 +51,10 @@ export const buildUpdateRemoteServerRawQuery = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.length < 1) {
|
if (options.length < 1) {
|
||||||
throw new BadRequestException('No fields to update');
|
throw new RemoteServerException(
|
||||||
|
'No fields to update',
|
||||||
|
RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawQuery = `UPDATE metadata."remoteServer" SET ${options.join(
|
const rawQuery = `UPDATE metadata."remoteServer" SET ${options.join(
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import {
|
||||||
|
RemoteServerException,
|
||||||
|
RemoteServerExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-server.exception';
|
||||||
|
import {
|
||||||
|
UserInputError,
|
||||||
|
ForbiddenError,
|
||||||
|
ConflictError,
|
||||||
|
InternalServerError,
|
||||||
|
NotFoundError,
|
||||||
|
} from 'src/engine/utils/graphql-errors.util';
|
||||||
|
|
||||||
|
export const remoteServerGraphqlApiExceptionHandler = (error: any) => {
|
||||||
|
if (error instanceof RemoteServerException) {
|
||||||
|
switch (error.code) {
|
||||||
|
case RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND:
|
||||||
|
throw new NotFoundError(error.message);
|
||||||
|
case RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT:
|
||||||
|
throw new UserInputError(error.message);
|
||||||
|
case RemoteServerExceptionCode.REMOTE_SERVER_MUTATION_NOT_ALLOWED:
|
||||||
|
throw new ForbiddenError(error.message);
|
||||||
|
case RemoteServerExceptionCode.REMOTE_SERVER_ALREADY_EXISTS:
|
||||||
|
throw new ConflictError(error.message);
|
||||||
|
default:
|
||||||
|
throw new InternalServerError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
@ -1,7 +1,10 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { isDefined } from 'class-validator';
|
import { isDefined } from 'class-validator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
RemoteServerException,
|
||||||
|
RemoteServerExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-server.exception';
|
||||||
|
|
||||||
const INPUT_REGEX = /^([A-Za-z0-9\-_.@]+)$/;
|
const INPUT_REGEX = /^([A-Za-z0-9\-_.@]+)$/;
|
||||||
|
|
||||||
export const validateObjectAgainstInjections = (input: object) => {
|
export const validateObjectAgainstInjections = (input: object) => {
|
||||||
@ -21,6 +24,9 @@ export const validateObjectAgainstInjections = (input: object) => {
|
|||||||
|
|
||||||
export const validateStringAgainstInjections = (input: string) => {
|
export const validateStringAgainstInjections = (input: string) => {
|
||||||
if (!INPUT_REGEX.test(input)) {
|
if (!INPUT_REGEX.test(input)) {
|
||||||
throw new BadRequestException('Invalid remote server input');
|
throw new RemoteServerException(
|
||||||
|
'Invalid remote server input',
|
||||||
|
RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -7,6 +5,10 @@ import {
|
|||||||
FeatureFlagKeys,
|
FeatureFlagKeys,
|
||||||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
import { RemoteServerType } from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
import { RemoteServerType } from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
||||||
|
import {
|
||||||
|
RemoteServerException,
|
||||||
|
RemoteServerExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/remote-server/remote-server.exception';
|
||||||
|
|
||||||
export const validateRemoteServerType = async (
|
export const validateRemoteServerType = async (
|
||||||
remoteServerType: RemoteServerType,
|
remoteServerType: RemoteServerType,
|
||||||
@ -24,7 +26,10 @@ export const validateRemoteServerType = async (
|
|||||||
const featureFlagEnabled = featureFlag && featureFlag.value;
|
const featureFlagEnabled = featureFlag && featureFlag.value;
|
||||||
|
|
||||||
if (!featureFlagEnabled) {
|
if (!featureFlagEnabled) {
|
||||||
throw new BadRequestException(`Type ${remoteServerType} is not supported.`);
|
throw new RemoteServerException(
|
||||||
|
`Type ${remoteServerType} is not supported.`,
|
||||||
|
RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,8 +40,9 @@ const getFeatureFlagKey = (remoteServerType: RemoteServerType) => {
|
|||||||
case RemoteServerType.STRIPE_FDW:
|
case RemoteServerType.STRIPE_FDW:
|
||||||
return FeatureFlagKeys.IsStripeIntegrationEnabled;
|
return FeatureFlagKeys.IsStripeIntegrationEnabled;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestException(
|
throw new RemoteServerException(
|
||||||
`Type ${remoteServerType} is not supported.`,
|
`Type ${remoteServerType} is not supported.`,
|
||||||
|
RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
|
import {
|
||||||
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
validateMetadataName,
|
||||||
|
InvalidStringException,
|
||||||
|
} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||||
|
|
||||||
describe('validateMetadataName', () => {
|
describe('validateMetadataName', () => {
|
||||||
it('does not throw if string is valid', () => {
|
it('does not throw if string is valid', () => {
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
|
|
||||||
|
|
||||||
const VALID_STRING_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
|
const VALID_STRING_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
|
||||||
|
|
||||||
export const validateMetadataName = (string: string) => {
|
export const validateMetadataName = (string: string) => {
|
||||||
@ -7,3 +5,11 @@ export const validateMetadataName = (string: string) => {
|
|||||||
throw new InvalidStringException(string);
|
throw new InvalidStringException(string);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class InvalidStringException extends Error {
|
||||||
|
constructor(string: string) {
|
||||||
|
const message = `String "${string}" is not valid`;
|
||||||
|
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,10 @@ import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadat
|
|||||||
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
||||||
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
|
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
|
||||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
export type BasicFieldMetadataType =
|
export type BasicFieldMetadataType =
|
||||||
| FieldMetadataType.UUID
|
| FieldMetadataType.UUID
|
||||||
@ -66,8 +70,9 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF
|
|||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Column name not found for current or altered field metadata, can be due to a missing or an invalid target column map. Current column name: ${currentColumnName}, Altered column name: ${alteredColumnName}.`,
|
`Column name not found for current or altered field metadata, can be due to a missing or an invalid target column map. Current column name: ${currentColumnName}, Altered column name: ${alteredColumnName}.`,
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new WorkspaceMigrationException(
|
||||||
`Column name not found for current or altered field metadata`,
|
`Column name not found for current or altered field metadata`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,10 @@ import {
|
|||||||
WorkspaceMigrationColumnAlter,
|
WorkspaceMigrationColumnAlter,
|
||||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
export class ColumnActionAbstractFactory<
|
export class ColumnActionAbstractFactory<
|
||||||
T extends FieldMetadataType | 'default',
|
T extends FieldMetadataType | 'default',
|
||||||
@ -32,7 +36,10 @@ export class ColumnActionAbstractFactory<
|
|||||||
return this.handleCreateAction(alteredFieldMetadata, options);
|
return this.handleCreateAction(alteredFieldMetadata, options);
|
||||||
case WorkspaceMigrationColumnActionType.ALTER: {
|
case WorkspaceMigrationColumnActionType.ALTER: {
|
||||||
if (!currentFieldMetadata) {
|
if (!currentFieldMetadata) {
|
||||||
throw new Error('current field metadata is required for alter');
|
throw new WorkspaceMigrationException(
|
||||||
|
'current field metadata is required for alter',
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.handleAlterAction(
|
return this.handleAlterAction(
|
||||||
@ -43,8 +50,10 @@ export class ColumnActionAbstractFactory<
|
|||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
this.logger.error(`Invalid action: ${action}`);
|
this.logger.error(`Invalid action: ${action}`);
|
||||||
|
throw new WorkspaceMigrationException(
|
||||||
throw new Error('[AbstractFactory]: invalid action');
|
'[AbstractFactory]: invalid action',
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_ACTION,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +62,10 @@ export class ColumnActionAbstractFactory<
|
|||||||
_fieldMetadata: FieldMetadataInterface<T>,
|
_fieldMetadata: FieldMetadataInterface<T>,
|
||||||
_options?: WorkspaceColumnActionOptions,
|
_options?: WorkspaceColumnActionOptions,
|
||||||
): WorkspaceMigrationColumnCreate[] {
|
): WorkspaceMigrationColumnCreate[] {
|
||||||
throw new Error('handleCreateAction method not implemented.');
|
throw new WorkspaceMigrationException(
|
||||||
|
'handleCreateAction method not implemented.',
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_ACTION,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleAlterAction(
|
protected handleAlterAction(
|
||||||
@ -61,6 +73,9 @@ export class ColumnActionAbstractFactory<
|
|||||||
_alteredFieldMetadata: FieldMetadataInterface<T>,
|
_alteredFieldMetadata: FieldMetadataInterface<T>,
|
||||||
_options?: WorkspaceColumnActionOptions,
|
_options?: WorkspaceColumnActionOptions,
|
||||||
): WorkspaceMigrationColumnAlter[] {
|
): WorkspaceMigrationColumnAlter[] {
|
||||||
throw new Error('handleAlterAction method not implemented.');
|
throw new WorkspaceMigrationException(
|
||||||
|
'handleAlterAction method not implemented.',
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_ACTION,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,10 @@ import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/works
|
|||||||
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
|
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
|
||||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
export type CompositeFieldMetadataType =
|
export type CompositeFieldMetadataType =
|
||||||
| FieldMetadataType.ADDRESS
|
| FieldMetadataType.ADDRESS
|
||||||
@ -34,8 +38,9 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co
|
|||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
|
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new WorkspaceMigrationException(
|
||||||
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
|
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,8 +79,9 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co
|
|||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Composite type not found for field metadata type: ${currentFieldMetadata.type} or ${alteredFieldMetadata.type}`,
|
`Composite type not found for field metadata type: ${currentFieldMetadata.type} or ${alteredFieldMetadata.type}`,
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new WorkspaceMigrationException(
|
||||||
`Composite type not found for field metadata type: ${currentFieldMetadata.type} or ${alteredFieldMetadata.type}`,
|
`Composite type not found for field metadata type: ${currentFieldMetadata.type} or ${alteredFieldMetadata.type}`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +97,9 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<Co
|
|||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Current property not found for altered property: ${alteredProperty.name}`,
|
`Current property not found for altered property: ${alteredProperty.name}`,
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new WorkspaceMigrationException(
|
||||||
`Current property not found for altered property: ${alteredProperty.name}`,
|
`Current property not found for altered property: ${alteredProperty.name}`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,10 @@ import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadat
|
|||||||
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
||||||
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
|
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
|
||||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
export type EnumFieldMetadataType =
|
export type EnumFieldMetadataType =
|
||||||
| FieldMetadataType.RATING
|
| FieldMetadataType.RATING
|
||||||
@ -82,8 +86,9 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie
|
|||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Column name not found for current or altered field metadata, can be due to a missing or an invalid target column map. Current column name: ${currentColumnName}, Altered column name: ${alteredColumnName}.`,
|
`Column name not found for current or altered field metadata, can be due to a missing or an invalid target column map. Current column name: ${currentColumnName}, Altered column name: ${alteredColumnName}.`,
|
||||||
);
|
);
|
||||||
throw new Error(
|
throw new WorkspaceMigrationException(
|
||||||
`Column name not found for current or altered field metadata`,
|
`Column name not found for current or altered field metadata`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
export const fieldMetadataTypeToColumnType = <Type extends FieldMetadataType>(
|
export const fieldMetadataTypeToColumnType = <Type extends FieldMetadataType>(
|
||||||
fieldMetadataType: Type,
|
fieldMetadataType: Type,
|
||||||
@ -34,6 +38,9 @@ export const fieldMetadataTypeToColumnType = <Type extends FieldMetadataType>(
|
|||||||
case FieldMetadataType.RAW_JSON:
|
case FieldMetadataType.RAW_JSON:
|
||||||
return 'jsonb';
|
return 'jsonb';
|
||||||
default:
|
default:
|
||||||
throw new Error(`Cannot convert ${fieldMetadataType} to column type.`);
|
throw new WorkspaceMigrationException(
|
||||||
|
`Cannot convert ${fieldMetadataType} to column type.`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class WorkspaceMigrationException extends CustomException {
|
||||||
|
code: WorkspaceMigrationExceptionCode;
|
||||||
|
constructor(message: string, code: WorkspaceMigrationExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WorkspaceMigrationExceptionCode {
|
||||||
|
NO_FACTORY_FOUND = 'NO_FACTORY_FOUND',
|
||||||
|
INVALID_ACTION = 'INVALID_ACTION',
|
||||||
|
INVALID_FIELD_METADATA = 'INVALID_FIELD_METADATA',
|
||||||
|
}
|
||||||
@ -12,6 +12,10 @@ import {
|
|||||||
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
||||||
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
||||||
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceMigrationFactory {
|
export class WorkspaceMigrationFactory {
|
||||||
@ -131,7 +135,10 @@ export class WorkspaceMigrationFactory {
|
|||||||
undefinedOrAlteredFieldMetadata,
|
undefinedOrAlteredFieldMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new Error(`No field metadata provided for action ${action}`);
|
throw new WorkspaceMigrationException(
|
||||||
|
`No field metadata provided for action ${action}`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_ACTION,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnActions = this.createColumnAction(
|
const columnActions = this.createColumnAction(
|
||||||
@ -161,7 +168,10 @@ export class WorkspaceMigrationFactory {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new Error(`No factory found for type ${alteredFieldMetadata.type}`);
|
throw new WorkspaceMigrationException(
|
||||||
|
`No factory found for type ${alteredFieldMetadata.type}`,
|
||||||
|
WorkspaceMigrationExceptionCode.NO_FACTORY_FOUND,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return factory.create(
|
return factory.create(
|
||||||
|
|||||||
@ -183,3 +183,11 @@ export class TimeoutError extends BaseGraphQLError {
|
|||||||
Object.defineProperty(this, 'name', { value: 'TimeoutError' });
|
Object.defineProperty(this, 'name', { value: 'TimeoutError' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InternalServerError extends BaseGraphQLError {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message, ErrorCode.INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'name', { value: 'InternalServerError' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
8
packages/twenty-server/src/utils/custom-exception.ts
Normal file
8
packages/twenty-server/src/utils/custom-exception.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export class CustomException extends Error {
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
constructor(message: string, code: string) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user