0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

refactor(core): fix org apis (#4890)

This commit is contained in:
Gao Sun 2023-11-15 17:20:31 +08:00 committed by GitHub
parent d200ca56c8
commit bfea0b0fdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 6 deletions

View file

@ -1,5 +1,13 @@
import { OrganizationRoles } from '@logto/schemas';
import {
type CreateOrganizationRole,
OrganizationRoles,
organizationRoleWithScopesGuard,
} from '@logto/schemas';
import { generateStandardId } from '@logto/shared';
import { z } from 'zod';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import SchemaRouter from '#src/utils/SchemaRouter.js';
import { type AuthedRouter, type RouterInitArgs } from '../types.js';
@ -20,9 +28,63 @@ export default function organizationRoleRoutes<T extends AuthedRouter>(
]: RouterInitArgs<T>
) {
const router = new SchemaRouter(OrganizationRoles, roles, {
disabled: { get: true, post: true },
errorHandler,
searchFields: ['name'],
});
router.get(
'/',
koaPagination(),
koaGuard({
response: organizationRoleWithScopesGuard.array(),
status: [200],
}),
async (ctx, next) => {
const { limit, offset } = ctx.pagination;
const [count, entities] = await roles.findAll(limit, offset);
ctx.pagination.totalCount = count;
ctx.body = entities;
return next();
}
);
/** Allows to carry an initial set of scopes for creating a new organization role. */
type CreateOrganizationRolePayload = Omit<CreateOrganizationRole, 'id'> & {
organizationScopeIds: string[];
};
const createGuard: z.ZodType<CreateOrganizationRolePayload, z.ZodTypeDef, unknown> =
OrganizationRoles.createGuard
.omit({
id: true,
})
.extend({
organizationScopeIds: z.array(z.string()).default([]),
});
router.post(
'/',
koaGuard({
body: createGuard,
response: OrganizationRoles.guard,
status: [201, 422],
}),
async (ctx, next) => {
const { organizationScopeIds: scopeIds, ...data } = ctx.guard.body;
const role = await roles.insert({ id: generateStandardId(), ...data });
if (scopeIds.length > 0) {
await rolesScopes.insert(...scopeIds.map<[string, string]>((id) => [role.id, id]));
}
ctx.body = role;
ctx.status = 201;
return next();
}
);
router.addRelationRoutes(rolesScopes, 'scopes');
originalRouter.use(router.routes());

View file

@ -1,4 +1,9 @@
import { type OrganizationScope, type OrganizationRole, type Organization } from '@logto/schemas';
import {
type OrganizationScope,
type OrganizationRole,
type Organization,
type OrganizationRoleWithScopes,
} from '@logto/schemas';
import { trySafe } from '@silverhand/essentials';
import {
@ -20,10 +25,11 @@ export class OrganizationRoleApiTest extends OrganizationRoleApi {
return this.#roles;
}
override async create(data: CreateOrganizationRolePostData): Promise<OrganizationRole> {
override async create(data: CreateOrganizationRolePostData): Promise<OrganizationRoleWithScopes> {
const created = await super.create(data);
this.roles.push(created);
return created;
// eslint-disable-next-line no-restricted-syntax -- to override the type
return created as OrganizationRoleWithScopes;
}
/**

View file

@ -1,7 +1,7 @@
import assert from 'node:assert';
import { generateStandardId } from '@logto/shared';
import { isKeyInObject } from '@silverhand/essentials';
import { isKeyInObject, pick } from '@silverhand/essentials';
import { HTTPError } from 'got';
import { OrganizationRoleApiTest, OrganizationScopeApiTest } from '#src/helpers/organization.js';
@ -12,9 +12,10 @@ const randomId = () => generateStandardId(4);
describe('organization role APIs', () => {
describe('organization roles', () => {
const roleApi = new OrganizationRoleApiTest();
const scopeApi = new OrganizationScopeApiTest();
afterEach(async () => {
await roleApi.cleanUp();
await Promise.all([roleApi.cleanUp(), scopeApi.cleanUp()]);
});
it('should fail if the name of the new organization role already exists', async () => {
@ -71,6 +72,28 @@ describe('organization role APIs', () => {
expect(role).toStrictEqual(createdRole);
});
it('should be able to create a new organization with initial scopes', async () => {
const [scope1, scope2] = await Promise.all([
scopeApi.create({ name: 'test' + randomId() }),
scopeApi.create({ name: 'test' + randomId() }),
]);
const createdRole = await roleApi.create({
name: 'test' + randomId(),
description: 'test description.',
organizationScopeIds: [scope1.id, scope2.id],
});
const scopes = await roleApi.getScopes(createdRole.id);
const roles = await roleApi.getList();
const roleWithScopes = roles.find((role) => role.id === createdRole.id);
for (const scope of [scope1, scope2]) {
expect(roleWithScopes?.scopes).toContainEqual(
expect.objectContaining(pick(scope, 'id', 'name'))
);
expect(scopes).toContainEqual(expect.objectContaining(pick(scope, 'id', 'name')));
}
});
it('should fail when try to get an organization role that does not exist', async () => {
const response = await roleApi.get('0').catch((error: unknown) => error);