0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-17 22:04:19 -05:00

feat: update virtual connector loading to getConfig by id (#2508)

This commit is contained in:
Darcy Ye 2022-11-23 14:11:13 +08:00 committed by GitHub
parent 973846eaed
commit f0f9bec107
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 80 deletions

View file

@ -3,7 +3,7 @@ import type { Connector } from '@logto/schemas';
import { ConnectorType } from '@logto/schemas';
import { any } from 'zod';
import type { VirtualConnector, LogtoConnector } from '#src/connectors/types.js';
import type { LogtoConnector, ConnectorFactory } from '#src/connectors/types.js';
import {
mockConnector0,
@ -43,10 +43,11 @@ export const mockLogtoConnector = {
configGuard: any(),
};
export const mockVirtualConnector: VirtualConnector = {
export const mockConnectorFactory: ConnectorFactory = {
metadata: mockMetadata,
type: ConnectorType.Social,
...mockLogtoConnector,
path: 'random_path',
createConnector: jest.fn(),
};
export const mockConnectorList: Connector[] = [

View file

@ -14,16 +14,17 @@ import { findAllConnectors, insertConnector } from '#src/queries/connector.js';
import { defaultConnectorMethods } from './consts.js';
import { metaUrl } from './meta-url.js';
import type { VirtualConnector, LogtoConnector } from './types.js';
import type { ConnectorFactory, LogtoConnector } from './types.js';
import { getConnectorConfig, readUrl, validateConnectorModule } from './utilities/index.js';
const currentDirname = path.dirname(fileURLToPath(metaUrl));
// eslint-disable-next-line @silverhand/fp/no-let
let cachedVirtualConnectors: VirtualConnector[] | undefined;
export const loadVirtualConnectors = async () => {
if (cachedVirtualConnectors) {
return cachedVirtualConnectors;
// eslint-disable-next-line @silverhand/fp/no-let
let cachedConnectorFactories: ConnectorFactory[] | undefined;
export const loadConnectorFactories = async () => {
if (cachedConnectorFactories) {
return cachedConnectorFactories;
}
const coreDirectory = await findPackage(currentDirname);
@ -35,7 +36,7 @@ export const loadVirtualConnectors = async () => {
const connectorPackages = await getConnectorPackagesFromDirectory(directory);
const connectors = await Promise.all(
const connectorFactories = await Promise.all(
connectorPackages.map(async ({ path: packagePath, name }) => {
try {
// TODO: fix type and remove `/lib/index.js` suffix once we upgrade all connectors to ESM
@ -50,28 +51,12 @@ export const loadVirtualConnectors = async () => {
const rawConnector = await createConnector({ getConfig: getConnectorConfig });
validateConnectorModule(rawConnector);
const connector: VirtualConnector = {
...defaultConnectorMethods,
...rawConnector,
metadata: {
...rawConnector.metadata,
logo: await readUrl(rawConnector.metadata.logo, packagePath, 'svg'),
logoDark:
rawConnector.metadata.logoDark &&
(await readUrl(rawConnector.metadata.logoDark, packagePath, 'svg')),
readme: await readUrl(rawConnector.metadata.readme, packagePath, 'text'),
configTemplate: await readUrl(
rawConnector.metadata.configTemplate,
packagePath,
'text'
),
},
validateConfig: (config: unknown) => {
validateConfig(config, rawConnector.configGuard);
},
return {
metadata: rawConnector.metadata,
type: rawConnector.type,
createConnector,
path: packagePath + '/lib/index.js',
};
return connector;
} catch (error: unknown) {
if (error instanceof Error) {
console.log(
@ -89,34 +74,72 @@ export const loadVirtualConnectors = async () => {
);
// eslint-disable-next-line @silverhand/fp/no-mutation
cachedVirtualConnectors = connectors.filter(
(connector): connector is VirtualConnector => connector !== undefined
cachedConnectorFactories = connectorFactories.filter(
(connectorFactory): connectorFactory is ConnectorFactory => connectorFactory !== undefined
);
return cachedVirtualConnectors;
return cachedConnectorFactories;
};
export const getLogtoConnectors = async (): Promise<LogtoConnector[]> => {
const connectors = await findAllConnectors();
const databaseConnectors = await findAllConnectors();
const virtualConnectors = await loadVirtualConnectors();
const logtoConnectors = await Promise.all(
databaseConnectors.map(async (databaseConnector) => {
const { id, metadata, connectorId } = databaseConnector;
return connectors
.map((connector) => {
const { metadata, connectorId } = connector;
const virtualConnector = virtualConnectors.find(({ metadata: { id } }) => id === connectorId);
const connectorFactories = await loadConnectorFactories();
const connectorFactory = connectorFactories.find(
({ metadata }) => metadata.id === connectorId
);
if (!virtualConnector) {
if (!connectorFactory) {
return;
}
return {
...virtualConnector,
metadata: { ...virtualConnector.metadata, ...metadata },
dbEntry: connector,
const { createConnector, path: packagePath } = connectorFactory;
try {
const rawConnector = await createConnector({
getConfig: async () => {
return getConnectorConfig(id);
},
});
validateConnectorModule(rawConnector);
const connector: AllConnector = {
...defaultConnectorMethods,
...rawConnector,
metadata: {
...rawConnector.metadata,
logo: await readUrl(rawConnector.metadata.logo, packagePath, 'svg'),
logoDark:
rawConnector.metadata.logoDark &&
(await readUrl(rawConnector.metadata.logoDark, packagePath, 'svg')),
readme: await readUrl(rawConnector.metadata.readme, packagePath, 'text'),
configTemplate: await readUrl(
rawConnector.metadata.configTemplate,
packagePath,
'text'
),
...metadata,
},
};
return {
...connector,
validateConfig: (config: unknown) => {
validateConfig(config, rawConnector.configGuard);
},
dbEntry: databaseConnector,
};
} catch {}
})
.filter((connector): connector is LogtoConnector => connector !== undefined);
);
return logtoConnectors.filter(
(logtoConnector): logtoConnector is LogtoConnector => logtoConnector !== undefined
);
};
export const getLogtoConnectorById = async (id: string): Promise<LogtoConnector> => {
@ -136,8 +159,10 @@ export const getLogtoConnectorById = async (id: string): Promise<LogtoConnector>
export const initConnectors = async () => {
const connectors = await findAllConnectors();
const existingConnectors = new Map(connectors.map((connector) => [connector.id, connector]));
const allConnectors = await loadVirtualConnectors();
const existingConnectors = new Map(
connectors.map((connector) => [connector.connectorId, connector])
);
const allConnectors = await loadConnectorFactories();
const newConnectors = allConnectors.filter(({ metadata: { id } }) => {
const connector = existingConnectors.get(id);

View file

@ -1,4 +1,4 @@
import type { AllConnector } from '@logto/connector-kit';
import type { AllConnector, CreateConnector } from '@logto/connector-kit';
import type { Connector, PasscodeType } from '@logto/schemas';
import { z } from 'zod';
@ -19,13 +19,17 @@ export type SocialUserInfo = z.infer<typeof socialUserInfoGuard>;
/**
* Dynamic loaded connector type.
*/
export type VirtualConnector<T extends AllConnector = AllConnector> = T & {
validateConfig: (config: unknown) => void;
export type ConnectorFactory<T extends AllConnector = AllConnector> = Pick<
T,
'type' | 'metadata'
> & {
createConnector: CreateConnector<AllConnector>;
path: string;
};
/**
* The connector type with full context.
*/
export type LogtoConnector<T extends AllConnector = AllConnector> = VirtualConnector<T> & {
dbEntry: Connector;
};
export type LogtoConnector<T extends AllConnector = AllConnector> = T & {
validateConfig: (config: unknown) => void;
} & { dbEntry: Connector };

View file

@ -6,11 +6,11 @@ import { any } from 'zod';
import {
mockMetadata,
mockConnector,
mockVirtualConnector,
mockConnectorFactory,
mockLogtoConnectorList,
} from '#src/__mocks__/index.js';
import { defaultConnectorMethods } from '#src/connectors/consts.js';
import type { VirtualConnector, LogtoConnector } from '#src/connectors/types.js';
import type { ConnectorFactory, LogtoConnector } from '#src/connectors/types.js';
import RequestError from '#src/errors/RequestError/index.js';
import { countConnectorByConnectorId, deleteConnectorById } from '#src/queries/connector.js';
import assertThat from '#src/utils/assert-that.js';
@ -18,8 +18,8 @@ import { createRequester } from '#src/utils/test-utils.js';
import connectorRoutes from './connector.js';
const loadVirtualConnectorsPlaceHolder = jest.fn() as jest.MockedFunction<
() => Promise<VirtualConnector[]>
const loadConnectorFactoriesPlaceHolder = jest.fn() as jest.MockedFunction<
() => Promise<ConnectorFactory[]>
>;
const getLogtoConnectorsPlaceHolder = jest.fn() as jest.MockedFunction<
() => Promise<LogtoConnector[]>
@ -32,7 +32,7 @@ jest.mock('#src/queries/connector.js', () => ({
}));
jest.mock('#src/connectors/index.js', () => ({
loadVirtualConnectors: async () => loadVirtualConnectorsPlaceHolder(),
loadConnectorFactories: async () => loadConnectorFactoriesPlaceHolder(),
getLogtoConnectors: async () => getLogtoConnectorsPlaceHolder(),
getLogtoConnectorById: async (connectorId: string) => {
const connectors = await getLogtoConnectorsPlaceHolder();
@ -112,10 +112,10 @@ describe('connector route', () => {
});
it('should post a new connector record', async () => {
loadVirtualConnectorsPlaceHolder.mockResolvedValueOnce([
loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
{
...mockVirtualConnector,
metadata: { ...mockVirtualConnector.metadata, id: 'connectorId' },
...mockConnectorFactory,
metadata: { ...mockConnectorFactory.metadata, id: 'connectorId' },
},
]);
mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
@ -135,11 +135,11 @@ describe('connector route', () => {
expect(response).toHaveProperty('statusCode', 200);
});
it('throws when virtual connector not found', async () => {
loadVirtualConnectorsPlaceHolder.mockResolvedValueOnce([
it('throws when connector factory not found', async () => {
loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
{
...mockVirtualConnector,
metadata: { ...mockVirtualConnector.metadata, id: 'connectorId' },
...mockConnectorFactory,
metadata: { ...mockConnectorFactory.metadata, id: 'connectorId' },
},
]);
mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
@ -150,11 +150,11 @@ describe('connector route', () => {
expect(response).toHaveProperty('statusCode', 422);
});
it('should post a new record when add more than 1 instance with virtual connector', async () => {
loadVirtualConnectorsPlaceHolder.mockResolvedValueOnce([
it('should post a new record when add more than 1 instance with connector factory', async () => {
loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
{
...mockVirtualConnector,
metadata: { ...mockVirtualConnector.metadata, id: 'id0', isStandard: true },
...mockConnectorFactory,
metadata: { ...mockConnectorFactory.metadata, id: 'id0', isStandard: true },
},
]);
mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 1 });
@ -174,11 +174,11 @@ describe('connector route', () => {
expect(response).toHaveProperty('statusCode', 200);
});
it('throws when add more than 1 instance with non-virtual connector', async () => {
loadVirtualConnectorsPlaceHolder.mockResolvedValueOnce([
it('throws when add more than 1 instance with non-connector factory', async () => {
loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
{
...mockVirtualConnector,
metadata: { ...mockVirtualConnector.metadata, id: 'id0' },
...mockConnectorFactory,
metadata: { ...mockConnectorFactory.metadata, id: 'id0' },
},
]);
mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 1 });

View file

@ -8,7 +8,7 @@ import { object, string } from 'zod';
import {
getLogtoConnectorById,
getLogtoConnectors,
loadVirtualConnectors,
loadConnectorFactories,
} from '#src/connectors/index.js';
import type { LogtoConnector } from '#src/connectors/types.js';
import RequestError from '#src/errors/RequestError/index.js';
@ -101,10 +101,12 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
body,
} = ctx.guard;
const virtualConnectors = await loadVirtualConnectors();
const virtualConnector = virtualConnectors.find(({ metadata: { id } }) => id === connectorId);
const connectorFactories = await loadConnectorFactories();
const connectorFactory = connectorFactories.find(
({ metadata: { id } }) => id === connectorId
);
if (!virtualConnector) {
if (!connectorFactory) {
throw new RequestError({
code: 'connector.not_found_with_connector_id',
status: 422,
@ -113,7 +115,7 @@ export default function connectorRoutes<T extends AuthedRouter>(router: T) {
const { count } = await countConnectorByConnectorId(connectorId);
assertThat(
count === 0 || virtualConnector.metadata.isStandard === true,
count === 0 || connectorFactory.metadata.isStandard === true,
new RequestError({
code: 'connector.multiple_instances_not_supported',
status: 422,