Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 apps/backend/src/foodRequests/request.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class RequestsController {
return this.requestsService.find(pantryId);
}

@Roles(Role.VOLUNTEER, Role.PANTRY, Role.ADMIN)
@Get('/:requestId/order-details')
async getAllOrderDetailsFromRequest(
@Param('requestId', ParseIntPipe) requestId: number,
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/src/foodRequests/request.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ describe('RequestsService', () => {
});

describe('find', () => {
it('should return all food requests for a specific pantry', async () => {
it('should return all food requests for a specific pantry with pantry details', async () => {
const pantryId = 1;
const result = await service.find(pantryId);

Expand All @@ -206,6 +206,7 @@ describe('RequestsService', () => {
result.forEach((request) => {
expect(request.orders).toBeDefined();
});
expect(result.every((r) => r.pantry)).toBeDefined();
});

it('should return empty array for pantry with no requests', async () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/foodRequests/request.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export class RequestsService {

return await this.repo.find({
where: { pantryId },
relations: ['orders'],
relations: ['orders', 'pantry'],
});
}

Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/pantries/pantries.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class PantriesController {
return this.pantriesService.getPendingPantries();
}

@Roles(Role.PANTRY, Role.ADMIN)
@Roles(Role.PANTRY, Role.ADMIN, Role.VOLUNTEER)
@Get('/:pantryId')
async getPantry(
@Param('pantryId', ParseIntPipe) pantryId: number,
Expand Down
33 changes: 28 additions & 5 deletions apps/backend/src/volunteers/volunteers.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { VolunteersController } from './volunteers.controller';
import { UsersController } from '../users/users.controller';
import { UsersService } from '../users/users.service';
import { User } from '../users/users.entity';
import { Role } from '../users/types';
import { Test, TestingModule } from '@nestjs/testing';
import { mock } from 'jest-mock-extended';
import { Pantry } from '../pantries/pantries.entity';
import { VolunteersService } from './volunteers.service';
import { FoodRequest } from '../foodRequests/request.entity';
import { AuthenticatedRequest } from '../auth/authenticated-request';

const mockVolunteersService = mock<VolunteersService>();

Expand Down Expand Up @@ -156,11 +155,35 @@ describe('VolunteersController', () => {

expect(result).toEqual(updatedUser);
expect(result.pantries).toHaveLength(2);
expect(result.pantries![0].pantryId).toBe(1);
expect(result.pantries![1].pantryId).toBe(3);
expect(result.pantries?.[0].pantryId).toBe(1);
expect(result.pantries?.[1].pantryId).toBe(3);
expect(
mockVolunteersService.assignPantriesToVolunteer,
).toHaveBeenCalledWith(3, pantryIds);
});
});

describe('GET /me/assigned-requests', () => {
it('returns assigned requests when req.currentUser is present', async () => {
const req: AuthenticatedRequest = {
user: { id: 1 },
} as AuthenticatedRequest;
const foodRequests: Partial<FoodRequest>[] = [
{ requestId: 10 },
{ requestId: 5 },
];
mockVolunteersService.findRequestsByVolunteer.mockResolvedValueOnce(
foodRequests as FoodRequest[],
);

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

expect(result).toEqual(foodRequests);
expect(
mockVolunteersService.findRequestsByVolunteer,
).toHaveBeenCalledWith(1);
});
});
});
13 changes: 13 additions & 0 deletions apps/backend/src/volunteers/volunteers.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {
ParseIntPipe,
Post,
Body,
Req,
} from '@nestjs/common';
import { User } from '../users/users.entity';
import { Pantry } from '../pantries/pantries.entity';
import { VolunteersService } from './volunteers.service';
import { Role } from '../users/types';
import { Roles } from '../auth/roles.decorator';
import { Assignments } from './types';
import { FoodRequest } from '../foodRequests/request.entity';
import { AuthenticatedRequest } from '../auth/authenticated-request';

@Controller('volunteers')
export class VolunteersController {
Expand Down Expand Up @@ -42,4 +45,14 @@ export class VolunteersController {
): Promise<User> {
return this.volunteersService.assignPantriesToVolunteer(id, pantryIds);
}

@Roles(Role.VOLUNTEER)
@Get('/me/assigned-requests')
async getAssignedRequests(
@Req() req: AuthenticatedRequest,
): Promise<FoodRequest[]> {
const currentUser = req.user;

return this.volunteersService.findRequestsByVolunteer(currentUser.id);
}
}
2 changes: 2 additions & 0 deletions apps/backend/src/volunteers/volunteers.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { AuthModule } from '../auth/auth.module';
import { VolunteersController } from './volunteers.controller';
import { VolunteersService } from './volunteers.service';
import { UsersModule } from '../users/users.module';
import { RequestsModule } from '../foodRequests/request.module';

@Module({
imports: [
TypeOrmModule.forFeature([User]),
UsersModule,
forwardRef(() => PantriesModule),
forwardRef(() => AuthModule),
RequestsModule,
],
controllers: [VolunteersController],
providers: [VolunteersService],
Expand Down
101 changes: 95 additions & 6 deletions apps/backend/src/volunteers/volunteers.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { testDataSource } from '../config/typeormTestDataSource';
import { UsersService } from '../users/users.service';
import { PantriesService } from '../pantries/pantries.service';
import { AuthService } from '../auth/auth.service';
import { RequestsService } from '../foodRequests/request.service';
import { FoodRequest } from '../foodRequests/request.entity';
import { Order } from '../orders/order.entity';
import { DonationItem } from '../donationItems/donationItems.entity';
import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity';

jest.setTimeout(60000);

Expand All @@ -25,6 +30,7 @@ describe('VolunteersService', () => {
VolunteersService,
UsersService,
PantriesService,
RequestsService,
{
provide: AuthService,
useValue: {},
Expand All @@ -37,6 +43,22 @@ describe('VolunteersService', () => {
provide: getRepositoryToken(Pantry),
useValue: testDataSource.getRepository(Pantry),
},
{
provide: getRepositoryToken(FoodRequest),
useValue: testDataSource.getRepository(FoodRequest),
},
{
provide: getRepositoryToken(Order),
useValue: testDataSource.getRepository(Order),
},
{
provide: getRepositoryToken(FoodManufacturer),
useValue: testDataSource.getRepository(FoodManufacturer),
},
{
provide: getRepositoryToken(DonationItem),
useValue: testDataSource.getRepository(DonationItem),
},
],
}).compile();

Expand Down Expand Up @@ -177,8 +199,8 @@ describe('VolunteersService', () => {
expect(beforePantryIds).toEqual([2, 3]);

const result = await service.assignPantriesToVolunteer(7, [1, 4]);
expect(result.pantries!).toHaveLength(4);
const afterPantryIds = result.pantries!.map((p) => p.pantryId);
expect(result.pantries).toHaveLength(4);
const afterPantryIds = result.pantries?.map((p) => p.pantryId);
expect(afterPantryIds).toEqual([2, 3, 1, 4]);
});

Expand All @@ -191,8 +213,8 @@ describe('VolunteersService', () => {
expect(beforeAssignment).toEqual([]);

const result = await service.assignPantriesToVolunteer(6, [2, 3]);
expect(result.pantries!).toHaveLength(2);
const pantryIds = result.pantries!.map((p) => p.pantryId);
expect(result.pantries).toHaveLength(2);
const pantryIds = result.pantries?.map((p) => p.pantryId);
expect(pantryIds).toEqual([2, 3]);
});

Expand All @@ -203,9 +225,76 @@ describe('VolunteersService', () => {
expect(beforePantryIds).toEqual([2, 3]);

const result = await service.assignPantriesToVolunteer(7, [2, 3]);
expect(result.pantries!).toHaveLength(2);
const pantryIds = result.pantries!.map((p) => p.pantryId);
expect(result.pantries).toHaveLength(2);
const pantryIds = result.pantries?.map((p) => p.pantryId);
expect(pantryIds).toEqual([2, 3]);
});
});

describe('findRequestsByVolunteer', () => {
it('returned requests include pantry info', async () => {
const requests = await service.findRequestsByVolunteer(7);
requests.forEach((request) => {
expect(request.pantry).toBeDefined();
expect(request.pantry).toHaveProperty('pantryName');
});
});

it('returns requests only from assigned pantries', async () => {
const volunteerId = 6;

const assignedPantries = await service.getVolunteerPantries(volunteerId);
const assignedPantryIds = assignedPantries.map((p) => p.pantryId);

const requests = await service.findRequestsByVolunteer(volunteerId);
requests.forEach((request) => {
expect(assignedPantryIds).toContain(request.pantryId);
});
});

it('returns empty array when volunteer has no assigned pantries', async () => {
const volunteerId = await testDataSource
.query(
`
INSERT INTO users (first_name, last_name, email, phone, role)
VALUES ('Test', 'Volunteer', 'test@volunteer.com', '537-280-1238', 'volunteer')
RETURNING user_id
`,
)
.then((rows) => rows[0].user_id);

const result = await service.findRequestsByVolunteer(volunteerId);
expect(result).toEqual([]);
});

it('returns empty array when assigned pantries have no requests', async () => {
const volunteerId = 8;

const assignedPantries = await service.getVolunteerPantries(volunteerId);
const assignedPantryIds = assignedPantries.map((p) => p.pantryId);
await testDataSource.query(
`DELETE FROM allocations
WHERE order_id IN (
SELECT o.order_id FROM orders o
JOIN food_requests fr ON o.request_id = fr.request_id
WHERE fr.pantry_id = ANY($1)
)`,
[assignedPantryIds],
);
await testDataSource.query(
`DELETE FROM orders
WHERE request_id IN (
SELECT request_id FROM food_requests WHERE pantry_id = ANY($1)
)`,
[assignedPantryIds],
);
await testDataSource.query(
`DELETE FROM food_requests WHERE pantry_id = ANY($1)`,
[assignedPantryIds],
);

const requests = await service.findRequestsByVolunteer(volunteerId);
expect(requests).toEqual([]);
});
});
});
16 changes: 16 additions & 0 deletions apps/backend/src/volunteers/volunteers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { Pantry } from '../pantries/pantries.entity';
import { PantriesService } from '../pantries/pantries.service';
import { UsersService } from '../users/users.service';
import { Assignments } from './types';
import { FoodRequest } from '../foodRequests/request.entity';
import { RequestsService } from '../foodRequests/request.service';

@Injectable()
export class VolunteersService {
Expand All @@ -16,6 +18,7 @@ export class VolunteersService {
private repo: Repository<User>,
private usersService: UsersService,
private pantriesService: PantriesService,
private requestsService: RequestsService,
) {}

async findOne(id: number): Promise<User> {
Expand Down Expand Up @@ -73,4 +76,17 @@ export class VolunteersService {
volunteer.pantries = [...existingPantries, ...newPantries];
return this.repo.save(volunteer);
}

async findRequestsByVolunteer(volunteerId: number): Promise<FoodRequest[]> {
validateId(volunteerId, 'Volunteer');

const pantries = await this.getVolunteerPantries(volunteerId);
const pantryIds = pantries.map((p) => p.pantryId);

const requestArrays = await Promise.all(
pantryIds.map((id) => this.requestsService.find(id)),
);

return requestArrays.flat();
}
}
6 changes: 6 additions & 0 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
OrderSummary,
UserDto,
OrderDetails,
FoodRequestSummaryDto,
Assignments,
} from 'types/types';

Expand Down Expand Up @@ -283,6 +284,11 @@ export class ApiClient {
return data as FoodRequest[];
}

public async getVolunteerAssignedRequests(): Promise<FoodRequest[]> {
const data = await this.get(`/api/volunteers/me/assigned-requests`);
return data as FoodRequest[];
}

public async confirmDelivery(
requestId: number,
data: FormData,
Expand Down
9 changes: 9 additions & 0 deletions apps/frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { Authenticator } from '@aws-amplify/ui-react';
import FoodManufacturerApplication from '@containers/foodManufacturerApplication';
import { submitManufacturerApplicationForm } from '@components/forms/manufacturerApplicationForm';
import AssignedPantries from '@containers/volunteerAssignedPantries';
import VolunteerRequestManagement from '@containers/volunteerRequestManagement';

Amplify.configure(CognitoAuthConfig);

Expand Down Expand Up @@ -189,6 +190,14 @@ const router = createBrowserRouter([
</ProtectedRoute>
),
},
{
path: '/volunteer-request-management',
element: (
<ProtectedRoute>
<VolunteerRequestManagement />
</ProtectedRoute>
),
},
],
},
]);
Expand Down
Loading