Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ddbe3fb
email templates
Juwang110 Apr 13, 2026
4058b03
multiple items for email template
Juwang110 Apr 16, 2026
61f6859
logic for sending emails
Juwang110 Apr 20, 2026
83baa1c
tests
Juwang110 Apr 21, 2026
ff7d8e6
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 Apr 21, 2026
5deb81b
fix bug
Juwang110 Apr 21, 2026
6006842
comments
Juwang110 Apr 29, 2026
dd564f4
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 Apr 29, 2026
721c0c4
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 Apr 29, 2026
25664af
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 Apr 30, 2026
ef55069
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 1, 2026
6ef0e51
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 4, 2026
2470ddb
comments
Juwang110 May 4, 2026
aa7871a
finish test
Juwang110 May 5, 2026
0c8f55a
tests and comments
Juwang110 May 5, 2026
8446636
shipment address instead of mailing
Juwang110 May 6, 2026
0701342
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 6, 2026
1b7750f
food request closed email
Juwang110 May 6, 2026
f658e67
comments
Juwang110 May 7, 2026
0a285e3
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 7, 2026
685bc01
minor refactoring comments
Juwang110 May 13, 2026
4d1417a
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 13, 2026
d77f5b8
some comments
Juwang110 May 13, 2026
8f34729
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 18, 2026
3144a19
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 19, 2026
c78d2e8
comments
Juwang110 May 19, 2026
6218796
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 26, 2026
518e45b
comments
Juwang110 May 26, 2026
6b739df
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 27, 2026
7005536
comments
Juwang110 May 27, 2026
8036959
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 May 28, 2026
3bd4c45
comments
Juwang110 May 28, 2026
c7ef755
Merge branch 'main' into jw/ssf-188-automated-order-lifecycle-emails
Juwang110 Jun 2, 2026
c1db6c6
comment
Juwang110 Jun 2, 2026
c88f4b3
remove console log
Juwang110 Jun 2, 2026
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
23 changes: 0 additions & 23 deletions apps/backend/src/donationItems/donationItems.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,6 @@ export class DonationItemsService {
return items;
}

async getAssociatedDonationIds(
donationItemIds: number[],
): Promise<Set<number>> {
donationItemIds.forEach((id) => validateId(id, 'Donation Item'));

const items = await this.repo.find({
where: { itemId: In(donationItemIds) },
select: ['itemId', 'donationId'],
});

const foundIds = new Set(items.map((i) => i.itemId));

const missingIds = donationItemIds.filter((id) => !foundIds.has(id));

if (missingIds.length > 0) {
throw new NotFoundException(
`Donation items not found for ID(s): ${missingIds.join(', ')}`,
);
}

return new Set(items.map((i) => i.donationId));
}

