Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Make Customers ChannelAware #454

Merged
merged 6 commits into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
487 changes: 487 additions & 0 deletions packages/core/e2e/customer-channel.e2e-spec.ts

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions packages/core/e2e/customer.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ let sendEmailFn: jest.Mock;
class TestEmailPlugin implements OnModuleInit {
constructor(private eventBus: EventBus) {}
onModuleInit() {
this.eventBus.ofType(AccountRegistrationEvent).subscribe((event) => {
this.eventBus.ofType(AccountRegistrationEvent).subscribe(event => {
sendEmailFn(event);
});
}
Expand Down Expand Up @@ -170,7 +170,7 @@ describe('Customer resolver', () => {
});

expect(result.customer!.addresses!.length).toBe(2);
firstCustomerAddressIds = result.customer!.addresses!.map((a) => a.id).sort();
firstCustomerAddressIds = result.customer!.addresses!.map(a => a.id).sort();
});

it('updateCustomerAddress updates the country', async () => {
Expand Down Expand Up @@ -209,7 +209,7 @@ describe('Customer resolver', () => {
id: firstCustomer.id,
});
const otherAddress = result2.customer!.addresses!.filter(
(a) => a.id !== firstCustomerAddressIds[1],
a => a.id !== firstCustomerAddressIds[1],
)[0]!;
expect(otherAddress.defaultShippingAddress).toBe(false);
expect(otherAddress.defaultBillingAddress).toBe(false);
Expand All @@ -233,7 +233,7 @@ describe('Customer resolver', () => {
id: firstCustomer.id,
});
const otherAddress2 = result4.customer!.addresses!.filter(
(a) => a.id !== firstCustomerAddressIds[0],
a => a.id !== firstCustomerAddressIds[0],
)[0]!;
expect(otherAddress2.defaultShippingAddress).toBe(false);
expect(otherAddress2.defaultBillingAddress).toBe(false);
Expand Down Expand Up @@ -336,10 +336,10 @@ describe('Customer resolver', () => {
);
expect(customer!.addresses!.length).toBe(2);
const defaultAddress = customer!.addresses!.filter(
(a) => a.defaultBillingAddress && a.defaultShippingAddress,
a => a.defaultBillingAddress && a.defaultShippingAddress,
);
const otherAddress = customer!.addresses!.filter(
(a) => !a.defaultBillingAddress && !a.defaultShippingAddress,
a => !a.defaultBillingAddress && !a.defaultShippingAddress,
);
expect(defaultAddress.length).toBe(1);
expect(otherAddress.length).toBe(1);
Expand Down Expand Up @@ -448,7 +448,7 @@ describe('Customer resolver', () => {
GET_CUSTOMER_LIST,
);

expect(result.customers.items.map((c) => c.id).includes(thirdCustomer.id)).toBe(false);
expect(result.customers.items.map(c => c.id).includes(thirdCustomer.id)).toBe(false);
});

it(
Expand Down Expand Up @@ -593,7 +593,7 @@ const GET_CUSTOMER_WITH_USER = gql`
}
`;

const CREATE_ADDRESS = gql`
export const CREATE_ADDRESS = gql`
mutation CreateAddress($id: ID!, $input: CreateAddressInput!) {
createCustomerAddress(customerId: $id, input: $input) {
id
Expand All @@ -615,7 +615,7 @@ const CREATE_ADDRESS = gql`
}
`;

const UPDATE_ADDRESS = gql`
export const UPDATE_ADDRESS = gql`
mutation UpdateAddress($input: UpdateAddressInput!) {
updateCustomerAddress(input: $input) {
id
Expand Down Expand Up @@ -660,7 +660,7 @@ export const UPDATE_CUSTOMER = gql`
${CUSTOMER_FRAGMENT}
`;

const DELETE_CUSTOMER = gql`
export const DELETE_CUSTOMER = gql`
mutation DeleteCustomer($id: ID!) {
deleteCustomer(id: $id) {
result
Expand Down
29 changes: 28 additions & 1 deletion packages/core/src/api/middleware/auth-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { Request, Response } from 'express';
import { ForbiddenError } from '../../common/error/errors';
import { ConfigService } from '../../config/config.service';
import { CachedSession } from '../../config/session-cache/session-cache-strategy';
import { Customer } from '../../entity/customer/customer.entity';
import { ChannelService } from '../../service/services/channel.service';
import { CustomerService } from '../../service/services/customer.service';
import { SessionService } from '../../service/services/session.service';
import { extractSessionToken } from '../common/extract-session-token';
import { parseContext } from '../common/parse-context';
Expand All @@ -26,6 +29,8 @@ export class AuthGuard implements CanActivate {
private configService: ConfigService,
private requestContextService: RequestContextService,
private sessionService: SessionService,
private customerService: CustomerService,
private channelService: ChannelService,
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
Expand All @@ -35,9 +40,31 @@ export class AuthGuard implements CanActivate {
const isPublic = !!permissions && permissions.includes(Permission.Public);
const hasOwnerPermission = !!permissions && permissions.includes(Permission.Owner);
const session = await this.getSession(req, res, hasOwnerPermission);
const requestContext = await this.requestContextService.fromRequest(req, info, permissions, session);
let requestContext = await this.requestContextService.fromRequest(req, info, permissions, session);
(req as any)[REQUEST_CONTEXT_KEY] = requestContext;

// In case the session does not have an activeChannelId or the activeChannelId
// does not correspond to the current channel, the activeChannelId on the session is set
if (session && (!session.activeChannelId || session.activeChannelId !== requestContext.channelId)) {
await this.sessionService.setActiveChannel(session, requestContext.channel);
if (requestContext.activeUserId) {
const customer = await this.customerService.findOneByUserId(
requestContext,
requestContext.activeUserId,
false,
);
// To avoid assigning the customer to the active channel on every request,
// it is only done on the first request and whenever the channel changes
if (customer) {
await this.channelService.assignToChannels(Customer, customer.id, [
requestContext.channelId,
]);
}
}
requestContext = await this.requestContextService.fromRequest(req, info, permissions, session);
(req as any)[REQUEST_CONTEXT_KEY] = requestContext;
}

if (authDisabled || !permissions || isPublic) {
return true;
} else {
Expand Down
21 changes: 15 additions & 6 deletions packages/core/src/api/resolvers/admin/customer.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,20 @@ export class CustomerResolver {

@Query()
@Allow(Permission.ReadCustomer)
async customers(@Args() args: QueryCustomersArgs): Promise<PaginatedList<Customer>> {
return this.customerService.findAll(args.options || undefined);
async customers(
@Ctx() ctx: RequestContext,
@Args() args: QueryCustomersArgs,
): Promise<PaginatedList<Customer>> {
return this.customerService.findAll(ctx, args.options || undefined);
}

@Query()
@Allow(Permission.ReadCustomer)
async customer(@Args() args: QueryCustomerArgs): Promise<Customer | undefined> {
return this.customerService.findOne(args.id);
async customer(
@Ctx() ctx: RequestContext,
@Args() args: QueryCustomerArgs,
): Promise<Customer | undefined> {
return this.customerService.findOne(ctx, args.id);
}

@Mutation()
Expand Down Expand Up @@ -93,8 +99,11 @@ export class CustomerResolver {

@Mutation()
@Allow(Permission.DeleteCustomer)
async deleteCustomer(@Args() args: MutationDeleteCustomerArgs): Promise<DeletionResponse> {
return this.customerService.softDelete(args.id);
async deleteCustomer(
@Ctx() ctx: RequestContext,
@Args() args: MutationDeleteCustomerArgs,
): Promise<DeletionResponse> {
return this.customerService.softDelete(ctx, args.id);
}

@Mutation()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class CustomerAdminEntityResolver {
if (customer.groups) {
return customer.groups;
}
return this.customerService.getCustomerGroups(customer.id);
return this.customerService.getCustomerGroups(ctx, customer.id);
}

@ResolveField()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export class CustomerGroupEntityResolver {
@Parent() customerGroup: CustomerGroup,
@Args() args: QueryCustomersArgs,
): Promise<PaginatedList<Customer>> {
return this.customerGroupService.getGroupCustomers(customerGroup.id, args.options || undefined);
return this.customerGroupService.getGroupCustomers(ctx, customerGroup.id, args.options || undefined);
}
}
6 changes: 3 additions & 3 deletions packages/core/src/api/resolvers/shop/shop-auth.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
) {
super(authService, userService, administratorService, configService);
this.nativeAuthStrategyIsConfigured = !!this.configService.authOptions.shopAuthenticationStrategy.find(
(strategy) => strategy.name === NATIVE_AUTH_STRATEGY_NAME,
strategy => strategy.name === NATIVE_AUTH_STRATEGY_NAME,
);
}

Expand Down Expand Up @@ -194,7 +194,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
this.requireNativeAuthStrategy();
const result = await super.updatePassword(ctx, args.currentPassword, args.newPassword);
if (result && ctx.activeUserId) {
const customer = await this.customerService.findOneByUserId(ctx.activeUserId);
const customer = await this.customerService.findOneByUserId(ctx, ctx.activeUserId);
if (customer) {
await this.historyService.createHistoryEntryForCustomer({
ctx,
Expand Down Expand Up @@ -234,7 +234,7 @@ export class ShopAuthResolver extends BaseAuthResolver {
private requireNativeAuthStrategy() {
if (!this.nativeAuthStrategyIsConfigured) {
const authStrategyNames = this.configService.authOptions.shopAuthenticationStrategy
.map((s) => s.name)
.map(s => s.name)
.join(', ');
const errorMessage =
'This GraphQL operation requires that the NativeAuthenticationStrategy be configured for the Shop API.\n' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class ShopCustomerResolver {
async activeCustomer(@Ctx() ctx: RequestContext): Promise<Customer | undefined> {
const userId = ctx.activeUserId;
if (userId) {
return this.customerService.findOneByUserId(userId);
return this.customerService.findOneByUserId(ctx, userId);
}
}

Expand Down Expand Up @@ -87,7 +87,7 @@ export class ShopCustomerResolver {
if (!userId) {
throw new ForbiddenError();
}
const customer = await this.customerService.findOneByUserId(userId);
const customer = await this.customerService.findOneByUserId(ctx, userId);
if (!customer) {
throw new InternalServerError(`error.no-customer-found-for-current-user`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export class ShopOrderResolver {
}
const sessionOrder = await this.getOrderFromContext(ctx);
if (sessionOrder) {
const customer = await this.customerService.createOrUpdate(args.input, true);
const customer = await this.customerService.createOrUpdate(ctx, args.input, true);
return this.orderService.addCustomerToOrder(ctx, sessionOrder.id, customer);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LanguageCode } from '@vendure/common/lib/generated-types';
import { ID } from '@vendure/common/lib/shared-types';

import { RequestContext } from '../../../api/common/request-context';
import { idsAreEqual } from '../../../common/utils';
import { OrderLine } from '../../../entity/order-line/order-line.entity';
import { Order } from '../../../entity/order/order.entity';
Expand All @@ -20,7 +21,7 @@ export const containsProducts = new PromotionCondition({
label: [{ languageCode: LanguageCode.en, value: 'Product variants' }],
},
},
async check(order: Order, args) {
async check(ctx: RequestContext, order: Order, args) {
const ids = args.productVariantIds;
let matches = 0;
for (const line of order.lines) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LanguageCode } from '@vendure/common/lib/generated-types';
import { ID } from '@vendure/common/lib/shared-types';

import { RequestContext } from '../../../api/common/request-context';
import { TtlCache } from '../../../common/ttl-cache';
import { idsAreEqual } from '../../../common/utils';
import { Order } from '../../../entity/order/order.entity';
Expand All @@ -26,14 +27,14 @@ export const customerGroup = new PromotionCondition({
const { CustomerService } = await import('../../../service/services/customer.service');
customerService = injector.get(CustomerService);
},
async check(order: Order, args) {
async check(ctx: RequestContext, order: Order, args) {
if (!order.customer) {
return false;
}
const customerId = order.customer.id;
let groupIds = cache.get(customerId);
if (!groupIds) {
const groups = await customerService.getCustomerGroups(customerId);
const groups = await customerService.getCustomerGroups(ctx, customerId);
groupIds = groups.map(g => g.id);
cache.set(customerId, groupIds);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LanguageCode } from '@vendure/common/lib/generated-types';

import { RequestContext } from '../../../api/common/request-context';
import { Order } from '../../../entity/order/order.entity';
import { PromotionCondition } from '../promotion-condition';
import { FacetValueChecker } from '../utils/facet-value-checker';
Expand All @@ -19,7 +20,7 @@ export const hasFacetValues = new PromotionCondition({
facetValueChecker = new FacetValueChecker(injector.getConnection());
},
// tslint:disable-next-line:no-shadowed-variable
async check(order: Order, args) {
async check(ctx: RequestContext, order: Order, args) {
let matches = 0;
for (const line of order.lines) {
if (await facetValueChecker.hasFacetValues(line, args.facets)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { LanguageCode } from '@vendure/common/lib/generated-types';

import { RequestContext } from '../../../api/common/request-context';
import { Order } from '../../../entity/order/order.entity';
import { PromotionCondition } from '../promotion-condition';

export const minimumOrderAmount = new PromotionCondition({
Expand All @@ -12,7 +14,7 @@ export const minimumOrderAmount = new PromotionCondition({
},
taxInclusive: { type: 'boolean' },
},
check(order, args) {
check(ctx: RequestContext, order: Order, args) {
if (args.taxInclusive) {
return order.subTotal >= args.amount;
} else {
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/config/promotion/promotion-condition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ConfigArg } from '@vendure/common/lib/generated-types';
import { ConfigArgType, ID } from '@vendure/common/lib/shared-types';

import { RequestContext } from '../../api/common/request-context';
import {
ConfigArgs,
ConfigArgValues,
Expand All @@ -18,6 +19,7 @@ import { Order } from '../../entity/order/order.entity';
* @docsPage promotion-condition
*/
export type CheckPromotionConditionFn<T extends ConfigArgs> = (
ctx: RequestContext,
order: Order,
args: ConfigArgValues<T>,
) => boolean | Promise<boolean>;
Expand Down Expand Up @@ -61,7 +63,7 @@ export class PromotionCondition<T extends ConfigArgs = ConfigArgs> extends Confi
this.priorityValue = config.priorityValue || 0;
}

async check(order: Order, args: ConfigArg[]): Promise<boolean> {
return this.checkFn(order, this.argsArrayToHash(args));
async check(ctx: RequestContext, order: Order, args: ConfigArg[]): Promise<boolean> {
return this.checkFn(ctx, order, this.argsArrayToHash(args));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export type CachedSession = {
activeOrderId?: ID;
authenticationStrategy?: string;
user?: CachedSessionUser;
activeChannelId?: ID;
};

/**
Expand Down
Loading