mirror of
https://github.com/logto-io/logto.git
synced 2025-02-03 21:48:55 -05:00
refactor(core): return roles in organization app get api
This commit is contained in:
parent
6dd487269d
commit
b839f6c46f
13 changed files with 291 additions and 70 deletions
|
@ -1,5 +1,6 @@
|
|||
import type { Application, CreateApplication } from '@logto/schemas';
|
||||
import { ApplicationType, Applications, SearchJointMode } from '@logto/schemas';
|
||||
import { pick } from '@silverhand/essentials';
|
||||
import type { CommonQueryMethods, SqlSqlToken } from '@silverhand/slonik';
|
||||
import { sql } from '@silverhand/slonik';
|
||||
|
||||
|
@ -23,22 +24,31 @@ import {
|
|||
|
||||
const { table, fields } = convertToIdentifiers(Applications);
|
||||
|
||||
const buildApplicationConditions = (search: Search) => {
|
||||
const hasSearch = search.matches.length > 0;
|
||||
const searchFields = [
|
||||
Applications.fields.id,
|
||||
Applications.fields.name,
|
||||
Applications.fields.description,
|
||||
];
|
||||
/**
|
||||
* The schema field keys that can be used for searching apps. For the actual field names,
|
||||
* see {@link Applications.fields} and {@link applicationSearchFields}.
|
||||
*/
|
||||
export const applicationSearchKeys = Object.freeze(['id', 'name', 'description'] satisfies Array<
|
||||
keyof Application
|
||||
>);
|
||||
|
||||
/**
|
||||
* The actual database field names that can be used for searching apps. For the schema field
|
||||
* keys, see {@link userSearchKeys}.
|
||||
*/
|
||||
const applicationSearchFields = Object.freeze(
|
||||
Object.values(pick(Applications.fields, ...applicationSearchKeys))
|
||||
);
|
||||
|
||||
const buildApplicationConditions = (search: Search) => {
|
||||
return conditionalSql(
|
||||
hasSearch,
|
||||
search.matches.length > 0,
|
||||
() =>
|
||||
/**
|
||||
* Avoid specifying the DB column type when calling the API (which is meaningless).
|
||||
* Should specify the DB column type of enum type.
|
||||
*/
|
||||
sql`${buildConditionsFromSearch(search, searchFields)}`
|
||||
sql`${buildConditionsFromSearch(search, applicationSearchFields)}`
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
Organizations,
|
||||
Applications,
|
||||
OrganizationApplicationRelations,
|
||||
type Application,
|
||||
OrganizationRoles,
|
||||
OrganizationRoleApplicationRelations,
|
||||
type ApplicationWithOrganizationRoles,
|
||||
} from '@logto/schemas';
|
||||
import { type CommonQueryMethods, sql } from '@silverhand/slonik';
|
||||
|
||||
import { type SearchOptions, buildSearchSql } from '#src/database/utils.js';
|
||||
import { TwoRelationsQueries, type GetEntitiesOptions } from '#src/utils/RelationQueries.js';
|
||||
import { convertToIdentifiers } from '#src/utils/sql.js';
|
||||
|
||||
import { type applicationSearchKeys } from '../application.js';
|
||||
|
||||
import { aggregateRoles } from './utils.js';
|
||||
|
||||
export class ApplicationRelationQueries extends TwoRelationsQueries<
|
||||
typeof Organizations,
|
||||
typeof Applications
|
||||
> {
|
||||
constructor(pool: CommonQueryMethods) {
|
||||
super(pool, OrganizationApplicationRelations.table, Organizations, Applications);
|
||||
}
|
||||
|
||||
/** Get the applications of an organization with their organization roles. */
|
||||
async getApplicationsByOrganizationId(
|
||||
organizationId: string,
|
||||
{ limit, offset }: GetEntitiesOptions,
|
||||
search?: SearchOptions<(typeof applicationSearchKeys)[number]>
|
||||
): Promise<[totalCount: number, applications: readonly Application[]]> {
|
||||
const roles = convertToIdentifiers(OrganizationRoles, true);
|
||||
const applications = convertToIdentifiers(Applications, true);
|
||||
const { fields } = convertToIdentifiers(OrganizationApplicationRelations, true);
|
||||
const relations = convertToIdentifiers(OrganizationRoleApplicationRelations, true);
|
||||
|
||||
const [{ count }, entities] = await Promise.all([
|
||||
this.pool.one<{ count: string }>(sql`
|
||||
select count(*)
|
||||
from ${this.table}
|
||||
left join ${applications.table}
|
||||
on ${fields.applicationId} = ${applications.fields.id}
|
||||
where ${fields.organizationId} = ${organizationId}
|
||||
${buildSearchSql(Applications, search, sql`and `)}
|
||||
`),
|
||||
this.pool.any<ApplicationWithOrganizationRoles>(sql`
|
||||
select
|
||||
${sql.join(Object.values(applications.fields), sql`, `)},
|
||||
${aggregateRoles()}
|
||||
from ${this.table}
|
||||
left join ${applications.table}
|
||||
on ${fields.applicationId} = ${applications.fields.id}
|
||||
left join ${relations.table}
|
||||
on ${relations.fields.applicationId} = ${applications.fields.id}
|
||||
and ${relations.fields.organizationId} = ${fields.organizationId}
|
||||
left join ${roles.table}
|
||||
on ${relations.fields.organizationRoleId} = ${roles.fields.id}
|
||||
where ${fields.organizationId} = ${organizationId}
|
||||
${buildSearchSql(Applications, search, sql`and `)}
|
||||
group by ${applications.fields.id}
|
||||
limit ${limit}
|
||||
offset ${offset}
|
||||
`),
|
||||
]);
|
||||
|
||||
return [Number(count), entities];
|
||||
}
|
||||
}
|
|
@ -22,8 +22,6 @@ import {
|
|||
Resources,
|
||||
Users,
|
||||
OrganizationJitRoles,
|
||||
OrganizationApplicationRelations,
|
||||
Applications,
|
||||
} from '@logto/schemas';
|
||||
import { sql, type CommonQueryMethods } from '@silverhand/slonik';
|
||||
|
||||
|
@ -32,6 +30,7 @@ import { TwoRelationsQueries } from '#src/utils/RelationQueries.js';
|
|||
import SchemaQueries from '#src/utils/SchemaQueries.js';
|
||||
import { conditionalSql, convertToIdentifiers } from '#src/utils/sql.js';
|
||||
|
||||
import { ApplicationRelationQueries } from './application-relations.js';
|
||||
import { ApplicationRoleRelationQueries } from './application-role-relations.js';
|
||||
import { EmailDomainQueries } from './email-domains.js';
|
||||
import { SsoConnectorQueries } from './sso-connectors.js';
|
||||
|
@ -288,12 +287,7 @@ export default class OrganizationQueries extends SchemaQueries<
|
|||
/** Queries for organization - organization role - user relations. */
|
||||
usersRoles: new UserRoleRelationQueries(this.pool),
|
||||
/** Queries for organization - application relations. */
|
||||
apps: new TwoRelationsQueries(
|
||||
this.pool,
|
||||
OrganizationApplicationRelations.table,
|
||||
Organizations,
|
||||
Applications
|
||||
),
|
||||
apps: new ApplicationRelationQueries(this.pool),
|
||||
/** Queries for organization - organization role - application relations. */
|
||||
appsRoles: new ApplicationRoleRelationQueries(this.pool),
|
||||
invitationsRoles: new TwoRelationsQueries(
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
type OrganizationWithRoles,
|
||||
type UserWithOrganizationRoles,
|
||||
type FeaturedUser,
|
||||
userInfoSelectFields,
|
||||
} from '@logto/schemas';
|
||||
import { sql, type CommonQueryMethods } from '@silverhand/slonik';
|
||||
|
||||
|
@ -16,6 +17,8 @@ import { convertToIdentifiers } from '#src/utils/sql.js';
|
|||
|
||||
import { type userSearchKeys } from '../user.js';
|
||||
|
||||
import { aggregateRoles } from './utils.js';
|
||||
|
||||
/** The query class for the organization - user relation. */
|
||||
export class UserRelationQueries extends TwoRelationsQueries<typeof Organizations, typeof Users> {
|
||||
constructor(pool: CommonQueryMethods) {
|
||||
|
@ -81,7 +84,7 @@ export class UserRelationQueries extends TwoRelationsQueries<typeof Organization
|
|||
return this.pool.any<OrganizationWithRoles>(sql`
|
||||
select
|
||||
${expandFields(Organizations, true)},
|
||||
${this.#aggregateRoles()}
|
||||
${aggregateRoles()}
|
||||
from ${this.table}
|
||||
left join ${organizations.table}
|
||||
on ${fields.organizationId} = ${organizations.fields.id}
|
||||
|
@ -95,7 +98,7 @@ export class UserRelationQueries extends TwoRelationsQueries<typeof Organization
|
|||
`);
|
||||
}
|
||||
|
||||
/** Get the users in an organization and their roles. */
|
||||
/** Get the users in an organization with their organization roles. */
|
||||
async getUsersByOrganizationId(
|
||||
organizationId: string,
|
||||
{ limit, offset }: GetEntitiesOptions,
|
||||
|
@ -117,19 +120,22 @@ export class UserRelationQueries extends TwoRelationsQueries<typeof Organization
|
|||
`),
|
||||
this.pool.any<UserWithOrganizationRoles>(sql`
|
||||
select
|
||||
${users.table}.*,
|
||||
${this.#aggregateRoles()}
|
||||
${sql.join(
|
||||
userInfoSelectFields.map((field) => users.fields[field]),
|
||||
sql`, `
|
||||
)},
|
||||
${aggregateRoles()}
|
||||
from ${this.table}
|
||||
left join ${users.table}
|
||||
on ${fields.userId} = ${users.fields.id}
|
||||
left join ${relations.table}
|
||||
on ${fields.userId} = ${relations.fields.userId}
|
||||
on ${relations.fields.userId} = ${users.fields.id}
|
||||
and ${fields.organizationId} = ${relations.fields.organizationId}
|
||||
left join ${roles.table}
|
||||
on ${relations.fields.organizationRoleId} = ${roles.fields.id}
|
||||
where ${fields.organizationId} = ${organizationId}
|
||||
${buildSearchSql(Users, search, sql`and `)}
|
||||
group by ${users.table}.id
|
||||
group by ${users.fields.id}
|
||||
limit ${limit}
|
||||
offset ${offset}
|
||||
`),
|
||||
|
@ -137,26 +143,4 @@ export class UserRelationQueries extends TwoRelationsQueries<typeof Organization
|
|||
|
||||
return [Number(count), entities];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SQL for aggregating the organization roles with basic information (id and name)
|
||||
* into a JSON array.
|
||||
*
|
||||
* @param as The alias of the aggregated roles. Defaults to `organizationRoles`.
|
||||
*/
|
||||
#aggregateRoles(as = 'organizationRoles') {
|
||||
const roles = convertToIdentifiers(OrganizationRoles, true);
|
||||
|
||||
return sql`
|
||||
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), -- left join could produce nulls as roles
|
||||
'[]'
|
||||
) as ${sql.identifier([as])}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
26
packages/core/src/queries/organization/utils.ts
Normal file
26
packages/core/src/queries/organization/utils.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { OrganizationRoles } from '@logto/schemas';
|
||||
import { sql } from '@silverhand/slonik';
|
||||
|
||||
import { convertToIdentifiers } from '#src/utils/sql.js';
|
||||
|
||||
/**
|
||||
* Build the SQL for aggregating the organization roles with basic information (id and name)
|
||||
* into a JSON array.
|
||||
*
|
||||
* @param as The alias of the aggregated roles. Defaults to `organizationRoles`.
|
||||
*/
|
||||
export const aggregateRoles = (as = 'organizationRoles') => {
|
||||
const roles = convertToIdentifiers(OrganizationRoles, true);
|
||||
|
||||
return sql`
|
||||
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), -- left join could produce nulls as roles
|
||||
'[]'
|
||||
) as ${sql.identifier([as])}
|
||||
`;
|
||||
};
|
|
@ -51,7 +51,7 @@ export const userSearchKeys = Object.freeze([
|
|||
'primaryPhone',
|
||||
'username',
|
||||
'name',
|
||||
] as const);
|
||||
] satisfies Array<keyof User>);
|
||||
|
||||
/**
|
||||
* The actual database field names that can be used for searching users. For the schema field
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
import { type OrganizationKeys, type CreateOrganization, type Organization } from '@logto/schemas';
|
||||
import {
|
||||
type OrganizationKeys,
|
||||
type CreateOrganization,
|
||||
type Organization,
|
||||
applicationWithOrganizationRolesGuard,
|
||||
} from '@logto/schemas';
|
||||
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 { applicationSearchKeys } from '#src/queries/application.js';
|
||||
import type OrganizationQueries from '#src/queries/organization/index.js';
|
||||
import type SchemaRouter from '#src/utils/SchemaRouter.js';
|
||||
import { parseSearchOptions } from '#src/utils/search.js';
|
||||
|
||||
import applicationRoleRelationRoutes from './role-relations.js';
|
||||
|
||||
|
@ -14,9 +24,36 @@ export default function applicationRoutes(
|
|||
if (EnvSet.values.isDevFeaturesEnabled) {
|
||||
// MARK: Organization - application relation routes
|
||||
router.addRelationRoutes(organizations.relations.apps, undefined, {
|
||||
disabled: { get: true },
|
||||
hookEvent: 'Organization.Membership.Updated',
|
||||
});
|
||||
|
||||
router.get(
|
||||
'/:id/applications',
|
||||
koaPagination(),
|
||||
koaGuard({
|
||||
query: z.object({ q: z.string().optional() }),
|
||||
params: z.object({ id: z.string().min(1) }),
|
||||
response: applicationWithOrganizationRolesGuard.array(),
|
||||
status: [200, 404],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const search = parseSearchOptions(applicationSearchKeys, ctx.guard.query);
|
||||
|
||||
const [totalCount, entities] =
|
||||
await organizations.relations.apps.getApplicationsByOrganizationId(
|
||||
ctx.guard.params.id,
|
||||
ctx.pagination,
|
||||
search
|
||||
);
|
||||
|
||||
ctx.pagination.totalCount = totalCount;
|
||||
ctx.body = entities;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
// MARK: Organization - application role relation routes
|
||||
applicationRoleRelationRoutes(router, organizations);
|
||||
}
|
||||
|
|
|
@ -182,7 +182,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/organizations/{id}/users/{userId}/roles/{roleId}": {
|
||||
"/api/organizations/{id}/users/{userId}/roles/{organizationRoleId}": {
|
||||
"delete": {
|
||||
"summary": "Remove a role from a user in an organization",
|
||||
"description": "Remove a role assignment from a user in the specified organization.",
|
||||
|
|
|
@ -106,17 +106,17 @@ export default function userRoleRelationRoutes(
|
|||
);
|
||||
|
||||
router.delete(
|
||||
`${pathname}/:roleId`,
|
||||
`${pathname}/:organizationRoleId`,
|
||||
koaGuard({
|
||||
params: z.object({ ...params, roleId: z.string().min(1) }),
|
||||
params: z.object({ ...params, organizationRoleId: z.string().min(1) }),
|
||||
status: [204, 422, 404],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id, roleId, userId } = ctx.guard.params;
|
||||
const { id, organizationRoleId, userId } = ctx.guard.params;
|
||||
|
||||
await organizations.relations.usersRoles.delete({
|
||||
organizationId: id,
|
||||
organizationRoleId: roleId,
|
||||
organizationRoleId,
|
||||
userId,
|
||||
});
|
||||
|
||||
|
|
|
@ -86,8 +86,13 @@ export class RelationApiFactory<RelationSchema extends Record<string, unknown>>
|
|||
return this.config.relationKey;
|
||||
}
|
||||
|
||||
async getList(id: string, page?: number, pageSize?: number): Promise<RelationSchema[]> {
|
||||
const searchParams = new URLSearchParams();
|
||||
async getList(
|
||||
id: string,
|
||||
page?: number,
|
||||
pageSize?: number,
|
||||
extraParams?: ConstructorParameters<typeof URLSearchParams>[0]
|
||||
): Promise<RelationSchema[]> {
|
||||
const searchParams = new URLSearchParams(extraParams);
|
||||
|
||||
if (page) {
|
||||
searchParams.append('page', String(page));
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import assert from 'node:assert';
|
||||
|
||||
import { ApplicationType, type Application } from '@logto/schemas';
|
||||
import {
|
||||
ApplicationType,
|
||||
type ApplicationWithOrganizationRoles,
|
||||
type Application,
|
||||
} from '@logto/schemas';
|
||||
import { HTTPError } from 'ky';
|
||||
|
||||
import {
|
||||
|
@ -12,6 +16,82 @@ import { devFeatureTest, generateTestName } from '#src/utils.js';
|
|||
|
||||
// TODO: Remove this prefix
|
||||
devFeatureTest.describe('organization application APIs', () => {
|
||||
describe('organization get applications', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const applications: Application[] = [];
|
||||
const createApplication = async (...args: Parameters<typeof createApplicationApi>) => {
|
||||
const created = await createApplicationApi(...args);
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
||||
applications.push(created);
|
||||
return created;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const createdApplications = await Promise.all(
|
||||
Array.from({ length: 30 }).map(async () =>
|
||||
createApplication(generateTestName(), ApplicationType.MachineToMachine)
|
||||
)
|
||||
);
|
||||
await organizationApi.applications.add(
|
||||
organization.id,
|
||||
createdApplications.map(({ id }) => id)
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all([
|
||||
organizationApi.cleanUp(),
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
...applications.map(async ({ id }) => deleteApplication(id).catch(() => {})),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to get organization applications with pagination', async () => {
|
||||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const fetchedApps1 = await organizationApi.applications.getList(organizationId, 1, 20);
|
||||
const fetchedApps2 = await organizationApi.applications.getList(organizationId, 2, 10);
|
||||
expect(fetchedApps2.length).toBe(10);
|
||||
expect(fetchedApps2[0]?.id).not.toBeFalsy();
|
||||
expect(fetchedApps2[0]?.id).toBe(fetchedApps1[10]?.id);
|
||||
});
|
||||
|
||||
it('should be able to get organization applications with search', async () => {
|
||||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const fetchedApps = await organizationApi.applications.getList(organizationId, 1, 20, {
|
||||
q: applications[0]!.name,
|
||||
});
|
||||
expect(fetchedApps.length).toBe(1);
|
||||
expect(fetchedApps[0]!.id).toBe(applications[0]!.id);
|
||||
expect(fetchedApps[0]).toMatchObject(applications[0]!);
|
||||
});
|
||||
|
||||
it('should be able to get organization applications with their roles', async () => {
|
||||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const app = applications[0]!;
|
||||
const roles = await Promise.all([
|
||||
organizationApi.roleApi.create({ name: generateTestName() }),
|
||||
organizationApi.roleApi.create({ name: generateTestName() }),
|
||||
]);
|
||||
const roleIds = roles.map(({ id }) => id);
|
||||
await organizationApi.addApplicationRoles(organizationId, app.id, roleIds);
|
||||
|
||||
const [fetchedApp] = await organizationApi.applications.getList(
|
||||
organizationId,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
q: app.name,
|
||||
}
|
||||
);
|
||||
expect(fetchedApp).toMatchObject(app);
|
||||
expect((fetchedApp as ApplicationWithOrganizationRoles).organizationRoles).toHaveLength(2);
|
||||
expect((fetchedApp as ApplicationWithOrganizationRoles).organizationRoles).toEqual(
|
||||
expect.arrayContaining(roles.map(({ id }) => expect.objectContaining({ id })))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organization - application relations', () => {
|
||||
const organizationApi = new OrganizationApiTest();
|
||||
const applications: Application[] = [];
|
||||
|
@ -57,14 +137,16 @@ devFeatureTest.describe('organization application APIs', () => {
|
|||
);
|
||||
|
||||
await organizationApi.applications.add(organization.id, [application.id]);
|
||||
expect(await organizationApi.applications.getList(organization.id)).toContainEqual(
|
||||
application
|
||||
);
|
||||
expect(await organizationApi.applications.getList(organization.id)).toContainEqual({
|
||||
...application,
|
||||
organizationRoles: [],
|
||||
});
|
||||
|
||||
await organizationApi.applications.delete(organization.id, application.id);
|
||||
expect(await organizationApi.applications.getList(organization.id)).not.toContainEqual(
|
||||
application
|
||||
);
|
||||
expect(await organizationApi.applications.getList(organization.id)).not.toContainEqual({
|
||||
...application,
|
||||
organizationRoles: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when try to delete application from an organization that does not exist', async () => {
|
||||
|
|
|
@ -36,14 +36,14 @@ describe('organization user APIs', () => {
|
|||
page: 2,
|
||||
page_size: 10,
|
||||
});
|
||||
expect(users2.length).toBeGreaterThanOrEqual(10);
|
||||
expect(users2.length).toBe(10);
|
||||
expect(users2[0]?.id).not.toBeFalsy();
|
||||
expect(users2[0]?.id).toBe(users1[10]?.id);
|
||||
expect(total1).toBe(30);
|
||||
expect(total2).toBe(30);
|
||||
});
|
||||
|
||||
it('should be able to get organization users with search keyword', async () => {
|
||||
it('should be able to get organization users with search', async () => {
|
||||
const organizationId = organizationApi.organizations[0]!.id;
|
||||
const username = generateTestName();
|
||||
const createdUser = await userApi.create({ username });
|
||||
|
@ -73,11 +73,8 @@ describe('organization user APIs', () => {
|
|||
expect(usersWithRoles).toHaveLength(1);
|
||||
expect(usersWithRoles[0]).toMatchObject(user);
|
||||
expect(usersWithRoles[0]!.organizationRoles).toHaveLength(2);
|
||||
expect(usersWithRoles[0]!.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: roles[0].id })
|
||||
);
|
||||
expect(usersWithRoles[0]!.organizationRoles).toContainEqual(
|
||||
expect.objectContaining({ id: roles[1].id })
|
||||
expect(usersWithRoles[0]!.organizationRoles).toEqual(
|
||||
expect.arrayContaining(roles.map(({ id }) => expect.objectContaining({ id })))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
Organizations,
|
||||
type OrganizationInvitation,
|
||||
OrganizationInvitations,
|
||||
type Application,
|
||||
Applications,
|
||||
} from '../db-entries/index.js';
|
||||
import { type ToZodObject } from '../utils/zod.js';
|
||||
|
||||
|
@ -87,10 +89,10 @@ export const organizationWithOrganizationRolesGuard: ToZodObject<OrganizationWit
|
|||
|
||||
/**
|
||||
* The user entity with the `organizationRoles` field that contains the roles of
|
||||
* the user in a specific organization.
|
||||
* the user in the organization.
|
||||
*/
|
||||
export type UserWithOrganizationRoles = UserInfo & {
|
||||
/** The roles of the user in a specific organization. */
|
||||
/** The roles of the user in the organization. */
|
||||
organizationRoles: OrganizationRoleEntity[];
|
||||
};
|
||||
|
||||
|
@ -108,6 +110,20 @@ export type OrganizationWithFeatured = Organization & {
|
|||
featuredUsers?: FeaturedUser[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The application entity with the `organizationRoles` field that contains the roles
|
||||
* of the application in the organization.
|
||||
*/
|
||||
export type ApplicationWithOrganizationRoles = Application & {
|
||||
/** The roles of the application in the organization. */
|
||||
organizationRoles: OrganizationRoleEntity[];
|
||||
};
|
||||
|
||||
export const applicationWithOrganizationRolesGuard: ToZodObject<ApplicationWithOrganizationRoles> =
|
||||
Applications.guard.extend({
|
||||
organizationRoles: organizationRoleEntityGuard.array(),
|
||||
});
|
||||
|
||||
/**
|
||||
* The organization invitation with additional fields:
|
||||
*
|
||||
|
|
Loading…
Add table
Reference in a new issue