Browse Source

feat(core): Allow Roles to have Channels specified on update

Adds a new `channelIds: [ID!]` field to the `CreateRoleInput`.
Michael Bromley 6 years ago
parent
commit
b3dd6c1109

+ 2 - 1
packages/common/src/generated-types.ts

@@ -533,10 +533,10 @@ export type CreatePromotionInput = {
 };
 
 export type CreateRoleInput = {
-  channelIds?: Maybe<Array<Scalars['ID']>>,
   code: Scalars['String'],
   description: Scalars['String'],
   permissions: Array<Permission>,
+  channelIds?: Maybe<Array<Scalars['ID']>>,
 };
 
 export type CreateShippingMethodInput = {
@@ -3414,6 +3414,7 @@ export type UpdateRoleInput = {
   code?: Maybe<Scalars['String']>,
   description?: Maybe<Scalars['String']>,
   permissions?: Maybe<Array<Permission>>,
+  channelIds?: Maybe<Array<Scalars['ID']>>,
 };
 
 export type UpdateShippingMethodInput = {

+ 1 - 21
packages/core/e2e/channel.e2e-spec.ts

@@ -1,6 +1,5 @@
 /* tslint:disable:no-non-null-assertion */
 import { createTestEnvironment } from '@vendure/testing';
-import gql from 'graphql-tag';
 import path from 'path';
 
 import { dataDir, TEST_SETUP_TIMEOUT_MS, testConfig } from './config/test-config';
@@ -14,7 +13,7 @@ import {
     Me,
     Permission,
 } from './graphql/generated-e2e-admin-types';
-import { CREATE_ADMINISTRATOR, CREATE_ROLE, ME } from './graphql/shared-definitions';
+import { CREATE_ADMINISTRATOR, CREATE_CHANNEL, CREATE_ROLE, ME } from './graphql/shared-definitions';
 import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 
 describe('Channels', () => {
@@ -183,22 +182,3 @@ describe('Channels', () => {
         ]);
     });
 });
-
-const CREATE_CHANNEL = gql`
-    mutation CreateChannel($input: CreateChannelInput!) {
-        createChannel(input: $input) {
-            id
-            code
-            token
-            currencyCode
-            defaultLanguageCode
-            defaultShippingZone {
-                id
-            }
-            defaultTaxZone {
-                id
-            }
-            pricesIncludeTax
-        }
-    }
-`;

