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 (#930)
* 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:
parent
cd18a7a046
commit
054b0f7b6a
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
Connectors
SignInExperience/components
UserDetails/components
types
core/src
__mocks__
connectors
lib
queries
routes
schemas
|
@ -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,
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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' }),
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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' }),
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
// Note:Need to decodeURIComponent on code
|
||||
// https://stackoverflow.com/questions/51058256/google-api-node-js-invalid-grant-malformed-auth-code
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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],
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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 />
|
||||
)
|
||||
|
|
|
@ -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} />}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
|
|
@ -68,7 +68,7 @@ const UserConnectors = ({ userId, connectors, onDelete }: Props) => {
|
|||
};
|
||||
}
|
||||
|
||||
const { logo, name } = connector.metadata;
|
||||
const { logo, name } = connector;
|
||||
|
||||
return {
|
||||
logo,
|
||||
|
|
|
@ -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[];
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
@ -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 })
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue