[feat] Add updateRemoteServer endpoint (#5148)
## Context #4765 Following investigations ([#5083](https://github.com/twentyhq/twenty/issues/5083)) we decided to restrict updates of server from which zero tables have been synchronized only ## How was it tested Locally with /metadata 1. Updating a database that already has synchronized tables <img width="1072" alt="Capture d’écran 2024-04-24 à 16 16 05" src="https://github.com/twentyhq/twenty/assets/51697796/f9a84c34-2dcd-4f3c-b0bc-b710abae5021"> 2. Updating a database that has no synchronized tables <img width="843" alt="Capture d’écran 2024-04-24 à 16 17 28" src="https://github.com/twentyhq/twenty/assets/51697796/f320fe03-a6bc-4724-bcd0-4e89d3ac31f5"> + tested that the connection works well
This commit is contained in:
@ -0,0 +1,138 @@
|
||||
import { isDefined } from 'class-validator';
|
||||
|
||||
import {
|
||||
RemoteServerEntity,
|
||||
RemoteServerType,
|
||||
UserMappingOptions,
|
||||
} from 'src/engine/metadata-modules/remote-server/remote-server.entity';
|
||||
|
||||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
|
||||
const buildUserMappingOptionsQuery = (
|
||||
parameters: any[],
|
||||
parametersPositions: object,
|
||||
userMappingOptions: DeepPartial<UserMappingOptions>,
|
||||
): string | null => {
|
||||
const shouldUpdateUserMappingOptionsPassword = isDefined(
|
||||
userMappingOptions?.password,
|
||||
);
|
||||
|
||||
if (shouldUpdateUserMappingOptionsPassword) {
|
||||
parameters.push(userMappingOptions?.password);
|
||||
parametersPositions['password'] = parameters.length;
|
||||
}
|
||||
|
||||
const shouldUpdateUserMappingOptionsUsername = isDefined(
|
||||
userMappingOptions?.username,
|
||||
);
|
||||
|
||||
if (shouldUpdateUserMappingOptionsUsername) {
|
||||
parameters.push(userMappingOptions?.username);
|
||||
parametersPositions['username'] = parameters.length;
|
||||
}
|
||||
|
||||
if (
|
||||
shouldUpdateUserMappingOptionsPassword ||
|
||||
shouldUpdateUserMappingOptionsUsername
|
||||
) {
|
||||
return `"userMappingOptions" = jsonb_set(${
|
||||
shouldUpdateUserMappingOptionsPassword &&
|
||||
shouldUpdateUserMappingOptionsUsername
|
||||
? `jsonb_set(
|
||||
"userMappingOptions",
|
||||
'{username}',
|
||||
to_jsonb($${parametersPositions['username']}::text)
|
||||
),
|
||||
'{password}',
|
||||
to_jsonb($${parametersPositions['password']}::text)
|
||||
`
|
||||
: shouldUpdateUserMappingOptionsPassword
|
||||
? `"userMappingOptions",
|
||||
'{password}',
|
||||
to_jsonb($${parametersPositions['password']}::text)
|
||||
`
|
||||
: `"userMappingOptions",
|
||||
'{username}',
|
||||
to_jsonb($${parametersPositions['username']}::text)
|
||||
`
|
||||
})`;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// TO DO This only works for postgres_fdw type for now, lets make it more generic when we have a different type
|
||||
export const updateRemoteServerRawQuery = (
|
||||
remoteServerToUpdate: DeepPartial<RemoteServerEntity<RemoteServerType>> &
|
||||
Pick<RemoteServerEntity<RemoteServerType>, 'workspaceId' | 'id'>,
|
||||
): [any[], string] => {
|
||||
const parameters: any[] = [remoteServerToUpdate.id];
|
||||
const parametersPositions = {};
|
||||
|
||||
const options: string[] = [];
|
||||
|
||||
if (remoteServerToUpdate.userMappingOptions) {
|
||||
const userMappingOptionsQuery = buildUserMappingOptionsQuery(
|
||||
parameters,
|
||||
parametersPositions,
|
||||
remoteServerToUpdate.userMappingOptions,
|
||||
);
|
||||
|
||||
if (userMappingOptionsQuery) options.push(userMappingOptionsQuery);
|
||||
}
|
||||
|
||||
const shouldUpdateFdwDbname = isDefined(
|
||||
remoteServerToUpdate.foreignDataWrapperOptions?.dbname,
|
||||
);
|
||||
|
||||
if (shouldUpdateFdwDbname) {
|
||||
parameters.push(remoteServerToUpdate?.foreignDataWrapperOptions?.dbname);
|
||||
parametersPositions['dbname'] = parameters.length;
|
||||
}
|
||||
|
||||
const shouldUpdateFdwHost = isDefined(
|
||||
remoteServerToUpdate.foreignDataWrapperOptions?.host,
|
||||
);
|
||||
|
||||
if (shouldUpdateFdwHost) {
|
||||
parameters.push(remoteServerToUpdate?.foreignDataWrapperOptions?.host);
|
||||
parametersPositions['host'] = parameters.length;
|
||||
}
|
||||
|
||||
const shouldUpdateFdwPort = isDefined(
|
||||
remoteServerToUpdate.foreignDataWrapperOptions?.port,
|
||||
);
|
||||
|
||||
if (shouldUpdateFdwPort) {
|
||||
parameters.push(remoteServerToUpdate?.foreignDataWrapperOptions?.port);
|
||||
parametersPositions['port'] = parameters.length;
|
||||
}
|
||||
|
||||
if (shouldUpdateFdwDbname || shouldUpdateFdwHost || shouldUpdateFdwPort) {
|
||||
const fwdOptionsQuery = `"foreignDataWrapperOptions" = jsonb_set(${
|
||||
shouldUpdateFdwDbname && shouldUpdateFdwHost && shouldUpdateFdwPort
|
||||
? `jsonb_set(jsonb_set("foreignDataWrapperOptions", '{dbname}', to_jsonb($${parametersPositions['dbname']}::text)), '{host}', to_jsonb($${parametersPositions['host']}::text)), '{port}', to_jsonb($${parametersPositions['port']}::text)`
|
||||
: shouldUpdateFdwDbname && shouldUpdateFdwHost
|
||||
? `jsonb_set("foreignDataWrapperOptions", '{dbname}', to_jsonb($${parametersPositions['dbname']}::text)), '{host}', to_jsonb($${parametersPositions['host']}::text)`
|
||||
: shouldUpdateFdwDbname && shouldUpdateFdwPort
|
||||
? `jsonb_set("foreignDataWrapperOptions", '{dbname}', to_jsonb($${parametersPositions['dbname']}::text)), '{port}', to_jsonb($${parametersPositions['port']}::text)`
|
||||
: shouldUpdateFdwHost && shouldUpdateFdwPort
|
||||
? `jsonb_set("foreignDataWrapperOptions", '{host}', to_jsonb($${parametersPositions['host']}::text)), '{port}', to_jsonb($${parametersPositions['port']}::text)`
|
||||
: shouldUpdateFdwDbname
|
||||
? `"foreignDataWrapperOptions", '{dbname}', to_jsonb($${parametersPositions['dbname']}::text)`
|
||||
: shouldUpdateFdwHost
|
||||
? `"foreignDataWrapperOptions", '{host}', to_jsonb($${parametersPositions['host']}::text)`
|
||||
: `"foreignDataWrapperOptions", '{port}', to_jsonb($${parametersPositions['port']}::text)`
|
||||
})`;
|
||||
|
||||
options.push(fwdOptionsQuery);
|
||||
}
|
||||
|
||||
const rawQuery = `UPDATE metadata."remoteServer" SET ${options.join(
|
||||
', ',
|
||||
)} WHERE "id"= $1 RETURNING *`;
|
||||
|
||||
return [parameters, rawQuery];
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { InputType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { IsOptional } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class UserMappingOptionsInput {
|
||||
@IsOptional()
|
||||
@Field(() => String, { nullable: true })
|
||||
username: string;
|
||||
|
||||
@IsOptional()
|
||||
@Field(() => String, { nullable: true })
|
||||
password: string;
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
const INPUT_REGEX = /^([A-Za-z0-9\-_.@]+)$/;
|
||||
|
||||
export const validateObject = (input: object) => {
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
// Password are encrypted so we don't need to validate them
|
||||
if (key === 'password') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!INPUT_REGEX.test(value.toString())) {
|
||||
throw new Error('Invalid remote server input');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const validateString = (input: string) => {
|
||||
if (!INPUT_REGEX.test(input)) {
|
||||
throw new Error('Invalid remote server input');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
|
||||
const INPUT_REGEX = /^([A-Za-z0-9\-_.@]+)$/;
|
||||
|
||||
export const validateObjectAgainstInjections = (input: object) => {
|
||||
for (const [key, value] of Object.entries(input)) {
|
||||
// Password are encrypted so we don't need to validate them
|
||||
if (key === 'password') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDefined(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
validateStringAgainstInjections(value.toString());
|
||||
}
|
||||
};
|
||||
|
||||
export const validateStringAgainstInjections = (input: string) => {
|
||||
if (!INPUT_REGEX.test(input)) {
|
||||
throw new BadRequestException('Invalid remote server input');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user