+ 26 - 25
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -533,10 +533,10 @@ export type CreatePromotionInput = {
 };
 
 export type CreateRoleInput = {
-    channelIds?: Maybe<Array<Scalars['ID']>>;
     code: Scalars['String'];
     description: Scalars['String'];
     permissions: Array<Permission>;
+    channelIds?: Maybe<Array<Scalars['ID']>>;
 };
 
 export type CreateShippingMethodInput = {
@@ -3313,6 +3313,7 @@ export type UpdateRoleInput = {
     code?: Maybe<Scalars['String']>;
     description?: Maybe<Scalars['String']>;
     permissions?: Maybe<Array<Permission>>;
+    channelIds?: Maybe<Array<Scalars['ID']>>;
 };
 
 export type UpdateShippingMethodInput = {
@@ -3403,20 +3404,6 @@ export type GetCustomerCountQuery = { __typename?: 'Query' } & {
     customers: { __typename?: 'CustomerList' } & Pick<CustomerList, 'totalItems'>;
 };
 
-export type CreateChannelMutationVariables = {
-    input: CreateChannelInput;
-};
-
-export type CreateChannelMutation = { __typename?: 'Mutation' } & {
-    createChannel: { __typename?: 'Channel' } & Pick<
-        Channel,
-        'id' | 'code' | 'token' | 'currencyCode' | 'defaultLanguageCode' | 'pricesIncludeTax'
-    > & {
-            defaultShippingZone: Maybe<{ __typename?: 'Zone' } & Pick<Zone, 'id'>>;
-            defaultTaxZone: Maybe<{ __typename?: 'Zone' } & Pick<Zone, 'id'>>;
-        };
-};
-
 export type GetCollectionsWithAssetsQueryVariables = {};
 
 export type GetCollectionsWithAssetsQuery = { __typename?: 'Query' } & {
@@ -4378,6 +4365,20 @@ export type MeQuery = { __typename?: 'Query' } & {
     me: Maybe<{ __typename?: 'CurrentUser' } & CurrentUserFragment>;
 };
 
+export type CreateChannelMutationVariables = {
+    input: CreateChannelInput;
+};
+
+export type CreateChannelMutation = { __typename?: 'Mutation' } & {
+    createChannel: { __typename?: 'Channel' } & Pick<
+        Channel,
+        'id' | 'code' | 'token' | 'currencyCode' | 'defaultLanguageCode' | 'pricesIncludeTax'
+    > & {
+            defaultShippingZone: Maybe<{ __typename?: 'Zone' } & Pick<Zone, 'id'>>;
+            defaultTaxZone: Maybe<{ __typename?: 'Zone' } & Pick<Zone, 'id'>>;
+        };
+};
+
 export type UpdateOptionGroupMutationVariables = {
     input: UpdateProductOptionGroupInput;
 };
@@ -5063,16 +5064,6 @@ export namespace GetCustomerCount {
     export type Customers = GetCustomerCountQuery['customers'];
 }
 
-export namespace CreateChannel {
-    export type Variables = CreateChannelMutationVariables;
-    export type Mutation = CreateChannelMutation;
-    export type CreateChannel = CreateChannelMutation['createChannel'];
-    export type DefaultShippingZone = NonNullable<
-        CreateChannelMutation['createChannel']['defaultShippingZone']
-    >;
-    export type DefaultTaxZone = NonNullable<CreateChannelMutation['createChannel']['defaultTaxZone']>;
-}
-
 export namespace GetCollectionsWithAssets {
     export type Variables = GetCollectionsWithAssetsQueryVariables;
     export type Query = GetCollectionsWithAssetsQuery;
@@ -5711,6 +5702,16 @@ export namespace Me {
     export type Me = CurrentUserFragment;
 }
 
+export namespace CreateChannel {
+    export type Variables = CreateChannelMutationVariables;
+    export type Mutation = CreateChannelMutation;
+    export type CreateChannel = CreateChannelMutation['createChannel'];
+    export type DefaultShippingZone = NonNullable<
+        CreateChannelMutation['createChannel']['defaultShippingZone']
+    >;
+    export type DefaultTaxZone = NonNullable<CreateChannelMutation['createChannel']['defaultTaxZone']>;
+}
+
 export namespace UpdateOptionGroup {
     export type Variables = UpdateOptionGroupMutationVariables;
     export type Mutation = UpdateOptionGroupMutation;

+ 19 - 0
packages/core/e2e/graphql/shared-definitions.ts

@@ -291,3 +291,22 @@ export const ME = gql`
     }
     ${CURRENT_USER_FRAGMENT}
 `;
+
+export const CREATE_CHANNEL = gql`
+    mutation CreateChannel($input: CreateChannelInput!) {
+        createChannel(input: $input) {
+            id
+            code
+            token
+            currencyCode
+            defaultLanguageCode
+            defaultShippingZone {
+                id
+            }
+            defaultTaxZone {
+                id
+            }
+            pricesIncludeTax
+        }
+    }
+`;

+ 92 - 3
packages/core/e2e/role.e2e-spec.ts

@@ -1,5 +1,9 @@
 import { omit } from '@vendure/common/lib/omit';
-import { CUSTOMER_ROLE_CODE, SUPER_ADMIN_ROLE_CODE } from '@vendure/common/lib/shared-constants';
+import {
+    CUSTOMER_ROLE_CODE,
+    DEFAULT_CHANNEL_CODE,
+    SUPER_ADMIN_ROLE_CODE,
+} from '@vendure/common/lib/shared-constants';
 import { createTestEnvironment } from '@vendure/testing';
 import gql from 'graphql-tag';
 import path from 'path';
@@ -8,14 +12,17 @@ import { dataDir, TEST_SETUP_TIMEOUT_MS, testConfig } from './config/test-config
 import { initialData } from './fixtures/e2e-initial-data';
 import { ROLE_FRAGMENT } from './graphql/fragments';
 import {
+    CreateChannel,
     CreateRole,
+    CurrencyCode,
     GetRole,
     GetRoles,
+    LanguageCode,
     Permission,
     Role,
     UpdateRole,
 } from './graphql/generated-e2e-admin-types';
-import { CREATE_ROLE } from './graphql/shared-definitions';
+import { CREATE_CHANNEL, CREATE_ROLE } from './graphql/shared-definitions';
 import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 
 describe('Role resolver', () => {
@@ -108,11 +115,18 @@ describe('Role resolver', () => {
         });
 
         createdRole = result.createRole;
-        expect(omit(createdRole, ['channels'])).toEqual({
+        expect(createdRole).toEqual({
             code: 'test',
             description: 'test role',
             id: 'T_5',
             permissions: [Permission.Authenticated, Permission.ReadCustomer, Permission.UpdateCustomer],
+            channels: [
+                {
+                    code: DEFAULT_CHANNEL_CODE,
+                    id: 'T_1',
+                    token: 'e2e-default-channel',
+                },
+            ],
         });
     });
 
@@ -217,6 +231,81 @@ describe('Role resolver', () => {
             });
         }, `The role '${CUSTOMER_ROLE_CODE}' cannot be modified`),
     );
+
+    describe('multi-channel', () => {
+        let secondChannel: CreateChannel.CreateChannel;
+        let multiChannelRole: CreateRole.CreateRole;
+
+        beforeAll(async () => {
+            const { createChannel } = await adminClient.query<
+                CreateChannel.Mutation,
+                CreateChannel.Variables
+            >(CREATE_CHANNEL, {
+                input: {
+                    code: 'second-channel',
+                    token: 'second-channel-token',
+                    defaultLanguageCode: LanguageCode.en,
+                    currencyCode: CurrencyCode.GBP,
+                    pricesIncludeTax: true,
+                    defaultShippingZoneId: 'T_1',
+                    defaultTaxZoneId: 'T_1',
+                },
+            });
+
+            secondChannel = createChannel;
+        });
+
+        it('createRole with specified channel', async () => {
+            const result = await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(CREATE_ROLE, {
+                input: {
+                    code: 'multi-test',
+                    description: 'multi channel test role',
+                    permissions: [Permission.ReadCustomer],
+                    channelIds: [secondChannel.id],
+                },
+            });
+
+            multiChannelRole = result.createRole;
+            expect(multiChannelRole).toEqual({
+                code: 'multi-test',
+                description: 'multi channel test role',
+                id: 'T_6',
+                permissions: [Permission.Authenticated, Permission.ReadCustomer],
+                channels: [
+                    {
+                        code: 'second-channel',
+                        id: 'T_2',
+                        token: 'second-channel-token',
+                    },
+                ],
+            });
+        });
+
+        it('updateRole with specified channel', async () => {
+            const { updateRole } = await adminClient.query<UpdateRole.Mutation, UpdateRole.Variables>(
+                UPDATE_ROLE,
+                {
+                    input: {
+                        id: multiChannelRole.id,
+                        channelIds: ['T_1', 'T_2'],
+                    },
+                },
+            );
+
+            expect(updateRole.channels).toEqual([
+                {
+                    code: DEFAULT_CHANNEL_CODE,
+                    id: 'T_1',
+                    token: 'e2e-default-channel',
+                },
+                {
+                    code: 'second-channel',
+                    id: 'T_2',
+                    token: 'second-channel-token',
+                },
+            ]);
+        });
+    });
 });
 
 export const GET_ROLES = gql`

+ 2 - 2
packages/core/src/api/resolvers/admin/role.resolver.ts

@@ -39,8 +39,8 @@ export class RoleResolver {
 
     @Mutation()
     @Allow(Permission.UpdateAdministrator)
-    updateRole(@Args() args: MutationUpdateRoleArgs): Promise<Role> {
+    updateRole(@Ctx() ctx: RequestContext, @Args() args: MutationUpdateRoleArgs): Promise<Role> {
         const { input } = args;
-        return this.roleService.update(input);
+        return this.roleService.update(ctx, input);
     }
 }

+ 2 - 2
packages/core/src/api/schema/admin-api/role.api.graphql

@@ -14,16 +14,16 @@ type Mutation {
 input RoleListOptions
 
 input CreateRoleInput {
-    channelIds: [ID!]
     code: String!
     description: String!
     permissions: [Permission!]!
+    channelIds: [ID!]
 }
 
-
 input UpdateRoleInput {
     id: ID!
     code: String
     description: String
     permissions: [Permission!]
+    channelIds: [ID!]
 }

+ 22 - 13
packages/core/src/service/services/role.service.ts

@@ -107,25 +107,14 @@ export class RoleService {
 
         let targetChannels: Channel[] = [];
         if (input.channelIds) {
-            for (const channelId of input.channelIds) {
-                const channel = await getEntityOrThrow(this.connection, Channel, channelId);
-                const hasPermission = await this.userHasPermissionOnChannel(
-                    ctx.activeUserId,
-                    channelId,
-                    Permission.CreateAdministrator,
-                );
-                if (!hasPermission) {
-                    throw new ForbiddenError();
-                }
-                targetChannels = [...targetChannels, channel];
-            }
+            targetChannels = await this.getPermittedChannels(input.channelIds, ctx.activeUserId);
         } else {
             targetChannels = [ctx.channel];
         }
         return this.createRoleForChannels(input, targetChannels);
     }
 
-    async update(input: UpdateRoleInput): Promise<Role> {
+    async update(ctx: RequestContext, input: UpdateRoleInput): Promise<Role> {
         this.checkPermissionsAreValid(input.permissions);
         const role = await this.findOne(input.id);
         if (!role) {
@@ -141,6 +130,9 @@ export class RoleService {
                 ? unique([Permission.Authenticated, ...input.permissions])
                 : undefined,
         });
+        if (input.channelIds && ctx.activeUserId) {
+            updatedRole.channels = await this.getPermittedChannels(input.channelIds, ctx.activeUserId);
+        }
         await this.connection.manager.save(updatedRole);
         return assertFound(this.findOne(role.id));
     }
@@ -149,6 +141,23 @@ export class RoleService {
         await this.channelService.assignToChannel(Role, roleId, channelId);
     }
 
+    private async getPermittedChannels(channelIds: ID[], activeUserId: ID): Promise<Channel[]> {
+        let permittedChannels: Channel[] = [];
+        for (const channelId of channelIds) {
+            const channel = await getEntityOrThrow(this.connection, Channel, channelId);
+            const hasPermission = await this.userHasPermissionOnChannel(
+                activeUserId,
+                channelId,
+                Permission.CreateAdministrator,
+            );
+            if (!hasPermission) {
+                throw new ForbiddenError();
+            }
+            permittedChannels = [...permittedChannels, channel];
+        }
+        return permittedChannels;
+    }
+
     private checkPermissionsAreValid(permissions?: string[] | null) {
         if (!permissions) {
             return;

File diff suppressed because it is too large
+ 0 - 0
schema-admin.json


Some files were not shown because too many files changed in this diff