mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(core): organization - user relation apis
This commit is contained in:
parent
eae1c5849e
commit
e6d3db6e80
6 changed files with 132 additions and 94 deletions
|
@ -7,6 +7,7 @@ import {
|
|||
OrganizationScopes,
|
||||
OrganizationRoleScopeRelations,
|
||||
Users,
|
||||
OrganizationUserRelations,
|
||||
} from '@logto/schemas';
|
||||
import { type CommonQueryMethods } from 'slonik';
|
||||
|
||||
|
@ -33,7 +34,7 @@ export default class OrganizationQueries extends SchemaQueries<
|
|||
OrganizationScopes
|
||||
),
|
||||
/** Queries for organization - user relations. */
|
||||
users: new RelationQueries(this.pool, 'organization_user_relations', Organizations, Users),
|
||||
users: new RelationQueries(this.pool, OrganizationUserRelations.table, Organizations, Users),
|
||||
};
|
||||
|
||||
constructor(pool: CommonQueryMethods) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Organizations } from '@logto/schemas';
|
||||
import { Organizations, Users } from '@logto/schemas';
|
||||
|
||||
import SchemaRouter, { SchemaActions } from '#src/utils/SchemaRouter.js';
|
||||
|
||||
|
@ -14,5 +14,7 @@ export default function organizationRoutes<T extends AuthedRouter>(
|
|||
) {
|
||||
const router = new SchemaRouter(Organizations, new SchemaActions(organizations));
|
||||
|
||||
router.addRelationRoutes(Users, organizations.relations.users);
|
||||
|
||||
originalRouter.use(router.routes());
|
||||
}
|
||||
|
|
|
@ -289,6 +289,7 @@ export default class SchemaRouter<
|
|||
relationSchemaIds: camelCaseSchemaId(relationSchema) + 's',
|
||||
};
|
||||
|
||||
// TODO: Add pagination support
|
||||
this.get(
|
||||
`/:id/${pathname}`,
|
||||
koaGuard({
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
import { type Organization } from '@logto/schemas';
|
||||
|
||||
import { authedAdminApi } from './api.js';
|
||||
import { ApiFactory } from './factory.js';
|
||||
|
||||
class OrganizationApi extends ApiFactory<Organization, { name: string; description?: string }> {
|
||||
constructor() {
|
||||
super('organizations');
|
||||
}
|
||||
|
||||
async addUsers(id: string, userIds: string[]): Promise<void> {
|
||||
await authedAdminApi.post(`${this.path}/${id}/users`, { json: { userIds } });
|
||||
}
|
||||
|
||||
async getUsers(id: string): Promise<Organization[]> {
|
||||
return authedAdminApi.get(`${this.path}/${id}/users`).json<Organization[]>();
|
||||
}
|
||||
|
||||
async deleteUser(id: string, userId: string): Promise<void> {
|
||||
await authedAdminApi.delete(`${this.path}/${id}/users/${userId}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** API methods for operating organizations. */
|
||||
|
|
|
@ -141,34 +141,7 @@ describe('organization role APIs', () => {
|
|||
});
|
||||
|
||||
describe('organization role - scope relations', () => {
|
||||
it('should be able to get scopes of a role', async () => {
|
||||
const [role, scope1, scope2] = await Promise.all([
|
||||
roleApi.create({ name: 'test' + randomId() }),
|
||||
scopeApi.create({ name: 'test' + randomId() }),
|
||||
scopeApi.create({ name: 'test' + randomId() }),
|
||||
]);
|
||||
await roleApi.addScopes(role.id, [scope1.id, scope2.id]);
|
||||
const scopes = await roleApi.getScopes(role.id);
|
||||
|
||||
expect(scopes).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: scope1.name,
|
||||
})
|
||||
);
|
||||
expect(scopes).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: scope2.name,
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
roleApi.delete(role.id),
|
||||
scopeApi.delete(scope1.id),
|
||||
scopeApi.delete(scope2.id),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to add scopes to a role', async () => {
|
||||
it('should be able to add and get scopes of a role', async () => {
|
||||
const [role, scope1, scope2] = await Promise.all([
|
||||
roleApi.create({ name: 'test' + randomId() }),
|
||||
scopeApi.create({ name: 'test' + randomId() }),
|
||||
|
|
|
@ -1,78 +1,126 @@
|
|||
import { generateStandardId } from '@logto/shared';
|
||||
import { HTTPError } from 'got';
|
||||
|
||||
import { createUser, deleteUser } from '#src/api/admin-user.js';
|
||||
import { organizationApi } from '#src/api/organization.js';
|
||||
|
||||
describe('organizations', () => {
|
||||
it('should get organizations successfully', async () => {
|
||||
await organizationApi.create({ name: 'test', description: 'A test organization.' });
|
||||
await organizationApi.create({ name: 'test2' });
|
||||
const organizations = await organizationApi.getList();
|
||||
const randomId = () => generateStandardId(4);
|
||||
|
||||
expect(organizations).toContainEqual(
|
||||
expect.objectContaining({ name: 'test', description: 'A test organization.' })
|
||||
);
|
||||
expect(organizations).toContainEqual(
|
||||
expect.objectContaining({ name: 'test2', description: null })
|
||||
);
|
||||
});
|
||||
// Add additional layer of describe to run tests in band
|
||||
describe('organization APIs', () => {
|
||||
describe('organizations', () => {
|
||||
it('should get organizations successfully', async () => {
|
||||
await organizationApi.create({ name: 'test', description: 'A test organization.' });
|
||||
await organizationApi.create({ name: 'test2' });
|
||||
const organizations = await organizationApi.getList();
|
||||
|
||||
it('should get organizations with pagination', async () => {
|
||||
// Add 20 organizations to exceed the default page size
|
||||
await Promise.all(
|
||||
Array.from({ length: 30 }).map(async () => organizationApi.create({ name: 'test' }))
|
||||
);
|
||||
expect(organizations).toContainEqual(
|
||||
expect.objectContaining({ name: 'test', description: 'A test organization.' })
|
||||
);
|
||||
expect(organizations).toContainEqual(
|
||||
expect.objectContaining({ name: 'test2', description: null })
|
||||
);
|
||||
|
||||
const organizations = await organizationApi.getList();
|
||||
expect(organizations).toHaveLength(20);
|
||||
|
||||
const organizations2 = await organizationApi.getList(
|
||||
new URLSearchParams({
|
||||
page: '2',
|
||||
page_size: '10',
|
||||
})
|
||||
);
|
||||
expect(organizations2.length).toBeGreaterThanOrEqual(10);
|
||||
expect(organizations2[0]?.id).not.toBeFalsy();
|
||||
expect(organizations2[0]?.id).toBe(organizations[10]?.id);
|
||||
});
|
||||
|
||||
it('should be able to create and get organizations by id', async () => {
|
||||
const createdOrganization = await organizationApi.create({ name: 'test' });
|
||||
const organization = await organizationApi.get(createdOrganization.id);
|
||||
|
||||
expect(organization).toStrictEqual(createdOrganization);
|
||||
});
|
||||
|
||||
it('should fail when try to get an organization that does not exist', async () => {
|
||||
const response = await organizationApi.get('0').catch((error: unknown) => error);
|
||||
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it('should be able to update organization', async () => {
|
||||
const createdOrganization = await organizationApi.create({ name: 'test' });
|
||||
const organization = await organizationApi.update(createdOrganization.id, {
|
||||
name: 'test2',
|
||||
description: 'test description.',
|
||||
await Promise.all(
|
||||
organizations.map(async (organization) => organizationApi.delete(organization.id))
|
||||
);
|
||||
});
|
||||
expect(organization).toStrictEqual({
|
||||
...createdOrganization,
|
||||
name: 'test2',
|
||||
description: 'test description.',
|
||||
|
||||
it('should get organizations with pagination', async () => {
|
||||
// Add organizations to exceed the default page size
|
||||
const allOrganizations = await Promise.all(
|
||||
Array.from({ length: 30 }).map(async () => organizationApi.create({ name: 'test' }))
|
||||
);
|
||||
|
||||
const organizations = await organizationApi.getList();
|
||||
expect(organizations).toHaveLength(20);
|
||||
|
||||
const organizations2 = await organizationApi.getList(
|
||||
new URLSearchParams({
|
||||
page: '2',
|
||||
page_size: '10',
|
||||
})
|
||||
);
|
||||
expect(organizations2.length).toBeGreaterThanOrEqual(10);
|
||||
expect(organizations2[0]?.id).not.toBeFalsy();
|
||||
expect(organizations2[0]?.id).toBe(organizations[10]?.id);
|
||||
|
||||
await Promise.all(
|
||||
allOrganizations.map(async (organization) => organizationApi.delete(organization.id))
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to create and get organizations by id', async () => {
|
||||
const createdOrganization = await organizationApi.create({ name: 'test' });
|
||||
const organization = await organizationApi.get(createdOrganization.id);
|
||||
|
||||
expect(organization).toStrictEqual(createdOrganization);
|
||||
await organizationApi.delete(createdOrganization.id);
|
||||
});
|
||||
|
||||
it('should fail when try to get an organization that does not exist', async () => {
|
||||
const response = await organizationApi.get('0').catch((error: unknown) => error);
|
||||
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it('should be able to update organization', async () => {
|
||||
const createdOrganization = await organizationApi.create({ name: 'test' });
|
||||
const organization = await organizationApi.update(createdOrganization.id, {
|
||||
name: 'test2',
|
||||
description: 'test description.',
|
||||
});
|
||||
expect(organization).toStrictEqual({
|
||||
...createdOrganization,
|
||||
name: 'test2',
|
||||
description: 'test description.',
|
||||
});
|
||||
await organizationApi.delete(createdOrganization.id);
|
||||
});
|
||||
|
||||
it('should be able to delete organization', async () => {
|
||||
const createdOrganization = await organizationApi.create({ name: 'test' });
|
||||
await organizationApi.delete(createdOrganization.id);
|
||||
const response = await organizationApi
|
||||
.get(createdOrganization.id)
|
||||
.catch((error: unknown) => error);
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it('should fail when try to delete an organization that does not exist', async () => {
|
||||
const response = await organizationApi.delete('0').catch((error: unknown) => error);
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to delete organization', async () => {
|
||||
const createdOrganization = await organizationApi.create({ name: 'test' });
|
||||
await organizationApi.delete(createdOrganization.id);
|
||||
const response = await organizationApi
|
||||
.get(createdOrganization.id)
|
||||
.catch((error: unknown) => error);
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
|
||||
});
|
||||
describe('organization - user relations', () => {
|
||||
it('should be able to add and get organization users', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ username: 'test' + randomId() }),
|
||||
createUser({ username: 'test' + randomId() }),
|
||||
]);
|
||||
|
||||
it('should fail when try to delete an organization that does not exist', async () => {
|
||||
const response = await organizationApi.delete('0').catch((error: unknown) => error);
|
||||
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
|
||||
await organizationApi.addUsers(organization.id, [user1.id, user2.id]);
|
||||
const users = await organizationApi.getUsers(organization.id);
|
||||
expect(users).toContainEqual(expect.objectContaining({ id: user1.id }));
|
||||
expect(users).toContainEqual(expect.objectContaining({ id: user2.id }));
|
||||
await Promise.all([
|
||||
organizationApi.delete(organization.id),
|
||||
deleteUser(user1.id),
|
||||
deleteUser(user2.id),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to delete organization user', async () => {
|
||||
const organization = await organizationApi.create({ name: 'test' });
|
||||
const user = await createUser({ username: 'test' + randomId() });
|
||||
|
||||
await organizationApi.addUsers(organization.id, [user.id]);
|
||||
await organizationApi.deleteUser(organization.id, user.id);
|
||||
const users = await organizationApi.getUsers(organization.id);
|
||||
expect(users).not.toContainEqual(user);
|
||||
await Promise.all([organizationApi.delete(organization.id), deleteUser(user.id)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue