Skip to content

Commit 920bfb0

Browse files
authored
Merge pull request #1176 from jboolean/merch-admin
Merch admin
2 parents 92ee6d9 + 46fb8ae commit 920bfb0

File tree

10 files changed

+7695
-1895
lines changed

10 files changed

+7695
-1895
lines changed

backend/checkMerchQueue.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import checkMerchQueueImpl from './src/cron/checkMerchQueue';
2+
import withSetup from './withSetup';
3+
4+
export const handler = withSetup(checkMerchQueueImpl);

backend/package-lock.json

Lines changed: 7607 additions & 1868 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/serverless.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ functions:
7878
handler: checkStaleStories.handler
7979
events:
8080
- schedule: cron(30 11 * * ? *)
81+
checkMerchQueue:
82+
stages:
83+
- production
84+
handler: checkMerchQueue.handler
85+
events:
86+
- schedule: cron(30 11 * * ? *)
8187
syncMap:
8288
stages:
8389
- production

backend/src/api/merch/MerchController.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import MerchCustomizationOptions from '../../entities/MerchCustomizationOptions'
1919
import MerchOrder from '../../entities/MerchOrder';
2020
import MerchOrderItem from '../../entities/MerchOrderItem';
2121
import MerchItemState from '../../enum/MerchItemState';
22-
import MerchOrderFulfillmentState from '../../enum/MerchOrderFulfillmentState';
2322
import MerchOrderState from '../../enum/MerchOrderState';
2423
import { getUserFromRequestOrCreateAndSetCookie } from '../auth/userAuthUtils';
2524
import { MerchOrderApiModel } from './OrderApiModel';
@@ -144,33 +143,15 @@ export class MerchController extends Controller {
144143
@Security('netlify', ['moderator'])
145144
@Get('orders/for-review')
146145
public async getOrdersForReview(): Promise<MerchOrderApiModel[]> {
147-
const orders = await getRepository(MerchOrder)
148-
.createQueryBuilder('order')
149-
.innerJoinAndSelect('order.items', 'items')
150-
.innerJoinAndSelect('order.user', 'user')
151-
.where({ state: MerchOrderState.PENDING_SUBMISSION })
152-
.orderBy('order.createdAt', 'DESC')
153-
.getMany();
146+
const orders = await MerchOrderService.getOrdersForReview();
154147

155148
return orders.map(orderToApi);
156149
}
157150

158151
@Security('netlify', ['moderator'])
159152
@Get('orders/needs-attention')
160153
public async getOrdersNeedingAttention(): Promise<MerchOrderApiModel[]> {
161-
const orders = await getRepository(MerchOrder)
162-
.createQueryBuilder('order')
163-
.innerJoinAndSelect('order.items', 'items')
164-
.innerJoinAndSelect('order.user', 'user')
165-
.where({ state: MerchOrderState.SUBMITTED_FOR_FULFILLMENT })
166-
.andWhere('order.fulfillmentState in (:...states)', {
167-
states: [
168-
MerchOrderFulfillmentState.FAILED,
169-
MerchOrderFulfillmentState.ON_HOLD,
170-
],
171-
})
172-
.orderBy('order.createdAt', 'DESC')
173-
.getMany();
154+
const orders = await MerchOrderService.getOrdersWithExceptions();
174155

175156
return orders.map(orderToApi);
176157
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import EmailTemplate from '../EmailTemplate';
2+
import EmailStreamType from './EmailStreamType';
3+
import Senders from './Senders';
4+
5+
type OrdersQueueTemplateData = {
6+
ordersCount: number;
7+
};
8+
9+
class MerchOrdersQueueTemplate extends EmailTemplate<
10+
OrdersQueueTemplateData,
11+
Record<string, never>
12+
> {
13+
alias = 'merch-orders-queue';
14+
from = Senders.SYSTEM;
15+
streamType = EmailStreamType.TRANSACTIONAL;
16+
}
17+
18+
export default new MerchOrdersQueueTemplate();

backend/src/business/email/templates/OrderEmailTemplateData.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export interface OrderTemplateData {
2+
orderId: string;
23
ordersUrl: string;
34
trackingUrl?: string;
45
}

backend/src/business/email/templates/ReviewQueueStaleTemplate.ts renamed to backend/src/business/email/templates/StoriesReviewQueueStaleTemplate.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ import EmailTemplate from '../EmailTemplate';
22
import EmailStreamType from './EmailStreamType';
33
import Senders from './Senders';
44

5-
type StoryEmailTemplateData = {
5+
type StoryReviewEmailTemplateData = {
66
storiesCount: number;
77
reviewStoriesUrl: string;
88
stats: { reviewer: string; count: number }[];
99
};
1010

11-
class ReviewQueueStaleTemplate extends EmailTemplate<
12-
StoryEmailTemplateData,
11+
class StoriesReviewQueueStaleTemplate extends EmailTemplate<
12+
StoryReviewEmailTemplateData,
1313
Record<string, never>
1414
> {
1515
alias = 'review-queue-stale';
1616
from = Senders.PERSONAL;
1717
streamType = EmailStreamType.TRANSACTIONAL;
1818
}
1919

20-
export default new ReviewQueueStaleTemplate();
20+
export default new StoriesReviewQueueStaleTemplate();

backend/src/business/merch/MerchOrderService.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const API_BASE = isProduction()
2323

2424
function createOrderEmailTemplateData(order: MerchOrder): OrderTemplateData {
2525
return {
26+
orderId: order.id.toString(),
2627
ordersUrl: UserService.createMagicLinkUrl(
2728
API_BASE,
2829
order.userId,
@@ -223,3 +224,29 @@ export async function onShipmentSent(
223224
await EmailService.sendTemplateEmail(orderShippedEmail);
224225
}
225226
}
227+
228+
export async function getOrdersForReview(): Promise<MerchOrder[]> {
229+
return getRepository(MerchOrder)
230+
.createQueryBuilder('order')
231+
.innerJoinAndSelect('order.items', 'items')
232+
.innerJoinAndSelect('order.user', 'user')
233+
.where({ state: MerchOrderState.PENDING_SUBMISSION })
234+
.orderBy('order.createdAt', 'DESC')
235+
.getMany();
236+
}
237+
238+
export async function getOrdersWithExceptions(): Promise<MerchOrder[]> {
239+
return await getRepository(MerchOrder)
240+
.createQueryBuilder('order')
241+
.innerJoinAndSelect('order.items', 'items')
242+
.innerJoinAndSelect('order.user', 'user')
243+
.where({ state: MerchOrderState.SUBMITTED_FOR_FULFILLMENT })
244+
.andWhere('order.fulfillmentState in (:...states)', {
245+
states: [
246+
MerchOrderFulfillmentState.FAILED,
247+
MerchOrderFulfillmentState.ON_HOLD,
248+
],
249+
})
250+
.orderBy('order.createdAt', 'DESC')
251+
.getMany();
252+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import EmailService from '../business/email/EmailService';
2+
import ReviewQueueStaleTemplate from '../business/email/templates/MerchOrdersQueueTemplate';
3+
import { getOrdersForReview } from '../business/merch/MerchOrderService';
4+
5+
export default async function checkMerchQueue(): Promise<void> {
6+
const ordersForReview = await getOrdersForReview();
7+
8+
if (ordersForReview.length === 0) {
9+
console.log('No orders found');
10+
return;
11+
}
12+
13+
const ordersCount = ordersForReview.length;
14+
15+
const email = ReviewQueueStaleTemplate.createTemplatedEmail({
16+
templateContext: {
17+
ordersCount,
18+
},
19+
metadata: {},
20+
to: process.env.MODERATORS_TO_EMAIL ?? '',
21+
});
22+
23+
await EmailService.sendTemplateEmail(email);
24+
}

backend/src/cron/checkStaleStories.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import EmailService from '../business/email/EmailService';
2+
import ReviewQueueStaleTemplate from '../business/email/templates/StoriesReviewQueueStaleTemplate';
13
import StoryState from '../enum/StoryState';
24
import StoryRepository from '../repositories/StoryRepository';
3-
import EmailService from '../business/email/EmailService';
4-
import ReviewQueueStaleTemplate from '../business/email/templates/ReviewQueueStaleTemplate';
55

66
const STALENESS_THRESHOLD_MS = 1000 * 60 * 60 * 24 * 2;
77

0 commit comments

Comments
 (0)