0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-14 23:11:31 -05:00

feat: remove target, platform from connector schema and add id to metadata ()

* feat(core,schema,connectors): remove target,platform form connector schema and add id to metadata

* feat(schema,ui,console): unwrap ConnectorMetadata in ConnectorDTO

* feat(schema,ui,console): connector id use dashline instead of underscore

* feat(connector-alipay*): fix typos

* feat(connector-alipay*): remove unnecessary type declaration
This commit is contained in:
Darcy Ye 2022-05-24 11:39:44 +08:00 committed by GitHub
parent cd18a7a046
commit 054b0f7b6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 187 additions and 289 deletions
packages
connector-alipay-native/src
connector-alipay/src
connector-aliyun-dm/src
connector-aliyun-sms/src
connector-facebook/src
connector-github/src
connector-google/src
connector-sendgrid-mail/src
connector-twilio-sms/src
connector-types/src
connector-wechat-native/src
connector-wechat/src
console/src
hooks
pages
ConnectorDetails
components/ConnectorTabs
index.tsx
Connectors
components
ConnectorName
CreateForm
GuideModal
index.tsx
SignInExperience/components
UserDetails/components
types
core/src
schemas

View file

@ -22,6 +22,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'alipay-native',
target: 'alipay',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,

View file

@ -37,15 +37,14 @@ import {
AccessTokenResponse,
UserInfoResponse,
} from './types';
import { signingPamameters } from './utils';
import type { SigningPamameters } from './utils';
import { signingParameters } from './utils';
export type { AlipayNativeConfig } from './types';
export class AlipayNativeConnector implements SocialConnector {
public metadata: ConnectorMetadata = defaultMetadata;
private readonly signingPamameters: SigningPamameters = signingPamameters;
private readonly signingParameters = signingParameters;
constructor(public readonly getConfig: GetConnectorConfig<AlipayNativeConfig>) {}
@ -58,7 +57,7 @@ export class AlipayNativeConnector implements SocialConnector {
};
public getAuthorizationUri: GetAuthorizationUri = async () => {
const { appId } = await this.getConfig(this.metadata.target, this.metadata.platform);
const { appId } = await this.getConfig(this.metadata.id);
const queryParameters = new URLSearchParams({ app_id: appId });
@ -66,7 +65,7 @@ export class AlipayNativeConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code): Promise<AccessTokenObject> => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
const initSearchParameters = {
method: methodForAccessToken,
format: 'JSON',
@ -77,7 +76,7 @@ export class AlipayNativeConnector implements SocialConnector {
charset: 'UTF8',
...config,
};
const signedSearchParameters = this.signingPamameters(initSearchParameters);
const signedSearchParameters = this.signingParameters(initSearchParameters);
const response = await got
.post(alipayEndpoint, {
@ -99,7 +98,7 @@ export class AlipayNativeConnector implements SocialConnector {
};
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
const { accessToken } = accessTokenObject;
assert(
accessToken && config,
@ -117,7 +116,7 @@ export class AlipayNativeConnector implements SocialConnector {
charset: 'UTF8',
...config,
};
const signedSearchParameters = this.signingPamameters(initSearchParameters);
const signedSearchParameters = this.signingParameters(initSearchParameters);
const response = await got
.post(alipayEndpoint, {

View file

@ -3,7 +3,7 @@ import {
mockedAlipayNativeConfigWithValidPrivateKey,
mockedAlipayNativePublicParameters,
} from './mock';
import { signingPamameters } from './utils';
import { signingParameters } from './utils';
const listenJSONParse = jest.spyOn(JSON, 'parse');
const listenJSONStringify = jest.spyOn(JSON, 'stringify');
@ -21,14 +21,14 @@ describe('signingParameters', () => {
};
it('should return exact signature with the given parameters (functionality check)', () => {
const decamelizedParameters = signingPamameters(testingParameters);
const decamelizedParameters = signingParameters(testingParameters);
expect(decamelizedParameters.sign).toBe(
'td9+u0puul3HgbwLGL1X6z/vKKB/K25K5pjtLT/snQOp292RX3Y5j+FQUVuazTI2l65GpoSgA83LWNT9htQgtmdBmkCQ3bO6RWs38+2ZmBmH7MvpHx4ebUDhtebLUmHNuRFaNcpAZW92b0ZSuuJuahpLK8VNBgXljq+x0aD7WCRudPxc9fikR65NGxr5bwepl/9IqgMxwtajh1+PEJyhGGJhJxS1dCktGN0EiWXWNiogYT8NlFVCmw7epByKzCBWu4sPflU52gJMFHTdbav/0Tk/ZBs8RyP8Z8kcJA0jom2iT+dHqDpgkdzEmsR360UVNKCu5X7ltIiiObsAWmfluQ=='
);
});
it('should return exact signature with the given parameters (with empty property in testingParameters)', () => {
const decamelizedParameters = signingPamameters({
const decamelizedParameters = signingParameters({
...testingParameters,
emptyProperty: '',
});
@ -38,12 +38,12 @@ describe('signingParameters', () => {
});
it('should not call JSON.parse() when biz_content is empty', () => {
signingPamameters(testingParameters);
signingParameters(testingParameters);
expect(listenJSONParse).not.toHaveBeenCalled();
});
it('should call JSON.parse() when biz_content is not empty', () => {
signingPamameters({
signingParameters({
...testingParameters,
biz_content: JSON.stringify({ AB: 'AB' }),
});
@ -51,7 +51,7 @@ describe('signingParameters', () => {
});
it('should call JSON.stringify() when some value is object string', () => {
signingPamameters({
signingParameters({
...testingParameters,
testObject: JSON.stringify({ AB: 'AB' }),
});

View file

@ -6,12 +6,12 @@ import snakeCaseKeys from 'snakecase-keys';
import { alipaySigningAlgorithmMapping } from './constant';
import { AlipayNativeConfig } from './types';
export type SigningPamameters = (
export type SigningParameters = (
parameters: AlipayNativeConfig & Record<string, string | undefined>
) => Record<string, string>;
// Reference: https://github.com/alipay/alipay-sdk-nodejs-all/blob/10d78e0adc7f310d5b07567ce7e4c13a3f6c768f/lib/util.ts
export const signingPamameters: SigningPamameters = (
export const signingParameters: SigningParameters = (
parameters: AlipayNativeConfig & Record<string, string | undefined>
): Record<string, string> => {
const { biz_content, privateKey, ...rest } = parameters;

View file

@ -23,6 +23,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'alipay-web',
target: 'alipay',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,

View file

@ -31,15 +31,14 @@ import {
defaultTimeout,
} from './constant';
import { alipayConfigGuard, AlipayConfig, AccessTokenResponse, UserInfoResponse } from './types';
import { signingPamameters } from './utils';
import type { SigningPamameters } from './utils';
import { signingParameters } from './utils';
export type { AlipayConfig } from './types';
export class AlipayConnector implements SocialConnector {
public metadata: ConnectorMetadata = defaultMetadata;
private readonly signingPamameters: SigningPamameters = signingPamameters;
private readonly signingParameters = signingParameters;
constructor(public readonly getConfig: GetConnectorConfig<AlipayConfig>) {}
@ -52,7 +51,7 @@ export class AlipayConnector implements SocialConnector {
};
public getAuthorizationUri: GetAuthorizationUri = async (redirectUri, state) => {
const { appId: app_id } = await this.getConfig(this.metadata.target, this.metadata.platform);
const { appId: app_id } = await this.getConfig(this.metadata.id);
const redirect_uri = encodeURI(redirectUri);
@ -67,7 +66,7 @@ export class AlipayConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code): Promise<AccessTokenObject> => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
const initSearchParameters = {
method: methodForAccessToken,
format: 'JSON',
@ -78,7 +77,7 @@ export class AlipayConnector implements SocialConnector {
charset: 'UTF8',
...config,
};
const signedSearchParameters = this.signingPamameters(initSearchParameters);
const signedSearchParameters = this.signingParameters(initSearchParameters);
const response = await got
.post(alipayEndpoint, {
@ -100,7 +99,7 @@ export class AlipayConnector implements SocialConnector {
};
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
const { accessToken } = accessTokenObject;
assert(
accessToken && config,
@ -118,7 +117,7 @@ export class AlipayConnector implements SocialConnector {
charset: 'UTF8',
...config,
};
const signedSearchParameters = this.signingPamameters(initSearchParameters);
const signedSearchParameters = this.signingParameters(initSearchParameters);
const response = await got
.post(alipayEndpoint, {

View file

@ -1,6 +1,6 @@
import { methodForAccessToken } from './constant';
import { mockedAlipayConfigWithValidPrivateKey, mockedAlipayPublicParameters } from './mock';
import { signingPamameters } from './utils';
import { signingParameters } from './utils';
const listenJSONParse = jest.spyOn(JSON, 'parse');
const listenJSONStringify = jest.spyOn(JSON, 'stringify');
@ -18,14 +18,14 @@ describe('signingParameters', () => {
};
it('should return exact signature with the given parameters (functionality check)', () => {
const decamelizedParameters = signingPamameters(testingParameters);
const decamelizedParameters = signingParameters(testingParameters);
expect(decamelizedParameters.sign).toBe(
'td9+u0puul3HgbwLGL1X6z/vKKB/K25K5pjtLT/snQOp292RX3Y5j+FQUVuazTI2l65GpoSgA83LWNT9htQgtmdBmkCQ3bO6RWs38+2ZmBmH7MvpHx4ebUDhtebLUmHNuRFaNcpAZW92b0ZSuuJuahpLK8VNBgXljq+x0aD7WCRudPxc9fikR65NGxr5bwepl/9IqgMxwtajh1+PEJyhGGJhJxS1dCktGN0EiWXWNiogYT8NlFVCmw7epByKzCBWu4sPflU52gJMFHTdbav/0Tk/ZBs8RyP8Z8kcJA0jom2iT+dHqDpgkdzEmsR360UVNKCu5X7ltIiiObsAWmfluQ=='
);
});
it('should return exact signature with the given parameters (with empty property in testingParameters)', () => {
const decamelizedParameters = signingPamameters({
const decamelizedParameters = signingParameters({
...testingParameters,
emptyProperty: '',
});
@ -35,12 +35,12 @@ describe('signingParameters', () => {
});
it('should not call JSON.parse() when biz_content is empty', () => {
signingPamameters(testingParameters);
signingParameters(testingParameters);
expect(listenJSONParse).not.toHaveBeenCalled();
});
it('should call JSON.parse() when biz_content is not empty', () => {
signingPamameters({
signingParameters({
...testingParameters,
biz_content: JSON.stringify({ AB: 'AB' }),
});
@ -48,7 +48,7 @@ describe('signingParameters', () => {
});
it('should call JSON.stringify() when some value is object string', () => {
signingPamameters({
signingParameters({
...testingParameters,
testObject: JSON.stringify({ AB: 'AB' }),
});

View file

@ -6,12 +6,12 @@ import snakeCaseKeys from 'snakecase-keys';
import { alipaySigningAlgorithmMapping } from './constant';
import { AlipayConfig } from './types';
export type SigningPamameters = (
export type SigningParameters = (
parameters: AlipayConfig & Record<string, string | undefined>
) => Record<string, string>;
// Reference: https://github.com/alipay/alipay-sdk-nodejs-all/blob/10d78e0adc7f310d5b07567ce7e4c13a3f6c768f/lib/util.ts
export const signingPamameters: SigningPamameters = (
export const signingParameters: SigningParameters = (
parameters: AlipayConfig & Record<string, string | undefined>
): Record<string, string> => {
const { biz_content, privateKey, ...rest } = parameters;

View file

@ -20,6 +20,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'aliyun-direct-mail',
target: 'aliyun-dm',
type: ConnectorType.Email,
platform: null,

View file

@ -32,7 +32,7 @@ export class AliyunDmConnector implements EmailConnector {
type,
data
) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
await this.validateConfig(config);
const { accessKeyId, accessKeySecret, accountName, fromAlias, templates } = config;
const template = templates.find((template) => template.usageType === type);

View file

@ -36,6 +36,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'aliyun-short-message-service',
target: 'aliyun-sms',
type: ConnectorType.SMS,
platform: null,

View file

@ -32,7 +32,7 @@ export class AliyunSmsConnector implements SmsConnector {
type,
{ code }
) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
await this.validateConfig(config);
const { accessKeyId, accessKeySecret, signName, templates } = config;
const template = templates.find(({ usageType }) => usageType === type);

View file

@ -25,6 +25,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'facebook-universal',
target: 'facebook',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,

View file

@ -46,7 +46,7 @@ export class FacebookConnector implements SocialConnector {
};
public getAuthorizationUri: GetAuthorizationUri = async (redirectUri, state) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
const queryParameters = new URLSearchParams({
client_id: config.clientId,
@ -61,8 +61,7 @@ export class FacebookConnector implements SocialConnector {
public getAccessToken: GetAccessToken = async (code, redirectUri) => {
const { clientId: client_id, clientSecret: client_secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
this.metadata.id
);
const { access_token: accessToken } = await got

View file

@ -16,6 +16,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'github-universal',
target: 'github',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,

View file

@ -36,7 +36,7 @@ export class GithubConnector implements SocialConnector {
};
public getAuthorizationUri: GetAuthorizationUri = async (redirectUri, state) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
const queryParameters = new URLSearchParams({
client_id: config.clientId,
@ -50,8 +50,7 @@ export class GithubConnector implements SocialConnector {
getAccessToken: GetAccessToken = async (code) => {
const { clientId: client_id, clientSecret: client_secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
this.metadata.id
);
const { access_token: accessToken } = await got

View file

@ -16,6 +16,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'google-universal',
target: 'google',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,

View file

@ -40,7 +40,7 @@ export class GoogleConnector implements SocialConnector {
};
public getAuthorizationUri: GetAuthorizationUri = async (redirectUri, state) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
const queryParameters = new URLSearchParams({
client_id: config.clientId,
@ -54,10 +54,7 @@ export class GoogleConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code, redirectUri) => {
const { clientId, clientSecret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
);
const { clientId, clientSecret } = await this.getConfig(this.metadata.id);
// NoteNeed to decodeURIComponent on code
// https://stackoverflow.com/questions/51058256/google-api-node-js-invalid-grant-malformed-auth-code

View file

@ -13,6 +13,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'sendgrid-email-service',
target: 'sendgrid-mail',
type: ConnectorType.Email,
platform: null,

View file

@ -39,7 +39,7 @@ export class SendGridMailConnector implements EmailConnector {
type,
data
) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
await this.validateConfig(config);
const { apiKey, fromEmail, fromName, templates } = config;
const template = templates.find((template) => template.usageType === type);

View file

@ -13,6 +13,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'twilio-short-message-service',
target: 'twilio-sms',
type: ConnectorType.SMS,
platform: null,

View file

@ -27,7 +27,7 @@ export class TwilioSmsConnector implements SmsConnector {
};
public sendMessage: EmailSendMessageFunction<SendSmsResponse> = async (address, type, data) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const config = await this.getConfig(this.metadata.id);
await this.validateConfig(config);
const { accountSID, authToken, fromMessagingServiceSID, templates } = config;
const template = templates.find((template) => template.usageType === type);

View file

@ -14,6 +14,7 @@ export enum ConnectorPlatform {
}
export interface ConnectorMetadata {
id: string;
target: string;
type: ConnectorType;
platform: Nullable<ConnectorPlatform>;
@ -102,7 +103,4 @@ export type GetUserInfo = (
accessTokenObject: AccessTokenObject
) => Promise<{ id: string } & Record<string, string | undefined>>;
export type GetConnectorConfig<T = Record<string, unknown>> = (
target: string,
platform: Nullable<ConnectorPlatform>
) => Promise<T>;
export type GetConnectorConfig<T = Record<string, unknown>> = (id: string) => Promise<T>;

View file

@ -16,6 +16,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'wechat-native',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,

View file

@ -48,7 +48,7 @@ export class WeChatNativeConnector implements SocialConnector {
};
public getAuthorizationUri: GetAuthorizationUri = async () => {
const { appId } = await this.getConfig(this.metadata.target, this.metadata.platform);
const { appId } = await this.getConfig(this.metadata.id);
const queryParameters = new URLSearchParams({
app_id: appId,
@ -58,10 +58,7 @@ export class WeChatNativeConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code) => {
const { appId: appid, appSecret: secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
);
const { appId: appid, appSecret: secret } = await this.getConfig(this.metadata.id);
const {
access_token: accessToken,

View file

@ -16,6 +16,7 @@ const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
id: 'wechat-web',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,

View file

@ -44,7 +44,7 @@ export class WeChatConnector implements SocialConnector {
};
public getAuthorizationUri: GetAuthorizationUri = async (redirectUri, state) => {
const { appId } = await this.getConfig(this.metadata.target, this.metadata.platform);
const { appId } = await this.getConfig(this.metadata.id);
const queryParameters = new URLSearchParams({
appid: appId,
@ -58,10 +58,7 @@ export class WeChatConnector implements SocialConnector {
};
public getAccessToken: GetAccessToken = async (code) => {
const { appId: appid, appSecret: secret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
);
const { appId: appid, appSecret: secret } = await this.getConfig(this.metadata.id);
const {
access_token: accessToken,

View file

@ -21,10 +21,10 @@ const useConnectorGroups = () => {
return [
...previous,
{
name: item.metadata.name,
logo: item.metadata.logo,
target: item.metadata.target,
type: item.metadata.type,
name: item.name,
logo: item.logo,
target: item.target,
type: item.type,
enabled: item.enabled,
connectors: [item],
},

View file

@ -30,12 +30,12 @@ const ConnectorTabs = ({ target, connectorId }: Props) => {
to={`/connectors/${connector.id}`}
className={classNames(styles.tab, connector.id === connectorId && styles.active)}
>
{connector.metadata.platform && (
{connector.platform && (
<div className={styles.icon}>
<ConnectorPlatformIcon platform={connector.metadata.platform} />
<ConnectorPlatformIcon platform={connector.platform} />
</div>
)}
{connector.metadata.platform}
{connector.platform}
{!connector.enabled && (
<div className={styles.notSet}>{t('connector_details.not_set')}</div>
)}

View file

@ -92,7 +92,7 @@ const ConnectorDetails = () => {
.json<ConnectorDTO>();
toast.success(t('connector_details.connector_deleted'));
if (data?.metadata.type === ConnectorType.Social) {
if (data?.type === ConnectorType.Social) {
navigate(`/connectors/social`);
} else {
navigate(`/connectors`);
@ -102,21 +102,21 @@ const ConnectorDetails = () => {
return (
<div className={detailsStyles.container}>
<LinkButton
to={data?.metadata.type === ConnectorType.Social ? '/connectors/social' : '/connectors'}
to={data?.type === ConnectorType.Social ? '/connectors/social' : '/connectors'}
icon={<Back />}
title="admin_console.connector_details.back_to_connectors"
className={styles.backLink}
/>
{isLoading && <DetailsSkeleton />}
{!data && error && <div>{`error occurred: ${error.body?.message ?? error.message}`}</div>}
{data?.metadata.type === ConnectorType.Social && (
<ConnectorTabs target={data.metadata.target} connectorId={data.id} />
{data?.type === ConnectorType.Social && (
<ConnectorTabs target={data.target} connectorId={data.id} />
)}
{data && (
<Card className={styles.header}>
<div className={styles.imagePlaceholder}>
{data.metadata.logo.startsWith('http') ? (
<img src={data.metadata.logo} className={styles.logo} />
{data.logo.startsWith('http') ? (
<img src={data.logo} className={styles.logo} />
) : (
<ImagePlaceholder size={60} borderRadius={16} />
)}
@ -124,7 +124,7 @@ const ConnectorDetails = () => {
<div className={styles.metadata}>
<div>
<div className={styles.name}>
<UnnamedTrans resource={data.metadata.name} />
<UnnamedTrans resource={data.name} />
</div>
<div className={styles.id}>{data.id}</div>
</div>
@ -150,13 +150,13 @@ const ConnectorDetails = () => {
setIsReadMeOpen(false);
}}
>
<Markdown>{data.metadata.readme}</Markdown>
<Markdown>{data.readme}</Markdown>
</Drawer>
<ActionMenu
buttonProps={{ icon: <More />, size: 'large' }}
title={t('connector_details.more_options')}
>
{data.metadata.type !== ConnectorType.Social && (
{data.type !== ConnectorType.Social && (
<ActionMenuItem
icon={<Reset />}
onClick={() => {
@ -164,7 +164,7 @@ const ConnectorDetails = () => {
}}
>
{t(
data.metadata.type === ConnectorType.SMS
data.type === ConnectorType.SMS
? 'connector_details.options_change_sms'
: 'connector_details.options_change_email'
)}
@ -176,7 +176,7 @@ const ConnectorDetails = () => {
</ActionMenu>
<CreateForm
isOpen={isSetupOpen}
type={data.metadata.type}
type={data.type}
onClose={() => {
setIsSetupOpen(false);
}}
@ -199,9 +199,7 @@ const ConnectorDetails = () => {
setConfig(value);
}}
/>
{data.metadata.type !== ConnectorType.Social && (
<SenderTester connectorType={data.metadata.type} />
)}
{data.type !== ConnectorType.Social && <SenderTester connectorType={data.type} />}
{saveError && <div>{saveError}</div>}
</div>
<div className={detailsStyles.footer}>

View file

@ -21,11 +21,11 @@ const ConnectorName = ({ connector, titlePlaceholder = '' }: Props) => {
return (
<Link to={`/connectors/${connector.id}`} className={styles.link}>
<ItemPreview
title={<UnnamedTrans resource={connector.metadata.name} />}
title={<UnnamedTrans resource={connector.name} />}
subtitle={connector.id}
icon={
connector.metadata.logo.startsWith('http') ? (
<img className={styles.logo} src={connector.metadata.logo} />
connector.logo.startsWith('http') ? (
<img className={styles.logo} src={connector.logo} />
) : (
<ImagePlaceholder />
)

View file

@ -27,7 +27,7 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
const [isGetStartedModalOpen, setIsGetStartedModalOpen] = useState(false);
const connectors = useMemo(
() => data?.filter((connector) => connector.metadata.type === type),
() => data?.filter((connector) => connector.type === type),
[data, type]
);
@ -84,7 +84,7 @@ const CreateForm = ({ onClose, isOpen: isFormOpen, type }: Props) => {
type="card"
onChange={setActiveConnectorId}
>
{connectors.map(({ id, metadata: { name, logo, description } }) => (
{connectors.map(({ id, name, logo, description }) => (
<Radio key={id} value={id} className={styles.connector}>
<div className={styles.logo}>
{logo.startsWith('http') ? <img src={logo} /> : <ImagePlaceholder size={32} />}

View file

@ -35,10 +35,7 @@ const GuideModal = ({ connector, isOpen, onClose }: Props) => {
const api = useApi();
const { updateConfigs } = useAdminConsoleConfigs();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
id: connectorId,
metadata: { type: connectorType, name, configTemplate, readme },
} = connector;
const { id: connectorId, type: connectorType, name, configTemplate, readme } = connector;
const locale = i18next.language;
// TODO: LOG-2393 should fix name[locale] syntax error

View file

@ -29,16 +29,12 @@ const Connectors = () => {
const isLoading = !data && !error;
const emailConnector = useMemo(
() =>
data?.find(
(connector) => connector.enabled && connector.metadata.type === ConnectorType.Email
),
() => data?.find((connector) => connector.enabled && connector.type === ConnectorType.Email),
[data]
);
const smsConnector = useMemo(
() =>
data?.find((connector) => connector.enabled && connector.metadata.type === ConnectorType.SMS),
() => data?.find((connector) => connector.enabled && connector.type === ConnectorType.SMS),
[data]
);
@ -47,7 +43,7 @@ const Connectors = () => {
return;
}
return data?.filter((connector) => connector.metadata.type === ConnectorType.Social);
return data?.filter((connector) => connector.type === ConnectorType.Social);
}, [data, isSocial]);
return (

View file

@ -34,7 +34,7 @@ const ConnectorSetupWarning = ({ method }: Props) => {
return null;
}
if (connectors.some(({ metadata, enabled }) => metadata.type === type && enabled)) {
if (connectors.some(({ type: connectorType, enabled }) => connectorType === type && enabled)) {
return null;
}

View file

@ -47,7 +47,7 @@ const ConnectorsTransfer = ({ value, onChange }: Props) => {
{connectors.length > 1 &&
connectors
.filter(({ enabled }) => enabled)
.map(({ metadata: { platform } }) => (
.map(({ platform }) => (
<div key={platform} className={styles.icon}>
{platform && <ConnectorPlatformIcon platform={platform} />}
</div>

View file

@ -37,9 +37,7 @@ const Preview = ({ signInExperience, className }: Props) => {
>(
(previous, connectorTarget) => [
...previous,
...allConnectors
.filter(({ metadata: { target } }) => target === connectorTarget)
.map(({ metadata, id }) => ({ ...metadata, id })),
...allConnectors.filter(({ target }) => target === connectorTarget),
],
[]
);

View file

@ -68,7 +68,7 @@ const UserConnectors = ({ userId, connectors, onDelete }: Props) => {
};
}
const { logo, name } = connector.metadata;
const { logo, name } = connector;
return {
logo,

View file

@ -1,6 +1,6 @@
import { ConnectorDTO } from '@logto/schemas';
export type ConnectorGroup = Pick<ConnectorDTO['metadata'], 'name' | 'logo' | 'target' | 'type'> & {
export type ConnectorGroup = Pick<ConnectorDTO, 'name' | 'logo' | 'target' | 'type'> & {
enabled: boolean;
connectors: ConnectorDTO[];
};

View file

@ -2,6 +2,7 @@ import { ConnectorPlatform } from '@logto/connector-types';
import { Connector, ConnectorMetadata, ConnectorType } from '@logto/schemas';
export const mockMetadata: ConnectorMetadata = {
id: 'id',
target: 'connector',
type: ConnectorType.Email,
platform: null,
@ -19,9 +20,7 @@ export const mockMetadata: ConnectorMetadata = {
};
export const mockConnector: Connector = {
id: 'connector',
target: 'connector',
platform: ConnectorPlatform.Universal,
id: 'id',
enabled: true,
config: {},
createdAt: 1_234_567_890_123,
@ -29,6 +28,7 @@ export const mockConnector: Connector = {
const mockMetadata0: ConnectorMetadata = {
...mockMetadata,
id: 'id0',
target: 'connector_0',
type: ConnectorType.Email,
platform: ConnectorPlatform.Universal,
@ -36,6 +36,7 @@ const mockMetadata0: ConnectorMetadata = {
const mockMetadata1: ConnectorMetadata = {
...mockMetadata,
id: 'id1',
target: 'connector_1',
type: ConnectorType.SMS,
platform: ConnectorPlatform.Universal,
@ -43,6 +44,7 @@ const mockMetadata1: ConnectorMetadata = {
const mockMetadata2: ConnectorMetadata = {
...mockMetadata,
id: 'id2',
target: 'connector_2',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
@ -50,6 +52,7 @@ const mockMetadata2: ConnectorMetadata = {
const mockMetadata3: ConnectorMetadata = {
...mockMetadata,
id: 'id3',
target: 'connector_3',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
@ -57,6 +60,7 @@ const mockMetadata3: ConnectorMetadata = {
const mockMetadata4: ConnectorMetadata = {
...mockMetadata,
id: 'id4',
target: 'connector_4',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
@ -64,6 +68,7 @@ const mockMetadata4: ConnectorMetadata = {
const mockMetadata5: ConnectorMetadata = {
...mockMetadata,
id: 'id5',
target: 'connector_5',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
@ -71,69 +76,56 @@ const mockMetadata5: ConnectorMetadata = {
const mockMetadata6: ConnectorMetadata = {
...mockMetadata,
id: 'id6',
target: 'connector_6',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
};
const mockConnector0: Connector = {
id: 'connector_0',
target: 'connector_0',
platform: ConnectorPlatform.Universal,
id: 'id0',
enabled: true,
config: {},
createdAt: 1_234_567_890_123,
};
const mockConnector1: Connector = {
id: 'connector_1',
target: 'connector_1',
platform: ConnectorPlatform.Universal,
id: 'id1',
enabled: true,
config: {},
createdAt: 1_234_567_890_234,
};
const mockConnector2: Connector = {
id: 'connector_2',
target: 'connector_2',
platform: ConnectorPlatform.Universal,
id: 'id2',
enabled: true,
config: {},
createdAt: 1_234_567_890_345,
};
const mockConnector3: Connector = {
id: 'connector_3',
target: 'connector_3',
platform: ConnectorPlatform.Universal,
id: 'id3',
enabled: true,
config: {},
createdAt: 1_234_567_890_456,
};
const mockConnector4: Connector = {
id: 'connector_4',
target: 'connector_4',
platform: ConnectorPlatform.Universal,
id: 'id4',
enabled: true,
config: {},
createdAt: 1_234_567_890_567,
};
const mockConnector5: Connector = {
id: 'connector_5',
target: 'connector_5',
platform: ConnectorPlatform.Universal,
id: 'id5',
enabled: true,
config: {},
createdAt: 1_234_567_890_567,
};
const mockConnector6: Connector = {
id: 'connector_6',
target: 'connector_6',
platform: ConnectorPlatform.Universal,
id: 'id6',
enabled: true,
config: {},
createdAt: 1_234_567_890_567,
@ -170,24 +162,15 @@ export const mockConnectorInstanceList: Array<{
metadata: mockMetadata3,
},
{
connector: {
...mockConnector4,
platform: null,
},
connector: mockConnector4,
metadata: { ...mockMetadata4, type: ConnectorType.Email, platform: null },
},
{
connector: {
...mockConnector5,
platform: null,
},
connector: mockConnector5,
metadata: { ...mockMetadata5, type: ConnectorType.SMS, platform: null },
},
{
connector: {
...mockConnector6,
platform: null,
},
connector: mockConnector6,
metadata: { ...mockMetadata6, type: ConnectorType.Email, platform: null },
},
];
@ -196,11 +179,10 @@ export const mockAliyunDmConnectorInstance = {
connector: {
...mockConnector,
id: 'aliyun-dm',
target: 'aliyun-dm',
platform: null,
},
metadata: {
...mockMetadata,
id: 'aliyun-dm',
target: 'aliyun-dm',
type: ConnectorType.Email,
platform: null,
@ -211,11 +193,10 @@ export const mockAliyunSmsConnectorInstance = {
connector: {
...mockConnector,
id: 'aliyun-sms',
target: 'aliyun-sms',
platform: null,
},
metadata: {
...mockMetadata,
id: 'aliyun-sms',
target: 'aliyun-sms',
type: ConnectorType.SMS,
platform: null,
@ -226,11 +207,10 @@ export const mockFacebookConnectorInstance = {
connector: {
...mockConnector,
id: 'facebook',
target: 'facebook',
platform: ConnectorPlatform.Web,
},
metadata: {
...mockMetadata,
id: 'facebook',
target: 'facebook',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
@ -241,11 +221,10 @@ export const mockGithubConnectorInstance = {
connector: {
...mockConnector,
id: 'github',
target: 'github',
platform: ConnectorPlatform.Web,
},
metadata: {
...mockMetadata,
id: 'github',
target: 'github',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
@ -255,12 +234,11 @@ export const mockGithubConnectorInstance = {
export const mockWechatConnectorInstance = {
connector: {
...mockConnector,
id: 'wechat',
target: 'wechat',
platform: ConnectorPlatform.Web,
id: 'wechat-web',
},
metadata: {
...mockMetadata,
id: 'wechat-web',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,
@ -271,11 +249,10 @@ export const mockWechatNativeConnectorInstance = {
connector: {
...mockConnector,
id: 'wechat-native',
target: 'wechat',
platform: ConnectorPlatform.Native,
},
metadata: {
...mockMetadata,
id: 'wechat-native',
target: 'wechat',
type: ConnectorType.Social,
platform: ConnectorPlatform.Native,
@ -286,12 +263,11 @@ export const mockGoogleConnectorInstance = {
connector: {
...mockConnector,
id: 'google',
target: 'google',
platform: ConnectorPlatform.Web,
enabled: false,
},
metadata: {
...mockMetadata,
id: 'google',
target: 'google',
type: ConnectorType.Social,
platform: ConnectorPlatform.Web,

View file

@ -11,89 +11,67 @@ import {
import RequestError from '@/errors/RequestError';
const alipayConnector = {
id: 'alipay',
target: 'alipay',
platform: ConnectorPlatform.Web,
id: 'alipay-web',
enabled: true,
config: {},
createdAt: 1_646_382_233_911,
};
const alipayNativeConnector = {
id: 'alipay-native',
target: 'alipay',
platform: ConnectorPlatform.Native,
enabled: false,
config: {},
createdAt: 1_646_382_233_911,
};
const aliyunDmConnector = {
id: 'aliyun-dm',
target: 'aliyun-dm',
platform: null,
id: 'aliyun-direct-mail',
enabled: true,
config: {},
createdAt: 1_646_382_233_911,
};
const aliyunSmsConnector = {
id: 'aliyun-sms',
target: 'aliyun-sms',
platform: null,
id: 'aliyun-short-message-service',
enabled: false,
config: {},
createdAt: 1_646_382_233_666,
};
const facebookConnector = {
id: 'facebook',
target: 'facebook',
platform: ConnectorPlatform.Universal,
id: 'facebook-universal',
enabled: true,
config: {},
createdAt: 1_646_382_233_333,
};
const githubConnector = {
id: 'github',
target: 'github',
platform: ConnectorPlatform.Universal,
id: 'github-universal',
enabled: true,
config: {},
createdAt: 1_646_382_233_555,
};
const googleConnector = {
id: 'google',
target: 'google',
platform: ConnectorPlatform.Universal,
id: 'google-universal',
enabled: false,
config: {},
createdAt: 1_646_382_233_000,
};
const sendGridMailConnector = {
id: 'sendgrid-mail',
target: 'sendgrid-mail',
platform: null,
id: 'sendgrid-email-service',
enabled: false,
config: {},
createdAt: 1_646_382_233_111,
};
const twilioSmsConnector = {
id: 'twilio-sms',
target: 'twilio-sms',
platform: null,
id: 'twilio-short-message-service',
enabled: false,
config: {},
createdAt: 1_646_382_233_000,
};
const wechatConnector = {
id: 'wechat',
target: 'wechat',
platform: ConnectorPlatform.Web,
id: 'wechat-web',
enabled: false,
config: {},
createdAt: 1_646_382_233_000,
};
const wechatNativeConnector = {
id: 'wechat-native',
target: 'wechat',
platform: ConnectorPlatform.Native,
enabled: false,
config: {},
createdAt: 1_646_382_233_000,
@ -163,7 +141,7 @@ describe('getConnectorInstanceById', () => {
});
test('should return the connector existing in DB', async () => {
const connectorInstance = await getConnectorInstanceById('aliyun-dm');
const connectorInstance = await getConnectorInstanceById('aliyun-direct-mail');
expect(connectorInstance).toHaveProperty('connector', aliyunDmConnector);
});
@ -187,12 +165,12 @@ describe('getConnectorInstanceById', () => {
describe('getSocialConnectorInstanceById', () => {
test('should return the connector existing in DB', async () => {
const socialConnectorInstance = await getSocialConnectorInstanceById('google');
const socialConnectorInstance = await getSocialConnectorInstanceById('google-universal');
expect(socialConnectorInstance).toHaveProperty('connector', googleConnector);
});
test('should throw on non-social connector', async () => {
const id = 'aliyun-dm';
const id = 'aliyun-direct-mail';
await expect(getSocialConnectorInstanceById(id)).rejects.toMatchError(
new RequestError({
code: 'entity.not_found',
@ -206,7 +184,11 @@ describe('getSocialConnectorInstanceById', () => {
describe('getEnabledSocialConnectorIds', () => {
test('should return the enabled social connectors existing in DB', async () => {
const enabledSocialConnectorIds = await getEnabledSocialConnectorIds();
expect(enabledSocialConnectorIds).toEqual(['alipay', 'facebook', 'github']);
expect(enabledSocialConnectorIds).toEqual([
'alipay-web',
'facebook-universal',
'github-universal',
]);
});
});
@ -217,12 +199,11 @@ describe('initConnectors', () => {
expect(insertConnector).toHaveBeenCalledTimes(connectors.length);
for (const [i, connector] of connectors.entries()) {
const { target, platform } = connector;
const { id } = connector;
expect(insertConnector).toHaveBeenNthCalledWith(
i + 1,
expect.objectContaining({
target,
platform,
id,
})
);
}

View file

@ -9,13 +9,12 @@ import { SendGridMailConnector } from '@logto/connector-sendgrid-email';
import { TwilioSmsConnector } from '@logto/connector-twilio-sms';
import { WeChatConnector } from '@logto/connector-wechat';
import { WeChatNativeConnector } from '@logto/connector-wechat-native';
import { nanoid } from 'nanoid';
import RequestError from '@/errors/RequestError';
import { findAllConnectors, insertConnector } from '@/queries/connector';
import { ConnectorInstance, ConnectorType, IConnector, SocialConnectorInstance } from './types';
import { buildIndexWithTargetAndPlatform, getConnectorConfig } from './utilities';
import { getConnectorConfig } from './utilities';
const allConnectors: IConnector[] = [
new AlipayConnector(getConnectorConfig),
@ -33,19 +32,14 @@ const allConnectors: IConnector[] = [
export const getConnectorInstances = async (): Promise<ConnectorInstance[]> => {
const connectors = await findAllConnectors();
const connectorMap = new Map(
connectors.map((connector) => [
buildIndexWithTargetAndPlatform(connector.target, connector.platform),
connector,
])
);
const connectorMap = new Map(connectors.map((connector) => [connector.id, connector]));
return allConnectors.map((element) => {
const { target, platform } = element.metadata;
const connector = connectorMap.get(buildIndexWithTargetAndPlatform(target, platform));
const { id } = element.metadata;
const connector = connectorMap.get(id);
if (!connector) {
throw new RequestError({ code: 'entity.not_found', target, platform, status: 404 });
throw new RequestError({ code: 'entity.not_found', id, status: 404 });
}
return { connector, ...element };
@ -104,25 +98,21 @@ export const getEnabledSocialConnectorIds = async <T extends ConnectorInstance>(
export const initConnectors = async () => {
const connectors = await findAllConnectors();
const existingConnectors = new Map(
connectors.map((connector) => [
buildIndexWithTargetAndPlatform(connector.target, connector.platform),
connector,
])
);
const newConnectors = allConnectors.filter(
({ metadata: { target, platform } }) =>
existingConnectors.get(buildIndexWithTargetAndPlatform(target, platform))?.platform !==
platform
);
const existingConnectors = new Map(connectors.map((connector) => [connector.id, connector]));
const newConnectors = allConnectors.filter(({ metadata: { id } }) => {
const connector = existingConnectors.get(id);
if (!connector) {
return true;
}
return connector.config === JSON.stringify({});
});
await Promise.all(
newConnectors.map(async ({ metadata: { target, platform } }) => {
const id = nanoid();
newConnectors.map(async ({ metadata: { id } }) => {
await insertConnector({
id,
target,
platform,
});
})
);

View file

@ -1,13 +1,12 @@
import { ConnectorPlatform } from '@logto/connector-types';
import { Connector } from '@logto/schemas';
import { buildIndexWithTargetAndPlatform, getConnectorConfig } from '.';
import RequestError from '@/errors/RequestError';
import { getConnectorConfig } from '.';
const connectors: Connector[] = [
{
id: 'id',
target: 'target',
platform: null,
enabled: true,
config: { foo: 'bar' },
createdAt: 0,
@ -21,15 +20,13 @@ jest.mock('@/queries/connector', () => ({
findAllConnectors: async () => findAllConnectors(),
}));
it('buildIndexWithTargetAndPlatform() with not-null `platform`', async () => {
expect(buildIndexWithTargetAndPlatform('target', ConnectorPlatform.Web)).toEqual('target_Web');
});
it('buildIndexWithTargetAndPlatform() with null `platform`', async () => {
expect(buildIndexWithTargetAndPlatform('target', null)).toEqual('target_null');
});
it('getConnectorConfig()', async () => {
const config = await getConnectorConfig('target', null);
it('getConnectorConfig() should return right config', async () => {
const config = await getConnectorConfig('id');
expect(config).toMatchObject({ foo: 'bar' });
});
it('getConnectorConfig() should throw error if connector not found', async () => {
await expect(getConnectorConfig('not-found')).rejects.toMatchError(
new RequestError({ code: 'entity.not_found', id: 'not-found', status: 404 })
);
});

View file

@ -1,26 +1,14 @@
import { ArbitraryObject, ConnectorPlatform } from '@logto/schemas';
import { Nullable } from '@silverhand/essentials';
import { ArbitraryObject } from '@logto/schemas';
import RequestError from '@/errors/RequestError';
import { findAllConnectors } from '@/queries/connector';
import assertThat from '@/utils/assert-that';
export const buildIndexWithTargetAndPlatform = (
target: string,
platform: Nullable<string>
): string => [target, platform ?? 'null'].join('_');
export const getConnectorConfig = async <T extends ArbitraryObject>(
target: string,
platform: Nullable<ConnectorPlatform>
): Promise<T> => {
export const getConnectorConfig = async <T extends ArbitraryObject>(id: string): Promise<T> => {
const connectors = await findAllConnectors();
const connector = connectors.find(
(connector) => connector.target === target && connector.platform === platform
);
const connector = connectors.find((connector) => connector.id === id);
if (!connector) {
throw new RequestError({ code: 'entity.not_found', target, platform, status: 404 });
}
assertThat(connector, new RequestError({ code: 'entity.not_found', id, status: 404 }));
return connector.config as T;
};

View file

@ -126,8 +126,6 @@ describe('sendPasscode', () => {
connector: {
...mockConnector,
id: 'id1',
target: 'connector1',
platform: null,
},
metadata: {
...mockMetadata,
@ -167,8 +165,6 @@ describe('sendPasscode', () => {
connector: {
...mockConnector,
id: 'id0',
target: 'connector0',
platform: null,
},
metadata: {
...mockMetadata,
@ -183,8 +179,6 @@ describe('sendPasscode', () => {
connector: {
...mockConnector,
id: 'id1',
target: 'connector1',
platform: null,
},
metadata: {
...mockMetadata,
@ -197,7 +191,7 @@ describe('sendPasscode', () => {
},
]);
const passcode: Passcode = {
id: 'id',
id: 'passcode_id',
interactionJti: 'jti',
phone: 'phone',
email: null,

View file

@ -46,21 +46,15 @@ describe('connector queries', () => {
};
const expectSql = `
insert into "connectors" ("id", "target", "platform", "enabled", "config")
values ($1, $2, $3, $4, $5)
insert into "connectors" ("id", "enabled", "config")
values ($1, $2, $3)
returning *
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql);
expect(values).toEqual([
connector.id,
connector.target,
connector.platform,
connector.enabled,
connector.config,
]);
expect(values).toEqual([connector.id, connector.enabled, connector.config]);
return createMockQueryResult([connector]);
});

View file

@ -22,6 +22,7 @@ import { createRequester } from '@/utils/test-utils';
import connectorRoutes from './connector';
const mockMetadata: ConnectorMetadata = {
id: 'id0',
target: 'connector_0',
type: ConnectorType.Social,
platform: null,
@ -35,9 +36,7 @@ const mockMetadata: ConnectorMetadata = {
configTemplate: 'config-template.md',
};
const mockConnector: Connector = {
id: 'connector_0',
target: 'connector_0',
platform: null,
id: 'id0',
enabled: true,
config: {},
createdAt: 1_234_567_890_123,
@ -135,7 +134,7 @@ describe('connector route', () => {
return { foundConnector, ...foundConnectorInstance };
});
const response = await connectorRequest.get('/connectors/connector_0').send({});
const response = await connectorRequest.get('/connectors/id0').send({});
expect(response).toHaveProperty('statusCode', 400);
});
@ -154,7 +153,7 @@ describe('connector route', () => {
return { foundConnector, ...foundConnectorInstance };
});
const response = await connectorRequest.get('/connectors/connector_0').send({});
const response = await connectorRequest.get('/connectors/id0').send({});
expect(response).toHaveProperty('statusCode', 200);
});
});
@ -195,7 +194,7 @@ describe('connector route', () => {
return { foundConnector, ...foundConnectorInstance };
});
const response = await connectorRequest
.patch('/connectors/connector_0/enabled')
.patch('/connectors/id0/enabled')
.send({ enabled: true });
expect(response).toHaveProperty('statusCode', 400);
});
@ -268,12 +267,12 @@ describe('connector route', () => {
getConnectorInstancesPlaceHolder.mockResolvedValueOnce(mockConnectorInstanceList);
const mockedMetadata = {
...mockMetadata,
id: 'connector_1',
id: 'id1',
type: ConnectorType.SMS,
};
const mockedConnector = {
...mockConnector,
id: 'connector_1',
id: 'id1',
name: 'connector_1',
platform: null,
type: ConnectorType.SMS,
@ -287,26 +286,26 @@ describe('connector route', () => {
};
});
const response = await connectorRequest
.patch('/connectors/connector_1/enabled')
.patch('/connectors/id1/enabled')
.send({ enabled: true });
expect(updateConnector).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
where: { id: 'connector_1' },
where: { id: 'id1' },
set: { enabled: false },
})
);
expect(updateConnector).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
where: { id: 'connector_5' },
where: { id: 'id5' },
set: { enabled: false },
})
);
expect(updateConnector).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
where: { id: 'connector_1' },
where: { id: 'id1' },
set: { enabled: true },
})
);
@ -410,12 +409,12 @@ describe('connector route', () => {
new RequestError({ code: 'entity.not_found', status: 404 })
);
const foundConnector = mockConnectorList.find((connector) => connector.id === 'connector0');
const foundConnector = mockConnectorList.find((connector) => connector.id === 'id_0');
assertThat(foundConnector, 'entity.not_found');
return { foundConnector, ...foundConnectorInstance };
});
const response = await connectorRequest.patch('/connectors/connector_0').send({});
const response = await connectorRequest.patch('/connectors/id0').send({});
expect(response).toHaveProperty('statusCode', 400);
});

View file

@ -17,7 +17,7 @@ import { AuthedRouter } from './types';
const transpileConnectorInstance = ({ connector, metadata }: ConnectorInstance): ConnectorDTO => ({
...connector,
metadata,
...metadata,
});
export default function connectorRoutes<T extends AuthedRouter>(router: T) {

View file

@ -5,6 +5,4 @@ import { Connector } from '../db-entries';
export type { ConnectorMetadata } from '@logto/connector-types';
export { ConnectorType, ConnectorPlatform } from '@logto/connector-types';
export interface ConnectorDTO extends Connector {
metadata: ConnectorMetadata;
}
export type ConnectorDTO = Connector & ConnectorMetadata;

View file

@ -1,11 +1,7 @@
create table connectors (
id varchar(128) not null,
target varchar(64) not null,
platform varchar(64),
enabled boolean not null default FALSE,
config jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb,
created_at timestamptz not null default(now()),
primary key (id)
);
create index connectors__target_platform on connectors (target, platform);