Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
62ca4fb
get approved and update volunteer endpoints
swarkewalia Dec 3, 2025
8907ee8
merge main
swarkewalia Dec 3, 2025
08d4047
merge
swarkewalia Dec 13, 2025
d2aef46
merge fixes
swarkewalia Dec 13, 2025
63fa43a
fix typeorm
swarkewalia Dec 13, 2025
265bad6
merge fix
swarkewalia Jan 18, 2026
1c948a7
Merge branch 'sk/SSF-97-pantry-management-backend' of https://github.…
swarkewalia Jan 20, 2026
ab18d04
Merge branch 'main' of https://github.com/Code-4-Community/ssf into s…
swarkewalia Jan 21, 2026
4256a27
Merge branch 'sk/SSF-97-pantry-management-backend' of https://github.…
swarkewalia Jan 21, 2026
fb5ac86
merge fix
swarkewalia Jan 21, 2026
3489633
gitignore fix
swarkewalia Jan 22, 2026
72fc25d
git ignore fix 2
swarkewalia Jan 22, 2026
ddda63f
auth service fix
swarkewalia Jan 22, 2026
ba519d2
extra files fixes
swarkewalia Jan 22, 2026
1681f42
originally changed files
swarkewalia Jan 22, 2026
fe9f0fd
updated package.json
swarkewalia Jan 24, 2026
b1fffbb
prettier
swarkewalia Jan 25, 2026
0f9df9c
fixed yarn.lock
swarkewalia Jan 25, 2026
af4fea6
add more approved pantry info
swarkewalia Jan 27, 2026
66a7677
Merge branch 'main' into sk/SSF-97-pantry-management-backend
swarkewalia Jan 27, 2026
85a317f
get endpoint test
swarkewalia Jan 27, 2026
0094616
Merge branch 'sk/SSF-97-pantry-management-backend' of https://github.…
swarkewalia Jan 27, 2026
842ccf0
updatepantryvolunteers endpoint
swarkewalia Jan 27, 2026
8f25130
updates
swarkewalia Jan 28, 2026
ae4f126
more changes
swarkewalia Jan 30, 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
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ npm-debug.log
yarn-error.log
testem.log
/typings
.nx

Choose a reason for hiding this comment

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

Why is this here? Can we get rid of it?



# System Files
.DS_Store
Thumbs.db
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/pantries/dtos/pantry-application.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,4 @@ export class PantryApplicationDto {
@IsOptional()
@IsBoolean()
newsletterSubscription?: boolean;
}
}
82 changes: 82 additions & 0 deletions apps/backend/src/pantries/pantries.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import { PantriesController } from './pantries.controller';
import { PantriesService } from './pantries.service';
import { OrdersService } from '../orders/order.service';
import { Order } from '../orders/order.entity';
import {
Activity,
AllergensConfidence,
ApprovedPantryResponse,
ClientVisitFrequency,
ReserveFoodForAllergic,
ServeAllergicChildren,
} from './types';
import { RefrigeratedDonation } from './types';

const mockPantriesService = mock<PantriesService>();
const mockOrdersService = mock<OrdersService>();
Expand Down Expand Up @@ -59,4 +68,77 @@ describe('PantriesController', () => {
expect(mockOrdersService.getOrdersByPantry).toHaveBeenCalledWith(24);
});
});

