Skip to content

Commit

Permalink
Merge pull request #5991 from logto-io/gao-reorg-org-routes
Browse files Browse the repository at this point in the history
refactor(core): reorg organization routes
  • Loading branch information
gao-sun authored Jun 6, 2024
2 parents 33537ef + ce91130 commit 0874b70
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 234 deletions.
3 changes: 2 additions & 1 deletion packages/core/src/queries/organization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
117 changes: 117 additions & 0 deletions packages/core/src/queries/organization/role-user-relations.ts
Original file line number Diff line number Diff line change
@@ -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<readonly OrganizationScope[]> {
const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true);
const roleScopeRelations = convertToIdentifiers(OrganizationRoleScopeRelations, true);
const scopes = convertToIdentifiers(OrganizationScopes, true);

return this.pool.any<OrganizationScope>(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<readonly ResourceScopeEntity[]> {
const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true);
const roleScopeRelations = convertToIdentifiers(OrganizationRoleResourceScopeRelations, true);
const scopes = convertToIdentifiers(Scopes, true);
const resources = convertToIdentifiers(Resources, true);

return this.pool.any<ResourceScopeEntity>(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`, `
)}
`);
});
}
}
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -170,103 +160,3 @@ export class UserRelationQueries extends TwoRelationsQueries<typeof Organization
`;
}
}

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<readonly OrganizationScope[]> {
const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true);
const roleScopeRelations = convertToIdentifiers(OrganizationRoleScopeRelations, true);
const scopes = convertToIdentifiers(OrganizationScopes, true);

return this.pool.any<OrganizationScope>(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<readonly ResourceScopeEntity[]> {
const { fields } = convertToIdentifiers(OrganizationRoleUserRelations, true);
const roleScopeRelations = convertToIdentifiers(OrganizationRoleResourceScopeRelations, true);
const scopes = convertToIdentifiers(Scopes, true);
const resources = convertToIdentifiers(Resources, true);

return this.pool.any<ResourceScopeEntity>(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`, `
)}
`);
});
}
}
Loading

0 comments on commit 0874b70

Please sign in to comment.