0
Fork 0
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:
Gao Sun 2023-10-14 20:06:14 +08:00
parent eae1c5849e
commit e6d3db6e80
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
6 changed files with 132 additions and 94 deletions

View file

@ -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) {

View file

@ -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());
}

View file

@ -289,6 +289,7 @@ export default class SchemaRouter<
relationSchemaIds: camelCaseSchemaId(relationSchema) + 's',
};
// TODO: Add pagination support
this.get(
`/:id/${pathname}`,
koaGuard({

View file

@ -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. */

View file

@ -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() }),

View file

@ -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)]);
});
});
});