describe('getApprovedPantries', () => {
it('should return approved pantries with volunteers', async () => {
const mockApprovedPantries: ApprovedPantryResponse[] = [
{
pantryId: 1,
pantryName: 'Community Food Pantry',

contactFirstName: 'John',
contactLastName: 'Smith',
contactEmail: 'john.smith@example.com',
contactPhone: '(508) 508-6789',

shipmentAddressLine1: '123 Main Street',
shipmentAddressCity: 'Boston',
shipmentAddressZip: '02101',
shipmentAddressCountry: 'United States',

allergenClients: '10 to 20',
restrictions: ['Peanuts', 'Dairy'],

refrigeratedDonation: RefrigeratedDonation.YES,
reserveFoodForAllergic: ReserveFoodForAllergic.YES,
reservationExplanation:
'We regularly serve clients with severe allergies.',

dedicatedAllergyFriendly: true,

clientVisitFrequency: ClientVisitFrequency.FEW_TIMES_A_MONTH,
identifyAllergensConfidence: AllergensConfidence.VERY_CONFIDENT,
serveAllergicChildren: ServeAllergicChildren.YES_MANY,

activities: [
Activity.POST_RESOURCE_FLYERS,
Activity.CREATE_LABELED_SHELF,
],
activitiesComments: 'Weekly food distribution events',

itemsInStock: 'Canned goods, rice, pasta',
needMoreOptions: 'Gluten-free and nut-free items',

newsletterSubscription: true,
},
];

mockPantriesService.getApprovedPantriesWithVolunteers.mockResolvedValue(
mockApprovedPantries,
);

const result = await controller.getApprovedPantries();

expect(result).toEqual(mockApprovedPantries);
expect(
mockPantriesService.getApprovedPantriesWithVolunteers,
).toHaveBeenCalledTimes(1);
});
});

describe('updatePantryVolunteers', () => {
it('should overwrite the set of volunteers assigned to a pantry', async () => {
const pantryId = 1;
const volunteerIds = [10, 11, 12];

mockPantriesService.updatePantryVolunteers.mockResolvedValue(undefined);

await controller.updatePantryVolunteers(pantryId, volunteerIds);

expect(mockPantriesService.updatePantryVolunteers).toHaveBeenCalledWith(
pantryId,
volunteerIds,
);
});
});
});
15 changes: 15 additions & 0 deletions apps/backend/src/pantries/pantries.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import {
Param,
ParseIntPipe,
Post,
Put,
ValidationPipe,
} from '@nestjs/common';
import { Pantry } from './pantries.entity';
import { PantriesService } from './pantries.service';
import { PantryApplicationDto } from './dtos/pantry-application.dto';
import { ApiBody } from '@nestjs/swagger';
import { ApprovedPantryResponse } from './types';
import {
Activity,
AllergensConfidence,
Expand All @@ -34,6 +36,11 @@ export class PantriesController {
return this.pantriesService.getPendingPantries();
}

@Get('/approved')

Choose a reason for hiding this comment

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

We should write a test for this as well.

async getApprovedPantries(): Promise<ApprovedPantryResponse[]> {
return this.pantriesService.getApprovedPantriesWithVolunteers();
}

@Get('/:pantryId')
async getPantry(
@Param('pantryId', ParseIntPipe) pantryId: number,
Expand Down Expand Up @@ -225,4 +232,12 @@ export class PantriesController {
): Promise<void> {
return this.pantriesService.deny(pantryId);
}

@Put('/:pantryId/volunteers')
async updatePantryVolunteers(
@Param('pantryId', ParseIntPipe) pantryId: number,
@Body() volunteerIds: number[],
): Promise<void> {
return this.pantriesService.updatePantryVolunteers(pantryId, volunteerIds);
}
}
2 changes: 1 addition & 1 deletion apps/backend/src/pantries/pantries.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class Pantry {
enum: ReserveFoodForAllergic,
enumName: 'reserve_food_for_allergic_enum',
})
reserveFoodForAllergic: string;
reserveFoodForAllergic: ReserveFoodForAllergic;

@Column({ name: 'reservation_explanation', type: 'text', nullable: true })
reservationExplanation?: string;
Expand Down
56 changes: 56 additions & 0 deletions apps/backend/src/pantries/pantries.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { validateId } from '../utils/validation.utils';
import { PantryStatus } from './types';
import { PantryApplicationDto } from './dtos/pantry-application.dto';
import { Role } from '../users/types';
import { ApprovedPantryResponse } from './types';

@Injectable()
export class PantriesService {
Expand Down Expand Up @@ -110,6 +111,61 @@ export class PantriesService {
await this.repo.update(id, { status: PantryStatus.DENIED });
}

async getApprovedPantriesWithVolunteers(): Promise<ApprovedPantryResponse[]> {

Choose a reason for hiding this comment

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

See comment in the type.ts file, this will need to change too.

const pantries = await this.repo.find({
where: { status: PantryStatus.APPROVED },
relations: ['pantryUser'],
});

return pantries.map((pantry) => ({
pantryId: pantry.pantryId,
pantryName: pantry.pantryName,
contactFirstName: pantry.pantryUser.firstName,
contactLastName: pantry.pantryUser.lastName,
contactEmail: pantry.pantryUser.email,
contactPhone: pantry.pantryUser.phone,
shipmentAddressLine1: pantry.shipmentAddressLine1,
shipmentAddressCity: pantry.shipmentAddressCity,
shipmentAddressZip: pantry.shipmentAddressZip,
shipmentAddressCountry: pantry.shipmentAddressCountry,
allergenClients: pantry.allergenClients,
restrictions: pantry.restrictions,
refrigeratedDonation: pantry.refrigeratedDonation,
reserveFoodForAllergic: pantry.reserveFoodForAllergic,
reservationExplanation: pantry.reservationExplanation,
dedicatedAllergyFriendly: pantry.dedicatedAllergyFriendly,
clientVisitFrequency: pantry.clientVisitFrequency,
identifyAllergensConfidence: pantry.identifyAllergensConfidence,
serveAllergicChildren: pantry.serveAllergicChildren,
activities: pantry.activities,
activitiesComments: pantry.activitiesComments,
itemsInStock: pantry.itemsInStock,
needMoreOptions: pantry.needMoreOptions,
newsletterSubscription: pantry.newsletterSubscription ?? false,
}));
}

async updatePantryVolunteers(
pantryId: number,
volunteerIds: number[],
): Promise<void> {
validateId(pantryId, 'Pantry');
volunteerIds.forEach((id) => validateId(id, 'Volunteer'));

const pantry = await this.repo.findOne({
where: { pantryId },
relations: ['volunteers'],
});

if (!pantry) {
throw new NotFoundException(`Pantry with ID ${pantryId} not found`);
}

pantry.volunteers = volunteerIds.map((id) => ({ id } as User));

await this.repo.save(pantry);
}

async findByIds(pantryIds: number[]): Promise<Pantry[]> {
pantryIds.forEach((id) => validateId(id, 'Pantry'));

Expand Down
43 changes: 43 additions & 0 deletions apps/backend/src/pantries/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
export interface ApprovedPantryResponse {
pantryId: number;
pantryName: string;
contactFirstName: string;
contactLastName: string;
contactEmail: string;
contactPhone: string;
shipmentAddressLine1: string;
shipmentAddressCity: string;
shipmentAddressZip: string;
shipmentAddressCountry?: string;
allergenClients: string;
restrictions: string[];
refrigeratedDonation: RefrigeratedDonation;
reserveFoodForAllergic: ReserveFoodForAllergic;
reservationExplanation?: string;
dedicatedAllergyFriendly: boolean;
clientVisitFrequency?: ClientVisitFrequency;
identifyAllergensConfidence?: AllergensConfidence;
serveAllergicChildren?: ServeAllergicChildren;
activities: Activity[];
activitiesComments?: string;
itemsInStock: string;
needMoreOptions: string;
newsletterSubscription: boolean;
}

export interface AssignedVolunteer {
userId: number;
name: string;
email: string;
phone: string;
role: string;
}

export interface AssignedVolunteer {
userId: number;
name: string;
email: string;
phone: string;
role: string;
}

export enum RefrigeratedDonation {
YES = 'Yes, always',
NO = 'No',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1252,4 +1252,4 @@ export const submitPantryApplicationForm: ActionFunction = async ({
: null;
};

export default PantryApplicationForm;
export default PantryApplicationForm;
2 changes: 1 addition & 1 deletion apps/frontend/src/components/forms/usPhoneInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ export const USPhoneInput: React.FC<USPhoneInputProps> = ({
{...inputProps}
/>
);
};
};
2 changes: 1 addition & 1 deletion apps/frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ root.render(
<App />
</ChakraProvider>
</StrictMode>,
);
);
2 changes: 1 addition & 1 deletion apps/frontend/src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,4 @@ const customConfig = defineConfig({
},
});

export const system = createSystem(defaultConfig, customConfig);
export const system = createSystem(defaultConfig, customConfig);
Loading