diff --git a/packages/core/src/libraries/user.ts b/packages/core/src/libraries/user.ts index a50129b1f..5ff96453b 100644 --- a/packages/core/src/libraries/user.ts +++ b/packages/core/src/libraries/user.ts @@ -50,8 +50,8 @@ export const createUserLibrary = (queries: Queries) => { const { pool, roles: { findRolesByRoleNames, insertRoles, findRoleByRoleName }, - users: { hasUser, hasUserWithEmail, hasUserWithId, hasUserWithPhone }, - usersRoles: { insertUsersRoles }, + users: { hasUser, hasUserWithEmail, hasUserWithId, hasUserWithPhone, findUsersByIds }, + usersRoles: { insertUsersRoles, findUsersRolesByRoleId }, } = queries; const generateUserId = async (retries = 500) => @@ -140,9 +140,26 @@ export const createUserLibrary = (queries: Queries) => { } }; + const findUsersByRoleName = async (roleName: string) => { + const role = await findRoleByRoleName(roleName); + + if (!role) { + return []; + } + + const usersRoles = await findUsersRolesByRoleId(role.id); + + if (usersRoles.length === 0) { + return []; + } + + return findUsersByIds(usersRoles.map(({ userId }) => userId)); + }; + return { generateUserId, insertUser, checkIdentifierCollision, + findUsersByRoleName, }; }; diff --git a/packages/core/src/middleware/koa-check-demo-app.ts b/packages/core/src/middleware/koa-check-demo-app.ts index f8dba3913..cfb1a4199 100644 --- a/packages/core/src/middleware/koa-check-demo-app.ts +++ b/packages/core/src/middleware/koa-check-demo-app.ts @@ -1,13 +1,13 @@ import { demoAppApplicationId } from '@logto/schemas'; import type { MiddlewareType } from 'koa'; -import { findApplicationById } from '#src/queries/application.js'; +import type Queries from '#src/tenants/Queries.js'; + +export default function koaCheckDemoApp( + queries: Queries +): MiddlewareType { + const { findApplicationById } = queries.applications; -export default function koaCheckDemoApp(): MiddlewareType< - StateT, - ContextT, - ResponseBodyT -> { return async (ctx, next) => { try { await findApplicationById(demoAppApplicationId); diff --git a/packages/core/src/oidc/adapter.test.ts b/packages/core/src/oidc/adapter.test.ts index 2b48e8582..ac8fada44 100644 --- a/packages/core/src/oidc/adapter.test.ts +++ b/packages/core/src/oidc/adapter.test.ts @@ -3,6 +3,7 @@ import { createMockUtils } from '@logto/shared/esm'; import snakecaseKeys from 'snakecase-keys'; import { mockApplication } from '#src/__mocks__/index.js'; +import { MockQueries } from '#src/test-utils/tenant.js'; import { getConstantClientMetadata } from './utils.js'; @@ -10,19 +11,6 @@ const { jest } = import.meta; const { mockEsm } = createMockUtils(jest); -mockEsm('#src/queries/application.js', () => ({ - findApplicationById: jest.fn(async (): Promise => mockApplication), -})); - -mockEsm('#src/queries/oidc-model-instance.js', () => ({ - upsertInstance: jest.fn(), - findPayloadById: jest.fn(), - findPayloadByPayloadField: jest.fn(), - consumeInstanceById: jest.fn(), - destroyInstanceById: jest.fn(), - revokeInstanceByGrantId: jest.fn(), -})); - mockEsm( 'date-fns', jest.fn(() => ({ @@ -31,6 +19,15 @@ mockEsm( ); const { default: postgresAdapter } = await import('./adapter.js'); + +const oidcModelInstances = { + upsertInstance: jest.fn(), + findPayloadById: jest.fn(), + findPayloadByPayloadField: jest.fn(), + consumeInstanceById: jest.fn(), + destroyInstanceById: jest.fn(), + revokeInstanceByGrantId: jest.fn(), +}; const { consumeInstanceById, destroyInstanceById, @@ -38,14 +35,19 @@ const { findPayloadByPayloadField, revokeInstanceByGrantId, upsertInstance, -} = await import('#src/queries/oidc-model-instance.js'); +} = oidcModelInstances; + +const queries = new MockQueries({ + applications: { findApplicationById: jest.fn(async (): Promise => mockApplication) }, + oidcModelInstances, +}); const now = Date.now(); describe('postgres Adapter', () => { it('Client Modal', async () => { const rejectError = new Error('Not implemented'); - const adapter = postgresAdapter('Client'); + const adapter = postgresAdapter(queries, 'Client'); await expect(adapter.upsert('client', {}, 0)).rejects.toMatchError(rejectError); await expect(adapter.findByUserCode('foo')).rejects.toMatchError(rejectError); @@ -82,7 +84,7 @@ describe('postgres Adapter', () => { const id = 'fooId'; const grantId = 'grantId'; const expireAt = 60; - const adapter = postgresAdapter(modelName); + const adapter = postgresAdapter(queries, modelName); await adapter.upsert(id, { uid, userCode }, expireAt); expect(upsertInstance).toBeCalledWith({ diff --git a/packages/core/src/oidc/adapter.ts b/packages/core/src/oidc/adapter.ts index e7198468c..2eaa2127f 100644 --- a/packages/core/src/oidc/adapter.ts +++ b/packages/core/src/oidc/adapter.ts @@ -8,15 +8,7 @@ import { errors } from 'oidc-provider'; import snakecaseKeys from 'snakecase-keys'; import envSet, { MountedApps } from '#src/env-set/index.js'; -import { findApplicationById } from '#src/queries/application.js'; -import { - consumeInstanceById, - destroyInstanceById, - findPayloadById, - findPayloadByPayloadField, - revokeInstanceByGrantId, - upsertInstance, -} from '#src/queries/oidc-model-instance.js'; +import type Queries from '#src/tenants/Queries.js'; import { appendPath } from '#src/utils/url.js'; import { getConstantClientMetadata } from './utils.js'; @@ -54,7 +46,22 @@ const buildDemoAppUris = ( return data; }; -export default function postgresAdapter(modelName: string): ReturnType { +export default function postgresAdapter( + queries: Queries, + modelName: string +): ReturnType { + const { + applications: { findApplicationById }, + oidcModelInstances: { + consumeInstanceById, + destroyInstanceById, + findPayloadById, + findPayloadByPayloadField, + revokeInstanceByGrantId, + upsertInstance, + }, + } = queries; + if (modelName === 'Client') { const reject = async () => { throw new Error('Not implemented'); diff --git a/packages/core/src/oidc/init.ts b/packages/core/src/oidc/init.ts index dc3668669..4ef0fca46 100644 --- a/packages/core/src/oidc/init.ts +++ b/packages/core/src/oidc/init.ts @@ -45,7 +45,7 @@ export default function initOidc(queries: Queries): Provider { } as const); const oidc = new Provider(issuer, { - adapter: postgresAdapter, + adapter: postgresAdapter.bind(null, queries), renderError: (_ctx, _out, error) => { console.error(error); diff --git a/packages/core/src/queries/application.ts b/packages/core/src/queries/application.ts index 8b3ad15b0..743e524f0 100644 --- a/packages/core/src/queries/application.ts +++ b/packages/core/src/queries/application.ts @@ -9,7 +9,6 @@ import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js' import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; import { getTotalRowCountWithPool } from '#src/database/row-count.js'; import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; -import envSet from '#src/env-set/index.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; const { table, fields } = convertToIdentifiers(Applications); @@ -65,14 +64,3 @@ export const createApplicationQueries = (pool: CommonQueryMethods) => { deleteApplicationById, }; }; - -/** @deprecated Will be removed soon. Use createApplicationQueries() factory instead. */ -export const { - findTotalNumberOfApplications, - findAllApplications, - findApplicationById, - insertApplication, - updateApplication, - updateApplicationById, - deleteApplicationById, -} = createApplicationQueries(envSet.pool); diff --git a/packages/core/src/queries/connector.ts b/packages/core/src/queries/connector.ts index 2f92056a6..441793acd 100644 --- a/packages/core/src/queries/connector.ts +++ b/packages/core/src/queries/connector.ts @@ -6,7 +6,6 @@ import { sql } from 'slonik'; import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; -import envSet from '#src/env-set/index.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; const { table, fields } = convertToIdentifiers(Connectors); @@ -72,14 +71,3 @@ export const createConnectorQueries = (pool: CommonQueryMethods) => { updateConnector, }; }; - -/** @deprecated Will be removed soon. Use createConnectorQueries() factory instead. */ -export const { - findAllConnectors, - findConnectorById, - countConnectorByConnectorId, - deleteConnectorById, - deleteConnectorByIds, - insertConnector, - updateConnector, -} = createConnectorQueries(envSet.pool); diff --git a/packages/core/src/queries/custom-phrase.ts b/packages/core/src/queries/custom-phrase.ts index 9eb30af1c..a100e2406 100644 --- a/packages/core/src/queries/custom-phrase.ts +++ b/packages/core/src/queries/custom-phrase.ts @@ -5,7 +5,6 @@ import type { CommonQueryMethods } from 'slonik'; import { sql } from 'slonik'; import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; -import envSet from '#src/env-set/index.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; const { table, fields } = convertToIdentifiers(CustomPhrases); @@ -69,12 +68,3 @@ export const createCustomPhraseQueries = (pool: CommonQueryMethods) => { deleteCustomPhraseByLanguageTag, }; }; - -/** @deprecated Will be removed soon. Use createCustomPhraseQueries() factory instead. */ -export const { - findAllCustomLanguageTags, - findAllCustomPhrases, - findCustomPhraseByLanguageTag, - upsertCustomPhrase, - deleteCustomPhraseByLanguageTag, -} = createCustomPhraseQueries(envSet.pool); diff --git a/packages/core/src/queries/log.ts b/packages/core/src/queries/log.ts index d5b83e262..9c2b71b7c 100644 --- a/packages/core/src/queries/log.ts +++ b/packages/core/src/queries/log.ts @@ -6,7 +6,6 @@ import { sql } from 'slonik'; import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js'; import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; -import envSet from '#src/env-set/index.js'; const { table, fields } = convertToIdentifiers(Logs); @@ -88,13 +87,3 @@ export const createLogQueries = (pool: CommonQueryMethods) => { countActiveUsersByTimeInterval, }; }; - -/** @deprecated Will be removed soon. Use createLogQueries() factory instead. */ -export const { - insertLog, - countLogs, - findLogs, - findLogById, - getDailyActiveUserCountsByTimeInterval, - countActiveUsersByTimeInterval, -} = createLogQueries(envSet.pool); diff --git a/packages/core/src/queries/oidc-model-instance.ts b/packages/core/src/queries/oidc-model-instance.ts index f91ee9b8a..d9e8c459c 100644 --- a/packages/core/src/queries/oidc-model-instance.ts +++ b/packages/core/src/queries/oidc-model-instance.ts @@ -131,14 +131,3 @@ export const createOidcModelInstanceQueries = (pool: CommonQueryMethods) => { revokeInstanceByUserId, }; }; - -/** @deprecated Will be removed soon. Use createOidcModelInstanceQueries() factory instead. */ -export const { - upsertInstance, - findPayloadById, - findPayloadByPayloadField, - consumeInstanceById, - destroyInstanceById, - revokeInstanceByGrantId, - revokeInstanceByUserId, -} = createOidcModelInstanceQueries(envSet.pool); diff --git a/packages/core/src/queries/passcode.ts b/packages/core/src/queries/passcode.ts index b79740933..bbd49c507 100644 --- a/packages/core/src/queries/passcode.ts +++ b/packages/core/src/queries/passcode.ts @@ -6,7 +6,6 @@ import type { CommonQueryMethods } from 'slonik'; import { sql } from 'slonik'; import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; -import envSet from '#src/env-set/index.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; const { table, fields } = convertToIdentifiers(Passcodes); @@ -82,14 +81,3 @@ export const createPasscodeQueries = (pool: CommonQueryMethods) => { deletePasscodesByIds, }; }; - -/** @deprecated Will be removed soon. Use createPasscodeQueries() factory instead. */ -export const { - findUnconsumedPasscodeByJtiAndType, - findUnconsumedPasscodesByJtiAndType, - insertPasscode, - consumePasscode, - increasePasscodeTryCount, - deletePasscodeById, - deletePasscodesByIds, -} = createPasscodeQueries(envSet.pool); diff --git a/packages/core/src/queries/resource.ts b/packages/core/src/queries/resource.ts index 075ee9e24..b13d6cf50 100644 --- a/packages/core/src/queries/resource.ts +++ b/packages/core/src/queries/resource.ts @@ -9,7 +9,6 @@ import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js' import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; import { getTotalRowCountWithPool } from '#src/database/row-count.js'; import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; -import envSet from '#src/env-set/index.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; const { table, fields } = convertToIdentifiers(Resources); @@ -80,16 +79,3 @@ export const createResourceQueries = (pool: CommonQueryMethods) => { deleteResourceById, }; }; - -/** @deprecated Will be removed soon. Use createResourceQueries() factory instead. */ -export const { - findTotalNumberOfResources, - findAllResources, - findResourceByIndicator, - findResourceById, - findResourcesByIds, - insertResource, - updateResource, - updateResourceById, - deleteResourceById, -} = createResourceQueries(envSet.pool); diff --git a/packages/core/src/queries/roles-scopes.ts b/packages/core/src/queries/roles-scopes.ts index a2ae2b080..f4391a263 100644 --- a/packages/core/src/queries/roles-scopes.ts +++ b/packages/core/src/queries/roles-scopes.ts @@ -4,7 +4,6 @@ import { convertToIdentifiers } from '@logto/shared'; import type { CommonQueryMethods } from 'slonik'; import { sql } from 'slonik'; -import envSet from '#src/env-set/index.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; const { table, fields } = convertToIdentifiers(RolesScopes); @@ -39,7 +38,3 @@ export const createRolesScopesQueries = (pool: CommonQueryMethods) => { return { insertRolesScopes, findRolesScopesByRoleId, deleteRolesScope }; }; - -/** @deprecated Will be removed soon. Use createRolesScopesQueries() factory instead. */ -export const { insertRolesScopes, findRolesScopesByRoleId, deleteRolesScope } = - createRolesScopesQueries(envSet.pool); diff --git a/packages/core/src/queries/scope.ts b/packages/core/src/queries/scope.ts index c6711e8e1..96c662845 100644 --- a/packages/core/src/queries/scope.ts +++ b/packages/core/src/queries/scope.ts @@ -8,7 +8,6 @@ import { sql } from 'slonik'; import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js'; import { buildInsertIntoWithPool } from '#src/database/insert-into.js'; import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; -import envSet from '#src/env-set/index.js'; import { DeletionError } from '#src/errors/SlonikError/index.js'; import type { Search } from '#src/utils/search.js'; import { buildConditionsFromSearch } from '#src/utils/search.js'; @@ -117,18 +116,3 @@ export const createScopeQueries = (pool: CommonQueryMethods) => { deleteScopeById, }; }; - -/** @deprecated Will be removed soon. Use createScopeQueries() factory instead. */ -export const { - findScopes, - countScopes, - findScopeByNameAndResourceId, - findScopesByResourceId, - findScopesByResourceIds, - findScopesByIds, - insertScope, - findScopeById, - updateScope, - updateScopeById, - deleteScopeById, -} = createScopeQueries(envSet.pool); diff --git a/packages/core/src/queries/setting.ts b/packages/core/src/queries/setting.ts index e93daad9a..43f6e575d 100644 --- a/packages/core/src/queries/setting.ts +++ b/packages/core/src/queries/setting.ts @@ -5,7 +5,6 @@ import type { CommonQueryMethods } from 'slonik'; import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js'; import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; -import envSet from '#src/env-set/index.js'; export const defaultSettingId = 'default'; @@ -23,6 +22,3 @@ export const createSettingQueries = (pool: CommonQueryMethods) => { return { getSetting, updateSetting }; }; - -/** @deprecated Will be removed soon. Use createSettingQueries() factory instead. */ -export const { getSetting, updateSetting } = createSettingQueries(envSet.pool); diff --git a/packages/core/src/queries/sign-in-experience.ts b/packages/core/src/queries/sign-in-experience.ts index 6dc71db23..104d530ea 100644 --- a/packages/core/src/queries/sign-in-experience.ts +++ b/packages/core/src/queries/sign-in-experience.ts @@ -4,7 +4,6 @@ import type { CommonQueryMethods } from 'slonik'; import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js'; import { buildUpdateWhereWithPool } from '#src/database/update-where.js'; -import envSet from '#src/env-set/index.js'; const id = 'default'; @@ -24,7 +23,3 @@ export const createSignInExperienceQueries = (pool: CommonQueryMethods) => { return { updateDefaultSignInExperience, findDefaultSignInExperience }; }; - -/** @deprecated Will be removed soon. Use createSignInExperienceQueries() factory instead. */ -export const { updateDefaultSignInExperience, findDefaultSignInExperience } = - createSignInExperienceQueries(envSet.pool); diff --git a/packages/core/src/queries/user.ts b/packages/core/src/queries/user.ts index c3b004abd..82312c365 100644 --- a/packages/core/src/queries/user.ts +++ b/packages/core/src/queries/user.ts @@ -11,8 +11,9 @@ import { DeletionError } from '#src/errors/SlonikError/index.js'; import type { Search } from '#src/utils/search.js'; import { buildConditionsFromSearch } from '#src/utils/search.js'; -import { findRoleByRoleName, findRolesByRoleIds } from './roles.js'; -import { findUsersRolesByRoleId, findUsersRolesByUserId } from './users-roles.js'; +// TODO: @sijie remove this +import { findRolesByRoleIds } from './roles.js'; +import { findUsersRolesByUserId } from './users-roles.js'; const { table, fields } = convertToIdentifiers(Users); @@ -215,22 +216,6 @@ export const createUserQueries = (pool: CommonQueryMethods) => { group by date(${fields.createdAt}) `); - const findUsersByRoleName = async (roleName: string) => { - const role = await findRoleByRoleName(roleName); - - if (!role) { - return []; - } - - const usersRoles = await findUsersRolesByRoleId(role.id); - - if (usersRoles.length === 0) { - return []; - } - - return findUsersByIds(usersRoles.map(({ userId }) => userId)); - }; - return { findUserByUsername, findUserByEmail, @@ -250,7 +235,6 @@ export const createUserQueries = (pool: CommonQueryMethods) => { deleteUserIdentity, hasActiveUsers, getDailyNewUserCountsByTimeInterval, - findUsersByRoleName, }; }; @@ -274,5 +258,4 @@ export const { deleteUserIdentity, hasActiveUsers, getDailyNewUserCountsByTimeInterval, - findUsersByRoleName, } = createUserQueries(envSet.pool); diff --git a/packages/core/src/routes/admin-user-role.test.ts b/packages/core/src/routes/admin-user-role.test.ts index 0233a3988..dc52c4645 100644 --- a/packages/core/src/routes/admin-user-role.test.ts +++ b/packages/core/src/routes/admin-user-role.test.ts @@ -1,29 +1,32 @@ -import { pickDefault, createMockUtils } from '@logto/shared/esm'; +import { pickDefault } from '@logto/shared/esm'; import { mockRole, mockUser } from '#src/__mocks__/index.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; import { createRequester } from '#src/utils/test-utils.js'; const { jest } = import.meta; -const { mockEsmWithActual } = createMockUtils(jest); +const users = { findUserById: jest.fn() }; -await mockEsmWithActual('#src/queries/user.js', () => ({ - findUserById: jest.fn(), -})); -const { findRolesByRoleIds } = await mockEsmWithActual('#src/queries/roles.js', () => ({ +const roles = { findRolesByRoleIds: jest.fn(), findRoleById: jest.fn(), -})); -const { findUsersRolesByUserId, insertUsersRoles, deleteUsersRolesByUserIdAndRoleId } = - await mockEsmWithActual('#src/queries/users-roles.js', () => ({ - findUsersRolesByUserId: jest.fn(), - insertUsersRoles: jest.fn(), - deleteUsersRolesByUserIdAndRoleId: jest.fn(), - })); +}; +const { findRolesByRoleIds } = roles; + +const usersRoles = { + findUsersRolesByUserId: jest.fn(), + insertUsersRoles: jest.fn(), + deleteUsersRolesByUserIdAndRoleId: jest.fn(), +}; +const { findUsersRolesByUserId, insertUsersRoles, deleteUsersRolesByUserIdAndRoleId } = usersRoles; + +const tenantContext = new MockTenant(undefined, { usersRoles, users, roles }); + const roleRoutes = await pickDefault(import('./admin-user-role.js')); describe('user role routes', () => { - const roleRequester = createRequester({ authedRoutes: roleRoutes }); + const roleRequester = createRequester({ authedRoutes: roleRoutes, tenantContext }); it('GET /users/:id/roles', async () => { findUsersRolesByUserId.mockResolvedValueOnce([]); diff --git a/packages/core/src/routes/admin-user-role.ts b/packages/core/src/routes/admin-user-role.ts index c80ae8e14..bbf5772ec 100644 --- a/packages/core/src/routes/admin-user-role.ts +++ b/packages/core/src/routes/admin-user-role.ts @@ -2,20 +2,19 @@ import { object, string } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; import koaGuard from '#src/middleware/koa-guard.js'; -import { findRolesByRoleIds, findRoleById } from '#src/queries/roles.js'; -import { findUserById } from '#src/queries/user.js'; -import { - deleteUsersRolesByUserIdAndRoleId, - findUsersRolesByUserId, - insertUsersRoles, -} from '#src/queries/users-roles.js'; import assertThat from '#src/utils/assert-that.js'; import type { AuthedRouter, RouterInitArgs } from './types.js'; export default function adminUserRoleRoutes( - ...[router]: RouterInitArgs + ...[router, { queries }]: RouterInitArgs ) { + const { + roles: { findRolesByRoleIds, findRoleById }, + users: { findUserById }, + usersRoles: { deleteUsersRolesByUserIdAndRoleId, findUsersRolesByUserId, insertUsersRoles }, + } = queries; + router.get( '/users/:userId/roles', koaGuard({ diff --git a/packages/core/src/routes/admin-user.ts b/packages/core/src/routes/admin-user.ts index 2b421366f..0275c8868 100644 --- a/packages/core/src/routes/admin-user.ts +++ b/packages/core/src/routes/admin-user.ts @@ -30,12 +30,11 @@ export default function adminUserRoutes( updateUserById, hasUserWithEmail, hasUserWithPhone, - findUsersByRoleName, }, usersRoles: { deleteUsersRolesByUserIdAndRoleId, findUsersRolesByRoleId, insertUsersRoles }, } = queries; const { - users: { checkIdentifierCollision, generateUserId, insertUser }, + users: { checkIdentifierCollision, generateUserId, insertUser, findUsersByRoleName }, } = libraries; router.get('/users', koaPagination(), async (ctx, next) => { diff --git a/packages/core/src/routes/application.test.ts b/packages/core/src/routes/application.test.ts index b2f95bd25..f3deae860 100644 --- a/packages/core/src/routes/application.test.ts +++ b/packages/core/src/routes/application.test.ts @@ -3,32 +3,12 @@ import { ApplicationType } from '@logto/schemas'; import { pickDefault, createMockUtils } from '@logto/shared/esm'; import { mockApplication } from '#src/__mocks__/index.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; const { jest } = import.meta; -const { mockEsm, mockEsmWithActual } = createMockUtils(jest); +const { mockEsmWithActual } = createMockUtils(jest); -const { findApplicationById } = await mockEsmWithActual('#src/queries/application.js', () => ({ - findTotalNumberOfApplications: jest.fn(async () => ({ count: 10 })), - findAllApplications: jest.fn(async () => [mockApplication]), - findApplicationById: jest.fn(async () => mockApplication), - deleteApplicationById: jest.fn(), - insertApplication: jest.fn( - async (body: CreateApplication): Promise => ({ - ...mockApplication, - ...body, - oidcClientMetadata: { - ...mockApplication.oidcClientMetadata, - ...body.oidcClientMetadata, - }, - }) - ), - updateApplicationById: jest.fn( - async (_, data: Partial): Promise => ({ - ...mockApplication, - ...data, - }) - ), -})); +const findApplicationById = jest.fn(async () => mockApplication); await mockEsmWithActual('@logto/core-kit', () => ({ // eslint-disable-next-line unicorn/consistent-function-scoping @@ -36,6 +16,31 @@ await mockEsmWithActual('@logto/core-kit', () => ({ generateStandardId: () => 'randomId', })); +const tenantContext = new MockTenant(undefined, { + applications: { + findTotalNumberOfApplications: jest.fn(async () => ({ count: 10 })), + findAllApplications: jest.fn(async () => [mockApplication]), + findApplicationById, + deleteApplicationById: jest.fn(), + insertApplication: jest.fn( + async (body: CreateApplication): Promise => ({ + ...mockApplication, + ...body, + oidcClientMetadata: { + ...mockApplication.oidcClientMetadata, + ...body.oidcClientMetadata, + }, + }) + ), + updateApplicationById: jest.fn( + async (_, data: Partial): Promise => ({ + ...mockApplication, + ...data, + }) + ), + }, +}); + const { createRequester } = await import('#src/utils/test-utils.js'); const applicationRoutes = await pickDefault(import('./application.js')); @@ -46,7 +51,7 @@ const customClientMetadata = { }; describe('application route', () => { - const applicationRequest = createRequester({ authedRoutes: applicationRoutes }); + const applicationRequest = createRequester({ authedRoutes: applicationRoutes, tenantContext }); it('GET /applications', async () => { const response = await applicationRequest.get('/applications'); diff --git a/packages/core/src/routes/application.ts b/packages/core/src/routes/application.ts index 2c9633735..a7f8a14fa 100644 --- a/packages/core/src/routes/application.ts +++ b/packages/core/src/routes/application.ts @@ -5,20 +5,23 @@ import { object, string } from 'zod'; import koaGuard from '#src/middleware/koa-guard.js'; import koaPagination from '#src/middleware/koa-pagination.js'; import { buildOidcClientMetadata } from '#src/oidc/utils.js'; -import { - deleteApplicationById, - findApplicationById, - findAllApplications, - insertApplication, - updateApplicationById, - findTotalNumberOfApplications, -} from '#src/queries/application.js'; import type { AuthedRouter, RouterInitArgs } from './types.js'; const applicationId = buildIdGenerator(21); -export default function applicationRoutes(...[router]: RouterInitArgs) { +export default function applicationRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { + deleteApplicationById, + findApplicationById, + findAllApplications, + insertApplication, + updateApplicationById, + findTotalNumberOfApplications, + } = queries.applications; + router.get('/applications', koaPagination(), async (ctx, next) => { const { limit, offset } = ctx.pagination; diff --git a/packages/core/src/routes/custom-phrase.test.ts b/packages/core/src/routes/custom-phrase.test.ts index c3ef1a614..4d1439df5 100644 --- a/packages/core/src/routes/custom-phrase.test.ts +++ b/packages/core/src/routes/custom-phrase.test.ts @@ -5,6 +5,7 @@ import { pickDefault, createMockUtils } from '@logto/shared/esm'; import { mockZhCnCustomPhrase, trTrTag, zhCnTag } from '#src/__mocks__/custom-phrase.js'; import { mockSignInExperience } from '#src/__mocks__/index.js'; import RequestError from '#src/errors/RequestError/index.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; import { createRequester } from '#src/utils/test-utils.js'; const { jest } = import.meta; @@ -16,12 +17,7 @@ const mockCustomPhrases: Record = { [mockLanguageTag]: mockPhrase, }; -const { - deleteCustomPhraseByLanguageTag, - findAllCustomPhrases, - findCustomPhraseByLanguageTag, - upsertCustomPhrase, -} = mockEsm('#src/queries/custom-phrase.js', () => ({ +const customPhrases = { deleteCustomPhraseByLanguageTag: jest.fn(async (languageTag: string) => { if (!mockCustomPhrases[languageTag]) { throw new RequestError({ code: 'entity.not_found', status: 404 }); @@ -38,15 +34,15 @@ const { return mockCustomPhrase; }), upsertCustomPhrase: jest.fn(async () => mockPhrase), -})); +}; +const { + deleteCustomPhraseByLanguageTag, + findAllCustomPhrases, + findCustomPhraseByLanguageTag, + upsertCustomPhrase, +} = customPhrases; -const { isStrictlyPartial } = mockEsm('#src/utils/translation.js', () => ({ - isStrictlyPartial: jest.fn(() => true), -})); - -const mockFallbackLanguage = trTrTag; - -mockEsm('#src/queries/sign-in-experience.js', () => ({ +const signInExperiences = { findDefaultSignInExperience: jest.fn( async (): Promise => ({ ...mockSignInExperience, @@ -56,10 +52,18 @@ mockEsm('#src/queries/sign-in-experience.js', () => ({ }, }) ), +}; + +const { isStrictlyPartial } = mockEsm('#src/utils/translation.js', () => ({ + isStrictlyPartial: jest.fn(() => true), })); +const mockFallbackLanguage = trTrTag; + +const tenantContext = new MockTenant(undefined, { customPhrases, signInExperiences }); + const customPhraseRoutes = await pickDefault(import('./custom-phrase.js')); -const customPhraseRequest = createRequester({ authedRoutes: customPhraseRoutes }); +const customPhraseRequest = createRequester({ authedRoutes: customPhraseRoutes, tenantContext }); describe('customPhraseRoutes', () => { afterEach(() => { diff --git a/packages/core/src/routes/custom-phrase.ts b/packages/core/src/routes/custom-phrase.ts index a04199abc..4e1f0a0b2 100644 --- a/packages/core/src/routes/custom-phrase.ts +++ b/packages/core/src/routes/custom-phrase.ts @@ -7,13 +7,6 @@ import { object } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; import koaGuard from '#src/middleware/koa-guard.js'; -import { - deleteCustomPhraseByLanguageTag, - findAllCustomPhrases, - findCustomPhraseByLanguageTag, - upsertCustomPhrase, -} from '#src/queries/custom-phrase.js'; -import { findDefaultSignInExperience } from '#src/queries/sign-in-experience.js'; import assertThat from '#src/utils/assert-that.js'; import { isStrictlyPartial } from '#src/utils/translation.js'; @@ -24,7 +17,19 @@ const cleanDeepTranslation = (translation: Translation) => // eslint-disable-next-line no-restricted-syntax cleanDeep(translation) as Translation; -export default function customPhraseRoutes(...[router]: RouterInitArgs) { +export default function customPhraseRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { + customPhrases: { + deleteCustomPhraseByLanguageTag, + findAllCustomPhrases, + findCustomPhraseByLanguageTag, + upsertCustomPhrase, + }, + signInExperiences: { findDefaultSignInExperience }, + } = queries; + router.get( '/custom-phrases', koaGuard({ diff --git a/packages/core/src/routes/dashboard.test.ts b/packages/core/src/routes/dashboard.test.ts index 7af6059ee..ea0dfd6a7 100644 --- a/packages/core/src/routes/dashboard.test.ts +++ b/packages/core/src/routes/dashboard.test.ts @@ -1,24 +1,18 @@ // The FP version works better for `format()` /* eslint-disable import/no-duplicates */ import { pickDefault } from '@logto/shared/esm'; -import { createMockUtils } from '@logto/shared/esm'; import { endOfDay, subDays } from 'date-fns'; import { format } from 'date-fns/fp'; +import { MockTenant } from '#src/test-utils/tenant.js'; import { createRequester } from '#src/utils/test-utils.js'; /* eslint-enable import/no-duplicates */ const { jest } = import.meta; -const { mockEsm } = createMockUtils(jest); const totalUserCount = 1000; const formatToQueryDate = format('yyyy-MM-dd'); -const { countUsers, getDailyNewUserCountsByTimeInterval } = mockEsm('#src/queries/user.js', () => ({ - countUsers: jest.fn(async () => ({ count: totalUserCount })), - getDailyNewUserCountsByTimeInterval: jest.fn(async () => mockDailyNewUserCounts), -})); - const mockDailyNewUserCounts = [ { date: '2022-05-01', count: 1 }, { date: '2022-05-02', count: 2 }, @@ -41,17 +35,23 @@ const mockDailyActiveUserCounts = [ const mockActiveUserCount = 1000; -const { getDailyActiveUserCountsByTimeInterval, countActiveUsersByTimeInterval } = mockEsm( - '#src/queries/log.js', - () => ({ - getDailyActiveUserCountsByTimeInterval: jest.fn().mockResolvedValue(mockDailyActiveUserCounts), - countActiveUsersByTimeInterval: jest.fn().mockResolvedValue({ count: mockActiveUserCount }), - }) -); +const users = { + countUsers: jest.fn(async () => ({ count: totalUserCount })), + getDailyNewUserCountsByTimeInterval: jest.fn(async () => mockDailyNewUserCounts), +}; +const { countUsers, getDailyNewUserCountsByTimeInterval } = users; + +const logs = { + getDailyActiveUserCountsByTimeInterval: jest.fn().mockResolvedValue(mockDailyActiveUserCounts), + countActiveUsersByTimeInterval: jest.fn().mockResolvedValue({ count: mockActiveUserCount }), +}; +const { getDailyActiveUserCountsByTimeInterval, countActiveUsersByTimeInterval } = logs; + +const tenantContext = new MockTenant(undefined, { logs, users }); const dashboardRoutes = await pickDefault(import('./dashboard.js')); describe('dashboardRoutes', () => { - const logRequest = createRequester({ authedRoutes: dashboardRoutes }); + const logRequest = createRequester({ authedRoutes: dashboardRoutes, tenantContext }); afterEach(() => { jest.clearAllMocks(); diff --git a/packages/core/src/routes/dashboard.ts b/packages/core/src/routes/dashboard.ts index 449c9b182..4ca59fe0a 100644 --- a/packages/core/src/routes/dashboard.ts +++ b/packages/core/src/routes/dashboard.ts @@ -3,11 +3,6 @@ import { endOfDay, format, subDays } from 'date-fns'; import { object, string } from 'zod'; import koaGuard from '#src/middleware/koa-guard.js'; -import { - countActiveUsersByTimeInterval, - getDailyActiveUserCountsByTimeInterval, -} from '#src/queries/log.js'; -import { countUsers, getDailyNewUserCountsByTimeInterval } from '#src/queries/user.js'; import type { AuthedRouter, RouterInitArgs } from './types.js'; @@ -17,7 +12,14 @@ const indices = (length: number) => [...Array.from({ length }).keys()]; const getEndOfDayTimestamp = (date: Date | number) => endOfDay(date).valueOf(); -export default function dashboardRoutes(...[router]: RouterInitArgs) { +export default function dashboardRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { + logs: { countActiveUsersByTimeInterval, getDailyActiveUserCountsByTimeInterval }, + users: { countUsers, getDailyNewUserCountsByTimeInterval }, + } = queries; + router.get('/dashboard/users/total', async (ctx, next) => { const { count: totalUserCount } = await countUsers(); ctx.body = { totalUserCount }; diff --git a/packages/core/src/routes/log.test.ts b/packages/core/src/routes/log.test.ts index a5773848e..10b64635b 100644 --- a/packages/core/src/routes/log.test.ts +++ b/packages/core/src/routes/log.test.ts @@ -1,27 +1,31 @@ import { LogResult } from '@logto/schemas'; import type { Log } from '@logto/schemas'; -import { pickDefault, createMockUtils } from '@logto/shared/esm'; +import { pickDefault } from '@logto/shared/esm'; +import { MockTenant } from '#src/test-utils/tenant.js'; import { createRequester } from '#src/utils/test-utils.js'; const { jest } = import.meta; -const { mockEsm } = createMockUtils(jest); const mockBody = { key: 'a', payload: { key: 'a', result: LogResult.Success }, createdAt: 123 }; const mockLog: Log = { id: '1', ...mockBody }; const mockLogs = [mockLog, { id: '2', ...mockBody }]; -const { countLogs, findLogs, findLogById } = mockEsm('#src/queries/log.js', () => ({ +const logs = { countLogs: jest.fn().mockResolvedValue({ count: mockLogs.length, }), findLogs: jest.fn().mockResolvedValue(mockLogs), findLogById: jest.fn().mockResolvedValue(mockLog), -})); +}; +const { countLogs, findLogs, findLogById } = logs; const logRoutes = await pickDefault(import('./log.js')); describe('logRoutes', () => { - const logRequest = createRequester({ authedRoutes: logRoutes }); + const logRequest = createRequester({ + authedRoutes: logRoutes, + tenantContext: new MockTenant(undefined, { logs }), + }); afterEach(() => { jest.clearAllMocks(); diff --git a/packages/core/src/routes/log.ts b/packages/core/src/routes/log.ts index dae86a430..e2db2aff3 100644 --- a/packages/core/src/routes/log.ts +++ b/packages/core/src/routes/log.ts @@ -3,11 +3,14 @@ import { object, string } from 'zod'; import koaGuard from '#src/middleware/koa-guard.js'; import koaPagination from '#src/middleware/koa-pagination.js'; -import { countLogs, findLogById, findLogs } from '#src/queries/log.js'; import type { AuthedRouter, RouterInitArgs } from './types.js'; -export default function logRoutes(...[router]: RouterInitArgs) { +export default function logRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { countLogs, findLogById, findLogs } = queries.logs; + router.get( '/logs', koaPagination(), diff --git a/packages/core/src/routes/role.test.ts b/packages/core/src/routes/role.test.ts index da202a158..feb7fa72b 100644 --- a/packages/core/src/routes/role.test.ts +++ b/packages/core/src/routes/role.test.ts @@ -1,68 +1,82 @@ import type { Role } from '@logto/schemas'; -import { pickDefault, createMockUtils } from '@logto/shared/esm'; +import { pickDefault } from '@logto/shared/esm'; import { mockRole, mockScope, mockUser, mockResource } from '#src/__mocks__/index.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; import { createRequester } from '#src/utils/test-utils.js'; const { jest } = import.meta; -const { mockEsm, mockEsmWithActual } = createMockUtils(jest); +const roles = { + findRoles: jest.fn(async (): Promise => [mockRole]), + countRoles: jest.fn(async () => ({ count: 10 })), + // eslint-disable-next-line @typescript-eslint/ban-types + findRoleByRoleName: jest.fn(async (): Promise => null), + insertRole: jest.fn(async (data) => ({ + ...data, + id: mockRole.id, + })), + deleteRoleById: jest.fn(), + findRoleById: jest.fn(), + updateRoleById: jest.fn(async (id, data) => ({ + ...mockRole, + ...data, + })), + findRolesByRoleIds: jest.fn(), +}; +const { findRoleByRoleName, findRoleById, deleteRoleById } = roles; -const { findRoleByRoleName, findRoleById, deleteRoleById } = mockEsm( - '#src/queries/roles.js', - () => ({ - findRoles: jest.fn(async (): Promise => [mockRole]), - countRoles: jest.fn(async () => ({ count: 10 })), - findRoleByRoleName: jest.fn(async (): Promise => undefined), - insertRole: jest.fn(async (data) => ({ - ...data, - id: mockRole.id, - })), - deleteRoleById: jest.fn(), - findRoleById: jest.fn(), - updateRoleById: jest.fn(async (id, data) => ({ - ...mockRole, - ...data, - })), - findRolesByRoleIds: jest.fn(), - }) -); -const { findScopeById, findScopesByIds } = await mockEsmWithActual('#src/queries/scope.js', () => ({ +const scopes = { findScopeById: jest.fn(), findScopesByIds: jest.fn(), -})); -await mockEsmWithActual('#src/queries/resource.js', () => ({ +}; +const { findScopeById, findScopesByIds } = scopes; + +const resources = { findResourcesByIds: jest.fn(async () => [mockResource]), -})); -const { insertRolesScopes, findRolesScopesByRoleId } = await mockEsmWithActual( - '#src/queries/roles-scopes.js', - () => ({ - insertRolesScopes: jest.fn(), - findRolesScopesByRoleId: jest.fn(), - deleteRolesScope: jest.fn(), - }) -); -const { findUsersByIds } = await mockEsmWithActual('#src/queries/user.js', () => ({ +}; + +const rolesScopes = { + insertRolesScopes: jest.fn(), + findRolesScopesByRoleId: jest.fn(), + deleteRolesScope: jest.fn(), +}; +const { insertRolesScopes, findRolesScopesByRoleId } = rolesScopes; + +const users = { findUsersByIds: jest.fn(), findUserById: jest.fn(), -})); +}; +const { findUsersByIds } = users; + +const usersRoles = { + insertUsersRoles: jest.fn(), + countUsersRolesByRoleId: jest.fn(), + findUsersRolesByRoleId: jest.fn(), + findFirstUsersRolesByRoleIdAndUserIds: jest.fn(), + deleteUsersRolesByUserIdAndRoleId: jest.fn(), +}; const { insertUsersRoles, findUsersRolesByRoleId, deleteUsersRolesByUserIdAndRoleId, findFirstUsersRolesByRoleIdAndUserIds, countUsersRolesByRoleId, -} = await mockEsmWithActual('#src/queries/users-roles.js', () => ({ - insertUsersRoles: jest.fn(), - countUsersRolesByRoleId: jest.fn(), - findUsersRolesByRoleId: jest.fn(), - findFirstUsersRolesByRoleIdAndUserIds: jest.fn(), - deleteUsersRolesByUserIdAndRoleId: jest.fn(), -})); +} = usersRoles; + const roleRoutes = await pickDefault(import('./role.js')); +const tenantContext = new MockTenant(undefined, { + usersRoles, + users, + rolesScopes, + resources, + scopes, + roles, +}); + describe('role routes', () => { - const roleRequester = createRequester({ authedRoutes: roleRoutes }); + const roleRequester = createRequester({ authedRoutes: roleRoutes, tenantContext }); it('GET /roles?page=1', async () => { countUsersRolesByRoleId.mockResolvedValueOnce({ count: 1 }); diff --git a/packages/core/src/routes/role.ts b/packages/core/src/routes/role.ts index 1612c1e57..bb23c1416 100644 --- a/packages/core/src/routes/role.ts +++ b/packages/core/src/routes/role.ts @@ -8,31 +8,6 @@ import { object, string, z } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; import koaGuard from '#src/middleware/koa-guard.js'; import koaPagination from '#src/middleware/koa-pagination.js'; -import { findResourcesByIds } from '#src/queries/resource.js'; -import { - deleteRolesScope, - findRolesScopesByRoleId, - insertRolesScopes, -} from '#src/queries/roles-scopes.js'; -import { - countRoles, - deleteRoleById, - findRoleById, - findRoleByRoleName, - findRoles, - insertRole, - updateRoleById, -} from '#src/queries/roles.js'; -import { findScopeById, findScopesByIds } from '#src/queries/scope.js'; -import { findUserById, findUsersByIds } from '#src/queries/user.js'; -import { - countUsersRolesByRoleId, - deleteUsersRolesByUserIdAndRoleId, - findFirstUsersRolesByRoleIdAndUserIds, - findUsersRolesByRoleId, - findUsersRolesByUserId, - insertUsersRoles, -} from '#src/queries/users-roles.js'; import assertThat from '#src/utils/assert-that.js'; import { parseSearchParamsForSearch } from '#src/utils/search.js'; @@ -40,7 +15,33 @@ import type { AuthedRouter, RouterInitArgs } from './types.js'; const roleId = buildIdGenerator(21); -export default function roleRoutes(...[router]: RouterInitArgs) { +export default function roleRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { + resources: { findResourcesByIds }, + rolesScopes: { deleteRolesScope, findRolesScopesByRoleId, insertRolesScopes }, + roles: { + countRoles, + deleteRoleById, + findRoleById, + findRoleByRoleName, + findRoles, + insertRole, + updateRoleById, + }, + scopes: { findScopeById, findScopesByIds }, + users: { findUserById, findUsersByIds }, + usersRoles: { + countUsersRolesByRoleId, + deleteUsersRolesByUserIdAndRoleId, + findFirstUsersRolesByRoleIdAndUserIds, + findUsersRolesByRoleId, + findUsersRolesByUserId, + insertUsersRoles, + }, + } = queries; + router.get('/roles', koaPagination({ isOptional: true }), async (ctx, next) => { const { limit, offset, disabled } = ctx.pagination; const { searchParams } = ctx.request.URL; diff --git a/packages/core/src/routes/setting.test.ts b/packages/core/src/routes/setting.test.ts index d1c0de397..3065c29d7 100644 --- a/packages/core/src/routes/setting.test.ts +++ b/packages/core/src/routes/setting.test.ts @@ -1,23 +1,25 @@ import type { Setting, CreateSetting } from '@logto/schemas'; -import { pickDefault, createMockUtils } from '@logto/shared/esm'; +import { pickDefault } from '@logto/shared/esm'; import { mockSetting } from '#src/__mocks__/index.js'; +import { MockTenant } from '#src/test-utils/tenant.js'; import { createRequester } from '#src/utils/test-utils.js'; -const { mockEsm } = createMockUtils(import.meta.jest); - -mockEsm('#src/queries/setting.js', () => ({ +const settings = { getSetting: async (): Promise => mockSetting, updateSetting: async (data: Partial): Promise => ({ ...mockSetting, ...data, }), -})); +}; const settingRoutes = await pickDefault(import('./setting.js')); describe('settings routes', () => { - const roleRequester = createRequester({ authedRoutes: settingRoutes }); + const roleRequester = createRequester({ + authedRoutes: settingRoutes, + tenantContext: new MockTenant(undefined, { settings }), + }); it('GET /settings', async () => { const response = await roleRequester.get('/settings'); diff --git a/packages/core/src/routes/setting.ts b/packages/core/src/routes/setting.ts index 213452751..042bad2cd 100644 --- a/packages/core/src/routes/setting.ts +++ b/packages/core/src/routes/setting.ts @@ -1,11 +1,14 @@ import { Settings } from '@logto/schemas'; import koaGuard from '#src/middleware/koa-guard.js'; -import { getSetting, updateSetting } from '#src/queries/setting.js'; import type { AuthedRouter, RouterInitArgs } from './types.js'; -export default function settingRoutes(...[router]: RouterInitArgs) { +export default function settingRoutes( + ...[router, { queries }]: RouterInitArgs +) { + const { getSetting, updateSetting } = queries.settings; + router.get('/settings', async (ctx, next) => { const { id, ...rest } = await getSetting(); ctx.body = rest; diff --git a/packages/core/src/tenants/Tenant.ts b/packages/core/src/tenants/Tenant.ts index b12c4e6ad..e629aa68e 100644 --- a/packages/core/src/tenants/Tenant.ts +++ b/packages/core/src/tenants/Tenant.ts @@ -73,7 +73,10 @@ export default class Tenant implements TenantContext { app.use( mount( '/' + MountedApps.DemoApp, - compose([koaCheckDemoApp(), koaSpaProxy(MountedApps.DemoApp, 5003, MountedApps.DemoApp)]) + compose([ + koaCheckDemoApp(this.queries), + koaSpaProxy(MountedApps.DemoApp, 5003, MountedApps.DemoApp), + ]) ) );