0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

Merge pull request #6078 from logto-io/gao-reorg-org-rotues

refactor(core): reorg organization routes
This commit is contained in:
Gao Sun 2024-06-22 09:23:03 +08:00 committed by GitHub
commit 6f06c418b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 150 additions and 100 deletions

View file

@ -55,7 +55,6 @@ jobs:
- uses: logto-io/actions-package-logto-artifact@v2
with:
artifact-name: dev-feature-disabled-integration-test-${{ github.sha }}
branch: ${{github.base_ref}}
pnpm-version: 9
run-logto:

View file

@ -11,10 +11,9 @@ import koaGuard from '#src/middleware/koa-guard.js';
import SchemaRouter from '#src/utils/SchemaRouter.js';
import assertThat from '#src/utils/assert-that.js';
import { errorHandler } from '../organization/utils.js';
import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
import { errorHandler } from './utils.js';
export default function organizationInvitationRoutes<T extends ManagementApiRouter>(
...[
originalRouter,

View file

@ -16,14 +16,13 @@ import { organizationRoleSearchKeys } from '#src/queries/organization/index.js';
import SchemaRouter from '#src/utils/SchemaRouter.js';
import { parseSearchOptions } from '#src/utils/search.js';
import { errorHandler } from '../organization/utils.js';
import {
type ManagementApiRouter,
type ManagementApiRouterContext,
type RouterInitArgs,
} from '../types.js';
import { errorHandler } from './utils.js';
export default function organizationRoleRoutes<T extends ManagementApiRouter>(
...[
originalRouter,

View file

@ -3,10 +3,9 @@ import { OrganizationScopes } from '@logto/schemas';
import koaQuotaGuard from '#src/middleware/koa-quota-guard.js';
import SchemaRouter from '#src/utils/SchemaRouter.js';
import { errorHandler } from '../organization/utils.js';
import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
import { errorHandler } from './utils.js';
export default function organizationScopeRoutes<T extends ManagementApiRouter>(
...[
originalRouter,

View file

@ -8,6 +8,7 @@
"paths": {
"/api/organizations/{id}/applications": {
"get": {
"tags": ["Dev feature"],
"summary": "Get organization applications",
"description": "Get applications associated with the organization.",
"responses": {
@ -17,6 +18,7 @@
}
},
"post": {
"tags": ["Dev feature"],
"summary": "Add organization application",
"description": "Add an application to the organization.",
"requestBody": {
@ -42,6 +44,7 @@
}
},
"put": {
"tags": ["Dev feature"],
"summary": "Replace organization applications",
"description": "Replace all applications associated with the organization with the given data.",
"requestBody": {
@ -69,6 +72,7 @@
},
"/api/organizations/{id}/applications/{applicationId}": {
"delete": {
"tags": ["Dev feature"],
"summary": "Remove organization application",
"description": "Remove an application from the organization.",
"responses": {
@ -80,6 +84,7 @@
},
"/api/organizations/{id}/applications/{applicationId}/roles": {
"get": {
"tags": ["Dev feature"],
"summary": "Get organization application roles",
"description": "Get roles associated with the application in the organization.",
"responses": {
@ -89,6 +94,7 @@
}
},
"post": {
"tags": ["Dev feature"],
"summary": "Add organization application role",
"description": "Add a role to the application in the organization.",
"requestBody": {
@ -114,6 +120,7 @@
}
},
"put": {
"tags": ["Dev feature"],
"summary": "Replace organization application roles",
"description": "Replace all roles associated with the application in the organization with the given data.",
"requestBody": {
@ -141,6 +148,7 @@
},
"/api/organizations/{id}/applications/{applicationId}/roles/{organizationRoleId}": {
"delete": {
"tags": ["Dev feature"],
"summary": "Remove organization application role",
"description": "Remove a role from the application in the organization.",
"responses": {

View file

@ -0,0 +1,23 @@
import { type OrganizationKeys, type CreateOrganization, type Organization } from '@logto/schemas';
import { EnvSet } from '#src/env-set/index.js';
import type OrganizationQueries from '#src/queries/organization/index.js';
import type SchemaRouter from '#src/utils/SchemaRouter.js';
import applicationRoleRelationRoutes from './role-relations.js';
/** Mounts the application-related routes on the organization router. */
export default function applicationRoutes(
router: SchemaRouter<OrganizationKeys, CreateOrganization, Organization>,
organizations: OrganizationQueries
) {
if (EnvSet.values.isDevFeaturesEnabled) {
// MARK: Organization - application relation routes
router.addRelationRoutes(organizations.relations.apps, undefined, {
hookEvent: 'Organization.Membership.Updated',
});
// MARK: Organization - application role relation routes
applicationRoleRelationRoutes(router, organizations);
}
}

View file

@ -1,28 +1,21 @@
import {
type OrganizationWithFeatured,
Organizations,
featuredUserGuard,
userWithOrganizationRolesGuard,
} from '@logto/schemas';
import { type OrganizationWithFeatured, Organizations, featuredUserGuard } from '@logto/schemas';
import { yes } from '@silverhand/essentials';
import { z } from 'zod';
import { EnvSet } from '#src/env-set/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import koaQuotaGuard from '#src/middleware/koa-quota-guard.js';
import { userSearchKeys } from '#src/queries/user.js';
import SchemaRouter from '#src/utils/SchemaRouter.js';
import { parseSearchOptions } from '#src/utils/search.js';
import organizationInvitationRoutes from '../organization-invitation/index.js';
import organizationRoleRoutes from '../organization-role/index.js';
import organizationScopeRoutes from '../organization-scope/index.js';
import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
import applicationRoleRelationRoutes from './index.application-role-relations.js';
import emailDomainRoutes from './index.jit.email-domains.js';
import userRoleRelationRoutes from './index.user-role-relations.js';
import organizationInvitationRoutes from './invitations.js';
import organizationRoleRoutes from './roles.js';
import organizationScopeRoutes from './scopes.js';
import applicationRoutes from './application/index.js';
import jitRoutes from './jit/index.js';
import userRoutes from './user/index.js';
import { errorHandler } from './utils.js';
export default function organizationRoutes<T extends ManagementApiRouter>(
@ -83,81 +76,9 @@ export default function organizationRoutes<T extends ManagementApiRouter>(
}
);
// MARK: Organization - user relation routes
router.addRelationRoutes(organizations.relations.users, undefined, {
disabled: { get: true },
hookEvent: 'Organization.Membership.Updated',
});
router.get(
'/:id/users',
koaPagination(),
koaGuard({
query: z.object({ q: z.string().optional() }),
params: z.object({ id: z.string().min(1) }),
response: userWithOrganizationRolesGuard.array(),
status: [200, 404],
}),
async (ctx, next) => {
const search = parseSearchOptions(userSearchKeys, ctx.guard.query);
const [totalCount, entities] = await organizations.relations.users.getUsersByOrganizationId(
ctx.guard.params.id,
ctx.pagination,
search
);
ctx.pagination.totalCount = totalCount;
ctx.body = entities;
return next();
}
);
// MARK: Organization - user role relation routes
router.post(
'/:id/users/roles',
koaGuard({
params: z.object({ id: z.string().min(1) }),
body: z.object({
userIds: z.string().min(1).array().nonempty(),
organizationRoleIds: z.string().min(1).array().nonempty(),
}),
status: [201, 422],
}),
async (ctx, next) => {
const { id } = ctx.guard.params;
const { userIds, organizationRoleIds } = ctx.guard.body;
await organizations.relations.usersRoles.insert(
...organizationRoleIds.flatMap((roleId) =>
userIds.map((userId) => ({ organizationId: id, organizationRoleId: roleId, userId }))
)
);
ctx.status = 201;
return next();
}
);
userRoleRelationRoutes(router, organizations);
if (EnvSet.values.isDevFeaturesEnabled) {
// MARK: Organization - application relation routes
router.addRelationRoutes(organizations.relations.apps, undefined, {
hookEvent: 'Organization.Membership.Updated',
});
// MARK: Organization - application role relation routes
applicationRoleRelationRoutes(router, organizations);
}
// MARK: Just-in-time provisioning
emailDomainRoutes(router, organizations);
router.addRelationRoutes(organizations.jit.roles, 'jit/roles', { isPaginationOptional: true });
router.addRelationRoutes(organizations.jit.ssoConnectors, 'jit/sso-connectors', {
isPaginationOptional: true,
});
userRoutes(router, organizations);
applicationRoutes(router, organizations);
jitRoutes(router, organizations);
// MARK: Mount sub-routes
organizationRoleRoutes(...args);

View file

@ -0,0 +1,18 @@
import { type OrganizationKeys, type CreateOrganization, type Organization } from '@logto/schemas';
import type OrganizationQueries from '#src/queries/organization/index.js';
import type SchemaRouter from '#src/utils/SchemaRouter.js';
import emailDomainRoutes from './email-domains.js';
/** Mounts the jit-related routes on the organization router. */
export default function jitRoutes(
router: SchemaRouter<OrganizationKeys, CreateOrganization, Organization>,
organizations: OrganizationQueries
) {
emailDomainRoutes(router, organizations);
router.addRelationRoutes(organizations.jit.roles, 'jit/roles', { isPaginationOptional: true });
router.addRelationRoutes(organizations.jit.ssoConnectors, 'jit/sso-connectors', {
isPaginationOptional: true,
});
}

View file

@ -0,0 +1,79 @@
import {
type OrganizationKeys,
type CreateOrganization,
type Organization,
userWithOrganizationRolesGuard,
} from '@logto/schemas';
import { z } from 'zod';
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';
import { userSearchKeys } from '#src/queries/user.js';
import type SchemaRouter from '#src/utils/SchemaRouter.js';
import { parseSearchOptions } from '#src/utils/search.js';
import userRoleRelationRoutes from './role-relations.js';
/** Mounts the user-related routes on the organization router. */
export default function userRoutes(
router: SchemaRouter<OrganizationKeys, CreateOrganization, Organization>,
organizations: OrganizationQueries
) {
router.addRelationRoutes(organizations.relations.users, undefined, {
disabled: { get: true },
hookEvent: 'Organization.Membership.Updated',
});
router.get(
'/:id/users',
koaPagination(),
koaGuard({
query: z.object({ q: z.string().optional() }),
params: z.object({ id: z.string().min(1) }),
response: userWithOrganizationRolesGuard.array(),
status: [200, 404],
}),
async (ctx, next) => {
const search = parseSearchOptions(userSearchKeys, ctx.guard.query);
const [totalCount, entities] = await organizations.relations.users.getUsersByOrganizationId(
ctx.guard.params.id,
ctx.pagination,
search
);
ctx.pagination.totalCount = totalCount;
ctx.body = entities;
return next();
}
);
router.post(
'/:id/users/roles',
koaGuard({
params: z.object({ id: z.string().min(1) }),
body: z.object({
userIds: z.string().min(1).array().nonempty(),
organizationRoleIds: z.string().min(1).array().nonempty(),
}),
status: [201, 422],
}),
async (ctx, next) => {
const { id } = ctx.guard.params;
const { userIds, organizationRoleIds } = ctx.guard.body;
await organizations.relations.usersRoles.insert(
...organizationRoleIds.flatMap((roleId) =>
userIds.map((userId) => ({ organizationId: id, organizationRoleId: roleId, userId }))
)
);
ctx.status = 201;
return next();
}
);
userRoleRelationRoutes(router, organizations);
}

View file

@ -1,18 +1,23 @@
import { OrganizationRoles, OrganizationScopes } from '@logto/schemas';
import type Router from 'koa-router';
import {
type CreateOrganization,
type Organization,
type OrganizationKeys,
OrganizationRoles,
OrganizationScopes,
} from '@logto/schemas';
import { z } from 'zod';
import RequestError from '#src/errors/RequestError/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import { type WithHookContext } from '#src/middleware/koa-management-api-hooks.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import type OrganizationQueries from '#src/queries/organization/index.js';
import type SchemaRouter from '#src/utils/SchemaRouter.js';
// Manually add these routes since I don't want to over-engineer the `SchemaRouter`.
// Update: Now we also have "organization - organization role - application" relations. Consider
// extracting the common logic to a class once we have one more relation like this.
export default function userRoleRelationRoutes(
router: Router<unknown, WithHookContext>,
router: SchemaRouter<OrganizationKeys, CreateOrganization, Organization>,
organizations: OrganizationQueries
) {
const params = Object.freeze({ id: z.string().min(1), userId: z.string().min(1) } as const);