Skip to content

Commit

Permalink
Merge pull request #6055 from logto-io/gao-refactor-relation-queries
Browse files Browse the repository at this point in the history
refactor(core): update relation queries
  • Loading branch information
gao-sun authored Jun 20, 2024
2 parents 09bd183 + b543356 commit e83e94f
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 48 deletions.
15 changes: 12 additions & 3 deletions packages/core/src/libraries/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,28 @@ export const createApplicationLibrary = (queries: Queries) => {
) => {
if (organizationScopes) {
await userConsentOrganizationScopes.insert(
...organizationScopes.map<[string, string]>((scope) => [applicationId, scope])
...organizationScopes.map((scope) => ({
applicationId,
organizationScopeId: scope,
}))
);
}

if (resourceScopes) {
await userConsentResourceScopes.insert(
...resourceScopes.map<[string, string]>((scope) => [applicationId, scope])
...resourceScopes.map((scope) => ({
applicationId,
scopeId: scope,
}))
);
}

if (organizationResourceScopes) {
await userConsentOrganizationResourceScopes.insert(
...organizationResourceScopes.map<[string, string]>((scope) => [applicationId, scope])
...organizationResourceScopes.map((scope) => ({
applicationId,
scopeId: scope,
}))
);
}

Expand Down
20 changes: 13 additions & 7 deletions packages/core/src/libraries/organization-invitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ export class OrganizationInvitationLibrary {

if (organizationRoleIds?.length) {
await organizationQueries.relations.invitationsRoles.insert(
...organizationRoleIds.map<[string, string]>((roleId) => [invitation.id, roleId])
...organizationRoleIds.map((roleId) => ({
organizationInvitationId: invitation.id,
organizationRoleId: roleId,
}))
);
}

Expand Down Expand Up @@ -165,15 +168,18 @@ export class OrganizationInvitationLibrary {
});
}

await organizationQueries.relations.users.insert([entity.organizationId, acceptedUserId]);
await organizationQueries.relations.users.insert({
organizationId: entity.organizationId,
userId: acceptedUserId,
});

