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

Email ics file for pickup events #443

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"ejs": "^3.1.3",
"express": "^4.17.1",
"faker": "^5.5.3",
"ics": "^3.7.2",
"jsonwebtoken": "^8.5.1",
"moment": "^2.27.0",
"moment-timezone": "^0.5.34",
Expand Down
38 changes: 36 additions & 2 deletions services/EmailService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MailService, MailDataRequired } from '@sendgrid/mail';
import { createEvent } from 'ics';
import * as ejs from 'ejs';
import * as fs from 'fs';
import * as path from 'path';
Expand Down Expand Up @@ -75,7 +76,8 @@ export default class EmailService {
}
}

public async sendOrderConfirmation(email: string, firstName: string, order: OrderInfo): Promise<void> {
public async sendOrderConfirmation(email: string, firstName: string, order: OrderInfo,
calendarInfo: OrderPickupEventCalendarInfo): Promise<void> {
try {
const data = {
to: email,
Expand All @@ -88,6 +90,14 @@ export default class EmailService {
pickupEvent: order.pickupEvent,
link: `${Config.client}/store/orders`,
}),
attachments: [
{
content: EmailService.createCalendarFile(calendarInfo),
filename: 'invite.ics',
type: 'text/calendar',
nik-dange marked this conversation as resolved.
Show resolved Hide resolved
disposition: 'attachment',
},
],
};
await this.sendEmail(data);
} catch (error) {
Expand Down Expand Up @@ -169,7 +179,8 @@ export default class EmailService {
}
}

public async sendOrderPickupUpdated(email: string, firstName: string, order: OrderInfo) {
public async sendOrderPickupUpdated(email: string, firstName: string, order: OrderInfo,
calendarInfo: OrderPickupEventCalendarInfo) {
try {
const data = {
to: email,
Expand All @@ -181,6 +192,14 @@ export default class EmailService {
orderItems: ejs.render(EmailService.itemDisplayTemplate, { items: order.items, totalCost: order.totalCost }),
link: `${Config.client}/store/orders`,
}),
attachments: [
{
content: EmailService.createCalendarFile(calendarInfo),
filename: 'invite.ics',
type: 'text/calendar',
disposition: 'attachment',
},
],
};
await this.sendEmail(data);
} catch (error) {
Expand Down Expand Up @@ -251,6 +270,14 @@ export default class EmailService {
return fs.readFileSync(path.join(__dirname, `../templates/${filename}`), 'utf-8');
}

private static createCalendarFile(calendarInfo: OrderPickupEventCalendarInfo) {
const response = createEvent(calendarInfo);
if (response.error) {
throw response.error;
}
return Buffer.from(response.value).toString('base64');
}

private sendEmail(data: EmailData) {
return this.mailer.send(data);
}
Expand Down Expand Up @@ -278,3 +305,10 @@ export interface OrderInfo {
totalCost: number;
pickupEvent: OrderPickupEventInfo;
}

export interface OrderPickupEventCalendarInfo {
start: number;
end: number;
title: string;
description: string;
}
17 changes: 14 additions & 3 deletions services/MerchStoreService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { EventModel } from '../models/EventModel';
import Repositories, { TransactionsManager } from '../repositories';
import { MerchandiseCollectionModel } from '../models/MerchandiseCollectionModel';
import { MerchCollectionPhotoModel } from '../models/MerchCollectionPhotoModel';
import EmailService, { OrderInfo, OrderPickupEventInfo } from './EmailService';
import EmailService, { OrderInfo, OrderPickupEventCalendarInfo, OrderPickupEventInfo } from './EmailService';
import { UserError } from '../utils/Errors';
import { OrderItemModel } from '../models/OrderItemModel';
import { OrderPickupEventModel } from '../models/OrderPickupEventModel';
Expand Down Expand Up @@ -593,7 +593,8 @@ export default class MerchStoreService {
totalCost: order.totalCost,
pickupEvent: MerchStoreService.toPickupEventUpdateInfo(order.pickupEvent),
};
this.emailService.sendOrderConfirmation(user.email, user.firstName, orderConfirmation);
const calendarInfo = MerchStoreService.toEventCalendarInfo(order.pickupEvent);
this.emailService.sendOrderConfirmation(user.email, user.firstName, orderConfirmation, calendarInfo);

return order;
}
Expand Down Expand Up @@ -709,7 +710,8 @@ export default class MerchStoreService {
throw new UserError('Cannot change order pickup to an event that starts in less than 2 days');
}
const orderInfo = await MerchStoreService.buildOrderUpdateInfo(order, newPickupEventForOrder, txn);
await this.emailService.sendOrderPickupUpdated(user.email, user.firstName, orderInfo);
const calendarInfo = MerchStoreService.toEventCalendarInfo(newPickupEventForOrder);
await this.emailService.sendOrderPickupUpdated(user.email, user.firstName, orderInfo, calendarInfo);
return orderRepository.upsertMerchOrder(order, {
pickupEvent: newPickupEventForOrder,
status: OrderStatus.PLACED,
Expand Down Expand Up @@ -903,6 +905,15 @@ export default class MerchStoreService {
};
}

private static toEventCalendarInfo(pickupEvent: OrderPickupEventModel): OrderPickupEventCalendarInfo {
return {
start: pickupEvent.start.getTime(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on adding location as a field to OrderPickupEvents?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing, we've decided to add it and copy over the location from the linkedEvent if none is provided. One potential issue is that if the linkedEvent location updates, this field will not, however, we do not think this will be a big problem as the user will be notified through discord.

end: pickupEvent.end.getTime(),
title: pickupEvent.title,
description: pickupEvent.description,
};
}

/**
* Process fulfillment updates for all order items of an order.
* If all items get fulfilled after this update, then the order is considered fulfilled.
Expand Down
Loading
Loading