async create(
donationId: number,
itemName: string,
Expand Down
111 changes: 111 additions & 0 deletions apps/backend/src/emails/emailTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,115 @@ export const emailTemplates = {
<p>Best regards,<br />The Securing Safe Food Team</p>
`,
}),

pantryRequestMatchedOrder: (params: {
pantryName: string;
items: { quantity: string; product: string }[];
brand: string;
volunteerName: string;
volunteerEmail: string;
}): EmailTemplate => ({
subject: 'Your Securing Safe Food Request Has Been Matched to a Delivery',
bodyHTML: `
<p>Hi ${params.pantryName},</p>
<p>
Good news! Your recent food request through Securing Safe Food has been successfully matched to an order and is now moving forward toward delivery.
</p>
<p><strong>Items you will receive from ${params.brand}:</strong></p>
<ul>
${params.items
.map((item) => `<li>${item.quantity} of ${item.product}</li>`)
.join('')}
</ul>
<p>
To view full order details, delivery updates, and any notes from the coordinating volunteer or food manufacturer, please <a href="${EMAIL_REDIRECT_URL}/login">log into the platform</a>.
</p>
<p>
If any details change on your end or you have updated availability, please update your request in the system or email your coordinator, ${
params.volunteerName
} at <a href="mailto:${params.volunteerEmail}">${
params.volunteerEmail
}</a>.
</p>
<p>
We will continue to keep you informed as the order progresses. We’re excited to help support your pantry and looking forward to this donation!
</p>
<p>Best regards,<br />The Securing Safe Food Team</p>
`,
}),

pantryRequestClosed: (params: {
Comment thread
Juwang110 marked this conversation as resolved.
pantryName: string;
volunteerName: string;
volunteerEmail: string;
}): EmailTemplate => ({
subject: 'Your Securing Safe Food Request Has Been Completed',
bodyHTML: `
<p>Hi ${params.pantryName},</p>
<p>
Your recent food request through Securing Safe Food has been marked as complete.
We are glad to fulfill your pantry's requests! If you would like to continue receiving
donations, please submit a new food request at any time to ensure there is no interruption
in future deliveries.
</p>
<p>
To submit a new request or view past orders, please log into the platform here:
<a href="${EMAIL_REDIRECT_URL}/login">${EMAIL_REDIRECT_URL}/login</a>
</p>
<p>
If you have any questions or feedback about this request, please do not hesitate to reach out.
You can contact your pantry coordinator, ${params.volunteerName}, at
<a href="mailto:${params.volunteerEmail}">${params.volunteerEmail}</a>.
</p>
<p>Best regards,<br />The Securing Safe Food Team</p>
`,
}),

fmDonationMatchedOrder: (params: {
manufacturerName: string;
items: { quantity: string; product: string }[];
pantryName: string;
pantryAddress: string;
volunteerName: string;
volunteerEmail: string;
}): EmailTemplate => ({
subject:
'Your Securing Safe Food Donation Has Been Matched to a Pantry Order',
bodyHTML: `
<p>Hi ${params.manufacturerName},</p>
<p>
Thank you for your continued partnership with Securing Safe Food. A donation you submitted has now been successfully matched to a pantry request and is moving forward towards fulfillment.
</p>
<p><strong>Matched Item(s):</strong><br /></p>
<ul>
${params.items
.map((item) => `<li>${item.quantity} of ${item.product}</li>`)
.join('')}
</ul>
<p>
<strong>Recipient Pantry:</strong> ${params.pantryName}<br />
Comment thread
Juwang110 marked this conversation as resolved.
</p>
<p>
<strong>Address:</strong><br />
${params.pantryAddress}
</p>
<p>
Please <a href="${EMAIL_REDIRECT_URL}/login">log into the platform</a> to review the full delivery details, timelines, and any special handling instructions associated with this shipment.
</p>
<p>
Your support plays a direct role in expanding access to allergen-safe foods, and we truly appreciate your commitment to this work.
</p>
<p>
If you have any questions or need assistance, please contact your coordinator, ${
params.volunteerName
} at <a href="mailto:${params.volunteerEmail}">${
params.volunteerEmail
}</a>.
</p>
<p>
Thank you so much.
</p>
<p>Best regards,<br />The Securing Safe Food Team</p>
`,
}),
};
18 changes: 15 additions & 3 deletions apps/backend/src/foodRequests/request.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from './dtos/matching.dto';
import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity';
import { Pantry } from '../pantries/pantries.entity';
import { AuthenticatedRequest } from '../auth/authenticated-request';
import { PantriesService } from '../pantries/pantries.service';
import { User } from '../users/users.entity';
import { Role } from '../users/types';
Expand Down Expand Up @@ -302,11 +303,22 @@ describe('RequestsController', () => {
it('should call requestsService.closeRequest', async () => {
const requestId = 1;

mockRequestsService.closeRequest.mockResolvedValueOnce(undefined);
mockRequestsService.closeRequest.mockResolvedValueOnce(
foodRequest1 as FoodRequest,
);

const req = { user: { id: 1 } };

await controller.closeRequest(requestId);
const result = await controller.closeRequest(
requestId,
req as AuthenticatedRequest,
);

expect(mockRequestsService.closeRequest).toHaveBeenCalledWith(requestId);
expect(result).toEqual(foodRequest1);
expect(mockRequestsService.closeRequest).toHaveBeenCalledWith(
requestId,
1,
);
});
});

Expand Down
7 changes: 5 additions & 2 deletions apps/backend/src/foodRequests/request.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
ValidationPipe,
Patch,
Delete,
Req,
} from '@nestjs/common';
import { AuthenticatedRequest } from '../auth/authenticated-request';
import { ApiBody } from '@nestjs/swagger';
import { RequestsService } from './request.service';
import { FoodRequest } from './request.entity';
Expand Down Expand Up @@ -196,7 +198,8 @@ export class RequestsController {
@Patch('/:requestId/close')
async closeRequest(
@Param('requestId', ParseIntPipe) requestId: number,
): Promise<void> {
await this.requestsService.closeRequest(requestId);
@Req() req: AuthenticatedRequest,
): Promise<FoodRequest> {
return this.requestsService.closeRequest(requestId, req.user.id);
}
}
4 changes: 4 additions & 0 deletions apps/backend/src/foodRequests/request.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Pantry } from '../pantries/pantries.entity';
import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity';
import { DonationItem } from '../donationItems/donationItems.entity';
import { EmailsModule } from '../emails/email.module';
import { User } from '../users/users.entity';
import { UsersModule } from '../users/users.module';

@Module({
imports: [
Expand All @@ -18,9 +20,11 @@ import { EmailsModule } from '../emails/email.module';
Pantry,
FoodManufacturer,
DonationItem,
User,
]),
AuthModule,
EmailsModule,
UsersModule,
],
controllers: [RequestsController],
providers: [RequestsService],
Expand Down
Loading
Loading