if (entity.organizationRoles.length > 0) {
await organizationQueries.relations.rolesUsers.insert(
...entity.organizationRoles.map<[string, string, string]>((role) => [
entity.organizationId,
role.id,
acceptedUserId,
])
...entity.organizationRoles.map((role) => ({
organizationId: entity.organizationId,
organizationRoleId: role.id,
userId: acceptedUserId,
}))
);
}
break;
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/libraries/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,18 +155,18 @@ export const createUserLibrary = (queries: Queries) => {

if (organizations.length > 0) {
await organizationQueries.relations.users.insert(
...organizations.map<[string, string]>(({ organizationId }) => [
...organizations.map(({ organizationId }) => ({
organizationId,
user.id,
])
userId: user.id,
}))
);

const data = organizations.flatMap(({ organizationId, organizationRoleIds }) =>
organizationRoleIds.map<[string, string, string]>((organizationRoleId) => [
organizationRoleIds.map((organizationRoleId) => ({
organizationId,
organizationRoleId,
user.id,
])
userId: user.id,
}))
);
if (data.length > 0) {
await organizationQueries.relations.rolesUsers.insert(...data);
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/oidc/grants/refresh-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,12 @@ export const buildHandler: (

if (organizationId) {
// Check membership
if (!(await queries.organizations.relations.users.exists(organizationId, account.accountId))) {
if (
!(await queries.organizations.relations.users.exists({
organizationId,
userId: account.accountId,
}))
) {
const error = new AccessDenied('user is not a member of the organization');
error.statusCode = 403;
throw error;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/oidc/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,5 +195,5 @@ export const isOrganizationConsentedToApplication = async (
accountId: string,
organizationId: string
) => {
return userConsentOrganizations.exists(applicationId, accountId, organizationId);
return userConsentOrganizations.exists({ applicationId, userId: accountId, organizationId });
};
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ export default function applicationUserConsentOrganizationRoutes<T extends Manag
await validateUserConsentOrganizationMembership(userId, organizationIds);

await applications.userConsentOrganizations.insert(
...organizationIds.map<[string, string, string]>((organizationId) => [
id,
...organizationIds.map((organizationId) => ({
applicationId: id,
userId,
organizationId,
])
}))
);

ctx.status = 201;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,12 @@ async function handleSubmitRegister(
// Create tenant organization and assign the admin user to it.
// This is only for Cloud integration tests and data alignment, OSS still uses the legacy Management API user role.
const organizationId = getTenantOrganizationId(defaultTenantId);
await organizations.relations.users.insert([organizationId, id]);
await organizations.relations.rolesUsers.insert([
await organizations.relations.users.insert({ organizationId, userId: id });
await organizations.relations.rolesUsers.insert({
organizationId,
getTenantRole(TenantRole.Admin).id,
id,
]);
organizationRoleId: getTenantRole(TenantRole.Admin).id,
userId: id,
});
}

await assignInteractionResults(ctx, provider, { login: { accountId: id } });
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/routes/interaction/consent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ export default function consentRoutes<T extends IRouterParamContext>(
await validateUserConsentOrganizationMembership(userId, organizationIds);

await queries.applications.userConsentOrganizations.insert(
...organizationIds.map<[string, string, string]>((organizationId) => [
...organizationIds.map((organizationId) => ({
applicationId,
userId,
organizationId,
])
}))
);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/routes/organization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ export default function organizationRoutes<T extends ManagementApiRouter>(
const { userIds, organizationRoleIds } = ctx.guard.body;

await organizations.relations.rolesUsers.insert(
...organizationRoleIds.flatMap<[string, string, string]>((roleId) =>
userIds.map<[string, string, string]>((userId) => [id, roleId, userId])
...organizationRoleIds.flatMap((roleId) =>
userIds.map((userId) => ({ organizationId: id, organizationRoleId: roleId, userId }))
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function userRoleRelationRoutes(
const { id, userId } = ctx.guard.params;

// Ensure membership
if (!(await organizations.relations.users.exists(id, userId))) {
if (!(await organizations.relations.users.exists({ organizationId: id, userId }))) {
throw new RequestError({ code: 'organization.require_membership', status: 422 });
}

Expand Down Expand Up @@ -87,7 +87,11 @@ export default function userRoleRelationRoutes(
const { organizationRoleIds } = ctx.guard.body;

await organizations.relations.rolesUsers.insert(
...organizationRoleIds.map<[string, string, string]>((roleId) => [id, roleId, userId])
...organizationRoleIds.map((roleId) => ({
organizationId: id,
organizationRoleId: roleId,
userId,
}))
);

ctx.status = 201;
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/routes/organization/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,19 @@ export default function organizationRoleRoutes<T extends ManagementApiRouter>(

if (organizationScopeIds.length > 0) {
await rolesScopes.insert(
...organizationScopeIds.map<[string, string]>((id) => [role.id, id])
...organizationScopeIds.map((id) => ({
organizationRoleId: role.id,
organizationScopeId: id,
}))
);
}

if (resourceScopeIds.length > 0) {
await rolesResourceScopes.insert(
...resourceScopeIds.map<[string, string]>((id) => [role.id, id])
...resourceScopeIds.map((id) => ({
organizationRoleId: role.id,
scopeId: id,
}))
);
}

Expand Down
36 changes: 23 additions & 13 deletions packages/core/src/utils/RelationQueries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type SchemaLike, type Table } from '@logto/shared';
import { type KeysToCamelCase } from '@silverhand/essentials';
import { type CamelCase, type KeysToCamelCase } from '@silverhand/essentials';
import { sql, type CommonQueryMethods } from '@silverhand/slonik';
import snakecaseKeys from 'snakecase-keys';
import { type z } from 'zod';
Expand All @@ -9,6 +9,10 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';

import { conditionalSql } from './sql.js';

const camelCase = <T extends string>(string: T): CamelCase<T> =>
// eslint-disable-next-line no-restricted-syntax, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
string.replaceAll(/_([a-z])/g, (_, letter) => letter.toUpperCase()) as CamelCase<T>;

type AtLeast2<T extends unknown[]> = `${T['length']}` extends '0' | '1' ? never : T;

type TableInfo<
Expand Down Expand Up @@ -56,11 +60,11 @@ export type GetEntitiesOptions = {
* To insert a new relation, we can use the {@link RelationQueries.insert} method:
*
* ```ts
* await userGroupRelations.insert(['user-id-1', 'group-id-1']);
* await userGroupRelations.insert({ userId: 'user-id-1', groupId: 'group-id-1' });
* // Insert multiple relations at once
* await userGroupRelations.insert(
* ['user-id-1', 'group-id-1'],
* ['user-id-2', 'group-id-1']
* { userId: 'user-id-1', groupId: 'group-id-1' },
* { userId: 'user-id-2', groupId: 'group-id-1' }
* );
* ```
*
Expand Down Expand Up @@ -111,15 +115,15 @@ export default class RelationQueries<
* ```ts
* const userGroupRelations = new RelationQueries(pool, 'user_group_relations', Users, Groups);
*
* userGroupRelations.insert(['user-id-1', 'group-id-1']);
* userGroupRelations.insert({ userId: 'user-id-1', groupId: 'group-id-1' });
* // Insert multiple relations at once
* userGroupRelations.insert(
* ['user-id-1', 'group-id-1'],
* ['user-id-2', 'group-id-1']
* { userId: 'user-id-1', groupId: 'group-id-1' },
* { userId: 'user-id-2', groupId: 'group-id-1' }
* );
* ```
*/
async insert(...data: ReadonlyArray<string[] & { length: Length }>) {
async insert(...data: ReadonlyArray<CamelCaseIdObject<Schemas[number]['tableSingular']>>) {
return this.pool.query(sql`
insert into ${this.table} (${sql.join(
this.schemas.map(({ tableSingular }) => sql.identifier([tableSingular + '_id'])),
Expand All @@ -129,7 +133,10 @@ export default class RelationQueries<
data.map(
(relation) =>
sql`(${sql.join(
relation.map((id) => sql`${id}`),
this.schemas.map(
// @ts-expect-error `tableSingular` loses its type here
({ tableSingular }) => sql`${relation[camelCase(tableSingular + '_id')]}`
),
sql`, `
)})`
),
Expand Down Expand Up @@ -240,17 +247,20 @@ export default class RelationQueries<
* ```ts
* const userGroupRelations = new RelationQueries(pool, 'user_group_relations', Users, Groups);
*
* userGroupRelations.exists('user-id-1', 'group-id-1');
* userGroupRelations.exists({ userId: 'user-id-1', groupId: 'group-id-1' });
* ```
*/
async exists(...ids: readonly string[] & { length: Length }) {
async exists(ids: CamelCaseIdObject<Schemas[number]['tableSingular']>) {
return this.pool.exists(sql`
select
from ${this.table}
where ${sql.join(
this.schemas.map(
({ tableSingular }, index) =>
sql`${sql.identifier([tableSingular + '_id'])} = ${ids[index] ?? sql`null`}`
({ tableSingular }) =>
sql`${sql.identifier([tableSingular + '_id'])} = ${
// @ts-expect-error `tableSingular` loses its type here
ids[camelCase(tableSingular + '_id')]
}`
),
sql` and `
)}
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/utils/SchemaRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,10 @@ export default class SchemaRouter<
} = ctx.guard;

await relationQueries.insert(
...(relationIds?.map<[string, string]>((relationId) => [id, relationId]) ?? [])
...(relationIds?.map((relationId) => ({
[columns.schemaId]: id,
[columns.relationSchemaId]: relationId,
})) ?? [])
);
appendHookContext(ctx, id);
ctx.status = 201;
Expand Down

0 comments on commit e83e94f

Please sign in to comment.