0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

refactor(core): reorg organization queries

This commit is contained in:
Gao Sun 2024-01-11 21:56:10 +08:00
parent 0f08608b96
commit f9750ace90
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
4 changed files with 210 additions and 203 deletions

View file

@ -34,7 +34,7 @@ import validatePresence from 'oidc-provider/lib/helpers/validate_presence.js';
import instance from 'oidc-provider/lib/helpers/weak_cache.js';
import { type EnvSet } from '#src/env-set/index.js';
import type OrganizationQueries from '#src/queries/organizations.js';
import type OrganizationQueries from '#src/queries/organization/index.js';
import assertThat from '#src/utils/assert-that.js';
import { getSharedResourceServerData, reversedResourceAccessTokenTtl } from '../resource.js';

View file

@ -0,0 +1,203 @@
import {
type Organization,
type CreateOrganization,
type OrganizationKeys,
Organizations,
OrganizationRoles,
OrganizationScopes,
OrganizationRoleScopeRelations,
type OrganizationRoleKeys,
type CreateOrganizationRole,
type OrganizationRole,
type OrganizationRoleWithScopes,
OrganizationInvitations,
type OrganizationInvitationKeys,
type CreateOrganizationInvitation,
type OrganizationInvitation,
type OrganizationInvitationEntity,
MagicLinks,
OrganizationInvitationRoleRelations,
} from '@logto/schemas';
import { conditionalSql, convertToIdentifiers } from '@logto/shared';
import { sql, type CommonQueryMethods } from 'slonik';
import { type SearchOptions, buildSearchSql, buildJsonObjectSql } from '#src/database/utils.js';
import { TwoRelationsQueries } from '#src/utils/RelationQueries.js';
import SchemaQueries from '#src/utils/SchemaQueries.js';
import { RoleUserRelationQueries, UserRelationQueries } from './relations.js';
class OrganizationRolesQueries extends SchemaQueries<
OrganizationRoleKeys,
CreateOrganizationRole,
OrganizationRole
> {
override async findById(id: string): Promise<Readonly<OrganizationRoleWithScopes>> {
return this.pool.one(this.#findWithScopesSql(id));
}
override async findAll(
limit: number,
offset: number,
search?: SearchOptions<OrganizationRoleKeys>
): Promise<[totalNumber: number, rows: Readonly<OrganizationRoleWithScopes[]>]> {
return Promise.all([
this.findTotalNumber(search),
this.pool.any(this.#findWithScopesSql(undefined, limit, offset, search)),
]);
}
#findWithScopesSql(
roleId?: string,
limit = 1,
offset = 0,
search?: SearchOptions<OrganizationRoleKeys>
) {
const { table, fields } = convertToIdentifiers(OrganizationRoles, true);
const relations = convertToIdentifiers(OrganizationRoleScopeRelations, true);
const scopes = convertToIdentifiers(OrganizationScopes, true);
return sql<OrganizationRoleWithScopes>`
select
${table}.*,
coalesce(
json_agg(
json_build_object(
'id', ${scopes.fields.id},
'name', ${scopes.fields.name}
) order by ${scopes.fields.name}
) filter (where ${scopes.fields.id} is not null),
'[]'
) as scopes -- left join could produce nulls as scopes
from ${table}
left join ${relations.table}
on ${relations.fields.organizationRoleId} = ${fields.id}
left join ${scopes.table}
on ${relations.fields.organizationScopeId} = ${scopes.fields.id}
${conditionalSql(roleId, (id) => {
return sql`where ${fields.id} = ${id}`;
})}
${buildSearchSql(OrganizationRoles, search)}
group by ${fields.id}
${conditionalSql(this.orderBy, ({ field, order }) => {
return sql`order by ${fields[field]} ${order === 'desc' ? sql`desc` : sql`asc`}`;
})}
limit ${limit}
offset ${offset}
`;
}
}
class OrganizationInvitationsQueries extends SchemaQueries<
OrganizationInvitationKeys,
CreateOrganizationInvitation,
OrganizationInvitation
> {
override async findById(id: string): Promise<Readonly<OrganizationInvitationEntity>> {
return this.pool.one(this.#findEntity(id));
}
override async findAll(
limit: number,
offset: number,
search?: SearchOptions<OrganizationInvitationKeys>
): Promise<[totalNumber: number, rows: Readonly<OrganizationInvitationEntity[]>]> {
return Promise.all([
this.findTotalNumber(search),
this.pool.any(this.#findEntity(undefined, limit, offset, search)),
]);
}
#findEntity(
invitationId?: string,
limit = 1,
offset = 0,
search?: SearchOptions<OrganizationInvitationKeys>
) {
const { table, fields } = convertToIdentifiers(OrganizationInvitations, true);
const magicLinks = convertToIdentifiers(MagicLinks, true);
const roleRelations = convertToIdentifiers(OrganizationInvitationRoleRelations, true);
const roles = convertToIdentifiers(OrganizationRoles, true);
return sql<OrganizationInvitationEntity>`
select
${table}.*,
coalesce(
json_agg(
json_build_object(
'id', ${roles.fields.id},
'name', ${roles.fields.name}
) order by ${roles.fields.name}
) filter (where ${roles.fields.id} is not null),
'[]'
) as "organizationRoles", -- left join could produce nulls
${buildJsonObjectSql(magicLinks.fields)} as "magicLink"
from ${table}
left join ${roleRelations.table}
on ${roleRelations.fields.invitationId} = ${fields.id}
left join ${roles.table}
on ${roles.fields.id} = ${roleRelations.fields.organizationRoleId}
left join ${magicLinks.table}
on ${magicLinks.fields.id} = ${fields.magicLinkId}
${conditionalSql(invitationId, (id) => {
return sql`where ${fields.id} = ${id}`;
})}
${buildSearchSql(OrganizationInvitations, search)}
group by ${fields.id}, ${magicLinks.fields.id}
${conditionalSql(this.orderBy, ({ field, order }) => {
return sql`order by ${fields[field]} ${order === 'desc' ? sql`desc` : sql`asc`}`;
})}
limit ${limit}
offset ${offset}
`;
}
}
export default class OrganizationQueries extends SchemaQueries<
OrganizationKeys,
CreateOrganization,
Organization
> {
/**
* Queries for roles in the organization template.
* @see {@link OrganizationRoles}
*/
roles = new OrganizationRolesQueries(this.pool, OrganizationRoles, {
field: 'name',
order: 'asc',
});
/**
* Queries for scopes in the organization template.
* @see {@link OrganizationScopes}
*/
scopes = new SchemaQueries(this.pool, OrganizationScopes, { field: 'name', order: 'asc' });
/**
* Queries for organization invitations.
* @see {@link OrganizationInvitations}
*/
invitations = new OrganizationInvitationsQueries(this.pool, OrganizationInvitations, {
field: 'createdAt',
order: 'desc',
});
/** Queries for relations that connected with organization-related entities. */
relations = {
/** Queries for organization role - organization scope relations. */
rolesScopes: new TwoRelationsQueries(
this.pool,
OrganizationRoleScopeRelations.table,
OrganizationRoles,
OrganizationScopes
),
/** Queries for organization - user relations. */
users: new UserRelationQueries(this.pool),
/** Queries for organization - organization role - user relations. */
rolesUsers: new RoleUserRelationQueries(this.pool),
};
constructor(pool: CommonQueryMethods) {
super(pool, Organizations);
}
}

View file

@ -1,8 +1,4 @@
/* eslint-disable max-lines -- refactor in the next pull */
import {
type Organization,
type CreateOrganization,
type OrganizationKeys,
Organizations,
OrganizationRoles,
OrganizationScopes,
@ -10,41 +6,24 @@ import {
Users,
OrganizationUserRelations,
OrganizationRoleUserRelations,
type OrganizationRoleKeys,
type CreateOrganizationRole,
type OrganizationRole,
type OrganizationRoleWithScopes,
type OrganizationWithRoles,
type UserWithOrganizationRoles,
type FeaturedUser,
type OrganizationScopeEntity,
OrganizationInvitations,
type OrganizationInvitationKeys,
type CreateOrganizationInvitation,
type OrganizationInvitation,
type OrganizationInvitationEntity,
MagicLinks,
OrganizationInvitationRoleRelations,
} from '@logto/schemas';
import { conditionalSql, convertToIdentifiers } from '@logto/shared';
import { convertToIdentifiers } from '@logto/shared';
import { sql, type CommonQueryMethods } from 'slonik';
import {
type SearchOptions,
buildSearchSql,
expandFields,
buildJsonObjectSql,
} from '#src/database/utils.js';
import { type SearchOptions, buildSearchSql, expandFields } from '#src/database/utils.js';
import RelationQueries, {
type GetEntitiesOptions,
TwoRelationsQueries,
} from '#src/utils/RelationQueries.js';
import SchemaQueries from '#src/utils/SchemaQueries.js';
import { type userSearchKeys } from './user.js';
import { type userSearchKeys } from '../user.js';
/** The query class for the organization - user relation. */
class UserRelationQueries extends TwoRelationsQueries<typeof Organizations, typeof Users> {
export class UserRelationQueries extends TwoRelationsQueries<typeof Organizations, typeof Users> {
constructor(pool: CommonQueryMethods) {
super(pool, OrganizationUserRelations.table, Organizations, Users);
}
@ -174,68 +153,7 @@ class UserRelationQueries extends TwoRelationsQueries<typeof Organizations, type
}
}
class OrganizationRolesQueries extends SchemaQueries<
OrganizationRoleKeys,
CreateOrganizationRole,
OrganizationRole
> {
override async findById(id: string): Promise<Readonly<OrganizationRoleWithScopes>> {
return this.pool.one(this.#findWithScopesSql(id));
}
override async findAll(
limit: number,
offset: number,
search?: SearchOptions<OrganizationRoleKeys>
): Promise<[totalNumber: number, rows: Readonly<OrganizationRoleWithScopes[]>]> {
return Promise.all([
this.findTotalNumber(search),
this.pool.any(this.#findWithScopesSql(undefined, limit, offset, search)),
]);
}
#findWithScopesSql(
roleId?: string,
limit = 1,
offset = 0,
search?: SearchOptions<OrganizationRoleKeys>
) {
const { table, fields } = convertToIdentifiers(OrganizationRoles, true);
const relations = convertToIdentifiers(OrganizationRoleScopeRelations, true);
const scopes = convertToIdentifiers(OrganizationScopes, true);
return sql<OrganizationRoleWithScopes>`
select
${table}.*,
coalesce(
json_agg(
json_build_object(
'id', ${scopes.fields.id},
'name', ${scopes.fields.name}
) order by ${scopes.fields.name}
) filter (where ${scopes.fields.id} is not null),
'[]'
) as scopes -- left join could produce nulls as scopes
from ${table}
left join ${relations.table}
on ${relations.fields.organizationRoleId} = ${fields.id}
left join ${scopes.table}
on ${relations.fields.organizationScopeId} = ${scopes.fields.id}
${conditionalSql(roleId, (id) => {
return sql`where ${fields.id} = ${id}`;
})}
${buildSearchSql(OrganizationRoles, search)}
group by ${fields.id}
${conditionalSql(this.orderBy, ({ field, order }) => {
return sql`order by ${fields[field]} ${order === 'desc' ? sql`desc` : sql`asc`}`;
})}
limit ${limit}
offset ${offset}
`;
}
}
class RoleUserRelationQueries extends RelationQueries<
export class RoleUserRelationQueries extends RelationQueries<
[typeof Organizations, typeof OrganizationRoles, typeof Users]
> {
constructor(pool: CommonQueryMethods) {
@ -304,117 +222,3 @@ class RoleUserRelationQueries extends RelationQueries<
});
}
}
class OrganizationInvitationsQueries extends SchemaQueries<
OrganizationInvitationKeys,
CreateOrganizationInvitation,
OrganizationInvitation
> {
override async findById(id: string): Promise<Readonly<OrganizationInvitationEntity>> {
return this.pool.one(this.#findEntity(id));
}
override async findAll(
limit: number,
offset: number,
search?: SearchOptions<OrganizationInvitationKeys>
): Promise<[totalNumber: number, rows: Readonly<OrganizationInvitationEntity[]>]> {
return Promise.all([
this.findTotalNumber(search),
this.pool.any(this.#findEntity(undefined, limit, offset, search)),
]);
}
#findEntity(
invitationId?: string,
limit = 1,
offset = 0,
search?: SearchOptions<OrganizationInvitationKeys>
) {
const { table, fields } = convertToIdentifiers(OrganizationInvitations, true);
const magicLinks = convertToIdentifiers(MagicLinks, true);
const roleRelations = convertToIdentifiers(OrganizationInvitationRoleRelations, true);
const roles = convertToIdentifiers(OrganizationRoles, true);
return sql<OrganizationInvitationEntity>`
select
${table}.*,
coalesce(
json_agg(
json_build_object(
'id', ${roles.fields.id},
'name', ${roles.fields.name}
) order by ${roles.fields.name}
) filter (where ${roles.fields.id} is not null),
'[]'
) as "organizationRoles", -- left join could produce nulls
${buildJsonObjectSql(magicLinks.fields)} as "magicLink"
from ${table}
left join ${roleRelations.table}
on ${roleRelations.fields.invitationId} = ${fields.id}
left join ${roles.table}
on ${roles.fields.id} = ${roleRelations.fields.organizationRoleId}
left join ${magicLinks.table}
on ${magicLinks.fields.id} = ${fields.magicLinkId}
${conditionalSql(invitationId, (id) => {
return sql`where ${fields.id} = ${id}`;
})}
${buildSearchSql(OrganizationInvitations, search)}
group by ${fields.id}, ${magicLinks.fields.id}
${conditionalSql(this.orderBy, ({ field, order }) => {
return sql`order by ${fields[field]} ${order === 'desc' ? sql`desc` : sql`asc`}`;
})}
limit ${limit}
offset ${offset}
`;
}
}
export default class OrganizationQueries extends SchemaQueries<
OrganizationKeys,
CreateOrganization,
Organization
> {
/**
* Queries for roles in the organization template.
* @see {@link OrganizationRoles}
*/
roles = new OrganizationRolesQueries(this.pool, OrganizationRoles, {
field: 'name',
order: 'asc',
});
/**
* Queries for scopes in the organization template.
* @see {@link OrganizationScopes}
*/
scopes = new SchemaQueries(this.pool, OrganizationScopes, { field: 'name', order: 'asc' });
/**
* Queries for organization invitations.
* @see {@link OrganizationInvitations}
*/
invitations = new OrganizationInvitationsQueries(this.pool, OrganizationInvitations, {
field: 'createdAt',
order: 'desc',
});
/** Queries for relations that connected with organization-related entities. */
relations = {
/** Queries for organization role - organization scope relations. */
rolesScopes: new TwoRelationsQueries(
this.pool,
OrganizationRoleScopeRelations.table,
OrganizationRoles,
OrganizationScopes
),
/** Queries for organization - user relations. */
users: new UserRelationQueries(this.pool),
/** Queries for organization - organization role - user relations. */
rolesUsers: new RoleUserRelationQueries(this.pool),
};
constructor(pool: CommonQueryMethods) {
super(pool, Organizations);
}
}
/* eslint-enable max-lines */

View file

@ -13,7 +13,7 @@ import { createHooksQueries } from '#src/queries/hooks.js';
import { createLogQueries } from '#src/queries/log.js';
import { createLogtoConfigQueries } from '#src/queries/logto-config.js';
import { createOidcModelInstanceQueries } from '#src/queries/oidc-model-instance.js';
import OrganizationQueries from '#src/queries/organizations.js';
import OrganizationQueries from '#src/queries/organization/index.js';
import { createPasscodeQueries } from '#src/queries/passcode.js';
import { createResourceQueries } from '#src/queries/resource.js';
import { createRolesScopesQueries } from '#src/queries/roles-scopes.js';