mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor(core): reorg organization routes
This commit is contained in:
parent
33537ef1af
commit
ec6f1d39d8
2 changed files with 136 additions and 121 deletions
|
@ -1,15 +1,12 @@
|
||||||
import {
|
import {
|
||||||
OrganizationRoles,
|
|
||||||
type OrganizationWithFeatured,
|
type OrganizationWithFeatured,
|
||||||
Organizations,
|
Organizations,
|
||||||
featuredUserGuard,
|
featuredUserGuard,
|
||||||
userWithOrganizationRolesGuard,
|
userWithOrganizationRolesGuard,
|
||||||
OrganizationScopes,
|
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { yes } from '@silverhand/essentials';
|
import { yes } from '@silverhand/essentials';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import RequestError from '#src/errors/RequestError/index.js';
|
|
||||||
import koaGuard from '#src/middleware/koa-guard.js';
|
import koaGuard from '#src/middleware/koa-guard.js';
|
||||||
import koaPagination from '#src/middleware/koa-pagination.js';
|
import koaPagination from '#src/middleware/koa-pagination.js';
|
||||||
import koaQuotaGuard from '#src/middleware/koa-quota-guard.js';
|
import koaQuotaGuard from '#src/middleware/koa-quota-guard.js';
|
||||||
|
@ -19,6 +16,7 @@ import { parseSearchOptions } from '#src/utils/search.js';
|
||||||
|
|
||||||
import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
|
import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
|
||||||
|
|
||||||
|
import userRoleRelationRoutes from './index.user-role-relations.js';
|
||||||
import organizationInvitationRoutes from './invitations.js';
|
import organizationInvitationRoutes from './invitations.js';
|
||||||
import organizationRoleRoutes from './roles.js';
|
import organizationRoleRoutes from './roles.js';
|
||||||
import organizationScopeRoutes from './scopes.js';
|
import organizationScopeRoutes from './scopes.js';
|
||||||
|
@ -135,125 +133,8 @@ export default function organizationRoutes<T extends ManagementApiRouter>(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Manually add these routes since I don't want to over-engineer the `SchemaRouter`
|
|
||||||
// MARK: Organization - user - organization role relation routes
|
// MARK: Organization - user - organization role relation routes
|
||||||
const params = Object.freeze({ id: z.string().min(1), userId: z.string().min(1) } as const);
|
userRoleRelationRoutes(router, organizations);
|
||||||
const pathname = '/:id/users/:userId/roles';
|
|
||||||
|
|
||||||
// The pathname of `.use()` will not match the end of the path, for example:
|
|
||||||
// `.use('/foo', ...)` will match both `/foo` and `/foo/bar`.
|
|
||||||
// See https://github.com/koajs/router/blob/02ad6eedf5ced6ec1eab2138380fd67c63e3f1d7/lib/router.js#L330-L333
|
|
||||||
router.use(pathname, koaGuard({ params: z.object(params) }), async (ctx, next) => {
|
|
||||||
const { id, userId } = ctx.guard.params;
|
|
||||||
|
|
||||||
// Ensure membership
|
|
||||||
if (!(await organizations.relations.users.exists(id, userId))) {
|
|
||||||
throw new RequestError({ code: 'organization.require_membership', status: 422 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
pathname,
|
|
||||||
koaPagination(),
|
|
||||||
koaGuard({
|
|
||||||
params: z.object(params),
|
|
||||||
response: OrganizationRoles.guard.array(),
|
|
||||||
status: [200, 422],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const { id, userId } = ctx.guard.params;
|
|
||||||
|
|
||||||
const [totalCount, entities] = await organizations.relations.rolesUsers.getEntities(
|
|
||||||
OrganizationRoles,
|
|
||||||
{
|
|
||||||
organizationId: id,
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
ctx.pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.pagination.totalCount = totalCount;
|
|
||||||
ctx.body = entities;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.put(
|
|
||||||
pathname,
|
|
||||||
koaGuard({
|
|
||||||
params: z.object(params),
|
|
||||||
body: z.object({ organizationRoleIds: z.string().min(1).array() }),
|
|
||||||
status: [204, 422],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const { id, userId } = ctx.guard.params;
|
|
||||||
const { organizationRoleIds } = ctx.guard.body;
|
|
||||||
|
|
||||||
await organizations.relations.rolesUsers.replace(id, userId, organizationRoleIds);
|
|
||||||
|
|
||||||
ctx.status = 204;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
pathname,
|
|
||||||
koaGuard({
|
|
||||||
params: z.object(params),
|
|
||||||
body: z.object({ organizationRoleIds: z.string().min(1).array().nonempty() }),
|
|
||||||
status: [201, 422],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const { id, userId } = ctx.guard.params;
|
|
||||||
const { organizationRoleIds } = ctx.guard.body;
|
|
||||||
|
|
||||||
await organizations.relations.rolesUsers.insert(
|
|
||||||
...organizationRoleIds.map<[string, string, string]>((roleId) => [id, roleId, userId])
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.status = 201;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.delete(
|
|
||||||
`${pathname}/:roleId`,
|
|
||||||
koaGuard({
|
|
||||||
params: z.object({ ...params, roleId: z.string().min(1) }),
|
|
||||||
status: [204, 422, 404],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const { id, roleId, userId } = ctx.guard.params;
|
|
||||||
|
|
||||||
await organizations.relations.rolesUsers.delete({
|
|
||||||
organizationId: id,
|
|
||||||
organizationRoleId: roleId,
|
|
||||||
userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.status = 204;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
'/:id/users/:userId/scopes',
|
|
||||||
koaGuard({
|
|
||||||
params: z.object(params),
|
|
||||||
response: z.array(OrganizationScopes.guard),
|
|
||||||
status: [200, 422],
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const { id, userId } = ctx.guard.params;
|
|
||||||
|
|
||||||
const scopes = await organizations.relations.rolesUsers.getUserScopes(id, userId);
|
|
||||||
|
|
||||||
ctx.body = scopes;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// MARK: Mount sub-routes
|
// MARK: Mount sub-routes
|
||||||
organizationRoleRoutes(...args);
|
organizationRoleRoutes(...args);
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { OrganizationRoles, OrganizationScopes } from '@logto/schemas';
|
||||||
|
import type Router from 'koa-router';
|
||||||
|
import { type IRouterParamContext } from 'koa-router';
|
||||||
|
import { 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 type OrganizationQueries from '#src/queries/organization/index.js';
|
||||||
|
|
||||||
|
// Manually add these routes since I don't want to over-engineer the `SchemaRouter`
|
||||||
|
export default function userRoleRelationRoutes(
|
||||||
|
router: Router<unknown, IRouterParamContext>,
|
||||||
|
organizations: OrganizationQueries
|
||||||
|
) {
|
||||||
|
// MARK: Organization - user - organization role relation routes
|
||||||
|
const params = Object.freeze({ id: z.string().min(1), userId: z.string().min(1) } as const);
|
||||||
|
const pathname = '/:id/users/:userId/roles';
|
||||||
|
|
||||||
|
// The pathname of `.use()` will not match the end of the path, for example:
|
||||||
|
// `.use('/foo', ...)` will match both `/foo` and `/foo/bar`.
|
||||||
|
// See https://github.com/koajs/router/blob/02ad6eedf5ced6ec1eab2138380fd67c63e3f1d7/lib/router.js#L330-L333
|
||||||
|
router.use(pathname, koaGuard({ params: z.object(params) }), async (ctx, next) => {
|
||||||
|
const { id, userId } = ctx.guard.params;
|
||||||
|
|
||||||
|
// Ensure membership
|
||||||
|
if (!(await organizations.relations.users.exists(id, userId))) {
|
||||||
|
throw new RequestError({ code: 'organization.require_membership', status: 422 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
pathname,
|
||||||
|
koaPagination(),
|
||||||
|
koaGuard({
|
||||||
|
params: z.object(params),
|
||||||
|
response: OrganizationRoles.guard.array(),
|
||||||
|
status: [200, 422],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id, userId } = ctx.guard.params;
|
||||||
|
|
||||||
|
const [totalCount, entities] = await organizations.relations.rolesUsers.getEntities(
|
||||||
|
OrganizationRoles,
|
||||||
|
{
|
||||||
|
organizationId: id,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
ctx.pagination
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.pagination.totalCount = totalCount;
|
||||||
|
ctx.body = entities;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
|
pathname,
|
||||||
|
koaGuard({
|
||||||
|
params: z.object(params),
|
||||||
|
body: z.object({ organizationRoleIds: z.string().min(1).array() }),
|
||||||
|
status: [204, 422],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id, userId } = ctx.guard.params;
|
||||||
|
const { organizationRoleIds } = ctx.guard.body;
|
||||||
|
|
||||||
|
await organizations.relations.rolesUsers.replace(id, userId, organizationRoleIds);
|
||||||
|
|
||||||
|
ctx.status = 204;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
pathname,
|
||||||
|
koaGuard({
|
||||||
|
params: z.object(params),
|
||||||
|
body: z.object({ organizationRoleIds: z.string().min(1).array().nonempty() }),
|
||||||
|
status: [201, 422],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id, userId } = ctx.guard.params;
|
||||||
|
const { organizationRoleIds } = ctx.guard.body;
|
||||||
|
|
||||||
|
await organizations.relations.rolesUsers.insert(
|
||||||
|
...organizationRoleIds.map<[string, string, string]>((roleId) => [id, roleId, userId])
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.status = 201;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
`${pathname}/:roleId`,
|
||||||
|
koaGuard({
|
||||||
|
params: z.object({ ...params, roleId: z.string().min(1) }),
|
||||||
|
status: [204, 422, 404],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id, roleId, userId } = ctx.guard.params;
|
||||||
|
|
||||||
|
await organizations.relations.rolesUsers.delete({
|
||||||
|
organizationId: id,
|
||||||
|
organizationRoleId: roleId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.status = 204;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:id/users/:userId/scopes',
|
||||||
|
koaGuard({
|
||||||
|
params: z.object(params),
|
||||||
|
response: z.array(OrganizationScopes.guard),
|
||||||
|
status: [200, 422],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { id, userId } = ctx.guard.params;
|
||||||
|
|
||||||
|
const scopes = await organizations.relations.rolesUsers.getUserScopes(id, userId);
|
||||||
|
|
||||||
|
ctx.body = scopes;
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue