mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
fix(core): fix unpaginated scopes response (#3810)
This commit is contained in:
parent
f20f65407f
commit
e57fc80bd4
4 changed files with 45 additions and 19 deletions
|
@ -66,6 +66,11 @@ export const mockScope: Scope = {
|
||||||
createdAt: 1_645_334_775_356,
|
createdAt: 1_645_334_775_356,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mockScopeWithResource = {
|
||||||
|
...mockScope,
|
||||||
|
resource: mockResource,
|
||||||
|
};
|
||||||
|
|
||||||
export const mockRole: Role = {
|
export const mockRole: Role = {
|
||||||
tenantId: 'fake_tenant',
|
tenantId: 'fake_tenant',
|
||||||
id: 'role_id',
|
id: 'role_id',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Role } from '@logto/schemas';
|
import type { Role } from '@logto/schemas';
|
||||||
import { pickDefault } from '@logto/shared/esm';
|
import { pickDefault } from '@logto/shared/esm';
|
||||||
|
|
||||||
import { mockRole, mockScope, mockResource } from '#src/__mocks__/index.js';
|
import { mockRole, mockScope, mockResource, mockScopeWithResource } from '#src/__mocks__/index.js';
|
||||||
import { mockId, mockStandardId } from '#src/test-utils/nanoid.js';
|
import { mockId, mockStandardId } from '#src/test-utils/nanoid.js';
|
||||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||||
import { createRequester } from '#src/utils/test-utils.js';
|
import { createRequester } from '#src/utils/test-utils.js';
|
||||||
|
@ -68,7 +68,16 @@ describe('role scope routes', () => {
|
||||||
findScopesByIds.mockResolvedValueOnce([mockScope]);
|
findScopesByIds.mockResolvedValueOnce([mockScope]);
|
||||||
const response = await roleRequester.get(`/roles/${mockRole.id}/scopes`);
|
const response = await roleRequester.get(`/roles/${mockRole.id}/scopes`);
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
expect(response.body).toEqual([mockScope]);
|
expect(response.body).toEqual([mockScopeWithResource]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('GET /roles/:id/scopes (with pagination)', async () => {
|
||||||
|
findRoleById.mockResolvedValueOnce(mockRole);
|
||||||
|
findRolesScopesByRoleId.mockResolvedValueOnce([]);
|
||||||
|
findScopesByIds.mockResolvedValueOnce([mockScope]);
|
||||||
|
const response = await roleRequester.get(`/roles/${mockRole.id}/scopes?page=1`);
|
||||||
|
expect(response.status).toEqual(200);
|
||||||
|
expect(response.body).toEqual([mockScopeWithResource]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('POST /roles/:id/scopes', async () => {
|
it('POST /roles/:id/scopes', async () => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { ScopeResponse } from '@logto/schemas';
|
import type { Scope, ScopeResponse } from '@logto/schemas';
|
||||||
|
import { scopeResponseGuard } from '@logto/schemas';
|
||||||
import { generateStandardId } from '@logto/shared';
|
import { generateStandardId } from '@logto/shared';
|
||||||
import { tryThat } from '@silverhand/essentials';
|
import { tryThat } from '@silverhand/essentials';
|
||||||
import { object, string } from 'zod';
|
import { object, string } from 'zod';
|
||||||
|
@ -21,11 +22,26 @@ export default function roleScopeRoutes<T extends AuthedRouter>(
|
||||||
scopes: { findScopeById, findScopesByIds, countScopesByScopeIds, searchScopesByScopeIds },
|
scopes: { findScopeById, findScopesByIds, countScopesByScopeIds, searchScopesByScopeIds },
|
||||||
} = queries;
|
} = queries;
|
||||||
|
|
||||||
|
const attachResourceToScopes = async (scopes: readonly Scope[]): Promise<ScopeResponse[]> => {
|
||||||
|
const resources = await findResourcesByIds(scopes.map(({ resourceId }) => resourceId));
|
||||||
|
return scopes.map((scope) => {
|
||||||
|
const resource = resources.find(({ id }) => id === scope.resourceId);
|
||||||
|
|
||||||
|
assertThat(resource, new Error(`Cannot find resource for id ${scope.resourceId}`));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...scope,
|
||||||
|
resource,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/roles/:id/scopes',
|
'/roles/:id/scopes',
|
||||||
koaPagination({ isOptional: true }),
|
koaPagination({ isOptional: true }),
|
||||||
koaGuard({
|
koaGuard({
|
||||||
params: object({ id: string().min(1) }),
|
params: object({ id: string().min(1) }),
|
||||||
|
response: scopeResponseGuard.array(),
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
|
@ -43,7 +59,9 @@ export default function roleScopeRoutes<T extends AuthedRouter>(
|
||||||
const scopeIds = rolesScopes.map(({ scopeId }) => scopeId);
|
const scopeIds = rolesScopes.map(({ scopeId }) => scopeId);
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
ctx.body = await searchScopesByScopeIds(scopeIds, search);
|
const scopes = await searchScopesByScopeIds(scopeIds, search);
|
||||||
|
|
||||||
|
ctx.body = await attachResourceToScopes(scopes);
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -53,21 +71,9 @@ export default function roleScopeRoutes<T extends AuthedRouter>(
|
||||||
searchScopesByScopeIds(scopeIds, search, limit, offset),
|
searchScopesByScopeIds(scopeIds, search, limit, offset),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const resources = await findResourcesByIds(scopes.map(({ resourceId }) => resourceId));
|
|
||||||
const result: ScopeResponse[] = scopes.map((scope) => {
|
|
||||||
const resource = resources.find(({ id }) => id === scope.resourceId);
|
|
||||||
|
|
||||||
assertThat(resource, new Error(`Cannot find resource for id ${scope.resourceId}`));
|
|
||||||
|
|
||||||
return {
|
|
||||||
...scope,
|
|
||||||
resource,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return totalCount to pagination middleware
|
// Return totalCount to pagination middleware
|
||||||
ctx.pagination.totalCount = count;
|
ctx.pagination.totalCount = count;
|
||||||
ctx.body = result;
|
ctx.body = await attachResourceToScopes(scopes);
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
import type { Resource, Scope } from '../db-entries/index.js';
|
import { type z } from 'zod';
|
||||||
|
|
||||||
export type ScopeResponse = Scope & { resource: Resource };
|
import { Resources, Scopes } from '../db-entries/index.js';
|
||||||
|
|
||||||
|
export const scopeResponseGuard = Scopes.guard.extend({
|
||||||
|
resource: Resources.guard,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ScopeResponse = z.infer<typeof scopeResponseGuard>;
|
||||||
|
|
Loading…
Reference in a new issue