Skip to content

Commit

Permalink
Merge pull request #6078 from logto-io/gao-reorg-org-rotues
Browse files Browse the repository at this point in the history
refactor(core): reorg organization routes
  • Loading branch information
gao-sun authored Jun 22, 2024
2 parents cc37247 + 3fbf213 commit 6f06c41
Show file tree
Hide file tree
Showing 19 changed files with 150 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -17,6 +18,7 @@
}
},
"post": {
"tags": ["Dev feature"],
"summary": "Add organization application",
"description": "Add an application to the organization.",
"requestBody": {
Expand All @@ -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": {
Expand Down Expand Up @@ -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": {
Expand All @@ -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": {
Expand All @@ -89,6 +94,7 @@
}
},
"post": {
"tags": ["Dev feature"],
"summary": "Add organization application role",
"description": "Add a role to the application in the organization.",
"requestBody": {
Expand All @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
23 changes: 23 additions & 0 deletions packages/core/src/routes/organization/application/index.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
99 changes: 10 additions & 89 deletions packages/core/src/routes/organization/index.ts
Original file line number Diff line number Diff line change
@@ -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>(
Expand Down Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/routes/organization/jit/index.ts
Original file line number Diff line number Diff line change
@@ -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,
});
}
79 changes: 79 additions & 0 deletions packages/core/src/routes/organization/user/index.ts
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
Expand Down

0 comments on commit 6f06c41

Please sign in to comment.