From ce911309da89fd4de1afac221de763fbbeb0c591 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Wed, 5 Jun 2024 18:25:18 +0800 Subject: [PATCH] refactor(core): reorg organization queries --- .../core/src/queries/organization/index.ts | 3 +- .../organization/role-user-relations.ts | 117 ++++++++++++++++++ .../{relations.ts => user-relations.ts} | 114 +---------------- 3 files changed, 121 insertions(+), 113 deletions(-) create mode 100644 packages/core/src/queries/organization/role-user-relations.ts rename packages/core/src/queries/organization/{relations.ts => user-relations.ts} (58%) diff --git a/packages/core/src/queries/organization/index.ts b/packages/core/src/queries/organization/index.ts index 573c82411..5d7ddb31f 100644 --- a/packages/core/src/queries/organization/index.ts +++ b/packages/core/src/queries/organization/index.ts @@ -28,7 +28,8 @@ import { TwoRelationsQueries } from '#src/utils/RelationQueries.js'; import SchemaQueries from '#src/utils/SchemaQueries.js'; import { conditionalSql, convertToIdentifiers } from '#src/utils/sql.js'; -import { RoleUserRelationQueries, UserRelationQueries } from './relations.js'; +import { RoleUserRelationQueries } from './role-user-relations.js'; +import { UserRelationQueries } from './user-relations.js'; /** * The schema field keys that can be used for searching roles. diff --git a/packages/core/src/queries/organization/role-user-relations.ts b/packages/core/src/queries/organization/role-user-relations.ts new file mode 100644 index 000000000..5710bd110 --- /dev/null +++ b/packages/core/src/queries/organization/role-user-relations.ts @@ -0,0 +1,117 @@ +import { + Organizations, + OrganizationRoles, + OrganizationScopes, + OrganizationRoleScopeRelations, + Users, + OrganizationRoleUserRelations, + type OrganizationScope, + type ResourceScopeEntity, + Scopes, + OrganizationRoleResourceScopeRelations, + Resources, +} from '@logto/schemas'; +import { sql, type CommonQueryMethods } from '@silverhand/slonik'; + +import RelationQueries from '#src/utils/RelationQueries.js'; +import { conditionalSql, convertToIdentifiers } from '#src/utils/sql.js'; + +export class RoleUserRelationQueries extends RelationQueries< + [typeof Organizations, typeof OrganizationRoles, typeof Users] +> { + constructor(pool: CommonQueryMethods) { + super(pool, OrganizationRoleUserRelations.table, Organizations, OrganizationRoles, Users); + } + + /** Get the available scopes of a user in an organization. */ + async getUserScopes( + organizationId: string, + userId: string + ): Promise { + const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true); + const roleScopeRelations = convertToIdentifiers(OrganizationRoleScopeRelations, true); + const scopes = convertToIdentifiers(OrganizationScopes, true); + + return this.pool.any(sql` + select distinct on (${scopes.fields.id}) + ${sql.join(Object.values(scopes.fields), sql`, `)} + from ${this.table} + join ${roleScopeRelations.table} + on ${roleScopeRelations.fields.organizationRoleId} = ${fields.organizationRoleId} + join ${scopes.table} + on ${scopes.fields.id} = ${roleScopeRelations.fields.organizationScopeId} + where ${fields.organizationId} = ${organizationId} + and ${fields.userId} = ${userId} + `); + } + + /** + * Get the available resource scopes of a user in all organizations. + * If `organizationId` is provided, it will only search in that organization. + */ + async getUserResourceScopes( + userId: string, + resourceIndicator: string, + organizationId?: string + ): Promise { + const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true); + const roleScopeRelations = convertToIdentifiers(OrganizationRoleResourceScopeRelations, true); + const scopes = convertToIdentifiers(Scopes, true); + const resources = convertToIdentifiers(Resources, true); + + return this.pool.any(sql` + select distinct on (${scopes.fields.id}) + ${scopes.fields.id}, ${scopes.fields.name} + from ${this.table} + join ${roleScopeRelations.table} + on ${roleScopeRelations.fields.organizationRoleId} = ${fields.organizationRoleId} + join ${scopes.table} + on ${scopes.fields.id} = ${roleScopeRelations.fields.scopeId} + join ${resources.table} + on ${resources.fields.id} = ${scopes.fields.resourceId} + where ${fields.userId} = ${userId} + and ${resources.fields.indicator} = ${resourceIndicator} + ${conditionalSql(organizationId, (value) => sql`and ${fields.organizationId} = ${value}`)} + `); + } + + /** Replace the roles of a user in an organization. */ + async replace(organizationId: string, userId: string, roleIds: string[]) { + const users = convertToIdentifiers(Users); + const relations = convertToIdentifiers(OrganizationRoleUserRelations); + + return this.pool.transaction(async (transaction) => { + // Lock user + await transaction.query(sql` + select id + from ${users.table} + where ${users.fields.id} = ${userId} + for update + `); + + // Delete old relations + await transaction.query(sql` + delete from ${relations.table} + where ${relations.fields.userId} = ${userId} + and ${relations.fields.organizationId} = ${organizationId} + `); + + // Insert new relations + if (roleIds.length === 0) { + return; + } + + await transaction.query(sql` + insert into ${relations.table} ( + ${relations.fields.userId}, + ${relations.fields.organizationId}, + ${relations.fields.organizationRoleId} + ) + values ${sql.join( + roleIds.map((roleId) => sql`(${userId}, ${organizationId}, ${roleId})`), + sql`, ` + )} + `); + }); + } +} diff --git a/packages/core/src/queries/organization/relations.ts b/packages/core/src/queries/organization/user-relations.ts similarity index 58% rename from packages/core/src/queries/organization/relations.ts rename to packages/core/src/queries/organization/user-relations.ts index d78f99320..142783a0a 100644 --- a/packages/core/src/queries/organization/relations.ts +++ b/packages/core/src/queries/organization/user-relations.ts @@ -1,28 +1,18 @@ import { Organizations, OrganizationRoles, - OrganizationScopes, - OrganizationRoleScopeRelations, Users, OrganizationUserRelations, OrganizationRoleUserRelations, type OrganizationWithRoles, type UserWithOrganizationRoles, type FeaturedUser, - type OrganizationScope, - type ResourceScopeEntity, - Scopes, - OrganizationRoleResourceScopeRelations, - Resources, } from '@logto/schemas'; import { sql, type CommonQueryMethods } from '@silverhand/slonik'; import { type SearchOptions, buildSearchSql, expandFields } from '#src/database/utils.js'; -import RelationQueries, { - type GetEntitiesOptions, - TwoRelationsQueries, -} from '#src/utils/RelationQueries.js'; -import { conditionalSql, convertToIdentifiers } from '#src/utils/sql.js'; +import { type GetEntitiesOptions, TwoRelationsQueries } from '#src/utils/RelationQueries.js'; +import { convertToIdentifiers } from '#src/utils/sql.js'; import { type userSearchKeys } from '../user.js'; @@ -170,103 +160,3 @@ export class UserRelationQueries extends TwoRelationsQueries { - constructor(pool: CommonQueryMethods) { - super(pool, OrganizationRoleUserRelations.table, Organizations, OrganizationRoles, Users); - } - - /** Get the available scopes of a user in an organization. */ - async getUserScopes( - organizationId: string, - userId: string - ): Promise { - const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true); - const roleScopeRelations = convertToIdentifiers(OrganizationRoleScopeRelations, true); - const scopes = convertToIdentifiers(OrganizationScopes, true); - - return this.pool.any(sql` - select distinct on (${scopes.fields.id}) - ${sql.join(Object.values(scopes.fields), sql`, `)} - from ${this.table} - join ${roleScopeRelations.table} - on ${roleScopeRelations.fields.organizationRoleId} = ${fields.organizationRoleId} - join ${scopes.table} - on ${scopes.fields.id} = ${roleScopeRelations.fields.organizationScopeId} - where ${fields.organizationId} = ${organizationId} - and ${fields.userId} = ${userId} - `); - } - - /** - * Get the available resource scopes of a user in all organizations. - * If `organizationId` is provided, it will only search in that organization. - */ - async getUserResourceScopes( - userId: string, - resourceIndicator: string, - organizationId?: string - ): Promise { - const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true); - const roleScopeRelations = convertToIdentifiers(OrganizationRoleResourceScopeRelations, true); - const scopes = convertToIdentifiers(Scopes, true); - const resources = convertToIdentifiers(Resources, true); - - return this.pool.any(sql` - select distinct on (${scopes.fields.id}) - ${scopes.fields.id}, ${scopes.fields.name} - from ${this.table} - join ${roleScopeRelations.table} - on ${roleScopeRelations.fields.organizationRoleId} = ${fields.organizationRoleId} - join ${scopes.table} - on ${scopes.fields.id} = ${roleScopeRelations.fields.scopeId} - join ${resources.table} - on ${resources.fields.id} = ${scopes.fields.resourceId} - where ${fields.userId} = ${userId} - and ${resources.fields.indicator} = ${resourceIndicator} - ${conditionalSql(organizationId, (value) => sql`and ${fields.organizationId} = ${value}`)} - `); - } - - /** Replace the roles of a user in an organization. */ - async replace(organizationId: string, userId: string, roleIds: string[]) { - const users = convertToIdentifiers(Users); - const relations = convertToIdentifiers(OrganizationRoleUserRelations); - - return this.pool.transaction(async (transaction) => { - // Lock user - await transaction.query(sql` - select id - from ${users.table} - where ${users.fields.id} = ${userId} - for update - `); - - // Delete old relations - await transaction.query(sql` - delete from ${relations.table} - where ${relations.fields.userId} = ${userId} - and ${relations.fields.organizationId} = ${organizationId} - `); - - // Insert new relations - if (roleIds.length === 0) { - return; - } - - await transaction.query(sql` - insert into ${relations.table} ( - ${relations.fields.userId}, - ${relations.fields.organizationId}, - ${relations.fields.organizationRoleId} - ) - values ${sql.join( - roleIds.map((roleId) => sql`(${userId}, ${organizationId}, ${roleId})`), - sql`, ` - )} - `); - }); - } -}