|
|
@@ -26,6 +26,7 @@ import { NATIVE_AUTH_STRATEGY_NAME } from '../../config/auth/native-authenticati
|
|
|
import { ConfigService } from '../../config/config.service';
|
|
|
import { Address } from '../../entity/address/address.entity';
|
|
|
import { NativeAuthenticationMethod } from '../../entity/authentication-method/native-authentication-method.entity';
|
|
|
+import { Collection } from '../../entity/collection/collection.entity';
|
|
|
import { CustomerGroup } from '../../entity/customer-group/customer-group.entity';
|
|
|
import { Customer } from '../../entity/customer/customer.entity';
|
|
|
import { HistoryEntry } from '../../entity/history-entry/history-entry.entity';
|
|
|
@@ -58,19 +59,22 @@ export class CustomerService {
|
|
|
private historyService: HistoryService,
|
|
|
) {}
|
|
|
|
|
|
- findAll(options: ListQueryOptions<Customer> | undefined): Promise<PaginatedList<Customer>> {
|
|
|
+ findAll(
|
|
|
+ ctx: RequestContext,
|
|
|
+ options: ListQueryOptions<Customer> | undefined,
|
|
|
+ ): Promise<PaginatedList<Customer>> {
|
|
|
return this.listQueryBuilder
|
|
|
- .build(Customer, options, { where: { deletedAt: null } })
|
|
|
+ .build(Customer, options, { where: { deletedAt: null }, ctx })
|
|
|
.getManyAndCount()
|
|
|
.then(([items, totalItems]) => ({ items, totalItems }));
|
|
|
}
|
|
|
|
|
|
- findOne(id: ID): Promise<Customer | undefined> {
|
|
|
- return this.connection.getRepository(Customer).findOne(id, { where: { deletedAt: null } });
|
|
|
+ findOne(ctx: RequestContext, id: ID): Promise<Customer | undefined> {
|
|
|
+ return this.connection.getRepository(ctx, Customer).findOne(id, { where: { deletedAt: null } });
|
|
|
}
|
|
|
|
|
|
- findOneByUserId(userId: ID): Promise<Customer | undefined> {
|
|
|
- return this.connection.getRepository(Customer).findOne({
|
|
|
+ findOneByUserId(ctx: RequestContext, userId: ID): Promise<Customer | undefined> {
|
|
|
+ return this.connection.getRepository(ctx, Customer).findOne({
|
|
|
where: {
|
|
|
user: { id: userId },
|
|
|
deletedAt: null,
|
|
|
@@ -80,7 +84,7 @@ export class CustomerService {
|
|
|
|
|
|
findAddressesByCustomerId(ctx: RequestContext, customerId: ID): Promise<Address[]> {
|
|
|
return this.connection
|
|
|
- .getRepository(Address)
|
|
|
+ .getRepository(ctx, Address)
|
|
|
.createQueryBuilder('address')
|
|
|
.leftJoinAndSelect('address.country', 'country')
|
|
|
.leftJoinAndSelect('country.translations', 'countryTranslation')
|
|
|
@@ -94,9 +98,9 @@ export class CustomerService {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- async getCustomerGroups(customerId: ID): Promise<CustomerGroup[]> {
|
|
|
+ async getCustomerGroups(ctx: RequestContext | undefined, customerId: ID): Promise<CustomerGroup[]> {
|
|
|
const customerWithGroups = await this.connection
|
|
|
- .getRepository(Customer)
|
|
|
+ .getRepository(ctx, Customer)
|
|
|
.findOne(customerId, { relations: ['groups'] });
|
|
|
if (customerWithGroups) {
|
|
|
return customerWithGroups.groups;
|
|
|
@@ -109,13 +113,13 @@ export class CustomerService {
|
|
|
input.emailAddress = normalizeEmailAddress(input.emailAddress);
|
|
|
const customer = new Customer(input);
|
|
|
|
|
|
- const existingCustomer = await this.connection.getRepository(Customer).findOne({
|
|
|
+ const existingCustomer = await this.connection.getRepository(ctx, Customer).findOne({
|
|
|
where: {
|
|
|
emailAddress: input.emailAddress,
|
|
|
deletedAt: null,
|
|
|
},
|
|
|
});
|
|
|
- const existingUser = await this.connection.getRepository(User).findOne({
|
|
|
+ const existingUser = await this.connection.getRepository(ctx, User).findOne({
|
|
|
where: {
|
|
|
identifier: input.emailAddress,
|
|
|
deletedAt: null,
|
|
|
@@ -125,17 +129,17 @@ export class CustomerService {
|
|
|
if (existingCustomer || existingUser) {
|
|
|
throw new UserInputError(`error.email-address-must-be-unique`);
|
|
|
}
|
|
|
- customer.user = await this.userService.createCustomerUser(input.emailAddress, password);
|
|
|
+ customer.user = await this.userService.createCustomerUser(ctx, input.emailAddress, password);
|
|
|
|
|
|
if (password && password !== '') {
|
|
|
const verificationToken = customer.user.getNativeAuthenticationMethod().verificationToken;
|
|
|
if (verificationToken) {
|
|
|
- customer.user = await this.userService.verifyUserByToken(verificationToken);
|
|
|
+ customer.user = await this.userService.verifyUserByToken(ctx, verificationToken);
|
|
|
}
|
|
|
} else {
|
|
|
this.eventBus.publish(new AccountRegistrationEvent(ctx, customer.user));
|
|
|
}
|
|
|
- const createdCustomer = await await this.connection.getRepository(Customer).save(customer);
|
|
|
+ const createdCustomer = await await this.connection.getRepository(ctx, Customer).save(customer);
|
|
|
|
|
|
await this.historyService.createHistoryEntryForCustomer({
|
|
|
ctx,
|
|
|
@@ -165,7 +169,7 @@ export class CustomerService {
|
|
|
throw new UserInputError(`error.missing-password-on-registration`);
|
|
|
}
|
|
|
}
|
|
|
- let user = await this.userService.getUserByEmailAddress(input.emailAddress);
|
|
|
+ let user = await this.userService.getUserByEmailAddress(ctx, input.emailAddress);
|
|
|
const hasNativeAuthMethod = !!user?.authenticationMethods.find(
|
|
|
m => m instanceof NativeAuthenticationMethod,
|
|
|
);
|
|
|
@@ -177,7 +181,7 @@ export class CustomerService {
|
|
|
}
|
|
|
}
|
|
|
const customFields = (input as any).customFields;
|
|
|
- const customer = await this.createOrUpdate({
|
|
|
+ const customer = await this.createOrUpdate(ctx, {
|
|
|
emailAddress: input.emailAddress,
|
|
|
title: input.title || '',
|
|
|
firstName: input.firstName || '',
|
|
|
@@ -194,20 +198,25 @@ export class CustomerService {
|
|
|
},
|
|
|
});
|
|
|
if (!user) {
|
|
|
- user = await this.userService.createCustomerUser(input.emailAddress, input.password || undefined);
|
|
|
+ user = await this.userService.createCustomerUser(
|
|
|
+ ctx,
|
|
|
+ input.emailAddress,
|
|
|
+ input.password || undefined,
|
|
|
+ );
|
|
|
}
|
|
|
if (!hasNativeAuthMethod) {
|
|
|
user = await this.userService.addNativeAuthenticationMethod(
|
|
|
+ ctx,
|
|
|
user,
|
|
|
input.emailAddress,
|
|
|
input.password || undefined,
|
|
|
);
|
|
|
}
|
|
|
if (!user.verified) {
|
|
|
- user = await this.userService.setVerificationToken(user);
|
|
|
+ user = await this.userService.setVerificationToken(ctx, user);
|
|
|
}
|
|
|
customer.user = user;
|
|
|
- await this.connection.getRepository(Customer).save(customer, { reload: false });
|
|
|
+ await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
|
|
|
if (!user.verified) {
|
|
|
this.eventBus.publish(new AccountRegistrationEvent(ctx, user));
|
|
|
} else {
|
|
|
@@ -224,9 +233,9 @@ export class CustomerService {
|
|
|
}
|
|
|
|
|
|
async refreshVerificationToken(ctx: RequestContext, emailAddress: string): Promise<void> {
|
|
|
- const user = await this.userService.getUserByEmailAddress(emailAddress);
|
|
|
+ const user = await this.userService.getUserByEmailAddress(ctx, emailAddress);
|
|
|
if (user) {
|
|
|
- await this.userService.setVerificationToken(user);
|
|
|
+ await this.userService.setVerificationToken(ctx, user);
|
|
|
if (!user.verified) {
|
|
|
this.eventBus.publish(new AccountRegistrationEvent(ctx, user));
|
|
|
}
|
|
|
@@ -238,9 +247,9 @@ export class CustomerService {
|
|
|
verificationToken: string,
|
|
|
password?: string,
|
|
|
): Promise<Customer | undefined> {
|
|
|
- const user = await this.userService.verifyUserByToken(verificationToken, password);
|
|
|
+ const user = await this.userService.verifyUserByToken(ctx, verificationToken, password);
|
|
|
if (user) {
|
|
|
- const customer = await this.findOneByUserId(user.id);
|
|
|
+ const customer = await this.findOneByUserId(ctx, user.id);
|
|
|
if (!customer) {
|
|
|
throw new InternalServerError('error.cannot-locate-customer-for-user');
|
|
|
}
|
|
|
@@ -252,15 +261,15 @@ export class CustomerService {
|
|
|
strategy: NATIVE_AUTH_STRATEGY_NAME,
|
|
|
},
|
|
|
});
|
|
|
- return this.findOneByUserId(user.id);
|
|
|
+ return this.findOneByUserId(ctx, user.id);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async requestPasswordReset(ctx: RequestContext, emailAddress: string): Promise<void> {
|
|
|
- const user = await this.userService.setPasswordResetToken(emailAddress);
|
|
|
+ const user = await this.userService.setPasswordResetToken(ctx, emailAddress);
|
|
|
if (user) {
|
|
|
this.eventBus.publish(new PasswordResetEvent(ctx, user));
|
|
|
- const customer = await this.findOneByUserId(user.id);
|
|
|
+ const customer = await this.findOneByUserId(ctx, user.id);
|
|
|
if (!customer) {
|
|
|
throw new InternalServerError('error.cannot-locate-customer-for-user');
|
|
|
}
|
|
|
@@ -278,9 +287,9 @@ export class CustomerService {
|
|
|
passwordResetToken: string,
|
|
|
password: string,
|
|
|
): Promise<Customer | undefined> {
|
|
|
- const user = await this.userService.resetPasswordByToken(passwordResetToken, password);
|
|
|
+ const user = await this.userService.resetPasswordByToken(ctx, passwordResetToken, password);
|
|
|
if (user) {
|
|
|
- const customer = await this.findOneByUserId(user.id);
|
|
|
+ const customer = await this.findOneByUserId(ctx, user.id);
|
|
|
if (!customer) {
|
|
|
throw new InternalServerError('error.cannot-locate-customer-for-user');
|
|
|
}
|
|
|
@@ -299,15 +308,18 @@ export class CustomerService {
|
|
|
userId: ID,
|
|
|
newEmailAddress: string,
|
|
|
): Promise<boolean> {
|
|
|
- const userWithConflictingIdentifier = await this.userService.getUserByEmailAddress(newEmailAddress);
|
|
|
+ const userWithConflictingIdentifier = await this.userService.getUserByEmailAddress(
|
|
|
+ ctx,
|
|
|
+ newEmailAddress,
|
|
|
+ );
|
|
|
if (userWithConflictingIdentifier) {
|
|
|
throw new UserInputError('error.email-address-not-available');
|
|
|
}
|
|
|
- const user = await this.userService.getUserById(userId);
|
|
|
+ const user = await this.userService.getUserById(ctx, userId);
|
|
|
if (!user) {
|
|
|
return false;
|
|
|
}
|
|
|
- const customer = await this.findOneByUserId(user.id);
|
|
|
+ const customer = await this.findOneByUserId(ctx, user.id);
|
|
|
if (!customer) {
|
|
|
return false;
|
|
|
}
|
|
|
@@ -323,15 +335,15 @@ export class CustomerService {
|
|
|
});
|
|
|
if (this.configService.authOptions.requireVerification) {
|
|
|
user.getNativeAuthenticationMethod().pendingIdentifier = newEmailAddress;
|
|
|
- await this.userService.setIdentifierChangeToken(user);
|
|
|
+ await this.userService.setIdentifierChangeToken(ctx, user);
|
|
|
this.eventBus.publish(new IdentifierChangeRequestEvent(ctx, user));
|
|
|
return true;
|
|
|
} else {
|
|
|
const oldIdentifier = user.identifier;
|
|
|
user.identifier = newEmailAddress;
|
|
|
customer.emailAddress = newEmailAddress;
|
|
|
- await this.connection.getRepository(User).save(user, { reload: false });
|
|
|
- await this.connection.getRepository(Customer).save(customer, { reload: false });
|
|
|
+ await this.connection.getRepository(ctx, User).save(user, { reload: false });
|
|
|
+ await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
|
|
|
this.eventBus.publish(new IdentifierChangeEvent(ctx, user, oldIdentifier));
|
|
|
await this.historyService.createHistoryEntryForCustomer({
|
|
|
customerId: customer.id,
|
|
|
@@ -347,17 +359,17 @@ export class CustomerService {
|
|
|
}
|
|
|
|
|
|
async updateEmailAddress(ctx: RequestContext, token: string): Promise<boolean> {
|
|
|
- const { user, oldIdentifier } = await this.userService.changeIdentifierByToken(token);
|
|
|
+ const { user, oldIdentifier } = await this.userService.changeIdentifierByToken(ctx, token);
|
|
|
if (!user) {
|
|
|
return false;
|
|
|
}
|
|
|
- const customer = await this.findOneByUserId(user.id);
|
|
|
+ const customer = await this.findOneByUserId(ctx, user.id);
|
|
|
if (!customer) {
|
|
|
return false;
|
|
|
}
|
|
|
this.eventBus.publish(new IdentifierChangeEvent(ctx, user, oldIdentifier));
|
|
|
customer.emailAddress = user.identifier;
|
|
|
- await this.connection.getRepository(Customer).save(customer, { reload: false });
|
|
|
+ await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
|
|
|
await this.historyService.createHistoryEntryForCustomer({
|
|
|
customerId: customer.id,
|
|
|
ctx,
|
|
|
@@ -373,7 +385,7 @@ export class CustomerService {
|
|
|
async update(ctx: RequestContext, input: UpdateCustomerInput): Promise<Customer> {
|
|
|
const customer = await getEntityOrThrow(this.connection, Customer, input.id);
|
|
|
const updatedCustomer = patchEntity(customer, input);
|
|
|
- await this.connection.getRepository(Customer).save(customer, { reload: false });
|
|
|
+ await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
|
|
|
await this.historyService.createHistoryEntryForCustomer({
|
|
|
customerId: customer.id,
|
|
|
ctx,
|
|
|
@@ -382,19 +394,20 @@ export class CustomerService {
|
|
|
input,
|
|
|
},
|
|
|
});
|
|
|
- return assertFound(this.findOne(customer.id));
|
|
|
+ return assertFound(this.findOne(ctx, customer.id));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* For guest checkouts, we assume that a matching email address is the same customer.
|
|
|
*/
|
|
|
async createOrUpdate(
|
|
|
+ ctx: RequestContext,
|
|
|
input: Partial<CreateCustomerInput> & { emailAddress: string },
|
|
|
throwOnExistingUser: boolean = false,
|
|
|
): Promise<Customer> {
|
|
|
input.emailAddress = normalizeEmailAddress(input.emailAddress);
|
|
|
let customer: Customer;
|
|
|
- const existing = await this.connection.getRepository(Customer).findOne({
|
|
|
+ const existing = await this.connection.getRepository(ctx, Customer).findOne({
|
|
|
where: {
|
|
|
emailAddress: input.emailAddress,
|
|
|
deletedAt: null,
|
|
|
@@ -409,11 +422,11 @@ export class CustomerService {
|
|
|
} else {
|
|
|
customer = new Customer(input);
|
|
|
}
|
|
|
- return this.connection.getRepository(Customer).save(customer);
|
|
|
+ return this.connection.getRepository(ctx, Customer).save(customer);
|
|
|
}
|
|
|
|
|
|
async createAddress(ctx: RequestContext, customerId: ID, input: CreateAddressInput): Promise<Address> {
|
|
|
- const customer = await this.connection.getRepository(Customer).findOne(customerId, {
|
|
|
+ const customer = await this.connection.getRepository(ctx, Customer).findOne(customerId, {
|
|
|
where: { deletedAt: null },
|
|
|
relations: ['addresses'],
|
|
|
});
|
|
|
@@ -426,10 +439,10 @@ export class CustomerService {
|
|
|
...input,
|
|
|
country,
|
|
|
});
|
|
|
- const createdAddress = await this.connection.getRepository(Address).save(address);
|
|
|
+ const createdAddress = await this.connection.getRepository(ctx, Address).save(address);
|
|
|
customer.addresses.push(createdAddress);
|
|
|
- await this.connection.getRepository(Customer).save(customer, { reload: false });
|
|
|
- await this.enforceSingleDefaultAddress(createdAddress.id, input);
|
|
|
+ await this.connection.getRepository(ctx, Customer).save(customer, { reload: false });
|
|
|
+ await this.enforceSingleDefaultAddress(ctx, createdAddress.id, input);
|
|
|
await this.historyService.createHistoryEntryForCustomer({
|
|
|
customerId: customer.id,
|
|
|
ctx,
|
|
|
@@ -449,8 +462,8 @@ export class CustomerService {
|
|
|
address.country = translateDeep(address.country, ctx.languageCode);
|
|
|
}
|
|
|
let updatedAddress = patchEntity(address, input);
|
|
|
- updatedAddress = await this.connection.getRepository(Address).save(updatedAddress);
|
|
|
- await this.enforceSingleDefaultAddress(input.id, input);
|
|
|
+ updatedAddress = await this.connection.getRepository(ctx, Address).save(updatedAddress);
|
|
|
+ await this.enforceSingleDefaultAddress(ctx, input.id, input);
|
|
|
|
|
|
await this.historyService.createHistoryEntryForCustomer({
|
|
|
customerId: address.customer.id,
|
|
|
@@ -469,7 +482,7 @@ export class CustomerService {
|
|
|
relations: ['customer', 'country'],
|
|
|
});
|
|
|
address.country = translateDeep(address.country, ctx.languageCode);
|
|
|
- await this.reassignDefaultsForDeletedAddress(address);
|
|
|
+ await this.reassignDefaultsForDeletedAddress(ctx, address);
|
|
|
await this.historyService.createHistoryEntryForCustomer({
|
|
|
customerId: address.customer.id,
|
|
|
ctx,
|
|
|
@@ -478,15 +491,17 @@ export class CustomerService {
|
|
|
address: addressToLine(address),
|
|
|
},
|
|
|
});
|
|
|
- await this.connection.getRepository(Address).remove(address);
|
|
|
+ await this.connection.getRepository(ctx, Address).remove(address);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- async softDelete(customerId: ID): Promise<DeletionResponse> {
|
|
|
+ async softDelete(ctx: RequestContext, customerId: ID): Promise<DeletionResponse> {
|
|
|
const customer = await getEntityOrThrow(this.connection, Customer, customerId);
|
|
|
- await this.connection.getRepository(Customer).update({ id: customerId }, { deletedAt: new Date() });
|
|
|
+ await this.connection
|
|
|
+ .getRepository(ctx, Customer)
|
|
|
+ .update({ id: customerId }, { deletedAt: new Date() });
|
|
|
// tslint:disable-next-line:no-non-null-assertion
|
|
|
- await this.userService.softDelete(customer.user!.id);
|
|
|
+ await this.userService.softDelete(ctx, customer.user!.id);
|
|
|
return {
|
|
|
result: DeletionResult.DELETED,
|
|
|
};
|
|
|
@@ -519,7 +534,7 @@ export class CustomerService {
|
|
|
|
|
|
async deleteCustomerNote(ctx: RequestContext, id: ID): Promise<DeletionResponse> {
|
|
|
try {
|
|
|
- await this.historyService.deleteCustomerHistoryEntry(id);
|
|
|
+ await this.historyService.deleteCustomerHistoryEntry(ctx, id);
|
|
|
return {
|
|
|
result: DeletionResult.DELETED,
|
|
|
};
|
|
|
@@ -531,9 +546,13 @@ export class CustomerService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private async enforceSingleDefaultAddress(addressId: ID, input: CreateAddressInput | UpdateAddressInput) {
|
|
|
+ private async enforceSingleDefaultAddress(
|
|
|
+ ctx: RequestContext,
|
|
|
+ addressId: ID,
|
|
|
+ input: CreateAddressInput | UpdateAddressInput,
|
|
|
+ ) {
|
|
|
const result = await this.connection
|
|
|
- .getRepository(Address)
|
|
|
+ .getRepository(ctx, Address)
|
|
|
.findOne(addressId, { relations: ['customer', 'customer.addresses'] });
|
|
|
if (result) {
|
|
|
const customerAddressIds = result.customer.addresses
|
|
|
@@ -542,12 +561,12 @@ export class CustomerService {
|
|
|
|
|
|
if (customerAddressIds.length) {
|
|
|
if (input.defaultBillingAddress === true) {
|
|
|
- await this.connection.getRepository(Address).update(customerAddressIds, {
|
|
|
+ await this.connection.getRepository(ctx, Address).update(customerAddressIds, {
|
|
|
defaultBillingAddress: false,
|
|
|
});
|
|
|
}
|
|
|
if (input.defaultShippingAddress === true) {
|
|
|
- await this.connection.getRepository(Address).update(customerAddressIds, {
|
|
|
+ await this.connection.getRepository(ctx, Address).update(customerAddressIds, {
|
|
|
defaultShippingAddress: false,
|
|
|
});
|
|
|
}
|
|
|
@@ -560,12 +579,12 @@ export class CustomerService {
|
|
|
* billing. If so, attempt to transfer default status to one of the other addresses if there are
|
|
|
* any.
|
|
|
*/
|
|
|
- private async reassignDefaultsForDeletedAddress(addressToDelete: Address) {
|
|
|
+ private async reassignDefaultsForDeletedAddress(ctx: RequestContext, addressToDelete: Address) {
|
|
|
if (!addressToDelete.defaultBillingAddress && !addressToDelete.defaultShippingAddress) {
|
|
|
return;
|
|
|
}
|
|
|
const result = await this.connection
|
|
|
- .getRepository(Address)
|
|
|
+ .getRepository(ctx, Address)
|
|
|
.findOne(addressToDelete.id, { relations: ['customer', 'customer.addresses'] });
|
|
|
if (result) {
|
|
|
const customerAddresses = result.customer.addresses;
|
|
|
@@ -579,7 +598,7 @@ export class CustomerService {
|
|
|
if (addressToDelete.defaultBillingAddress) {
|
|
|
otherAddresses[0].defaultBillingAddress = true;
|
|
|
}
|
|
|
- await this.connection.getRepository(Address).save(otherAddresses[0], { reload: false });
|
|
|
+ await this.connection.getRepository(ctx, Address).save(otherAddresses[0], { reload: false });
|
|
|
}
|
|
|
}
|
|
|
}
|