From d3937413661715f71e5966a6646e3de16955e1a0 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Sun, 15 Oct 2023 13:54:58 +0800 Subject: [PATCH] feat(core): organization - user - organization role apis --- packages/core/src/queries/organizations.ts | 9 +++ packages/core/src/routes/organizations.ts | 79 ++++++++++++++++++- packages/core/src/utils/RelationQueries.ts | 34 +++++++- packages/core/src/utils/SchemaRouter.ts | 6 +- .../integration-tests/src/api/organization.ts | 14 +++- .../src/tests/api/organization.test.ts | 36 +++++++++ .../phrases/src/locales/de/errors/index.ts | 2 + .../src/locales/de/errors/organization.ts | 6 ++ .../phrases/src/locales/en/errors/index.ts | 2 + .../src/locales/en/errors/organization.ts | 5 ++ .../phrases/src/locales/es/errors/index.ts | 2 + .../src/locales/es/errors/organization.ts | 6 ++ .../phrases/src/locales/fr/errors/index.ts | 2 + .../src/locales/fr/errors/organization.ts | 6 ++ .../phrases/src/locales/it/errors/index.ts | 2 + .../src/locales/it/errors/organization.ts | 6 ++ .../phrases/src/locales/ja/errors/index.ts | 2 + .../src/locales/ja/errors/organization.ts | 6 ++ .../phrases/src/locales/ko/errors/index.ts | 2 + .../src/locales/ko/errors/organization.ts | 6 ++ .../phrases/src/locales/pl-pl/errors/index.ts | 2 + .../src/locales/pl-pl/errors/organization.ts | 6 ++ .../phrases/src/locales/pt-br/errors/index.ts | 2 + .../src/locales/pt-br/errors/organization.ts | 6 ++ .../phrases/src/locales/pt-pt/errors/index.ts | 2 + .../src/locales/pt-pt/errors/organization.ts | 6 ++ .../phrases/src/locales/ru/errors/index.ts | 2 + .../src/locales/ru/errors/organization.ts | 6 ++ .../phrases/src/locales/tr-tr/errors/index.ts | 2 + .../src/locales/tr-tr/errors/organization.ts | 6 ++ .../phrases/src/locales/zh-cn/errors/index.ts | 2 + .../src/locales/zh-cn/errors/organization.ts | 6 ++ .../phrases/src/locales/zh-hk/errors/index.ts | 2 + .../src/locales/zh-hk/errors/organization.ts | 6 ++ .../phrases/src/locales/zh-tw/errors/index.ts | 2 + .../src/locales/zh-tw/errors/organization.ts | 6 ++ 36 files changed, 287 insertions(+), 10 deletions(-) create mode 100644 packages/phrases/src/locales/de/errors/organization.ts create mode 100644 packages/phrases/src/locales/en/errors/organization.ts create mode 100644 packages/phrases/src/locales/es/errors/organization.ts create mode 100644 packages/phrases/src/locales/fr/errors/organization.ts create mode 100644 packages/phrases/src/locales/it/errors/organization.ts create mode 100644 packages/phrases/src/locales/ja/errors/organization.ts create mode 100644 packages/phrases/src/locales/ko/errors/organization.ts create mode 100644 packages/phrases/src/locales/pl-pl/errors/organization.ts create mode 100644 packages/phrases/src/locales/pt-br/errors/organization.ts create mode 100644 packages/phrases/src/locales/pt-pt/errors/organization.ts create mode 100644 packages/phrases/src/locales/ru/errors/organization.ts create mode 100644 packages/phrases/src/locales/tr-tr/errors/organization.ts create mode 100644 packages/phrases/src/locales/zh-cn/errors/organization.ts create mode 100644 packages/phrases/src/locales/zh-hk/errors/organization.ts create mode 100644 packages/phrases/src/locales/zh-tw/errors/organization.ts diff --git a/packages/core/src/queries/organizations.ts b/packages/core/src/queries/organizations.ts index 601ba3fa6..30ad9db0a 100644 --- a/packages/core/src/queries/organizations.ts +++ b/packages/core/src/queries/organizations.ts @@ -8,6 +8,7 @@ import { OrganizationRoleScopeRelations, Users, OrganizationUserRelations, + OrganizationRoleUserRelations, } from '@logto/schemas'; import { type CommonQueryMethods } from 'slonik'; @@ -35,6 +36,14 @@ export default class OrganizationQueries extends SchemaQueries< ), /** Queries for organization - user relations. */ users: new RelationQueries(this.pool, OrganizationUserRelations.table, Organizations, Users), + /** Queries for organization - organization role - user relations. */ + rolesUsers: new RelationQueries( + this.pool, + OrganizationRoleUserRelations.table, + Organizations, + OrganizationRoles, + Users + ), }; constructor(pool: CommonQueryMethods) { diff --git a/packages/core/src/routes/organizations.ts b/packages/core/src/routes/organizations.ts index 4ac17f014..e7ba28823 100644 --- a/packages/core/src/routes/organizations.ts +++ b/packages/core/src/routes/organizations.ts @@ -1,5 +1,8 @@ -import { Organizations } from '@logto/schemas'; +import { OrganizationRoles, Organizations } from '@logto/schemas'; +import { z } from 'zod'; +import RequestError from '#src/errors/RequestError/index.js'; +import koaGuard from '#src/middleware/koa-guard.js'; import SchemaRouter, { SchemaActions } from '#src/utils/SchemaRouter.js'; import { type AuthedRouter, type RouterInitArgs } from './types.js'; @@ -8,7 +11,7 @@ export default function organizationRoutes( ...[ originalRouter, { - queries: { organizations }, + queries: { organizations, users }, }, ]: RouterInitArgs ) { @@ -16,5 +19,77 @@ export default function organizationRoutes( router.addRelationRoutes(organizations.relations.users); + // Manually add these routes since I don't want to over-engineer the `SchemaRouter` + // MARK: Organization - user - organization role relation routes + const params = Object.freeze({ id: z.string().min(1), userId: z.string().min(1) } as const); + const pathname = '/:id/users/:userId/roles'; + + router.get( + pathname, + koaGuard({ + params: z.object(params), + response: OrganizationRoles.guard.array(), + status: [200, 404], + }), + // TODO: Add pagination + async (ctx, next) => { + const { id, userId } = ctx.guard.params; + + // Ensure both the organization and the role exist + await Promise.all([organizations.findById(id), users.findUserById(userId)]); + + ctx.body = await organizations.relations.rolesUsers.getEntries(OrganizationRoles, { + organizationId: id, + userId, + }); + return next(); + } + ); + + router.post( + pathname, + koaGuard({ + params: z.object(params), + body: z.object({ roleIds: z.string().min(1).array().nonempty() }), + status: [201, 404, 422], + }), + async (ctx, next) => { + const { id, userId } = ctx.guard.params; + const { roleIds } = ctx.guard.body; + + // Ensure membership + if (!(await organizations.relations.users.exists(id, userId))) { + throw new RequestError({ code: 'organization.require_membership', status: 422 }); + } + + await organizations.relations.rolesUsers.insert( + ...roleIds.map<[string, string, string]>((roleId) => [id, roleId, userId]) + ); + + ctx.status = 201; + return next(); + } + ); + + router.delete( + `${pathname}/:roleId`, + koaGuard({ + params: z.object({ ...params, roleId: z.string().min(1) }), + status: [204, 404], + }), + async (ctx, next) => { + const { id, roleId, userId } = ctx.guard.params; + + await organizations.relations.rolesUsers.delete({ + organizationId: id, + organizationRoleId: roleId, + userId, + }); + + ctx.status = 204; + return next(); + } + ); + originalRouter.use(router.routes()); } diff --git a/packages/core/src/utils/RelationQueries.ts b/packages/core/src/utils/RelationQueries.ts index 36f248ef0..b2dfa1232 100644 --- a/packages/core/src/utils/RelationQueries.ts +++ b/packages/core/src/utils/RelationQueries.ts @@ -80,6 +80,9 @@ export default class RelationQueries< * Each entry must contain the same number of ids as the number of relations, and * the order of the ids must match the order of the relations. * + * @param data Entries to insert. + * @returns A Promise that resolves to the query result. + * * @example * ```ts * const userGroupRelations = new RelationQueries(pool, 'user_group_relations', Users, Groups); @@ -91,9 +94,6 @@ export default class RelationQueries< * ['user-id-2', 'group-id-1'] * ); * ``` - * - * @param data Entries to insert. - * @returns A Promise that resolves to the query result. */ async insert(...data: ReadonlyArray) { return this.pool.query(sql` @@ -179,4 +179,32 @@ export default class RelationQueries< return rows; } + + /** + * Check if a relation exists. + * + * @param ids The ids of the entries to check. The order of the ids must match the order of the relations. + * @returns A Promise that resolves to `true` if the relation exists, otherwise `false`. + * + * @example + * ```ts + * const userGroupRelations = new RelationQueries(pool, 'user_group_relations', Users, Groups); + * + * userGroupRelations.exists('user-id-1', 'group-id-1'); + * ``` + */ + async exists(...ids: readonly string[] & { length: Length }) { + return this.pool.exists(sql` + select + from ${this.table} + where ${sql.join( + this.schemas.map( + ({ tableSingular }, index) => + sql`${sql.identifier([tableSingular + '_id'])} = ${ids[index] ?? sql`null`}` + ), + sql` and ` + )} + limit 1 + `); + } } diff --git a/packages/core/src/utils/SchemaRouter.ts b/packages/core/src/utils/SchemaRouter.ts index 760d78cbf..ca1c63a75 100644 --- a/packages/core/src/utils/SchemaRouter.ts +++ b/packages/core/src/utils/SchemaRouter.ts @@ -315,8 +315,7 @@ export default class SchemaRouter< koaGuard({ params: z.object({ id: z.string().min(1) }), body: z.object({ [columns.relationSchemaIds]: z.string().min(1).array().nonempty() }), - response: relationSchema.guard.array(), - status: [200, 404, 422], + status: [201, 404, 422], }), async (ctx, next) => { const { @@ -327,8 +326,7 @@ export default class SchemaRouter< await relationQueries.insert( ...(relationIds?.map<[string, string]>((relationId) => [id, relationId]) ?? []) ); - - ctx.body = await relationQueries.getEntries(relationSchema, { [columns.schemaId]: id }); + ctx.status = 201; return next(); } ); diff --git a/packages/integration-tests/src/api/organization.ts b/packages/integration-tests/src/api/organization.ts index fceaeeaa4..d71a4cd55 100644 --- a/packages/integration-tests/src/api/organization.ts +++ b/packages/integration-tests/src/api/organization.ts @@ -1,4 +1,4 @@ -import { type Organization } from '@logto/schemas'; +import { type Role, type Organization } from '@logto/schemas'; import { authedAdminApi } from './api.js'; import { ApiFactory } from './factory.js'; @@ -19,6 +19,18 @@ class OrganizationApi extends ApiFactory { await authedAdminApi.delete(`${this.path}/${id}/users/${userId}`); } + + async addUserRoles(id: string, userId: string, roleIds: string[]): Promise { + await authedAdminApi.post(`${this.path}/${id}/users/${userId}/roles`, { json: { roleIds } }); + } + + async getUserRoles(id: string, userId: string): Promise { + return authedAdminApi.get(`${this.path}/${id}/users/${userId}/roles`).json(); + } + + async deleteUserRole(id: string, userId: string, roleId: string): Promise { + await authedAdminApi.delete(`${this.path}/${id}/users/${userId}/roles/${roleId}`); + } } /** API methods for operating organizations. */ diff --git a/packages/integration-tests/src/tests/api/organization.test.ts b/packages/integration-tests/src/tests/api/organization.test.ts index d6b45e3f3..608469f33 100644 --- a/packages/integration-tests/src/tests/api/organization.test.ts +++ b/packages/integration-tests/src/tests/api/organization.test.ts @@ -1,7 +1,10 @@ +import assert from 'node:assert'; + import { generateStandardId } from '@logto/shared'; import { HTTPError } from 'got'; import { createUser, deleteUser } from '#src/api/admin-user.js'; +import { roleApi } from '#src/api/organization-role.js'; import { organizationApi } from '#src/api/organization.js'; const randomId = () => generateStandardId(4); @@ -123,4 +126,37 @@ describe('organization APIs', () => { await Promise.all([organizationApi.delete(organization.id), deleteUser(user.id)]); }); }); + + describe('organization - user - organization role relation routes', () => { + it("should be able to add and get user's organization roles", async () => { + const organization = await organizationApi.create({ name: 'test' }); + const user = await createUser({ username: 'test' + randomId() }); + const [role1, role2] = await Promise.all([ + roleApi.create({ name: 'test' + randomId() }), + roleApi.create({ name: 'test' + randomId() }), + ]); + + const response = await organizationApi + .addUserRoles(organization.id, user.id, [role1.id, role2.id]) + .catch((error: unknown) => error); + + assert(response instanceof HTTPError); + expect(response.response.statusCode).toBe(422); + expect(JSON.parse(String(response.response.body))).toMatchObject( + expect.objectContaining({ code: 'organization.require_membership' }) + ); + + await organizationApi.addUsers(organization.id, [user.id]); + await organizationApi.addUserRoles(organization.id, user.id, [role1.id, role2.id]); + const roles = await organizationApi.getUserRoles(organization.id, user.id); + expect(roles).toContainEqual(expect.objectContaining({ id: role1.id })); + expect(roles).toContainEqual(expect.objectContaining({ id: role2.id })); + await Promise.all([ + organizationApi.delete(organization.id), + deleteUser(user.id), + roleApi.delete(role1.id), + roleApi.delete(role2.id), + ]); + }); + }); }); diff --git a/packages/phrases/src/locales/de/errors/index.ts b/packages/phrases/src/locales/de/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/de/errors/index.ts +++ b/packages/phrases/src/locales/de/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/de/errors/organization.ts b/packages/phrases/src/locales/de/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/de/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/en/errors/index.ts b/packages/phrases/src/locales/en/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/en/errors/index.ts +++ b/packages/phrases/src/locales/en/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/en/errors/organization.ts b/packages/phrases/src/locales/en/errors/organization.ts new file mode 100644 index 000000000..fa1558b0d --- /dev/null +++ b/packages/phrases/src/locales/en/errors/organization.ts @@ -0,0 +1,5 @@ +const organization = { + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/es/errors/index.ts b/packages/phrases/src/locales/es/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/es/errors/index.ts +++ b/packages/phrases/src/locales/es/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/es/errors/organization.ts b/packages/phrases/src/locales/es/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/es/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/fr/errors/index.ts b/packages/phrases/src/locales/fr/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/fr/errors/index.ts +++ b/packages/phrases/src/locales/fr/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/fr/errors/organization.ts b/packages/phrases/src/locales/fr/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/fr/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/it/errors/index.ts b/packages/phrases/src/locales/it/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/it/errors/index.ts +++ b/packages/phrases/src/locales/it/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/it/errors/organization.ts b/packages/phrases/src/locales/it/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/it/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/ja/errors/index.ts b/packages/phrases/src/locales/ja/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/ja/errors/index.ts +++ b/packages/phrases/src/locales/ja/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/ja/errors/organization.ts b/packages/phrases/src/locales/ja/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/ja/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/ko/errors/index.ts b/packages/phrases/src/locales/ko/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/ko/errors/index.ts +++ b/packages/phrases/src/locales/ko/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/ko/errors/organization.ts b/packages/phrases/src/locales/ko/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/ko/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/pl-pl/errors/index.ts b/packages/phrases/src/locales/pl-pl/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/pl-pl/errors/index.ts +++ b/packages/phrases/src/locales/pl-pl/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/pl-pl/errors/organization.ts b/packages/phrases/src/locales/pl-pl/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/pl-pl/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/pt-br/errors/index.ts b/packages/phrases/src/locales/pt-br/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/pt-br/errors/index.ts +++ b/packages/phrases/src/locales/pt-br/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/pt-br/errors/organization.ts b/packages/phrases/src/locales/pt-br/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/pt-br/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/pt-pt/errors/index.ts b/packages/phrases/src/locales/pt-pt/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/pt-pt/errors/index.ts +++ b/packages/phrases/src/locales/pt-pt/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/pt-pt/errors/organization.ts b/packages/phrases/src/locales/pt-pt/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/pt-pt/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/ru/errors/index.ts b/packages/phrases/src/locales/ru/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/ru/errors/index.ts +++ b/packages/phrases/src/locales/ru/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/ru/errors/organization.ts b/packages/phrases/src/locales/ru/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/ru/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/tr-tr/errors/index.ts b/packages/phrases/src/locales/tr-tr/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/tr-tr/errors/index.ts +++ b/packages/phrases/src/locales/tr-tr/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/tr-tr/errors/organization.ts b/packages/phrases/src/locales/tr-tr/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/tr-tr/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/zh-cn/errors/index.ts b/packages/phrases/src/locales/zh-cn/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/zh-cn/errors/index.ts +++ b/packages/phrases/src/locales/zh-cn/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/zh-cn/errors/organization.ts b/packages/phrases/src/locales/zh-cn/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/zh-cn/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/zh-hk/errors/index.ts b/packages/phrases/src/locales/zh-hk/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/zh-hk/errors/index.ts +++ b/packages/phrases/src/locales/zh-hk/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/zh-hk/errors/organization.ts b/packages/phrases/src/locales/zh-hk/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/zh-hk/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization); diff --git a/packages/phrases/src/locales/zh-tw/errors/index.ts b/packages/phrases/src/locales/zh-tw/errors/index.ts index f4c8f1ed2..acf9eda5f 100644 --- a/packages/phrases/src/locales/zh-tw/errors/index.ts +++ b/packages/phrases/src/locales/zh-tw/errors/index.ts @@ -8,6 +8,7 @@ import hook from './hook.js'; import localization from './localization.js'; import log from './log.js'; import oidc from './oidc.js'; +import organization from './organization.js'; import password from './password.js'; import request from './request.js'; import resource from './resource.js'; @@ -44,6 +45,7 @@ const errors = { domain, subscription, application, + organization, }; export default Object.freeze(errors); diff --git a/packages/phrases/src/locales/zh-tw/errors/organization.ts b/packages/phrases/src/locales/zh-tw/errors/organization.ts new file mode 100644 index 000000000..2752f71f9 --- /dev/null +++ b/packages/phrases/src/locales/zh-tw/errors/organization.ts @@ -0,0 +1,6 @@ +const organization = { + /** UNTRANSLATED */ + require_membership: 'The user must be a member of the organization to proceed.', +}; + +export default Object.freeze(organization);