mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(schemas,core,phrases)!: add type to connector schema and sync to DB (#491)
* feat(schemas,core,phrases)!: add type in connector schema and sync to DB * chore(core): fix code about connector type
This commit is contained in:
parent
b098dd5f59
commit
71465ca999
13 changed files with 68 additions and 25 deletions
|
@ -13,42 +13,49 @@ import RequestError from '@/errors/RequestError';
|
|||
|
||||
const aliyunDmConnector = {
|
||||
id: 'aliyun-dm',
|
||||
type: ConnectorType.Email,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_911,
|
||||
};
|
||||
const aliyunSmsConnector = {
|
||||
id: 'aliyun-sms',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_666,
|
||||
};
|
||||
const facebookConnector = {
|
||||
id: 'facebook',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_333,
|
||||
};
|
||||
const githubConnector = {
|
||||
id: 'github',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_555,
|
||||
};
|
||||
const googleConnector = {
|
||||
id: 'google',
|
||||
type: ConnectorType.Social,
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_000,
|
||||
};
|
||||
const wechatConnector = {
|
||||
id: 'wechat',
|
||||
type: ConnectorType.Social,
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_000,
|
||||
};
|
||||
const wechatNativeConnector = {
|
||||
id: 'wechat-native',
|
||||
type: ConnectorType.Social,
|
||||
enabled: false,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_000,
|
||||
|
@ -170,8 +177,8 @@ describe('initConnectors', () => {
|
|||
expect(insertConnector).toHaveBeenCalledTimes(connectors.length);
|
||||
|
||||
for (const [i, connector] of connectors.entries()) {
|
||||
const { id } = connector;
|
||||
expect(insertConnector).toHaveBeenNthCalledWith(i + 1, { id });
|
||||
const { id, type } = connector;
|
||||
expect(insertConnector).toHaveBeenNthCalledWith(i + 1, { id, type });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -104,15 +104,11 @@ export const getConnectorInstanceByType = async <T extends ConnectorInstance>(
|
|||
|
||||
export const initConnectors = async () => {
|
||||
const connectors = await findAllConnectors();
|
||||
const existingConnectorIds = new Set(connectors.map((connector) => connector.id));
|
||||
|
||||
const existingConnectors = new Map(connectors.map((connector) => [connector.id, connector]));
|
||||
const newConnectors = allConnectors.filter(
|
||||
({ metadata: { id, type } }) => existingConnectors.get(id)?.type !== type
|
||||
);
|
||||
await Promise.all(
|
||||
allConnectors.map(async ({ metadata: { id } }) => {
|
||||
if (existingConnectorIds.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await insertConnector({ id });
|
||||
})
|
||||
newConnectors.map(async ({ metadata: { id, type } }) => insertConnector({ id, type }))
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { ConnectorType } from '@logto/schemas';
|
||||
|
||||
import { findConnectorById, updateConnector } from '@/queries/connector';
|
||||
|
||||
import { getConnectorConfig, updateConnectorConfig } from '.';
|
||||
|
@ -7,6 +9,7 @@ jest.mock('@/queries/connector');
|
|||
it('getConnectorConfig()', async () => {
|
||||
(findConnectorById as jest.MockedFunction<typeof findConnectorById>).mockResolvedValueOnce({
|
||||
id: 'id',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: { foo: 'bar' },
|
||||
createdAt: 0,
|
||||
|
|
|
@ -124,6 +124,7 @@ describe('sendPasscode', () => {
|
|||
mockedGetConnectorInstanceByType.mockResolvedValue({
|
||||
connector: {
|
||||
id: 'id',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: Date.now(),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Connectors, CreateConnector } from '@logto/schemas';
|
||||
import { Connectors, ConnectorType, CreateConnector } from '@logto/schemas';
|
||||
import { createMockPool, createMockQueryResult, sql, QueryResultRowType } from 'slonik';
|
||||
|
||||
import { convertToIdentifiers } from '@/database/utils';
|
||||
|
@ -65,19 +65,20 @@ describe('connector queries', () => {
|
|||
it('insertConnector', async () => {
|
||||
const connector: CreateConnector & QueryResultRowType = {
|
||||
id: 'foo',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
const expectSql = `
|
||||
insert into "connectors" ("id", "enabled")
|
||||
values ($1, $2)
|
||||
insert into "connectors" ("id", "type", "enabled")
|
||||
values ($1, $2, $3)
|
||||
returning *
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql);
|
||||
|
||||
expect(values).toEqual([connector.id, connector.enabled]);
|
||||
expect(values).toEqual([connector.id, connector.type, connector.enabled]);
|
||||
|
||||
return createMockQueryResult([connector]);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
ConnectorMetadata,
|
||||
EmailConnectorInstance,
|
||||
EmailMessageTypes,
|
||||
SmsConnectorInstance,
|
||||
ValidateConfig,
|
||||
} from '@/connectors/types';
|
||||
import RequestError from '@/errors/RequestError';
|
||||
|
@ -187,6 +188,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -229,6 +231,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -257,6 +260,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -299,6 +303,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_1',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
|
@ -357,6 +362,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_1',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
|
@ -385,6 +391,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_4',
|
||||
type: ConnectorType.Email,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
|
@ -464,6 +471,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -492,6 +500,7 @@ describe('connector route', () => {
|
|||
return {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -539,6 +548,7 @@ describe('connector route', () => {
|
|||
const mockedEmailConnector: EmailConnectorInstance = {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Email,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -580,9 +590,10 @@ describe('connector route', () => {
|
|||
});
|
||||
|
||||
it('should get SMS connector and send message', async () => {
|
||||
const mockedEmailConnector: EmailConnectorInstance = {
|
||||
const mockedSmsConnector: SmsConnectorInstance = {
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -605,10 +616,10 @@ describe('connector route', () => {
|
|||
};
|
||||
|
||||
getConnectorInstanceByTypePlaceHolder.mockImplementationOnce(async (_: ConnectorType) => {
|
||||
return mockedEmailConnector;
|
||||
return mockedSmsConnector;
|
||||
});
|
||||
|
||||
const sendMessageSpy = jest.spyOn(mockedEmailConnector, 'sendMessage');
|
||||
const sendMessageSpy = jest.spyOn(mockedSmsConnector, 'sendMessage');
|
||||
const response = await connectorRequest
|
||||
.post('/connectors/test/sms')
|
||||
.send({ phone: '12345678901' });
|
||||
|
|
|
@ -192,42 +192,49 @@ export const mockSignInExperience: SignInExperience = {
|
|||
export const mockConnectorList: Connector[] = [
|
||||
{
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Email,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
},
|
||||
{
|
||||
id: 'connector_1',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
},
|
||||
{
|
||||
id: 'connector_2',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_345,
|
||||
},
|
||||
{
|
||||
id: 'connector_3',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_456,
|
||||
},
|
||||
{
|
||||
id: 'connector_4',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
{
|
||||
id: 'connector_5',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
},
|
||||
{
|
||||
id: 'connector_6',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
|
@ -241,6 +248,7 @@ export const mockConnectorInstanceList: Array<{
|
|||
{
|
||||
connector: {
|
||||
id: 'connector_0',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_123,
|
||||
|
@ -257,6 +265,7 @@ export const mockConnectorInstanceList: Array<{
|
|||
{
|
||||
connector: {
|
||||
id: 'connector_1',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_234,
|
||||
|
@ -273,6 +282,7 @@ export const mockConnectorInstanceList: Array<{
|
|||
{
|
||||
connector: {
|
||||
id: 'connector_2',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_345,
|
||||
|
@ -289,6 +299,7 @@ export const mockConnectorInstanceList: Array<{
|
|||
{
|
||||
connector: {
|
||||
id: 'connector_3',
|
||||
type: ConnectorType.Social,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_456,
|
||||
|
@ -305,6 +316,7 @@ export const mockConnectorInstanceList: Array<{
|
|||
{
|
||||
connector: {
|
||||
id: 'connector_4',
|
||||
type: ConnectorType.Email,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
|
@ -321,6 +333,7 @@ export const mockConnectorInstanceList: Array<{
|
|||
{
|
||||
connector: {
|
||||
id: 'connector_5',
|
||||
type: ConnectorType.SMS,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
|
@ -337,6 +350,7 @@ export const mockConnectorInstanceList: Array<{
|
|||
{
|
||||
connector: {
|
||||
id: 'connector_6',
|
||||
type: ConnectorType.Email,
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_234_567_890_567,
|
||||
|
|
|
@ -336,6 +336,7 @@ const errors = {
|
|||
oauth_code_invalid: 'Unable to get access token, please check authorization code.',
|
||||
more_than_one_sms: 'The number of SMS connectors is larger then 1.',
|
||||
more_than_one_email: 'The number of Email connectors is larger then 1.',
|
||||
db_connector_type_mismatch: 'There is a connector in the DB that does not match the type.',
|
||||
},
|
||||
passcode: {
|
||||
phone_email_empty: 'Both phone and email are empty.',
|
||||
|
|
|
@ -334,6 +334,7 @@ const errors = {
|
|||
oauth_code_invalid: '无法获取 access_token,请检查授权 code 是否有效。',
|
||||
more_than_one_sms: '同时存在超过 1 个短信连接器。',
|
||||
more_than_one_email: '同时存在超过 1 个邮件连接器。',
|
||||
db_connector_type_mismatch: '数据库中存在一个类型不匹配的连接器。',
|
||||
},
|
||||
passcode: {
|
||||
phone_email_empty: '手机号与邮箱地址均为空。',
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { ArbitraryObject, arbitraryObjectGuard, GeneratedSchema, Guard } from '../foundations';
|
||||
import { ConnectorType } from './custom-types';
|
||||
|
||||
export type CreateConnector = {
|
||||
id: string;
|
||||
type: ConnectorType;
|
||||
enabled?: boolean;
|
||||
config?: ArbitraryObject;
|
||||
createdAt?: number;
|
||||
|
@ -13,6 +15,7 @@ export type CreateConnector = {
|
|||
|
||||
export type Connector = {
|
||||
id: string;
|
||||
type: ConnectorType;
|
||||
enabled: boolean;
|
||||
config: ArbitraryObject;
|
||||
createdAt: number;
|
||||
|
@ -20,6 +23,7 @@ export type Connector = {
|
|||
|
||||
const createGuard: Guard<CreateConnector> = z.object({
|
||||
id: z.string(),
|
||||
type: z.nativeEnum(ConnectorType),
|
||||
enabled: z.boolean().optional(),
|
||||
config: arbitraryObjectGuard.optional(),
|
||||
createdAt: z.number().optional(),
|
||||
|
@ -30,10 +34,11 @@ export const Connectors: GeneratedSchema<CreateConnector> = Object.freeze({
|
|||
tableSingular: 'connector',
|
||||
fields: {
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
enabled: 'enabled',
|
||||
config: 'config',
|
||||
createdAt: 'created_at',
|
||||
},
|
||||
fieldKeys: ['id', 'enabled', 'config', 'createdAt'],
|
||||
fieldKeys: ['id', 'type', 'enabled', 'config', 'createdAt'],
|
||||
createGuard,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,11 @@ export enum ApplicationType {
|
|||
SPA = 'SPA',
|
||||
Traditional = 'Traditional',
|
||||
}
|
||||
export enum ConnectorType {
|
||||
Email = 'Email',
|
||||
SMS = 'SMS',
|
||||
Social = 'Social',
|
||||
}
|
||||
export enum PasscodeType {
|
||||
SignIn = 'SignIn',
|
||||
Register = 'Register',
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import { Languages } from '@logto/phrases';
|
||||
|
||||
import { Connector } from '../db-entries';
|
||||
import { Connector, ConnectorType } from '../db-entries';
|
||||
|
||||
export enum ConnectorType {
|
||||
SMS = 'SMS',
|
||||
Email = 'Email',
|
||||
Social = 'Social',
|
||||
}
|
||||
export interface ConnectorMetadata {
|
||||
id: string;
|
||||
type: ConnectorType;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
create type connector_type as enum ('Email', 'SMS', 'Social');
|
||||
|
||||
create table connectors (
|
||||
id varchar(128) not null,
|
||||
type connector_type not null,
|
||||
enabled boolean not null default TRUE,
|
||||
config jsonb /* @use ArbitraryObject */ not null default '{}'::jsonb,
|
||||
created_at timestamptz not null default(now()),
|
||||
|
|
Loading…
Reference in a new issue