diff --git a/apps/backend/src/allocations/allocations.service.spec.ts b/apps/backend/src/allocations/allocations.service.spec.ts index 395c88ecb..1cfee9da9 100644 --- a/apps/backend/src/allocations/allocations.service.spec.ts +++ b/apps/backend/src/allocations/allocations.service.spec.ts @@ -55,30 +55,6 @@ describe('AllocationsService', () => { expect(service).toBeDefined(); }); - describe('getAllAllocationsByOrder', () => { - it('should return empty array for order with no allocations', async () => { - await testDataSource.query(`DELETE FROM allocations WHERE order_id = 1`); - - const result = await service.getAllAllocationsByOrder(1); - - expect(result).toEqual([]); - }); - - it('should return all allocations for a given order', async () => { - const result = await service.getAllAllocationsByOrder(2); - - expect(result).toHaveLength(3); - const quantities = result - .map((a) => a.allocatedQuantity) - .sort((a, b) => a! - b!); - expect(quantities).toEqual([15, 20, 30]); - result.forEach((a) => { - expect(a.allocationId).toBeDefined(); - expect(a.item).toBeDefined(); - }); - }); - }); - describe('createMultiple', () => { it('should create a single allocation and increment reservedQuantity', async () => { const orderId = 1; diff --git a/apps/backend/src/allocations/allocations.service.ts b/apps/backend/src/allocations/allocations.service.ts index bd951892c..cba5e6923 100644 --- a/apps/backend/src/allocations/allocations.service.ts +++ b/apps/backend/src/allocations/allocations.service.ts @@ -13,19 +13,6 @@ export class AllocationsService { private donationItemRepo: Repository, ) {} - async getAllAllocationsByOrder( - orderId: number, - ): Promise[]> { - return this.repo.find({ - where: { orderId }, - relations: ['item'], - select: { - allocationId: true, - allocatedQuantity: true, - }, - }); - } - // This function assumes that orderId and itemAllocations were already correctly validated (see call in create method of OrdersService) async createMultiple( orderId: number, diff --git a/apps/backend/src/auth/ownership.guard.ts b/apps/backend/src/auth/ownership.guard.ts index 4527d04a6..0d56280d3 100644 --- a/apps/backend/src/auth/ownership.guard.ts +++ b/apps/backend/src/auth/ownership.guard.ts @@ -44,7 +44,7 @@ export class OwnershipGuard implements CanActivate { return true; } - // Specified bypass ownership checks for other roles + // Specified roles bypass ownership checks for other roles if (config.bypassRoles?.includes(user.role as Role)) { return true; } diff --git a/apps/backend/src/donationItems/donationItems.controller.ts b/apps/backend/src/donationItems/donationItems.controller.ts index b4fd4f531..ca2cf168b 100644 --- a/apps/backend/src/donationItems/donationItems.controller.ts +++ b/apps/backend/src/donationItems/donationItems.controller.ts @@ -8,12 +8,38 @@ import { import { DonationItemsService } from './donationItems.service'; import { DonationItem } from './donationItems.entity'; import { AuthGuard } from '@nestjs/passport'; +import { Roles } from '../auth/roles.decorator'; +import { Role } from '../users/types'; +import { + CheckOwnership, + OwnerIdResolver, + pipeNullable, +} from '../auth/ownership.decorator'; +import { DonationService } from '../donations/donations.service'; +import { Donation } from '../donations/donations.entity'; + +const resolveDonationAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, +}) => + pipeNullable( + () => services.get(DonationService).findOne(entityId), + (donation: Donation) => [ + donation.foodManufacturer.foodManufacturerRepresentative.id, + ], + ); @Controller('donation-items') @UseGuards(AuthGuard('jwt')) export class DonationItemsController { constructor(private donationItemsService: DonationItemsService) {} + @CheckOwnership({ + idParam: 'donationId', + resolver: resolveDonationAuthorizedUserIds, + bypassRoles: [Role.ADMIN], + }) + @Roles(Role.ADMIN, Role.FOODMANUFACTURER) @Get('/:donationId/all') async getAllDonationItemsForDonation( @Param('donationId', ParseIntPipe) donationId: number, diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index 554afe7d6..d2c3d31aa 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -92,18 +92,6 @@ describe('DonationsController', () => { }); }); - describe('PATCH /:donationId/fulfill', () => { - it('should call donationService.fulfill', async () => { - const donationId = 1; - - mockDonationService.fulfill.mockResolvedValueOnce(undefined); - - await controller.fulfillDonation(donationId); - - expect(mockDonationService.fulfill).toHaveBeenCalledWith(donationId); - }); - }); - describe('PATCH /:donationId/item-details', () => { it('calls updateDonationItemDetails with the correct donationId and body, returns result', async () => { const donationId = 1; diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index ab493edd5..53128a3b8 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -18,19 +18,54 @@ import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donat import { FoodType } from '../donationItems/types'; import { Roles } from '../auth/roles.decorator'; import { Role } from '../users/types'; -import { CheckOwnership, pipeNullable } from '../auth/ownership.decorator'; +import { + CheckOwnership, + OwnerIdResolver, + pipeNullable, +} from '../auth/ownership.decorator'; import { FoodManufacturersService } from '../foodManufacturers/manufacturers.service'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; +const resolveDonationAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, +}) => + pipeNullable( + () => services.get(DonationService).findOne(entityId), + (donation: Donation) => [ + donation.foodManufacturer.foodManufacturerRepresentative.id, + ], + ); + +// For creating a donation, the foodManufacturerId comes from the request body +// and the only authorized non-admin caller is the manufacturer representative. +const resolveCreateDonationAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, +}) => + pipeNullable( + () => services.get(FoodManufacturersService).findOne(entityId), + (manufacturer: FoodManufacturer) => [ + manufacturer.foodManufacturerRepresentative.id, + ], + ); + @Controller('donations') export class DonationsController { constructor(private donationService: DonationService) {} + @Roles(Role.ADMIN) @Get() async getAllDonations(): Promise { return this.donationService.getAll(); } + @Roles(Role.FOODMANUFACTURER) + @CheckOwnership({ + idParam: 'foodManufacturerId', + idSource: 'body', + resolver: resolveCreateDonationAuthorizedUserIds, + }) @Post() @ApiBody({ description: 'Details for creating a donation', @@ -86,28 +121,10 @@ export class DonationsController { return this.donationService.create(body); } - @Patch('/:donationId/fulfill') - async fulfillDonation( - @Param('donationId', ParseIntPipe) donationId: number, - ): Promise { - await this.donationService.fulfill(donationId); - } - - @Roles(Role.ADMIN, Role.FOODMANUFACTURER) + @Roles(Role.FOODMANUFACTURER) @CheckOwnership({ idParam: 'donationId', - resolver: async ({ entityId, services }) => { - return pipeNullable( - () => services.get(DonationService).findOne(entityId), - (donation: Donation) => - services - .get(FoodManufacturersService) - .findOne(donation.foodManufacturer.foodManufacturerId), - (manufacturer: FoodManufacturer) => [ - manufacturer.foodManufacturerRepresentative.id, - ], - ); - }, + resolver: resolveDonationAuthorizedUserIds, }) @Patch('/:donationId/item-details') async updateDonationItemDetails( @@ -118,6 +135,12 @@ export class DonationsController { await this.donationService.updateDonationItemDetails(donationId, body); } + @Roles(Role.FOODMANUFACTURER) + @CheckOwnership({ + idParam: 'donationId', + resolver: resolveDonationAuthorizedUserIds, + }) + @Roles(Role.FOODMANUFACTURER) @Delete('/:donationId') async deleteDonation( @Param('donationId', ParseIntPipe) donationId: number, diff --git a/apps/backend/src/donations/donations.service.ts b/apps/backend/src/donations/donations.service.ts index 7fe8664b3..4e3228427 100644 --- a/apps/backend/src/donations/donations.service.ts +++ b/apps/backend/src/donations/donations.service.ts @@ -42,7 +42,10 @@ export class DonationService { const donation = await this.repo.findOne({ where: { donationId }, - relations: ['foodManufacturer'], + relations: [ + 'foodManufacturer', + 'foodManufacturer.foodManufacturerRepresentative', + ], }); if (!donation) { throw new NotFoundException(`Donation ${donationId} not found`); diff --git a/apps/backend/src/foodManufacturers/manufacturers.controller.spec.ts b/apps/backend/src/foodManufacturers/manufacturers.controller.spec.ts index f59dd7c77..ac7d46fa5 100644 --- a/apps/backend/src/foodManufacturers/manufacturers.controller.spec.ts +++ b/apps/backend/src/foodManufacturers/manufacturers.controller.spec.ts @@ -91,7 +91,7 @@ describe('FoodManufacturersController', () => { }); }); - describe('GET /:foodManufacturerId/donations', () => { + describe('GET /me/donations', () => { it('should return donation details for a given food manufacturer', async () => { const mockDonations: Partial[] = [ { @@ -135,16 +135,19 @@ describe('FoodManufacturersController', () => { const req = { user: { id: 1 } }; + mockManufacturersService.findByUserId.mockResolvedValueOnce({ + foodManufacturerId: 1, + } as FoodManufacturer); mockManufacturersService.getFMDonations.mockResolvedValue( mockDonationDetails, ); const result = await controller.getFoodManufacturerDonations( req as AuthenticatedRequest, - 1, ); expect(result).toBe(mockDonationDetails); + expect(mockManufacturersService.findByUserId).toHaveBeenCalledWith(1); expect(mockManufacturersService.getFMDonations).toHaveBeenCalledWith( 1, 1, @@ -152,8 +155,9 @@ describe('FoodManufacturersController', () => { }); }); - describe('GET /:foodManufacturerId/next-two-reminders', () => { - it('should return the next two upcoming donation reminders for a given food manufacturer', async () => { + describe('GET /me/next-two-reminders', () => { + it('should return the next two upcoming donation reminders for the authenticated manufacturer', async () => { + const req = { user: { id: 3 } }; const mockDonationReminders: DonationReminderDto[] = [ { donation: { @@ -171,13 +175,19 @@ describe('FoodManufacturersController', () => { }, ]; + mockManufacturersService.findByUserId.mockResolvedValueOnce({ + foodManufacturerId: 1, + } as FoodManufacturer); mockManufacturersService.getUpcomingDonationReminders.mockResolvedValue( mockDonationReminders, ); - const result = await controller.getNextTwoDonationReminders(1); + const result = await controller.getNextTwoDonationReminders( + req as AuthenticatedRequest, + ); expect(result).toEqual(mockDonationReminders); + expect(mockManufacturersService.findByUserId).toHaveBeenCalledWith(3); expect( mockManufacturersService.getUpcomingDonationReminders, ).toHaveBeenCalledWith(1); diff --git a/apps/backend/src/foodManufacturers/manufacturers.controller.ts b/apps/backend/src/foodManufacturers/manufacturers.controller.ts index b56ad4a86..f58aa8861 100644 --- a/apps/backend/src/foodManufacturers/manufacturers.controller.ts +++ b/apps/backend/src/foodManufacturers/manufacturers.controller.ts @@ -23,12 +23,28 @@ import { DonationDetailsDto, DonationReminderDto, } from './dtos/donation-details-dto'; -import { CheckOwnership, pipeNullable } from '../auth/ownership.decorator'; +import { + CheckOwnership, + OwnerIdResolver, + pipeNullable, +} from '../auth/ownership.decorator'; + +const resolveFoodManufacturerAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, +}) => + pipeNullable( + () => services.get(FoodManufacturersService).findOne(entityId), + (manufacturer: FoodManufacturer) => [ + manufacturer.foodManufacturerRepresentative.id, + ], + ); @Controller('manufacturers') export class FoodManufacturersController { constructor(private foodManufacturersService: FoodManufacturersService) {} + @Roles(Role.ADMIN) @Get('/pending') async getPendingManufacturers(): Promise { return this.foodManufacturersService.getPendingManufacturers(); @@ -45,6 +61,11 @@ export class FoodManufacturersController { return manufacturer.foodManufacturerId; } + @Roles(Role.ADMIN, Role.FOODMANUFACTURER) + @CheckOwnership({ + idParam: 'foodManufacturerId', + resolver: resolveFoodManufacturerAuthorizedUserIds, + }) @Get('/:foodManufacturerId') async getFoodManufacturer( @Param('foodManufacturerId', ParseIntPipe) foodManufacturerId: number, @@ -53,34 +74,29 @@ export class FoodManufacturersController { } @Roles(Role.FOODMANUFACTURER) - @Get('/:foodManufacturerId/donations') + @Get('/me/donations') async getFoodManufacturerDonations( @Req() req: AuthenticatedRequest, - @Param('foodManufacturerId', ParseIntPipe) foodManufacturerId: number, ): Promise { + const manufacturer = await this.foodManufacturersService.findByUserId( + req.user.id, + ); return this.foodManufacturersService.getFMDonations( - foodManufacturerId, + manufacturer.foodManufacturerId, req.user.id, ); } - @CheckOwnership({ - idParam: 'foodManufacturerId', - resolver: async ({ entityId, services }) => - pipeNullable( - () => services.get(FoodManufacturersService).findOne(entityId), - (manufacturer: FoodManufacturer) => [ - manufacturer.foodManufacturerRepresentative.id, - ], - ), - }) @Roles(Role.FOODMANUFACTURER) - @Get('/:foodManufacturerId/next-two-reminders') + @Get('/me/next-two-reminders') async getNextTwoDonationReminders( - @Param('foodManufacturerId', ParseIntPipe) foodManufacturerId: number, + @Req() req: AuthenticatedRequest, ): Promise { + const manufacturer = await this.foodManufacturersService.findByUserId( + req.user.id, + ); return this.foodManufacturersService.getUpcomingDonationReminders( - foodManufacturerId, + manufacturer.foodManufacturerId, ); } @@ -215,31 +231,37 @@ export class FoodManufacturersController { } @Roles(Role.FOODMANUFACTURER) - @Patch('/:manufacturerId/application') + @CheckOwnership({ + idParam: 'foodManufacturerId', + resolver: resolveFoodManufacturerAuthorizedUserIds, + }) + @Patch('/:foodManufacturerId/application') async updateFoodManufacturerApplication( @Req() req: AuthenticatedRequest, - @Param('manufacturerId', ParseIntPipe) manufacturerId: number, + @Param('foodManufacturerId', ParseIntPipe) foodManufacturerId: number, @Body(new ValidationPipe()) foodManufacturerData: UpdateFoodManufacturerApplicationDto, ): Promise { return this.foodManufacturersService.updateFoodManufacturerApplication( - manufacturerId, + foodManufacturerId, foodManufacturerData, req.user.id, ); } - @Patch('/:manufacturerId/approve') + @Roles(Role.ADMIN) + @Patch('/:foodManufacturerId/approve') async approveManufacturer( - @Param('manufacturerId', ParseIntPipe) manufacturerId: number, + @Param('foodManufacturerId', ParseIntPipe) foodManufacturerId: number, ): Promise { - return this.foodManufacturersService.approve(manufacturerId); + return this.foodManufacturersService.approve(foodManufacturerId); } - @Patch('/:manufacturerId/deny') + @Roles(Role.ADMIN) + @Patch('/:foodManufacturerId/deny') async denyManufacturer( - @Param('manufacturerId', ParseIntPipe) manufacturerId: number, + @Param('foodManufacturerId', ParseIntPipe) foodManufacturerId: number, ): Promise { - return this.foodManufacturersService.deny(manufacturerId); + return this.foodManufacturersService.deny(foodManufacturerId); } } diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index ae6017ef8..d3b2d5470 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -84,21 +84,6 @@ describe('RequestsController', () => { }); }); - describe('GET /:requestId', () => { - it('should call requestsService.findOne and return a specific food request', async () => { - const requestId = 1; - - mockRequestsService.findOne.mockResolvedValueOnce( - foodRequest1 as FoodRequest, - ); - - const result = await controller.getRequest(requestId); - - expect(result).toEqual(foodRequest1); - expect(mockRequestsService.findOne).toHaveBeenCalledWith(requestId); - }); - }); - describe('GET /:requestId/order-details', () => { it('should call requestsService.getOrderDetails and return all associated orders and their details', async () => { const mockOrderDetails: OrderDetailsDto[] = [ diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index 316bdec9f..b1f1ffa31 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -71,18 +71,6 @@ export class RequestsController { return this.requestsService.getAll(); } - @CheckOwnership({ - idParam: 'requestId', - resolver: resolveRequestAuthorizedUserIds, - }) - @Roles(Role.PANTRY, Role.ADMIN, Role.VOLUNTEER) - @Get('/:requestId') - async getRequest( - @Param('requestId', ParseIntPipe) requestId: number, - ): Promise { - return this.requestsService.findOne(requestId); - } - @CheckOwnership({ idParam: 'requestId', resolver: resolveRequestAuthorizedUserIds, @@ -163,7 +151,7 @@ export class RequestsController { ); } - @Roles(Role.PANTRY) + @Roles(Role.ADMIN, Role.VOLUNTEER) @CheckOwnership({ idParam: 'requestId', resolver: resolveRequestAuthorizedUserIds, @@ -176,7 +164,7 @@ export class RequestsController { await this.requestsService.update(requestId, body); } - @Roles(Role.PANTRY) + @Roles(Role.ADMIN, Role.VOLUNTEER) @CheckOwnership({ idParam: 'requestId', resolver: resolveRequestAuthorizedUserIds, diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index da8b70c01..e4ce974bc 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -141,20 +141,6 @@ describe('OrdersController', () => { }); }); - describe('getPantryFromOrder', () => { - it('should call ordersService.findOrderPantry and return pantry', async () => { - const orderId = 1; - mockOrdersService.findOrderPantry.mockResolvedValueOnce( - mockPantries[0] as Pantry, - ); - - const result = await controller.getPantryFromOrder(orderId); - - expect(result).toEqual(mockPantries[0] as Pantry); - expect(mockOrdersService.findOrderPantry).toHaveBeenCalledWith(orderId); - }); - }); - describe('getRequestFromOrder', () => { it('should call ordersService.findOrderFoodRequest and return food request', async () => { const orderId = 1; @@ -171,21 +157,6 @@ describe('OrdersController', () => { }); }); - describe('getAllAllocationsByOrder', () => { - it('should call allocationsService.getAllAllocationsByOrder and return allocations', async () => { - const orderId = 1; - mockAllocationsService.getAllAllocationsByOrder.mockResolvedValueOnce( - mockAllocations.slice(0, 2) as Allocation[], - ); - - const result = await controller.getAllAllocationsByOrder(orderId); - - expect(result).toEqual(mockAllocations.slice(0, 2) as Allocation[]); - expect( - mockAllocationsService.getAllAllocationsByOrder, - ).toHaveBeenCalledWith(orderId); - }); - }); describe('confirmDelivery', () => { beforeEach(() => { mockAWSS3Service.upload.mockReset(); diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index f53f25a5d..f9c0ed4ce 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -18,9 +18,17 @@ import { ApiBody } from '@nestjs/swagger'; import { OrdersService } from './order.service'; import { Order } from './order.entity'; import { Pantry } from '../pantries/pantries.entity'; -import { AllocationsService } from '../allocations/allocations.service'; -import { CheckOwnership, pipeNullable } from '../auth/ownership.decorator'; +import { OrderStatus } from './types'; +import { + CheckOwnership, + OwnerIdResolver, + pipeNullable, +} from '../auth/ownership.decorator'; import { PantriesService } from '../pantries/pantries.service'; +import { RequestsService } from '../foodRequests/request.service'; +import { FoodRequest } from '../foodRequests/request.entity'; +import { DonationService } from '../donations/donations.service'; +import { Donation } from '../donations/donations.entity'; import { BulkUpdateTrackingCostDto } from './dtos/bulk-update-tracking-cost.dto'; import { OrderDetailsDto } from './dtos/order-details.dto'; import { FoodRequestSummaryDto } from '../foodRequests/dtos/food-request-summary.dto'; @@ -33,19 +41,59 @@ import { CreateOrderDto } from './dtos/create-order.dto'; import { AuthenticatedRequest } from '../auth/authenticated-request'; import { Roles } from '../auth/roles.decorator'; import { Role } from '../users/types'; -import { OrderStatus } from './types'; + +const resolveOrderAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, + user, +}) => { + if (user?.role === Role.VOLUNTEER) { + return pipeNullable( + () => services.get(OrdersService).findOne(entityId), + (order: Order) => [order.assigneeId], + ); + } + return pipeNullable( + () => services.get(OrdersService).findOrderFoodRequest(entityId), + (request: FoodRequestSummaryDto) => + services.get(PantriesService).findOne(request.pantry.pantryId), + (pantry: Pantry) => [pantry.pantryUser.id], + ); +}; + +const resolveCreateOrderAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, +}) => + pipeNullable( + () => services.get(RequestsService).findOne(entityId), + (request: FoodRequest) => + services.get(PantriesService).findOne(request.pantryId), + (pantry: Pantry) => (pantry.volunteers ?? []).map((v) => v.id), + ); + +const resolveDonationAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, +}) => + pipeNullable( + () => services.get(DonationService).findOne(entityId), + (donation: Donation) => [ + donation.foodManufacturer.foodManufacturerRepresentative.id, + ], + ); @Controller('orders') export class OrdersController { constructor( private readonly ordersService: OrdersService, - private readonly allocationsService: AllocationsService, private readonly awsS3Service: AWSS3Service, ) {} // Called like: /?status=pending&pantryName=Test%20Pantry&pantryName=Test%20Pantry%202 // %20 is the URL encoded space character // This gets all orders where the status is pending and the pantry name is either Test Pantry or Test Pantry 2 + @Roles(Role.ADMIN) @Get('/') async getAllOrders( @Query('status') status?: string, @@ -57,25 +105,9 @@ export class OrdersController { return this.ordersService.getAll({ status, pantryNames }); } - @Get('/:orderId/pantry') - async getPantryFromOrder( - @Param('orderId', ParseIntPipe) orderId: number, - ): Promise { - return this.ordersService.findOrderPantry(orderId); - } - - // Test endpoint for right now @CheckOwnership({ idParam: 'orderId', - resolver: async ({ entityId, services }) => { - return pipeNullable( - () => services.get(OrdersService).findOrderFoodRequest(entityId), - (request: FoodRequestSummaryDto) => - services.get(PantriesService).findOne(request.pantry.pantryId), - (pantry: Pantry) => [pantry.pantryUser.id], - ); - }, - bypassRoles: [Role.VOLUNTEER, Role.ADMIN], + resolver: resolveOrderAuthorizedUserIds, }) @Roles(Role.VOLUNTEER, Role.PANTRY, Role.ADMIN) @Get('/:orderId/request') @@ -85,6 +117,11 @@ export class OrdersController { return this.ordersService.findOrderFoodRequest(orderId); } + @CheckOwnership({ + idParam: 'orderId', + resolver: resolveOrderAuthorizedUserIds, + }) + @Roles(Role.VOLUNTEER, Role.PANTRY, Role.ADMIN) @Get('/:orderId') async getOrder( @Param('orderId', ParseIntPipe) orderId: number, @@ -92,13 +129,12 @@ export class OrdersController { return this.ordersService.findOrderDetails(orderId); } - @Get('/:orderId/allocations') - async getAllAllocationsByOrder( - @Param('orderId', ParseIntPipe) orderId: number, - ) { - return this.allocationsService.getAllAllocationsByOrder(orderId); - } - + @Roles(Role.ADMIN, Role.VOLUNTEER) + @CheckOwnership({ + idParam: 'foodRequestId', + idSource: 'body', + resolver: resolveCreateOrderAuthorizedUserIds, + }) @Post('/') @ApiBody({ description: 'Details for creating a order', @@ -173,6 +209,11 @@ export class OrdersController { ); } + @Roles(Role.VOLUNTEER) + @CheckOwnership({ + idParam: 'orderId', + resolver: resolveOrderAuthorizedUserIds, + }) @Patch('/update-status/:orderId') async updateStatus( @Param('orderId', ParseIntPipe) orderId: number, @@ -185,6 +226,11 @@ export class OrdersController { } @Roles(Role.FOODMANUFACTURER) + @CheckOwnership({ + idParam: 'donationId', + idSource: 'body', + resolver: resolveDonationAuthorizedUserIds, + }) @Patch('/bulk-update-tracking-cost-info') async bulkUpdateTrackingCostInfo( @Body(new ValidationPipe()) dto: BulkUpdateTrackingCostDto, @@ -192,6 +238,11 @@ export class OrdersController { return this.ordersService.bulkUpdateTrackingCostInfo(dto); } + @CheckOwnership({ + idParam: 'orderId', + resolver: resolveOrderAuthorizedUserIds, + }) + @Roles(Role.PANTRY) @Patch('/:orderId/confirm-delivery') @ApiBody({ description: 'Details for a confirmation of order delivery form', @@ -257,11 +308,7 @@ export class OrdersController { @CheckOwnership({ idParam: 'orderId', - resolver: async ({ entityId, services }) => - pipeNullable( - () => services.get(OrdersService).findOne(entityId), - (order: Order) => [order.assigneeId], - ), + resolver: resolveOrderAuthorizedUserIds, }) @Roles(Role.VOLUNTEER) @Patch('/:orderId/complete-action') diff --git a/apps/backend/src/pantries/pantries.controller.spec.ts b/apps/backend/src/pantries/pantries.controller.spec.ts index bc2c83f7b..9cbd1a752 100644 --- a/apps/backend/src/pantries/pantries.controller.spec.ts +++ b/apps/backend/src/pantries/pantries.controller.spec.ts @@ -353,8 +353,9 @@ describe('PantriesController', () => { }); describe('getOrders', () => { - it('should return orders for a pantry', async () => { - const pantryId = 24; + it('should return orders for the authenticated pantry user', async () => { + const req = { user: { id: 5 } }; + const pantry: Partial = { pantryId: 24 }; const mockOrders: Partial[] = [ { @@ -365,16 +366,18 @@ describe('PantriesController', () => { }, ]; + mockPantriesService.findByUserId.mockResolvedValueOnce(pantry as Pantry); mockOrdersService.getOrdersByPantry.mockResolvedValue( mockOrders as OrderSummary[], ); - const result = await controller.getOrders(pantryId); + const result = await controller.getOrders(req as AuthenticatedRequest); expect(result).toEqual(mockOrders); expect(result).toHaveLength(2); expect(result[0].orderId).toBe(26); expect(result[1].orderId).toBe(27); + expect(mockPantriesService.findByUserId).toHaveBeenCalledWith(5); expect(mockOrdersService.getOrdersByPantry).toHaveBeenCalledWith(24); }); }); @@ -538,7 +541,9 @@ describe('PantriesController', () => { }); describe('getFoodRequests', () => { - it('should call requestsService.find and return all food requests for a specific pantry', async () => { + it('should call requestsService.find and return all food requests for the authenticated pantry user', async () => { + const req = { user: { id: 7 } }; + const pantry: Partial = { pantryId: 1 }; const foodRequests: Partial[] = [ foodRequest1, { @@ -546,18 +551,19 @@ describe('PantriesController', () => { pantryId: 1, }, ]; - const pantryId = 1; + mockPantriesService.findByUserId.mockResolvedValueOnce(pantry as Pantry); mockRequestsService.findAllForPantry.mockResolvedValueOnce( foodRequests as FoodRequest[], ); - const result = await controller.getFoodRequests(pantryId); + const result = await controller.getFoodRequests( + req as AuthenticatedRequest, + ); expect(result).toEqual(foodRequests); - expect(mockRequestsService.findAllForPantry).toHaveBeenCalledWith( - pantryId, - ); + expect(mockPantriesService.findByUserId).toHaveBeenCalledWith(7); + expect(mockRequestsService.findAllForPantry).toHaveBeenCalledWith(1); }); }); }); diff --git a/apps/backend/src/pantries/pantries.controller.ts b/apps/backend/src/pantries/pantries.controller.ts index b8286b0d4..d45ad0177 100644 --- a/apps/backend/src/pantries/pantries.controller.ts +++ b/apps/backend/src/pantries/pantries.controller.ts @@ -30,7 +30,11 @@ import { OrderSummary, } from './types'; import { OrdersService } from '../orders/order.service'; -import { CheckOwnership, pipeNullable } from '../auth/ownership.decorator'; +import { + CheckOwnership, + OwnerIdResolver, + pipeNullable, +} from '../auth/ownership.decorator'; import { Public } from '../auth/public.decorator'; import { AuthenticatedRequest } from '../auth/authenticated-request'; import { UpdatePantryApplicationDto } from './dtos/update-pantry-application.dto'; @@ -38,6 +42,15 @@ import { UpdatePantryVolunteersDto } from './dtos/update-pantry-volunteers-dto'; import { RequestsService } from '../foodRequests/request.service'; import { FoodRequestSummaryDto } from '../foodRequests/dtos/food-request-summary.dto'; +const resolvePantryAuthorizedUserIds: OwnerIdResolver = ({ + entityId, + services, +}) => + pipeNullable( + () => services.get(PantriesService).findOne(entityId), + (pantry: Pantry) => [pantry.pantryUser.id], + ); + @Controller('pantries') export class PantriesController { constructor( @@ -96,6 +109,7 @@ export class PantriesController { return this.pantriesService.getPantryAdminStatsOrderYears(); } + @Roles(Role.ADMIN) @Get('/approved') async getApprovedPantries(): Promise { return this.pantriesService.getApprovedPantriesWithVolunteers(); @@ -103,12 +117,7 @@ export class PantriesController { @CheckOwnership({ idParam: 'pantryId', - resolver: async ({ entityId, services }) => { - return pipeNullable( - () => services.get(PantriesService).findOne(entityId), - (pantry: Pantry) => [pantry.pantryUser.id], - ); - }, + resolver: resolvePantryAuthorizedUserIds, }) @Roles(Role.PANTRY, Role.ADMIN) @Get('/:pantryId') @@ -118,20 +127,20 @@ export class PantriesController { return this.pantriesService.findOne(pantryId); } - @Roles(Role.ADMIN, Role.PANTRY) - @Get('/:pantryId/orders') - async getOrders( - @Param('pantryId', ParseIntPipe) pantryId: number, - ): Promise { - return this.ordersService.getOrdersByPantry(pantryId); + @Roles(Role.PANTRY) + @Get('/me/orders') + async getOrders(@Req() req: AuthenticatedRequest): Promise { + const pantry = await this.pantriesService.findByUserId(req.user.id); + return this.ordersService.getOrdersByPantry(pantry.pantryId); } - @Roles(Role.PANTRY, Role.ADMIN) - @Get('/:pantryId/requests') + @Roles(Role.PANTRY) + @Get('/me/requests') async getFoodRequests( - @Param('pantryId', ParseIntPipe) pantryId: number, + @Req() req: AuthenticatedRequest, ): Promise { - return this.requestsService.findAllForPantry(pantryId); + const pantry = await this.pantriesService.findByUserId(req.user.id); + return this.requestsService.findAllForPantry(pantry.pantryId); } @ApiBody({ @@ -378,6 +387,10 @@ export class PantriesController { return this.pantriesService.addPantry(pantryData); } + @CheckOwnership({ + idParam: 'pantryId', + resolver: resolvePantryAuthorizedUserIds, + }) @Roles(Role.PANTRY) @Patch('/:pantryId/application') async updatePantryApplication( diff --git a/apps/backend/src/users/users.controller.ts b/apps/backend/src/users/users.controller.ts index cf27de69b..f43701343 100644 --- a/apps/backend/src/users/users.controller.ts +++ b/apps/backend/src/users/users.controller.ts @@ -8,7 +8,6 @@ import { Body, Patch, Req, - UseGuards, } from '@nestjs/common'; import { UsersService } from './users.service'; import { User } from './users.entity'; @@ -16,22 +15,29 @@ import { userSchemaDto } from './dtos/userSchema.dto'; import { UpdateUserInfoDto } from './dtos/update-user-info.dto'; import { PendingApplication, Role } from './types'; import { AuthenticatedRequest } from '../auth/authenticated-request'; -import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { AdminVolunteerStats } from './dtos/admin-volunteer-stats.dto'; import { PantryStatsDto } from '../pantries/dtos/pantry-stats.dto'; import { ManufacturerStatsDto } from '../foodManufacturers/dtos/manufacturer-stats.dto'; import { Roles } from '../auth/roles.decorator'; +import { CheckOwnership, OwnerIdResolver } from '../auth/ownership.decorator'; + +const resolveUserAuthorizedUserIds: OwnerIdResolver = async ({ entityId }) => [ + entityId, +]; @Controller('users') export class UsersController { constructor(private usersService: UsersService) {} - @UseGuards(JwtAuthGuard) @Get('/me') getCurrentUser(@Req() req: AuthenticatedRequest): Promise { return this.usersService.findOne(req.user.id); } + @CheckOwnership({ + idParam: 'id', + resolver: resolveUserAuthorizedUserIds, + }) @Get('/:id/stats') async getUserDashboardStats( @Param('id', ParseIntPipe) userId: number, @@ -45,6 +51,16 @@ export class UsersController { return this.usersService.getRecentPendingApplications(); } + @Roles(Role.ADMIN) + @Delete('/:id') + async removeUser(@Param('id', ParseIntPipe) userId: number): Promise { + return this.usersService.remove(userId); + } + + @CheckOwnership({ + idParam: 'id', + resolver: resolveUserAuthorizedUserIds, + }) @Patch('/:id') async updateInfo( @Param('id', ParseIntPipe) id: number, @@ -53,14 +69,9 @@ export class UsersController { return this.usersService.update(id, dto); } - // Keeping these two as functionality seems useful + @Roles(Role.ADMIN) @Post('/') async createUser(@Body() createUserDto: userSchemaDto): Promise { return this.usersService.create(createUserDto); } - - @Delete('/:id') - removeUser(@Param('id', ParseIntPipe) userId: number): Promise { - return this.usersService.remove(userId); - } } diff --git a/apps/backend/src/volunteers/volunteers.controller.ts b/apps/backend/src/volunteers/volunteers.controller.ts index a04bd01ad..70f05e3c8 100644 --- a/apps/backend/src/volunteers/volunteers.controller.ts +++ b/apps/backend/src/volunteers/volunteers.controller.ts @@ -7,6 +7,11 @@ import { Assignments, VolunteerOrder } from './types'; import { AuthenticatedRequest } from '../auth/authenticated-request'; import { OrdersService } from '../orders/order.service'; import { FoodRequestSummaryDto } from '../foodRequests/dtos/food-request-summary.dto'; +import { CheckOwnership, OwnerIdResolver } from '../auth/ownership.decorator'; + +const resolveVolunteerAuthorizedUserIds: OwnerIdResolver = async ({ + entityId, +}) => [entityId]; @Controller('volunteers') export class VolunteersController { @@ -21,7 +26,11 @@ export class VolunteersController { return this.volunteersService.getVolunteersAndPantryAssignments(); } - @Roles(Role.VOLUNTEER, Role.ADMIN) + @CheckOwnership({ + idParam: 'id', + resolver: resolveVolunteerAuthorizedUserIds, + }) + @Roles(Role.VOLUNTEER) @Get('/:id/pantries') async getVolunteerPantries( @Param('id', ParseIntPipe) id: number, @@ -47,8 +56,10 @@ export class VolunteersController { return this.volunteersService.getRecentOrders(req.user.id); } - // returns all orders globally - // only includes actionCompletion for orders assigned to the requesting volunteer + @CheckOwnership({ + idParam: 'id', + resolver: resolveVolunteerAuthorizedUserIds, + }) @Roles(Role.VOLUNTEER) @Get('/:id/orders') async getVolunteerOrders( diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index 5e822dfb1..c63ed7272 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -114,24 +114,12 @@ export class ApiClient { .then((response) => response.data); } - public async getAllDonationsByFoodManufacturer( - foodManufacturerId: number, - ): Promise { + public async getAllDonationsByFoodManufacturer(): Promise { return this.axiosInstance - .get(`/api/manufacturers/${foodManufacturerId}/donations`) + .get('/api/manufacturers/me/donations') .then((response) => response.data); } - public async fulfillDonation( - donationId: number, - body?: unknown, - ): Promise { - await this.axiosInstance.patch( - `/api/donations/${donationId}/fulfill`, - body ?? {}, - ); - } - public async postUser(data: UserDto): Promise { return this.axiosInstance .post(`/api/users`, data) @@ -164,15 +152,9 @@ export class ApiClient { .then((response) => response.data); } - public async getPantryFromOrder(orderId: number): Promise { - return this.axiosInstance - .get(`/api/orders/${orderId}/pantry`) - .then((response) => response.data); - } - - public async getPantryOrders(pantryId: number): Promise { + public async getPantryOrders(): Promise { return this.axiosInstance - .get(`/api/pantries/${pantryId}/orders`) + .get('/api/pantries/me/orders') .then((response) => response.data); } @@ -282,12 +264,6 @@ export class ApiClient { .then((response) => response.data); } - public async getFoodRequest(requestId: number): Promise { - return this.axiosInstance - .get(`/api/requests/${requestId}`) - .then((response) => response.data); - } - public async getDonationItemsByDonationId( donationId: number, ): Promise { @@ -344,14 +320,6 @@ export class ApiClient { .then((response) => response.data); } - public async getAllAllocationsByOrder( - orderId: number, - ): Promise { - return this.axiosInstance - .get(`/api/orders/${orderId}/allocations`) - .then((response) => response.data); - } - public async updatePantryApplicationData( pantryId: number, data: UpdatePantryApplicationDto, @@ -400,11 +368,9 @@ export class ApiClient { ); } - public async getPantryRequests( - pantryId: number, - ): Promise { + public async getPantryRequests(): Promise { return this.axiosInstance - .get(`/api/pantries/${pantryId}/requests`) + .get('/api/pantries/me/requests') .then((response) => response.data); } @@ -434,11 +400,9 @@ export class ApiClient { .then((response) => response.data); } - public async getNextTwoDonationReminders( - foodManufacturerId: number, - ): Promise { + public async getNextTwoDonationReminders(): Promise { return this.axiosInstance - .get(`/api/manufacturers/${foodManufacturerId}/next-two-reminders`) + .get(`/api/manufacturers/me/next-two-reminders`) .then((response) => response.data); } diff --git a/apps/frontend/src/components/forms/orderInformationModal.tsx b/apps/frontend/src/components/forms/orderInformationModal.tsx deleted file mode 100644 index c38b62a55..000000000 --- a/apps/frontend/src/components/forms/orderInformationModal.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { VStack, Text, Dialog } from '@chakra-ui/react'; -import { useState, useEffect } from 'react'; -import ApiClient from '@api/apiClient'; -import { Allocation, Pantry } from 'types/types'; -import { useModalBodyCleanup } from '../../hooks/modalBodyCleanup'; - -interface OrderInformationModalProps { - orderId: number; - isOpen: boolean; - onClose: () => void; -} - -const OrderInformationModal: React.FC = ({ - orderId, - isOpen, - onClose, -}) => { - useModalBodyCleanup(); - const [pantry, setPantry] = useState(null); - const [allocationItems, setAllocationItems] = useState([]); - - useEffect(() => { - if (isOpen) { - const fetchData = async () => { - try { - const pantryData = await ApiClient.getPantryFromOrder(orderId); - const allocationItemData = await ApiClient.getAllAllocationsByOrder( - orderId, - ); - - setPantry(pantryData); - setAllocationItems(allocationItemData); - } catch { - console.error('Error fetching order details:'); - } - }; - - fetchData(); - } - }, [isOpen, orderId]); - - return ( - { - if (!e.open) onClose(); - }} - closeOnInteractOutside - > - - - - Order Details - - {pantry ? ( - - - Pantry Name: {pantry.pantryName} - - - Order Items: - {allocationItems.length > 0 ? ( - allocationItems.map((allocation) => ( - - - {allocation.allocatedQuantity}{' '} - {allocation.item.itemName} - - )) - ) : ( - No order contents available - )} - - - ) : ( - No data to load - )} - - - - - - ); -}; - -export default OrderInformationModal; diff --git a/apps/frontend/src/containers/donationManagement.tsx b/apps/frontend/src/containers/donationManagement.tsx deleted file mode 100644 index e7d038f4c..000000000 --- a/apps/frontend/src/containers/donationManagement.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { - Center, - Table, - Button, - Box, - Text, - useDisclosure, -} from '@chakra-ui/react'; -import ApiClient from '@api/apiClient'; -import NewDonationFormModal from '@components/forms/newDonationFormModal'; -import { formatDate } from '@utils/utils'; -import { Donation, DonationItem } from 'types/types'; -import { FloatingAlert } from '@components/floatingAlert'; -import { useAlert } from '../hooks/alert'; - -const DonationManagement: React.FC = () => { - const { open, onOpen, onClose } = useDisclosure(); - const [alertState, setAlertMessage] = useAlert(); - const [donations, setDonations] = useState([]); - const [expandedDonationIds, setExpandedDonationIds] = useState([]); - const [donationItems, setDonationItems] = useState<{ - [key: number]: DonationItem[]; - }>({}); - const [donationItemStock, setDonationItemStock] = useState<{ - [key: number]: number; - }>({}); - const [manufacturerId, setManufacturerId] = useState(null); - - useEffect(() => { - ApiClient.getCurrentUserFoodManufacturerId() - .then(setManufacturerId) - .catch(() => setManufacturerId(null)); - }, []); - - const fetchDonations = async () => { - try { - const data = await ApiClient.getAllDonations(); - const sortedDonations = data.sort((a, b) => { - if (a.status === 'fulfilled' && b.status !== 'fulfilled') return 1; - if (a.status !== 'fulfilled' && b.status === 'fulfilled') return -1; - return 0; - }); - setDonations(sortedDonations); - } catch { - setAlertMessage('Error fetching donations'); - } - }; - - const fetchDonationItems = async (donationId: number) => { - try { - const items = await ApiClient.getDonationItemsByDonationId(donationId); - setDonationItems((prev) => ({ - ...prev, - [donationId]: items, - })); - - items.forEach((item: DonationItem) => { - setDonationItemStock((prev) => ({ - ...prev, - [item.itemId]: item.quantity - item.reservedQuantity, - })); - }); - } catch { - setAlertMessage('Error fetching donation items'); - } - }; - - const toggleDropdown = (donationId: number) => { - if (expandedDonationIds.includes(donationId)) { - setExpandedDonationIds((prev) => prev.filter((id) => id !== donationId)); - } else { - setExpandedDonationIds((prev) => [...prev, donationId]); - if (!donationItems[donationId]) { - fetchDonationItems(donationId); - } - } - }; - - const fulfillDonation = async (donationId: number) => { - try { - await ApiClient.fulfillDonation(donationId); - fetchDonations(); - } catch { - setAlertMessage('Failed to fulfill donation'); - } - }; - - useEffect(() => { - fetchDonations(); - }, []); - - return ( -
- {alertState && ( - - )} - - {manufacturerId !== null && ( - - )} - - - - Donation ID - Date Donated - Status - Remaining Stock - Actions - - - - {donations.map((donation) => ( - - {donation.donationId} - {formatDate(donation.dateDonated)} - {donation.status} - - {expandedDonationIds.includes(donation.donationId) && - donationItems[donation.donationId]?.map((item) => ( - - - Item Name: {item.itemName} - - - Food Type: {item.foodType} - - - Remaining Stock:{' '} - {donationItemStock[item.itemId]} - - - ))} - toggleDropdown(donation.donationId)} - mt={2} - > - {expandedDonationIds.includes(donation.donationId) - ? 'Hide Information' - : 'Show Information'} - - - - {donation.status !== 'fulfilled' && ( - - )} - - - ))} - - -
- ); -}; - -export default DonationManagement; diff --git a/apps/frontend/src/containers/foodManufacturerDashboard.tsx b/apps/frontend/src/containers/foodManufacturerDashboard.tsx index 93dd02e0d..ca0045ca6 100644 --- a/apps/frontend/src/containers/foodManufacturerDashboard.tsx +++ b/apps/frontend/src/containers/foodManufacturerDashboard.tsx @@ -37,8 +37,8 @@ const FoodManufacturerDashboard: React.FC = () => { } const [reminders, donations] = await Promise.allSettled([ - ApiClient.getNextTwoDonationReminders(fmId), - ApiClient.getAllDonationsByFoodManufacturer(fmId), + ApiClient.getNextTwoDonationReminders(), + ApiClient.getAllDonationsByFoodManufacturer(), ]); // If reminders is successfully retrieved from API with the Promise.allSettled diff --git a/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx b/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx index 8005e28eb..a178c0ee2 100644 --- a/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx +++ b/apps/frontend/src/containers/foodManufacturerDonationManagement.tsx @@ -60,9 +60,9 @@ const FoodManufacturerDonationManagement: React.FC = () => { ); // Fetch all donations on component mount and sorts them into their appropriate status lists - const fetchDonations = async (fmId: number) => { + const fetchDonations = async () => { try { - const data = await ApiClient.getAllDonationsByFoodManufacturer(fmId); + const data = await ApiClient.getAllDonationsByFoodManufacturer(); const grouped: Record = { [DonationStatus.AVAILABLE]: [], @@ -135,7 +135,7 @@ const FoodManufacturerDonationManagement: React.FC = () => { try { const fmId = await ApiClient.getCurrentUserFoodManufacturerId(); setManufacturerId(fmId); - const grouped = await fetchDonations(fmId); + const grouped = await fetchDonations(); if (grouped) openResubmitFromQueryParam(grouped); } catch { setErrorMessage('Error initializing donation management'); @@ -224,7 +224,7 @@ const FoodManufacturerDonationManagement: React.FC = () => { {manufacturerId !== null && ( fetchDonations(manufacturerId)} + onDonationSuccess={() => fetchDonations()} isOpen={isLogDonationOpen} onClose={() => setIsLogDonationOpen(false)} /> @@ -234,7 +234,7 @@ const FoodManufacturerDonationManagement: React.FC = () => { fetchDonations(manufacturerId)} + onSuccess={() => fetchDonations()} donations={Object.values(statusDonations).flat()} foodManufacturerId={manufacturerId} initialDonationId={ @@ -253,7 +253,7 @@ const FoodManufacturerDonationManagement: React.FC = () => { onClose={() => setSelectedActionDonation(null)} onSuccess={() => { setSelectedActionDonation(null); - if (manufacturerId !== null) fetchDonations(manufacturerId); + fetchDonations(); setSuccessMessage( 'Your details have been saved. Actions are complete once all shipment and item details are confirmed.', ); diff --git a/apps/frontend/src/containers/formRequests.tsx b/apps/frontend/src/containers/formRequests.tsx index 40a7cfb48..c0efe3dd6 100644 --- a/apps/frontend/src/containers/formRequests.tsx +++ b/apps/frontend/src/containers/formRequests.tsx @@ -49,7 +49,7 @@ const FormRequests: React.FC = () => { setPantryId(pantryId); if (pantryId) { try { - const data = await ApiClient.getPantryRequests(pantryId); + const data = await ApiClient.getPantryRequests(); const sortedData = data .slice() .sort((a, b) => b.requestId - a.requestId); diff --git a/apps/frontend/src/containers/pantryDashboard.tsx b/apps/frontend/src/containers/pantryDashboard.tsx index 182c9a9ef..8085b4716 100644 --- a/apps/frontend/src/containers/pantryDashboard.tsx +++ b/apps/frontend/src/containers/pantryDashboard.tsx @@ -36,7 +36,7 @@ const PantryDashboard: React.FC = () => { } try { - const pantryFoodRequests = await ApiClient.getPantryRequests(pantryId); + const pantryFoodRequests = await ApiClient.getPantryRequests(); const sortedFoodRequests = pantryFoodRequests.sort( (a: FoodRequestSummaryDto, b: FoodRequestSummaryDto) => new Date(b.requestedAt).getTime() - @@ -48,7 +48,7 @@ const PantryDashboard: React.FC = () => { } try { - const pantryOrders = await ApiClient.getPantryOrders(pantryId); + const pantryOrders = await ApiClient.getPantryOrders(); const sortedOrders = pantryOrders.sort( (a: OrderSummary, b: OrderSummary) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), diff --git a/apps/frontend/src/containers/pantryOrderManagement.tsx b/apps/frontend/src/containers/pantryOrderManagement.tsx index 5ee5339c2..a493b1e62 100644 --- a/apps/frontend/src/containers/pantryOrderManagement.tsx +++ b/apps/frontend/src/containers/pantryOrderManagement.tsx @@ -89,8 +89,7 @@ const PantryOrderManagement: React.FC = () => { const fetchOrders = useCallback(async () => { try { - const pantryId = await ApiClient.getCurrentUserPantryId(); - const data = await ApiClient.getPantryOrders(pantryId); + const data = await ApiClient.getPantryOrders(); const grouped: Record = { [OrderStatus.SHIPPED]: [],