From e43c333a02ade2d98244c0e541aeaf543b3e8d8d Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Wed, 20 May 2026 21:36:10 -0700 Subject: [PATCH 1/4] Complete audit --- .../dtos/create-donation-items.dto.ts | 16 --- .../donations/donations.controller.spec.ts | 87 ------------ .../src/donations/donations.controller.ts | 33 ----- .../src/donations/donations.service.spec.ts | 130 ------------------ .../src/donations/donations.service.ts | 90 ------------ .../foodRequests/dtos/update-request.dto.ts | 25 ---- .../foodRequests/request.controller.spec.ts | 30 ---- .../src/foodRequests/request.controller.ts | 17 --- .../src/foodRequests/request.service.spec.ts | 120 ---------------- .../src/foodRequests/request.service.ts | 67 --------- .../src/orders/order.controller.spec.ts | 66 --------- apps/backend/src/orders/order.controller.ts | 30 ---- apps/backend/src/orders/order.service.spec.ts | 70 ---------- apps/backend/src/orders/order.service.ts | 44 ------ .../src/users/users.controller.spec.ts | 11 -- apps/backend/src/users/users.controller.ts | 16 +-- .../volunteers/volunteers.controller.spec.ts | 35 ----- .../src/volunteers/volunteers.controller.ts | 24 +--- .../src/volunteers/volunteers.service.spec.ts | 40 ------ .../src/volunteers/volunteers.service.ts | 21 --- apps/frontend/src/api/apiClient.ts | 42 ------ 21 files changed, 7 insertions(+), 1007 deletions(-) delete mode 100644 apps/backend/src/foodRequests/dtos/update-request.dto.ts diff --git a/apps/backend/src/donationItems/dtos/create-donation-items.dto.ts b/apps/backend/src/donationItems/dtos/create-donation-items.dto.ts index 84c662cc7..3380ef7a6 100644 --- a/apps/backend/src/donationItems/dtos/create-donation-items.dto.ts +++ b/apps/backend/src/donationItems/dtos/create-donation-items.dto.ts @@ -1,8 +1,6 @@ import { IsNumber, IsString, - IsArray, - ValidateNested, Min, IsEnum, IsNotEmpty, @@ -11,7 +9,6 @@ import { IsInt, IsBoolean, } from 'class-validator'; -import { Type } from 'class-transformer'; import { FoodType } from '../types'; export class CreateDonationItemDto { @@ -46,16 +43,3 @@ export class CreateDonationItemDto { @IsBoolean() foodRescue!: boolean; } - -export class ReplaceDonationItemDto extends CreateDonationItemDto { - @IsOptional() - @IsNumber() - id?: number; -} - -export class ReplaceDonationItemsDto { - @IsArray() - @ValidateNested({ each: true }) - @Type(() => ReplaceDonationItemDto) - items!: ReplaceDonationItemDto[]; -} diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index 20400d45a..fc2da742a 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -7,8 +7,6 @@ import { CreateDonationDto } from './dtos/create-donation.dto'; import { CreateDonationItemDto } from '../donationItems/dtos/create-donation-items.dto'; import { DonationStatus, RecurrenceEnum } from './types'; import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donation-item-details.dto'; -import { ReplaceDonationItemsDto } from '../donationItems/dtos/create-donation-items.dto'; -import { FoodType } from '../donationItems/types'; const mockDonationService = mock(); @@ -26,8 +24,6 @@ describe('DonationsController', () => { let controller: DonationsController; beforeEach(async () => { - mockDonationService.getNumberOfDonations.mockReset(); - const module: TestingModule = await Test.createTestingModule({ controllers: [DonationsController], providers: [ @@ -45,44 +41,6 @@ describe('DonationsController', () => { expect(controller).toBeDefined(); }); - describe('GET /', () => { - it('should call donationService.getAll and return array of donations', async () => { - mockDonationService.getAll.mockResolvedValueOnce([ - donation1, - donation2, - ] as Donation[]); - - const result = await controller.getAllDonations(); - - expect(result).toEqual([donation1, donation2]); - expect(mockDonationService.getAll).toHaveBeenCalled(); - }); - }); - - describe('GET /count', () => { - it.each([[0], [5]])('should return %i donations', async (count) => { - mockDonationService.getNumberOfDonations.mockResolvedValue(count); - - const result = await controller.getNumberOfDonations(); - - expect(result).toBe(count); - expect(mockDonationService.getNumberOfDonations).toHaveBeenCalled(); - }); - }); - - describe('GET /:donationId', () => { - it('should return a donation for a given donation ID', async () => { - const mockDonation: Partial = { donationId: 1 }; - - mockDonationService.findOne.mockResolvedValue(mockDonation as Donation); - - const result = await controller.getDonation(1); - - expect(result).toBe(mockDonation); - expect(mockDonationService.findOne).toHaveBeenCalledWith(1); - }); - }); - describe('POST /', () => { it('should call donationService.create and return the created donation', async () => { const createBody: Partial = { @@ -157,49 +115,4 @@ describe('DonationsController', () => { ).toHaveBeenCalledWith(donationId, body); }); }); - - describe('PUT /:donationId/items', () => { - it('should call donationService.replaceDonationItems', async () => { - const donationId = 1; - - const replaceBody = { - items: [ - { - id: 1, - itemName: 'Apples', - quantity: 10, - foodType: FoodType.DAIRY_FREE_ALTERNATIVES, - }, - { - itemName: 'Oranges', - quantity: 5, - foodType: FoodType.DAIRY_FREE_ALTERNATIVES, - }, - ], - }; - - mockDonationService.replaceDonationItems.mockResolvedValueOnce(undefined); - - await controller.replaceDonationItems( - donationId, - replaceBody as ReplaceDonationItemsDto, - ); - - expect(mockDonationService.replaceDonationItems).toHaveBeenCalledWith( - donationId, - replaceBody, - ); - }); - }); - - describe('DELETE /:donationId', () => { - it('should call donationService.delete with the correct id', async () => { - const donationId = 1; - - await controller.deleteDonation(donationId); - - expect(mockDonationService.delete).toHaveBeenCalledWith(donationId); - expect(mockDonationService.delete).toHaveBeenCalledTimes(1); - }); - }); }); diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index 19657b089..0698fd8d1 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -17,7 +17,6 @@ import { RecurrenceEnum } from './types'; import { CreateDonationDto } from './dtos/create-donation.dto'; import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donation-item-details.dto'; import { FoodType } from '../donationItems/types'; -import { ReplaceDonationItemsDto } from '../donationItems/dtos/create-donation-items.dto'; import { Roles } from '../auth/roles.decorator'; import { Role } from '../users/types'; import { CheckOwnership, pipeNullable } from '../auth/ownership.decorator'; @@ -28,23 +27,6 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; export class DonationsController { constructor(private donationService: DonationService) {} - @Get() - async getAllDonations(): Promise { - return this.donationService.getAll(); - } - - @Get('/count') - async getNumberOfDonations(): Promise { - return this.donationService.getNumberOfDonations(); - } - - @Get('/:donationId') - async getDonation( - @Param('donationId', ParseIntPipe) donationId: number, - ): Promise { - return this.donationService.findOne(donationId); - } - @Post() @ApiBody({ description: 'Details for creating a donation', @@ -131,19 +113,4 @@ export class DonationsController { ): Promise { await this.donationService.updateDonationItemDetails(donationId, body); } - - @Put('/:donationId/items') - async replaceDonationItems( - @Param('donationId', ParseIntPipe) donationId: number, - @Body() body: ReplaceDonationItemsDto, - ): Promise { - await this.donationService.replaceDonationItems(donationId, body); - } - - @Delete('/:donationId') - async deleteDonation( - @Param('donationId', ParseIntPipe) donationId: number, - ): Promise { - return this.donationService.delete(donationId); - } } diff --git a/apps/backend/src/donations/donations.service.spec.ts b/apps/backend/src/donations/donations.service.spec.ts index 7fc1b0b67..5b3393856 100644 --- a/apps/backend/src/donations/donations.service.spec.ts +++ b/apps/backend/src/donations/donations.service.spec.ts @@ -12,10 +12,6 @@ import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donat import { DonationItemsService } from '../donationItems/donationItems.service'; import { Allocation } from '../allocations/allocations.entity'; import { DataSource, In } from 'typeorm'; -import { - ReplaceDonationItemDto, - ReplaceDonationItemsDto, -} from '../donationItems/dtos/create-donation-items.dto'; import { FoodType } from '../donationItems/types'; jest.setTimeout(60000); @@ -234,13 +230,6 @@ describe('DonationService', () => { }); }); - describe('getNumberOfDonations', () => { - it('returns total number of donations in the database', async () => { - const donationCount = await service.getNumberOfDonations(); - expect(donationCount).toEqual(4); - }); - }); - describe('matchAll', () => { it('updates all given donations to have status MATCHED', async () => { const donationId1 = 1; @@ -1060,125 +1049,6 @@ describe('DonationService', () => { }); }); - describe('replaceDonationItems', () => { - it('should replace donation items for an available donation', async () => { - const donationId = 1; - - // (update item1, remove item2, remove item3, add item 4) - const body = { - items: [ - { - id: 1, - itemName: 'Green Apples', - quantity: 15, - } as Partial, - { - itemName: 'Bananas', - quantity: 20, - foodType: FoodType.DAIRY_FREE_ALTERNATIVES, - } as Partial, - ], - } as ReplaceDonationItemsDto; - - // manually removing allocations for deleted item ids - await service['allocationRepo'].delete({ itemId: In([2, 3]) }); - - await service.replaceDonationItems(donationId, body); - - const updatedItems = await donationItemService.getAllDonationItems( - donationId, - ); - expect(updatedItems).toHaveLength(2); - - const updatedItemNames = updatedItems.map((i) => i.itemName); - expect(updatedItemNames).toContain('Green Apples'); // updated - expect(updatedItemNames).toContain('Bananas'); // new - expect(updatedItemNames).not.toContain('Canned Green Beans'); // deleted - expect(updatedItemNames).not.toContain('Whole Wheat Bread'); // deleted - }); - - it('should throw BadRequestException if allocation exists for deleted donation item', async () => { - const donationId = 1; - - // (update item1, remove item2, remove item3, add item 4) - const body = { - items: [ - { - id: 1, - itemName: 'Green Apples', - quantity: 15, - } as Partial, - { - itemName: 'Bananas', - quantity: 20, - foodType: FoodType.DAIRY_FREE_ALTERNATIVES, - } as Partial, - ], - } as ReplaceDonationItemsDto; - - await expect( - service.replaceDonationItems(donationId, body), - ).rejects.toThrow( - `Cannot delete donation item(s) with existing allocation(s), replacing donation items failed and not exectued`, - ); - }); - - it('should delete all donation items for an available donation when passed an empty array', async () => { - const donationId = 1; - - const body = { - items: [], - } as ReplaceDonationItemsDto; - - // manually removing allocations for deleted item ids - await service['allocationRepo'].delete({ itemId: In([1, 2, 3]) }); - - await service.replaceDonationItems(donationId, body); - - const updatedItems = await donationItemService.getAllDonationItems( - donationId, - ); - expect(updatedItems).toHaveLength(0); - }); - - it('should throw NotFoundException if donation does not exist', async () => { - const body = { items: [] }; - await expect(service.replaceDonationItems(9999, body)).rejects.toThrow( - `Donation 9999 not found`, - ); - }); - - it('should throw BadRequestException if donation is not AVAILABLE', async () => { - // Donation with status MATCHED - const donationId = 2; - - const body = { items: [] }; - await expect( - service.replaceDonationItems(donationId, body), - ).rejects.toThrow('Only available donations can be updated'); - }); - - it('should throw NotFoundException if trying to update an item that does not exist within current donation', async () => { - const donationId = 1; - - const body = { - items: [ - { - id: 9999, - itemName: 'Nonexistent', - quantity: 1, - } as Partial, - ], - } as ReplaceDonationItemsDto; - - await expect( - service.replaceDonationItems(donationId, body), - ).rejects.toThrow( - `Donation item 9999 for Donation ${donationId} not found`, - ); - }); - }); - describe('delete', () => { it('should delete an available donation and associated donation items', async () => { const donationId = 3; diff --git a/apps/backend/src/donations/donations.service.ts b/apps/backend/src/donations/donations.service.ts index 88263b43c..1e3982052 100644 --- a/apps/backend/src/donations/donations.service.ts +++ b/apps/backend/src/donations/donations.service.ts @@ -15,7 +15,6 @@ import { CreateDonationDto, RepeatOnDaysDto } from './dtos/create-donation.dto'; import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donation-item-details.dto'; import { DonationItemsService } from '../donationItems/donationItems.service'; -import { ReplaceDonationItemsDto } from '../donationItems/dtos/create-donation-items.dto'; import { DonationItem } from '../donationItems/donationItems.entity'; import { Allocation } from '../allocations/allocations.entity'; @@ -54,10 +53,6 @@ export class DonationService { }); } - async getNumberOfDonations(): Promise { - return this.repo.count(); - } - async create(donationData: CreateDonationDto): Promise { validateId(donationData.foodManufacturerId, 'Food Manufacturer'); const manufacturer = await this.manufacturerRepo.findOne({ @@ -408,91 +403,6 @@ export class DonationService { return donation; } - async replaceDonationItems( - donationId: number, - body: ReplaceDonationItemsDto, - ): Promise { - validateId(donationId, 'Donation'); - - const donation = await this.repo.findOne({ - where: { donationId }, - relations: ['donationItems'], - }); - - if (!donation) { - throw new NotFoundException(`Donation ${donationId} not found`); - } - - if (donation.status !== DonationStatus.AVAILABLE) { - throw new BadRequestException(`Only available donations can be updated`); - } - - const existingItems = donation.donationItems || []; - const incomingItems = body.items || []; - - const existingMap = new Map( - existingItems.map((item) => [item.itemId, item]), - ); - - const incomingIds = new Set( - incomingItems.filter((i) => i.id).map((i) => i.id), - ); - - const itemsToDelete = existingItems.filter( - (item) => !incomingIds.has(item.itemId), - ); - - donation.donationItems = []; - - for (const incoming of incomingItems) { - if (incoming.id) { - const existing = existingMap.get(incoming.id); - if (!existing) { - throw new NotFoundException( - `Donation item ${incoming.id} for Donation ${donationId} not found`, - ); - } - // Merge the incoming changes into the existing donation item entity by matching ids. - donation.donationItems.push( - this.donationItemsRepo.merge(existing, incoming), - ); - } else { - // Create new item and attach to donation - donation.donationItems.push( - this.donationItemsRepo.create({ ...incoming, donation }), - ); - } - } - - await this.dataSource.transaction(async (transactionManager) => { - const transactionRepo = transactionManager.getRepository(DonationItem); - const transactionAllocationRepo = - transactionManager.getRepository(Allocation); - - if (itemsToDelete.length > 0) { - const hasAllocations = await transactionAllocationRepo.exists({ - where: { - item: { - itemId: In(itemsToDelete.map((i) => i.itemId)), - }, - }, - }); - - if (hasAllocations) { - throw new BadRequestException( - `Cannot delete donation item(s) with existing allocation(s), replacing donation items failed and not exectued`, - ); - } - - await transactionRepo.remove(itemsToDelete); - } - - if (donation.donationItems.length > 0) { - await transactionRepo.save(donation.donationItems); - } - }); - } - async delete(donationId: number): Promise { validateId(donationId, 'Donation'); diff --git a/apps/backend/src/foodRequests/dtos/update-request.dto.ts b/apps/backend/src/foodRequests/dtos/update-request.dto.ts deleted file mode 100644 index 06c1fd277..000000000 --- a/apps/backend/src/foodRequests/dtos/update-request.dto.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - ArrayNotEmpty, - IsEnum, - IsNotEmpty, - IsOptional, - IsString, -} from 'class-validator'; -import { RequestSize } from '../types'; -import { FoodType } from '../../donationItems/types'; - -export class UpdateRequestDto { - @IsOptional() - @IsEnum(RequestSize) - requestedSize?: RequestSize; - - @IsOptional() - @ArrayNotEmpty() - @IsEnum(FoodType, { each: true }) - requestedFoodTypes?: FoodType[]; - - @IsOptional() - @IsString() - @IsNotEmpty() - additionalInformation?: string; -} diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 6e77d179d..0a2f7cfa1 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -8,7 +8,6 @@ import { OrderStatus } from '../orders/types'; import { FoodType } from '../donationItems/types'; import { OrderDetailsDto } from '../orders/dtos/order-details.dto'; import { CreateRequestDto } from './dtos/create-request.dto'; -import { UpdateRequestDto } from './dtos/update-request.dto'; import { DonationItemDetailsDto, MatchingItemsDto, @@ -44,8 +43,6 @@ describe('RequestsController', () => { mockRequestsService.findOne.mockReset(); mockRequestsService.create.mockReset(); mockRequestsService.getOrderDetails.mockReset(); - mockRequestsService.update.mockReset(); - mockRequestsService.delete.mockReset(); const module: TestingModule = await Test.createTestingModule({ controllers: [RequestsController], @@ -221,33 +218,6 @@ describe('RequestsController', () => { }); }); - describe('PATCH /:requestId', () => { - it('should update request with valid information', async () => { - mockRequestsService.update.mockResolvedValue(undefined); - - const updateRequestDto: UpdateRequestDto = { - requestedSize: RequestSize.MEDIUM, - }; - await controller.updateRequest(1, updateRequestDto); - - expect(mockRequestsService.update).toHaveBeenCalledWith( - 1, - updateRequestDto, - ); - }); - }); - - describe('DELETE /:requestId', () => { - it('should delete a request by id', async () => { - mockRequestsService.delete.mockResolvedValue(undefined); - - const result = await controller.deleteRequest(1); - - expect(result).toBeUndefined(); - expect(mockRequestsService.delete).toHaveBeenCalledWith(1); - }); - }); - describe('GET /:requestId/matching-manufacturers/:foodManufacturerId/available-items', () => { it('should call requestsService.getAvailableItems and return grouped items', async () => { const requestId = 1; diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index 1a494d3cc..ddb33938e 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -7,7 +7,6 @@ import { Body, ValidationPipe, Patch, - Delete, } from '@nestjs/common'; import { ApiBody } from '@nestjs/swagger'; import { RequestsService } from './request.service'; @@ -23,7 +22,6 @@ import { MatchingItemsDto, MatchingManufacturersDto, } from './dtos/matching.dto'; -import { UpdateRequestDto } from './dtos/update-request.dto'; @Controller('requests') export class RequestsController { @@ -105,21 +103,6 @@ export class RequestsController { ); } - @Patch('/:requestId') - async updateRequest( - @Param('requestId', ParseIntPipe) requestId: number, - @Body(new ValidationPipe()) body: UpdateRequestDto, - ): Promise { - await this.requestsService.update(requestId, body); - } - - @Delete('/:requestId') - async deleteRequest( - @Param('requestId', ParseIntPipe) requestId: number, - ): Promise { - return this.requestsService.delete(requestId); - } - @Roles(Role.VOLUNTEER) @Patch('/:requestId/close') async closeRequest( diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 67f2ce891..921c8370e 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -615,126 +615,6 @@ describe('RequestsService', () => { }); }); - describe('update', () => { - it('should update request attributes', async () => { - await testDataSource.query( - `DELETE FROM allocations WHERE order_id IN (SELECT order_id FROM orders WHERE request_id = 1)`, - ); - await testDataSource.query(`DELETE FROM orders WHERE request_id = 1`); - - await service.update(1, { - requestedSize: RequestSize.MEDIUM, - }); - - const fromDb = await service.findOne(1); - expect(fromDb.requestedSize).toBe(RequestSize.MEDIUM); - expect(fromDb.requestedFoodTypes).toEqual([ - FoodType.SEED_BUTTERS, - FoodType.GLUTEN_FREE_BREAD, - FoodType.DRIED_BEANS, - FoodType.DAIRY_FREE_ALTERNATIVES, - ]); - }); - - it('should throw NotFoundException when request is not found', async () => { - await expect( - service.update(9999, { requestedSize: RequestSize.MEDIUM }), - ).rejects.toThrow(new NotFoundException('Request 9999 not found')); - }); - - it('should update all request attributes when all fields are provided', async () => { - await testDataSource.query( - `DELETE FROM allocations WHERE order_id IN (SELECT order_id FROM orders WHERE request_id = 1)`, - ); - await testDataSource.query(`DELETE FROM orders WHERE request_id = 1`); - - await service.update(1, { - requestedSize: RequestSize.SMALL, - requestedFoodTypes: [FoodType.GRANOLA], - additionalInformation: 'Updated information', - }); - - const fromDb = await service.findOne(1); - expect(fromDb.requestedSize).toBe(RequestSize.SMALL); - expect(fromDb.requestedFoodTypes).toEqual([FoodType.GRANOLA]); - expect(fromDb.additionalInformation).toBe('Updated information'); - }); - - it('should throw BadRequestException when request is not active', async () => { - await testDataSource.query( - `UPDATE food_requests SET status = 'closed' WHERE request_id = 1`, - ); - - await expect( - service.update(1, { requestedSize: RequestSize.MEDIUM }), - ).rejects.toThrow( - new BadRequestException( - `Request must be ${FoodRequestStatus.ACTIVE} in order to be updated`, - ), - ); - }); - - it('should throw BadRequestException when request has orders', async () => { - await expect( - service.update(2, { requestedSize: RequestSize.MEDIUM }), - ).rejects.toThrow( - new BadRequestException( - `Request 2 cannot be updated if it still has orders associated with it`, - ), - ); - }); - - it('should throw BadRequestException when all DTO fields are undefined', async () => { - await expect(service.update(1, {})).rejects.toThrow( - new BadRequestException( - 'At least one field must be provided to update request', - ), - ); - }); - }); - - describe('delete', () => { - it('should delete a request by id', async () => { - await testDataSource.query( - `DELETE FROM allocations WHERE order_id IN (SELECT order_id FROM orders WHERE request_id = 1)`, - ); - await testDataSource.query(`DELETE FROM orders WHERE request_id = 1`); - - await service.delete(1); - - const fromDb = await testDataSource - .getRepository(FoodRequest) - .findOneBy({ requestId: 1 }); - expect(fromDb).toBeNull(); - }); - - it('should throw BadRequestException when request is not active', async () => { - await testDataSource.query( - `UPDATE food_requests SET status = 'closed' WHERE request_id = 1`, - ); - - await expect(service.delete(1)).rejects.toThrow( - new BadRequestException( - `Request must be ${FoodRequestStatus.ACTIVE} in order to be deleted`, - ), - ); - }); - - it('should throw BadRequestException when request has orders', async () => { - await expect(service.delete(2)).rejects.toThrow( - new BadRequestException( - `Request 2 cannot be deleted if it still has orders associated with it`, - ), - ); - }); - - it('should throw NotFoundException when request is not found', async () => { - await expect(service.delete(9999)).rejects.toThrow( - new NotFoundException('Request 9999 not found'), - ); - }); - }); - describe('closeRequest', () => { it('should close an active request', async () => { await service.closeRequest(3); diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 12a4dd511..1dd3b02c6 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -23,7 +23,6 @@ import { FoodType } from '../donationItems/types'; import { DonationItem } from '../donationItems/donationItems.entity'; import { EmailsService } from '../emails/email.service'; import { emailTemplates } from '../emails/emailTemplates'; -import { UpdateRequestDto } from './dtos/update-request.dto'; @Injectable() export class RequestsService { @@ -308,72 +307,6 @@ export class RequestsService { await this.repo.save(request); } - async update(requestId: number, dto: UpdateRequestDto): Promise { - validateId(requestId, 'Request'); - - if ( - dto.requestedSize == undefined && - dto.requestedFoodTypes == undefined && - dto.additionalInformation == undefined - ) { - throw new BadRequestException( - 'At least one field must be provided to update request', - ); - } - - const request = await this.repo.findOne({ - where: { requestId }, - relations: ['orders'], - }); - - if (!request) { - throw new NotFoundException(`Request ${requestId} not found`); - } - - if (request.status != FoodRequestStatus.ACTIVE) { - throw new BadRequestException( - `Request must be ${FoodRequestStatus.ACTIVE} in order to be updated`, - ); - } - - if (request.orders && request.orders.length > 0) { - throw new BadRequestException( - `Request ${requestId} cannot be updated if it still has orders associated with it`, - ); - } - - Object.assign(request, dto); - - await this.repo.save(request); - } - - async delete(requestId: number) { - validateId(requestId, 'Request'); - - const request = await this.repo.findOne({ - where: { requestId }, - relations: ['orders'], - }); - - if (!request) { - throw new NotFoundException(`Request ${requestId} not found`); - } - - if (request.status != FoodRequestStatus.ACTIVE) { - throw new BadRequestException( - `Request must be ${FoodRequestStatus.ACTIVE} in order to be deleted`, - ); - } - - if (request.orders && request.orders.length > 0) { - throw new BadRequestException( - `Request ${requestId} cannot be deleted if it still has orders associated with it`, - ); - } - - await this.repo.remove(request); - } - async closeRequest(requestId: number): Promise { validateId(requestId, 'Request'); diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index e645fdd72..5cec8d73c 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -141,33 +141,6 @@ describe('OrdersController', () => { }); }); - describe('getCurrentOrders', () => { - it('should call ordersService.getCurrentOrders and return orders', async () => { - mockOrdersService.getCurrentOrders.mockResolvedValueOnce([ - mockOrders[0], - mockOrders[2], - ] as Order[]); - - const result = await controller.getCurrentOrders(); - - expect(result).toEqual([mockOrders[0], mockOrders[2]] as Order[]); - expect(mockOrdersService.getCurrentOrders).toHaveBeenCalled(); - }); - }); - - describe('getPastOrders', () => { - it('should call ordersService.getPastOrders and return orders', async () => { - mockOrdersService.getPastOrders.mockResolvedValueOnce([ - mockOrders[1], - ] as Order[]); - - const result = await controller.getPastOrders(); - - expect(result).toEqual([mockOrders[1]] as Order[]); - expect(mockOrdersService.getPastOrders).toHaveBeenCalled(); - }); - }); - describe('getPantryFromOrder', () => { it('should call ordersService.findOrderPantry and return pantry', async () => { const orderId = 1; @@ -198,22 +171,6 @@ describe('OrdersController', () => { }); }); - describe('getManufacturerFromOrder', () => { - it('should call ordersService.findOrderFoodManufacturer and return FM', async () => { - const orderId = 1; - mockOrdersService.findOrderFoodManufacturer.mockResolvedValueOnce( - mockFoodManufacturer as FoodManufacturer, - ); - - const result = await controller.getManufacturerFromOrder(orderId); - - expect(result).toEqual(mockFoodManufacturer as FoodManufacturer); - expect(mockOrdersService.findOrderFoodManufacturer).toHaveBeenCalledWith( - orderId, - ); - }); - }); - describe('getAllAllocationsByOrder', () => { it('should call allocationsService.getAllAllocationsByOrder and return allocations', async () => { const orderId = 1; @@ -306,29 +263,6 @@ describe('OrdersController', () => { }); }); - describe('updateStatus', () => { - it('should call ordersService.updateStatus', async () => { - const status = OrderStatus.DELIVERED; - const orderId = 1; - - await controller.updateStatus(orderId, status); - - expect(mockOrdersService.updateStatus).toHaveBeenCalledWith( - orderId, - status, - ); - }); - - it('should throw with invalid status', async () => { - const invalidStatus = 'invalid status'; - const orderId = 1; - - await expect( - controller.updateStatus(orderId, invalidStatus), - ).rejects.toThrow(new BadRequestException('Invalid status')); - }); - }); - describe('bulkUpdateTrackingCostInfo', () => { it('should call ordersService.bulkUpdateTrackingCostInfo with correct parameters', async () => { const dto: BulkUpdateTrackingCostDto = { diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 5f00b9ec0..04def0c35 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -18,9 +18,7 @@ import { ApiBody } from '@nestjs/swagger'; import { OrdersService } from './order.service'; import { Order } from './order.entity'; import { Pantry } from '../pantries/pantries.entity'; -import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; import { AllocationsService } from '../allocations/allocations.service'; -import { OrderStatus } from './types'; import { CheckOwnership, pipeNullable } from '../auth/ownership.decorator'; import { PantriesService } from '../pantries/pantries.service'; import { BulkUpdateTrackingCostDto } from './dtos/bulk-update-tracking-cost.dto'; @@ -58,16 +56,6 @@ export class OrdersController { return this.ordersService.getAll({ status, pantryNames }); } - @Get('/get-current-orders') - async getCurrentOrders(): Promise { - return this.ordersService.getCurrentOrders(); - } - - @Get('/get-past-orders') - async getPastOrders(): Promise { - return this.ordersService.getPastOrders(); - } - @Get('/:orderId/pantry') async getPantryFromOrder( @Param('orderId', ParseIntPipe) orderId: number, @@ -96,13 +84,6 @@ export class OrdersController { return this.ordersService.findOrderFoodRequest(orderId); } - @Get('/:orderId/manufacturer') - async getManufacturerFromOrder( - @Param('orderId', ParseIntPipe) orderId: number, - ): Promise { - return this.ordersService.findOrderFoodManufacturer(orderId); - } - @Get('/:orderId') async getOrder( @Param('orderId', ParseIntPipe) orderId: number, @@ -191,17 +172,6 @@ export class OrdersController { ); } - @Patch('/update-status/:orderId') - async updateStatus( - @Param('orderId', ParseIntPipe) orderId: number, - @Body('newStatus') newStatus: string, - ): Promise { - if (!Object.values(OrderStatus).includes(newStatus as OrderStatus)) { - throw new BadRequestException('Invalid status'); - } - return this.ordersService.updateStatus(orderId, newStatus as OrderStatus); - } - @Roles(Role.FOODMANUFACTURER) @Patch('/bulk-update-tracking-cost-info') async bulkUpdateTrackingCostInfo( diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index b1ac6e6cc..403d4d74e 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -290,30 +290,6 @@ describe('OrdersService', () => { }); }); - describe('getCurrentOrders', () => { - it(`returns only orders with status 'pending' or 'shipped'`, async () => { - const orders = await service.getCurrentOrders(); - expect(orders).toHaveLength(2); - expect( - orders.every( - (order) => - order.status === OrderStatus.PENDING || - order.status === OrderStatus.SHIPPED, - ), - ).toBe(true); - }); - }); - - describe('getPastOrders', () => { - it(`returns only orders with status 'delivered'`, async () => { - const orders = await service.getPastOrders(); - expect(orders).toHaveLength(2); - expect( - orders.every((order) => order.status === OrderStatus.DELIVERED), - ).toBe(true); - }); - }); - describe('findOne', () => { it('returns order by ID', async () => { const orderId = 1; @@ -355,52 +331,6 @@ describe('OrdersService', () => { }); }); - describe('findOrderFoodManufacturer', () => { - it('returns FM of order', async () => { - const foodManufacturer = await service.findOrderFoodManufacturer(2); - - expect(foodManufacturer).toBeDefined(); - expect(foodManufacturer.foodManufacturerName).toEqual('Healthy Foods Co'); - expect(foodManufacturer.foodManufacturerId).toEqual(2); - }); - - it('throws NotFoundException for non-existent order', async () => { - await expect(service.findOrderFoodManufacturer(9999)).rejects.toThrow( - new NotFoundException('Order 9999 not found'), - ); - }); - }); - - describe('updateStatus', () => { - it('updates order status to delivered', async () => { - const orderId = 3; - const order = await service.findOne(orderId); - - expect(order.status).toEqual(OrderStatus.SHIPPED); - expect(order.shippedAt).toBeDefined(); - - await service.updateStatus(orderId, OrderStatus.DELIVERED); - const updatedOrder = await service.findOne(orderId); - - expect(updatedOrder.status).toEqual(OrderStatus.DELIVERED); - expect(updatedOrder.deliveredAt).toBeDefined(); - }); - - it('updates order status to shipped', async () => { - const orderId = 4; - const order = await service.findOne(orderId); - - expect(order.status).toEqual(OrderStatus.PENDING); - - await service.updateStatus(orderId, OrderStatus.SHIPPED); - const updatedOrder = await service.findOne(orderId); - - expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); - expect(updatedOrder.shippedAt).toBeDefined(); - expect(updatedOrder.deliveredAt).toBeNull(); - }); - }); - describe('getOrdersByPantry', () => { it('returns order from pantry ID', async () => { const pantryId = 1; diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 8ac255843..1a3287fa2 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -7,7 +7,6 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { Repository, In, DataSource } from 'typeorm'; import { Order } from './order.entity'; import { Pantry } from '../pantries/pantries.entity'; -import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; import { validateId } from '../utils/validation.utils'; import { DonationService } from '../donations/donations.service'; import { OrderStatus, VolunteerAction } from './types'; @@ -33,7 +32,6 @@ export class OrdersService { @InjectRepository(Pantry) private pantryRepo: Repository, @InjectRepository(Donation) private donationRepo: Repository, @InjectRepository(DonationItem) - private donationItemRepo: Repository, private requestsService: RequestsService, private donationService: DonationService, private manufacturerService: FoodManufacturersService, @@ -160,18 +158,6 @@ export class OrdersService { })); } - async getCurrentOrders() { - return this.repo.find({ - where: { status: In([OrderStatus.PENDING, OrderStatus.SHIPPED]) }, - }); - } - - async getPastOrders() { - return this.repo.find({ - where: { status: OrderStatus.DELIVERED }, - }); - } - /* This create method follows these high level steps: 1. Validate the request status is active before allowing order creation. @@ -388,36 +374,6 @@ export class OrdersService { }; } - async findOrderFoodManufacturer(orderId: number): Promise { - validateId(orderId, 'Order'); - - const order = await this.repo.findOne({ - where: { orderId }, - relations: ['foodManufacturer'], - }); - - if (!order) { - throw new NotFoundException(`Order ${orderId} not found`); - } - return order.foodManufacturer; - } - - async updateStatus(orderId: number, newStatus: OrderStatus) { - validateId(orderId, 'Order'); - - await this.repo - .createQueryBuilder() - .update(Order) - .set({ - status: newStatus as OrderStatus, - shippedAt: newStatus === OrderStatus.SHIPPED ? new Date() : undefined, - deliveredAt: - newStatus === OrderStatus.DELIVERED ? new Date() : undefined, - }) - .where('order_id = :orderId', { orderId }) - .execute(); - } - async confirmDelivery( orderId: number, dto: ConfirmDeliveryDto, diff --git a/apps/backend/src/users/users.controller.spec.ts b/apps/backend/src/users/users.controller.spec.ts index 1a62dad9b..fd923d516 100644 --- a/apps/backend/src/users/users.controller.spec.ts +++ b/apps/backend/src/users/users.controller.spec.ts @@ -64,17 +64,6 @@ describe('UsersController', () => { }); }); - describe('GET /:id', () => { - it('should return a user by id', async () => { - mockUserService.findOne.mockResolvedValue(mockUser1 as User); - - const result = await controller.getUser(1); - - expect(result).toEqual(mockUser1); - expect(mockUserService.findOne).toHaveBeenCalledWith(1); - }); - }); - describe('DELETE /:id', () => { it('should remove a user by id', async () => { mockUserService.remove.mockResolvedValue(mockUser1 as User); diff --git a/apps/backend/src/users/users.controller.ts b/apps/backend/src/users/users.controller.ts index 8da88631b..cf27de69b 100644 --- a/apps/backend/src/users/users.controller.ts +++ b/apps/backend/src/users/users.controller.ts @@ -32,11 +32,6 @@ export class UsersController { return this.usersService.findOne(req.user.id); } - @Get('/:id') - async getUser(@Param('id', ParseIntPipe) userId: number): Promise { - return this.usersService.findOne(userId); - } - @Get('/:id/stats') async getUserDashboardStats( @Param('id', ParseIntPipe) userId: number, @@ -50,11 +45,6 @@ export class UsersController { return this.usersService.getRecentPendingApplications(); } - @Delete('/:id') - removeUser(@Param('id', ParseIntPipe) userId: number): Promise { - return this.usersService.remove(userId); - } - @Patch('/:id') async updateInfo( @Param('id', ParseIntPipe) id: number, @@ -63,8 +53,14 @@ export class UsersController { return this.usersService.update(id, dto); } + // Keeping these two as functionality seems useful @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.spec.ts b/apps/backend/src/volunteers/volunteers.controller.spec.ts index c492d02b3..8c9acd510 100644 --- a/apps/backend/src/volunteers/volunteers.controller.spec.ts +++ b/apps/backend/src/volunteers/volunteers.controller.spec.ts @@ -119,17 +119,6 @@ describe('VolunteersController', () => { }); }); - describe('GET /:id', () => { - it('should return a user by id', async () => { - mockVolunteersService.findOne.mockResolvedValue(mockVolunteer1 as User); - - const result = await controller.getVolunteer(1); - - expect(result).toEqual(mockVolunteer1); - expect(mockVolunteersService.findOne).toHaveBeenCalledWith(1); - }); - }); - describe('GET /:id/pantries', () => { it('should return pantries assigned to a user', async () => { mockVolunteersService.getVolunteerPantries.mockResolvedValue( @@ -146,30 +135,6 @@ describe('VolunteersController', () => { }); }); - describe('POST /:id/pantries', () => { - it('should assign pantries to a volunteer and return result', async () => { - const pantryIds = [1, 3]; - const updatedUser = { - ...mockVolunteer3, - pantries: [mockPantries[0] as Pantry, mockPantries[2] as Pantry], - } as User; - - mockVolunteersService.assignPantriesToVolunteer.mockResolvedValue( - updatedUser, - ); - - const result = await controller.assignPantries(3, pantryIds); - - expect(result).toEqual(updatedUser); - expect(result.pantries).toHaveLength(2); - 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 = { diff --git a/apps/backend/src/volunteers/volunteers.controller.ts b/apps/backend/src/volunteers/volunteers.controller.ts index 7e6c08f08..7644aa06b 100644 --- a/apps/backend/src/volunteers/volunteers.controller.ts +++ b/apps/backend/src/volunteers/volunteers.controller.ts @@ -1,13 +1,4 @@ -import { - Controller, - Get, - Param, - ParseIntPipe, - Post, - Body, - Req, -} from '@nestjs/common'; -import { User } from '../users/users.entity'; +import { Controller, Get, Param, ParseIntPipe, Req } from '@nestjs/common'; import { Pantry } from '../pantries/pantries.entity'; import { VolunteersService } from './volunteers.service'; import { Role } from '../users/types'; @@ -38,19 +29,6 @@ export class VolunteersController { return this.volunteersService.getVolunteerPantries(id); } - @Get('/:id') - async getVolunteer(@Param('id', ParseIntPipe) userId: number): Promise { - return this.volunteersService.findOne(userId); - } - - @Post('/:id/pantries') - async assignPantries( - @Param('id', ParseIntPipe) id: number, - @Body('pantryIds') pantryIds: number[], - ): Promise { - return this.volunteersService.assignPantriesToVolunteer(id, pantryIds); - } - @Roles(Role.VOLUNTEER) @Get('/me/assigned-requests') async getAssignedRequests( diff --git a/apps/backend/src/volunteers/volunteers.service.spec.ts b/apps/backend/src/volunteers/volunteers.service.spec.ts index b665751b0..3ee6787d8 100644 --- a/apps/backend/src/volunteers/volunteers.service.spec.ts +++ b/apps/backend/src/volunteers/volunteers.service.spec.ts @@ -226,46 +226,6 @@ describe('VolunteersService', () => { }); }); - describe('assignPantriesToVolunteer', () => { - it('assigns new pantries to a volunteer with existing assignments', async () => { - const beforeAssignment = await service.getVolunteerPantries(7); - expect(beforeAssignment).toHaveLength(2); - const beforePantryIds = beforeAssignment.map((p) => p.pantryId); - 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(afterPantryIds).toEqual([2, 3, 1, 4]); - }); - - it('assigns pantries to a volunteer with no existing assignments', async () => { - await testDataSource.query( - `DELETE FROM "volunteer_assignments" WHERE volunteer_id = 6`, - ); - - const beforeAssignment = await service.getVolunteerPantries(6); - 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(pantryIds).toEqual([2, 3]); - }); - - it('does not contain duplicate pantry assignments when called with ones that already exist', async () => { - const beforeAssignment = await service.getVolunteerPantries(7); - expect(beforeAssignment).toHaveLength(2); - const beforePantryIds = beforeAssignment.map((p) => p.pantryId); - 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(pantryIds).toEqual([2, 3]); - }); - }); - describe('findRequestsByVolunteer', () => { it('returned requests include pantry info', async () => { const requests = await service.findRequestsByVolunteer(7); diff --git a/apps/backend/src/volunteers/volunteers.service.ts b/apps/backend/src/volunteers/volunteers.service.ts index b3cfe114d..376b27c6d 100644 --- a/apps/backend/src/volunteers/volunteers.service.ts +++ b/apps/backend/src/volunteers/volunteers.service.ts @@ -5,7 +5,6 @@ import { User } from '../users/users.entity'; import { Role } from '../users/types'; import { validateId } from '../utils/validation.utils'; import { Pantry } from '../pantries/pantries.entity'; -import { PantriesService } from '../pantries/pantries.service'; import { UsersService } from '../users/users.service'; import { Assignments, VolunteerOrder } from './types'; import { RequestsService } from '../foodRequests/request.service'; @@ -18,7 +17,6 @@ export class VolunteersService { @InjectRepository(User) private repo: Repository, private usersService: UsersService, - private pantriesService: PantriesService, private requestsService: RequestsService, private ordersService: OrdersService, ) {} @@ -69,25 +67,6 @@ export class VolunteersService { return this.ordersService.getRecentOrdersByAssignee(volunteerId); } - async assignPantriesToVolunteer( - volunteerId: number, - pantryIds: number[], - ): Promise { - const volunteer = await this.findOne(volunteerId); - - const uniquePantryIds = new Set(pantryIds); - - const pantries = await this.pantriesService.findByIds([...uniquePantryIds]); - const existingPantries = volunteer.pantries || []; - const existingPantryIds = new Set(existingPantries.map((p) => p.pantryId)); - const newPantries = pantries.filter( - (p) => !existingPantryIds.has(p.pantryId), - ); - - volunteer.pantries = [...existingPantries, ...newPantries]; - return this.repo.save(volunteer); - } - async findRequestsByVolunteer( volunteerId: number, ): Promise { diff --git a/apps/frontend/src/api/apiClient.ts b/apps/frontend/src/api/apiClient.ts index d3ca7f72f..ff731e2e2 100644 --- a/apps/frontend/src/api/apiClient.ts +++ b/apps/frontend/src/api/apiClient.ts @@ -132,12 +132,6 @@ export class ApiClient { ); } - public async getRepresentativeUser(userId: number): Promise { - return this.axiosInstance - .get(`/api/users/${userId}`) - .then((response) => response.data); - } - public async postUser(data: UserDto): Promise { return this.axiosInstance .post(`/api/users`, data) @@ -294,12 +288,6 @@ export class ApiClient { .then((response) => response.data); } - public async getDonation(donationId: number): Promise { - return this.axiosInstance - .get(`/api/donations/${donationId}`) - .then((response) => response.data); - } - public async getDonationItemsByDonationId( donationId: number, ): Promise { @@ -308,14 +296,6 @@ export class ApiClient { .then((response) => response.data); } - public async getManufacturerFromOrder( - orderId: number, - ): Promise { - return this.axiosInstance - .get(`/api/orders/${orderId}/manufacturer`) - .then((response) => response.data); - } - public async confirmOrderDelivery( orderId: number, dto: ConfirmDeliveryDto, @@ -350,18 +330,6 @@ export class ApiClient { .then((response) => response.data); } - public async getCurrentOrders(): Promise { - return this.axiosInstance - .get('/api/orders/get-current-orders') - .then((response) => response.data); - } - - public async getPastOrders(): Promise { - return this.axiosInstance - .get('/api/orders/get-past-orders') - .then((response) => response.data); - } - public async getOrderDetailsListFromRequest( requestId: number, ): Promise { @@ -384,16 +352,6 @@ export class ApiClient { .then((response) => response.data); } - public async updateOrderStatus( - orderId: number, - newStatus: 'shipped' | 'delivered', - ): Promise { - await this.axiosInstance.patch(`/api/orders/update-status/${orderId}`, { - orderId, - newStatus, - }); - } - public async updatePantryApplicationData( pantryId: number, data: UpdatePantryApplicationDto, From 6e11311a876a4ace15794c6a2a58b5eb62a0af4f Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Wed, 20 May 2026 21:59:47 -0700 Subject: [PATCH 2/4] Fixed tests and removed unused auth --- .../allocations/allocations.service.spec.ts | 1 - apps/backend/src/auth/auth.controller.spec.ts | 45 ---- apps/backend/src/auth/auth.controller.ts | 99 --------- apps/backend/src/auth/auth.module.ts | 2 - apps/backend/src/auth/auth.service.ts | 205 ------------------ .../src/auth/dtos/confirm-password.dto.ts | 12 - apps/backend/src/auth/dtos/delete-user.dto.ts | 6 - .../src/auth/dtos/forgot-password.dto.ts | 6 - .../src/auth/dtos/refresh-token.dto.ts | 9 - .../src/auth/dtos/sign-in-response.dto.ts | 7 - apps/backend/src/auth/dtos/sign-in.dto.ts | 9 - apps/backend/src/auth/dtos/verify-user.dto.ts | 9 - apps/backend/src/auth/jwt.strategy.ts | 2 +- .../donations/donations.controller.spec.ts | 10 - .../src/donations/donations.controller.ts | 3 - .../manufacturers.service.spec.ts | 5 +- apps/backend/src/orders/order.service.ts | 3 +- 17 files changed, 5 insertions(+), 428 deletions(-) delete mode 100644 apps/backend/src/auth/auth.controller.spec.ts delete mode 100644 apps/backend/src/auth/auth.controller.ts delete mode 100644 apps/backend/src/auth/dtos/confirm-password.dto.ts delete mode 100644 apps/backend/src/auth/dtos/delete-user.dto.ts delete mode 100644 apps/backend/src/auth/dtos/forgot-password.dto.ts delete mode 100644 apps/backend/src/auth/dtos/refresh-token.dto.ts delete mode 100644 apps/backend/src/auth/dtos/sign-in-response.dto.ts delete mode 100644 apps/backend/src/auth/dtos/sign-in.dto.ts delete mode 100644 apps/backend/src/auth/dtos/verify-user.dto.ts diff --git a/apps/backend/src/allocations/allocations.service.spec.ts b/apps/backend/src/allocations/allocations.service.spec.ts index cfb8d7bdb..395c88ecb 100644 --- a/apps/backend/src/allocations/allocations.service.spec.ts +++ b/apps/backend/src/allocations/allocations.service.spec.ts @@ -1,6 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { BadRequestException } from '@nestjs/common'; import { testDataSource } from '../config/typeormTestDataSource'; import { AllocationsService } from './allocations.service'; import { Allocation } from './allocations.entity'; diff --git a/apps/backend/src/auth/auth.controller.spec.ts b/apps/backend/src/auth/auth.controller.spec.ts deleted file mode 100644 index 9fbfa5c54..000000000 --- a/apps/backend/src/auth/auth.controller.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthController } from './auth.controller'; -import { AuthService } from './auth.service'; -import { UsersService } from '../users/users.service'; - -const mockAuthService: Partial = { - signup: jest.fn(), - signin: jest.fn(), - verifyUser: jest.fn(), - refreshToken: jest.fn(), - forgotPassword: jest.fn(), - confirmForgotPassword: jest.fn(), - deleteUser: jest.fn(), -}; - -const mockUsersService: Partial = { - create: jest.fn(), - findOne: jest.fn(), - remove: jest.fn(), -}; - -describe('AuthController', () => { - let controller: AuthController; - let authService: AuthService; - let usersService: UsersService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AuthController], - providers: [ - UsersService, - { provide: AuthService, useValue: mockAuthService }, - { provide: UsersService, useValue: mockUsersService }, - ], - }).compile(); - - controller = module.get(AuthController); - authService = module.get(AuthService); - usersService = module.get(UsersService); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/apps/backend/src/auth/auth.controller.ts b/apps/backend/src/auth/auth.controller.ts deleted file mode 100644 index a13828459..000000000 --- a/apps/backend/src/auth/auth.controller.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { BadRequestException, Body, Controller, Post } from '@nestjs/common'; - -import { SignInDto } from './dtos/sign-in.dto'; -import { SignUpDto } from './dtos/sign-up.dto'; -import { AuthService } from './auth.service'; -import { UsersService } from '../users/users.service'; -import { VerifyUserDto } from './dtos/verify-user.dto'; -import { DeleteUserDto } from './dtos/delete-user.dto'; -import { User } from '../users/users.entity'; -import { SignInResponseDto } from './dtos/sign-in-response.dto'; -import { RefreshTokenDto } from './dtos/refresh-token.dto'; -import { ConfirmPasswordDto } from './dtos/confirm-password.dto'; -import { ForgotPasswordDto } from './dtos/forgot-password.dto'; -import { Role } from '../users/types'; -import { userSchemaDto } from '../users/dtos/userSchema.dto'; - -@Controller('auth') -export class AuthController { - constructor( - private authService: AuthService, - private usersService: UsersService, - ) {} - - @Post('/signup') - async createUser(@Body() signUpDto: SignUpDto): Promise { - // By default, creates a standard user - try { - await this.authService.signup(signUpDto); - } catch (e: unknown) { - const message = - e instanceof Error - ? e.message - : 'Unexpected error occurred when signing up user'; - throw new BadRequestException(message); - } - - const createUserDto: userSchemaDto = { - email: signUpDto.email, - firstName: signUpDto.firstName, - lastName: signUpDto.lastName, - phone: signUpDto.phone, - role: Role.VOLUNTEER, - }; - const user = await this.usersService.create(createUserDto); - - return user; - } - - // TODO deprecated if verification code is replaced by link - @Post('/verify') - verifyUser(@Body() body: VerifyUserDto): void { - try { - this.authService.verifyUser(body.email, body.verificationCode); - } catch (e: unknown) { - const message = - e instanceof Error - ? e.message - : 'Unexpected error occurred when verifying user'; - throw new BadRequestException(message); - } - } - - @Post('/signin') - signin(@Body() signInDto: SignInDto): Promise { - return this.authService.signin(signInDto); - } - - @Post('/refresh') - refresh(@Body() refreshDto: RefreshTokenDto): Promise { - return this.authService.refreshToken(refreshDto); - } - - @Post('/forgotPassword') - forgotPassword(@Body() body: ForgotPasswordDto): Promise { - return this.authService.forgotPassword(body.email); - } - - @Post('/confirmPassword') - confirmPassword(@Body() body: ConfirmPasswordDto): Promise { - return this.authService.confirmForgotPassword(body); - } - - @Post('/delete') - async delete(@Body() body: DeleteUserDto): Promise { - const user = await this.usersService.findOne(body.userId); - - try { - await this.authService.deleteUser(user.email); - } catch (e: unknown) { - const message = - e instanceof Error - ? e.message - : 'Unexpected error occurred when deleting user'; - throw new BadRequestException(message); - } - - this.usersService.remove(user.id); - } -} diff --git a/apps/backend/src/auth/auth.module.ts b/apps/backend/src/auth/auth.module.ts index 3af03db00..98aa13897 100644 --- a/apps/backend/src/auth/auth.module.ts +++ b/apps/backend/src/auth/auth.module.ts @@ -1,6 +1,5 @@ import { Module, forwardRef } from '@nestjs/common'; import { PassportModule } from '@nestjs/passport'; -import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { JwtStrategy } from './jwt.strategy'; import { UsersModule } from '../users/users.module'; @@ -10,7 +9,6 @@ import { UsersModule } from '../users/users.module'; forwardRef(() => UsersModule), PassportModule.register({ defaultStrategy: 'jwt' }), ], - controllers: [AuthController], providers: [AuthService, JwtStrategy], exports: [AuthService, JwtStrategy], }) diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts index ae5ef432c..d03fcfde7 100644 --- a/apps/backend/src/auth/auth.service.ts +++ b/apps/backend/src/auth/auth.service.ts @@ -2,42 +2,17 @@ import { ConflictException, Injectable, InternalServerErrorException, - NotFoundException, } from '@nestjs/common'; import { - AdminDeleteUserCommand, - AdminInitiateAuthCommand, - AdminInitiateAuthCommandOutput, CognitoIdentityProviderClient, - ConfirmForgotPasswordCommand, - ConfirmSignUpCommand, - ForgotPasswordCommand, - SignUpCommand, AdminCreateUserCommand, - AuthenticationResultType, } from '@aws-sdk/client-cognito-identity-provider'; import CognitoAuthConfig from './aws-exports'; import { SignUpDto } from './dtos/sign-up.dto'; -import { SignInDto } from './dtos/sign-in.dto'; -import { SignInResponseDto } from './dtos/sign-in-response.dto'; import { createHmac } from 'crypto'; -import { RefreshTokenDto } from './dtos/refresh-token.dto'; -import { Role } from '../users/types'; -import { ConfirmPasswordDto } from './dtos/confirm-password.dto'; import { validateEnv } from '../utils/validation.utils'; -type SignInAuthResult = AuthenticationResultType & { - AccessToken: string; - RefreshToken: string; - IdToken: string; -}; - -type RefreshAuthResult = AuthenticationResultType & { - AccessToken: string; - IdToken: string; -}; - @Injectable() export class AuthService { private readonly providerClient: CognitoIdentityProviderClient; @@ -65,45 +40,6 @@ export class AuthService { return hmac.digest('base64'); } - async signup( - { firstName, lastName, email, password }: SignUpDto, - role: Role = Role.VOLUNTEER, - ): Promise { - // Needs error handling - const signUpCommand = new SignUpCommand({ - ClientId: CognitoAuthConfig.userPoolClientId, - SecretHash: this.calculateHash(email), - Username: email, - Password: password, - UserAttributes: [ - { - Name: 'name', - Value: `${firstName} ${lastName}`, - }, - // Optional: add a custom Cognito attribute called "role" that also stores the user's status/role - // If you choose to do so, you'll have to first add this custom attribute in your user pool - { - Name: 'custom:role', - Value: role, - }, - ], - }); - - try { - const response = await this.providerClient.send(signUpCommand); - - if (response.UserConfirmed == null) { - throw new InternalServerErrorException( - 'Missing UserConfirmed from Cognito', - ); - } - - return response.UserConfirmed; - } catch (err: unknown) { - throw new InternalServerErrorException('Failed to sign up user'); - } - } - async adminCreateUser({ firstName, lastName, @@ -134,145 +70,4 @@ export class AuthService { } } } - - async verifyUser(email: string, verificationCode: string): Promise { - const confirmCommand = new ConfirmSignUpCommand({ - ClientId: CognitoAuthConfig.userPoolClientId, - SecretHash: this.calculateHash(email), - Username: email, - ConfirmationCode: verificationCode, - }); - - await this.providerClient.send(confirmCommand); - } - - async signin({ email, password }: SignInDto): Promise { - const signInCommand = new AdminInitiateAuthCommand({ - AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', - ClientId: CognitoAuthConfig.userPoolClientId, - UserPoolId: CognitoAuthConfig.userPoolId, - AuthParameters: { - USERNAME: email, - PASSWORD: password, - SECRET_HASH: this.calculateHash(email), - }, - }); - - const response = await this.providerClient.send(signInCommand); - - const authResult = - this.validateAuthenticationResultTokensForSignIn(response); - - return { - accessToken: authResult.AccessToken, - refreshToken: authResult.RefreshToken, - idToken: authResult.IdToken, - }; - } - - // Refresh token hash uses a user's sub (unique ID), not their username (typically their email) - async refreshToken({ - refreshToken, - userSub, - }: RefreshTokenDto): Promise { - const refreshCommand = new AdminInitiateAuthCommand({ - AuthFlow: 'REFRESH_TOKEN_AUTH', - ClientId: CognitoAuthConfig.userPoolClientId, - UserPoolId: CognitoAuthConfig.userPoolId, - AuthParameters: { - REFRESH_TOKEN: refreshToken, - SECRET_HASH: this.calculateHash(userSub), - }, - }); - - const response = await this.providerClient.send(refreshCommand); - - const authResult = - this.validateAuthenticationResultTokensForRefresh(response); - - return { - accessToken: authResult.AccessToken, - refreshToken: refreshToken, - idToken: authResult.IdToken, - }; - } - - async forgotPassword(email: string) { - const forgotCommand = new ForgotPasswordCommand({ - ClientId: CognitoAuthConfig.userPoolClientId, - Username: email, - SecretHash: this.calculateHash(email), - }); - - await this.providerClient.send(forgotCommand); - } - - async confirmForgotPassword({ - email, - confirmationCode, - newPassword, - }: ConfirmPasswordDto) { - const confirmComamnd = new ConfirmForgotPasswordCommand({ - ClientId: CognitoAuthConfig.userPoolClientId, - SecretHash: this.calculateHash(email), - Username: email, - ConfirmationCode: confirmationCode, - Password: newPassword, - }); - - await this.providerClient.send(confirmComamnd); - } - - async deleteUser(email: string): Promise { - const adminDeleteUserCommand = new AdminDeleteUserCommand({ - Username: email, - UserPoolId: CognitoAuthConfig.userPoolId, - }); - - await this.providerClient.send(adminDeleteUserCommand); - } - - private validateAuthenticationResultTokensForSignIn( - commandOutput: AdminInitiateAuthCommandOutput, - ): SignInAuthResult { - const result = commandOutput.AuthenticationResult; - - if (result == null) { - throw new NotFoundException( - 'No associated authentication result for sign in', - ); - } - - if ( - result.AccessToken == null || - result.RefreshToken == null || - result.IdToken == null - ) { - throw new NotFoundException( - 'Necessary Authentication Result tokens not found for sign in ', - ); - } - - return result as SignInAuthResult; - } - - private validateAuthenticationResultTokensForRefresh( - commandOutput: AdminInitiateAuthCommandOutput, - ): RefreshAuthResult { - const result = commandOutput.AuthenticationResult; - - if (result == null) { - throw new NotFoundException( - 'No associated authentication result for refresh', - ); - } - - if (result.AccessToken == null || result.IdToken == null) { - throw new NotFoundException( - 'Necessary Authentication Result tokens not found for refresh', - ); - } - - return result as RefreshAuthResult; - } } diff --git a/apps/backend/src/auth/dtos/confirm-password.dto.ts b/apps/backend/src/auth/dtos/confirm-password.dto.ts deleted file mode 100644 index 921deff68..000000000 --- a/apps/backend/src/auth/dtos/confirm-password.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IsEmail, IsString } from 'class-validator'; - -export class ConfirmPasswordDto { - @IsEmail() - email!: string; - - @IsString() - newPassword!: string; - - @IsString() - confirmationCode!: string; -} diff --git a/apps/backend/src/auth/dtos/delete-user.dto.ts b/apps/backend/src/auth/dtos/delete-user.dto.ts deleted file mode 100644 index 78c4a7fa9..000000000 --- a/apps/backend/src/auth/dtos/delete-user.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IsPositive } from 'class-validator'; - -export class DeleteUserDto { - @IsPositive() - userId!: number; -} diff --git a/apps/backend/src/auth/dtos/forgot-password.dto.ts b/apps/backend/src/auth/dtos/forgot-password.dto.ts deleted file mode 100644 index e65f7d5bb..000000000 --- a/apps/backend/src/auth/dtos/forgot-password.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IsEmail } from 'class-validator'; - -export class ForgotPasswordDto { - @IsEmail() - email!: string; -} diff --git a/apps/backend/src/auth/dtos/refresh-token.dto.ts b/apps/backend/src/auth/dtos/refresh-token.dto.ts deleted file mode 100644 index 47feef29d..000000000 --- a/apps/backend/src/auth/dtos/refresh-token.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IsString } from 'class-validator'; - -export class RefreshTokenDto { - @IsString() - refreshToken!: string; - - @IsString() - userSub!: string; -} diff --git a/apps/backend/src/auth/dtos/sign-in-response.dto.ts b/apps/backend/src/auth/dtos/sign-in-response.dto.ts deleted file mode 100644 index 24fc08994..000000000 --- a/apps/backend/src/auth/dtos/sign-in-response.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class SignInResponseDto { - accessToken!: string; - - refreshToken!: string; - - idToken!: string; -} diff --git a/apps/backend/src/auth/dtos/sign-in.dto.ts b/apps/backend/src/auth/dtos/sign-in.dto.ts deleted file mode 100644 index 0be7d513b..000000000 --- a/apps/backend/src/auth/dtos/sign-in.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IsEmail, IsString } from 'class-validator'; - -export class SignInDto { - @IsEmail() - email!: string; - - @IsString() - password!: string; -} diff --git a/apps/backend/src/auth/dtos/verify-user.dto.ts b/apps/backend/src/auth/dtos/verify-user.dto.ts deleted file mode 100644 index b7f300fc6..000000000 --- a/apps/backend/src/auth/dtos/verify-user.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IsEmail, IsString } from 'class-validator'; - -export class VerifyUserDto { - @IsEmail() - email!: string; - - @IsString() - verificationCode!: string; -} diff --git a/apps/backend/src/auth/jwt.strategy.ts b/apps/backend/src/auth/jwt.strategy.ts index d1e2b583f..89225f859 100644 --- a/apps/backend/src/auth/jwt.strategy.ts +++ b/apps/backend/src/auth/jwt.strategy.ts @@ -33,7 +33,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { try { const user = await this.usersService.findUserByCognitoId(payload.sub); return user; - } catch (err) { + } catch { return null; // Passport treats null as unauthenticated → clean 401 } } diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index fc2da742a..40e50cc12 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -10,16 +10,6 @@ import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donat const mockDonationService = mock(); -const donation1: Partial = { - donationId: 1, - status: DonationStatus.MATCHED, -}; - -const donation2: Partial = { - donationId: 2, - status: DonationStatus.FULFILLED, -}; - describe('DonationsController', () => { let controller: DonationsController; diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index 0698fd8d1..253a7e255 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -2,13 +2,10 @@ import { Controller, Post, Body, - Get, Patch, Param, ParseIntPipe, ParseArrayPipe, - Put, - Delete, } from '@nestjs/common'; import { ApiBody } from '@nestjs/swagger'; import { Donation } from './donations.entity'; diff --git a/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts b/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts index 8074c2981..9ab5b2e04 100644 --- a/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts +++ b/apps/backend/src/foodManufacturers/manufacturers.service.spec.ts @@ -610,10 +610,11 @@ describe('FoodManufacturersService', () => { }); it('returns next two upcoming donation reminders from same donation', async () => { - const futureDate1 = new Date(); + const base = new Date(); + const futureDate1 = new Date(base); futureDate1.setDate(futureDate1.getDate() + 30); clampDay(futureDate1); - const futureDate2 = new Date(); + const futureDate2 = new Date(base); futureDate2.setDate(futureDate2.getDate() + 60); clampDay(futureDate2); diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 1a3287fa2..9877d96aa 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -4,7 +4,7 @@ import { NotFoundException, } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { Repository, In, DataSource } from 'typeorm'; +import { Repository, DataSource } from 'typeorm'; import { Order } from './order.entity'; import { Pantry } from '../pantries/pantries.entity'; import { validateId } from '../utils/validation.utils'; @@ -31,7 +31,6 @@ export class OrdersService { @InjectRepository(Order) private repo: Repository, @InjectRepository(Pantry) private pantryRepo: Repository, @InjectRepository(Donation) private donationRepo: Repository, - @InjectRepository(DonationItem) private requestsService: RequestsService, private donationService: DonationService, private manufacturerService: FoodManufacturersService, From 32725eeae3fe4a7d00f70ce2f13371af9b521b49 Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Sun, 24 May 2026 12:21:21 -0700 Subject: [PATCH 3/4] Comments --- apps/backend/src/app.controller.spec.ts | 22 ----------------- apps/backend/src/app.controller.ts | 13 ---------- apps/backend/src/app.module.ts | 4 ---- apps/backend/src/app.service.spec.ts | 21 ---------------- apps/backend/src/app.service.ts | 8 ------- .../donations/donations.controller.spec.ts | 24 +++++++++++++++++++ .../src/donations/donations.controller.ts | 6 +++++ 7 files changed, 30 insertions(+), 68 deletions(-) delete mode 100644 apps/backend/src/app.controller.spec.ts delete mode 100644 apps/backend/src/app.controller.ts delete mode 100644 apps/backend/src/app.service.spec.ts delete mode 100644 apps/backend/src/app.service.ts diff --git a/apps/backend/src/app.controller.spec.ts b/apps/backend/src/app.controller.spec.ts deleted file mode 100644 index de8007e18..000000000 --- a/apps/backend/src/app.controller.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; - -import { AppController } from './app.controller'; -import { AppService } from './app.service'; - -describe('AppController', () => { - let app: TestingModule; - - beforeAll(async () => { - app = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); - }); - - describe('getData', () => { - it('should return "Hello API"', () => { - const appController = app.get(AppController); - expect(appController.getData()).toEqual({ message: 'Hello API' }); - }); - }); -}); diff --git a/apps/backend/src/app.controller.ts b/apps/backend/src/app.controller.ts deleted file mode 100644 index dff210a84..000000000 --- a/apps/backend/src/app.controller.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; - -import { AppService } from './app.service'; - -@Controller() -export class AppController { - constructor(private readonly appService: AppService) {} - - @Get() - getData() { - return this.appService.getData(); - } -} diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index cb0ef03fc..69c50b294 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -2,8 +2,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { RequestsModule } from './foodRequests/request.module'; import { PantriesModule } from './pantries/pantries.module'; -import { AppController } from './app.controller'; -import { AppService } from './app.service'; import { UsersModule } from './users/users.module'; import { AuthModule } from './auth/auth.module'; import { ConfigModule, ConfigService } from '@nestjs/config'; @@ -47,9 +45,7 @@ import { OwnershipGuard } from './auth/ownership.guard'; AllocationModule, VolunteersModule, ], - controllers: [AppController], providers: [ - AppService, { provide: APP_GUARD, useClass: JwtAuthGuard, diff --git a/apps/backend/src/app.service.spec.ts b/apps/backend/src/app.service.spec.ts deleted file mode 100644 index 42cf0a259..000000000 --- a/apps/backend/src/app.service.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Test } from '@nestjs/testing'; - -import { AppService } from './app.service'; - -describe('AppService', () => { - let service: AppService; - - beforeAll(async () => { - const app = await Test.createTestingModule({ - providers: [AppService], - }).compile(); - - service = app.get(AppService); - }); - - describe('getData', () => { - it('should return "Hello API"', () => { - expect(service.getData()).toEqual({ message: 'Hello API' }); - }); - }); -}); diff --git a/apps/backend/src/app.service.ts b/apps/backend/src/app.service.ts deleted file mode 100644 index cd8cedeff..000000000 --- a/apps/backend/src/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getData(): { message: string } { - return { message: 'Hello API' }; - } -} diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index 40e50cc12..0a4bba6bb 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -10,6 +10,16 @@ import { UpdateDonationItemDetailsDto } from '../donationItems/dtos/update-donat const mockDonationService = mock(); +const donation1: Partial = { + donationId: 1, + status: DonationStatus.MATCHED, +}; + +const donation2: Partial = { + donationId: 2, + status: DonationStatus.FULFILLED, +}; + describe('DonationsController', () => { let controller: DonationsController; @@ -31,6 +41,20 @@ describe('DonationsController', () => { expect(controller).toBeDefined(); }); + describe('GET /', () => { + it('should call donationService.getAll and return array of donations', async () => { + mockDonationService.getAll.mockResolvedValueOnce([ + donation1, + donation2, + ] as Donation[]); + + const result = await controller.getAllDonations(); + + expect(result).toEqual([donation1, donation2]); + expect(mockDonationService.getAll).toHaveBeenCalled(); + }); + }); + describe('POST /', () => { it('should call donationService.create and return the created donation', async () => { const createBody: Partial = { diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index 253a7e255..872815eaf 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -6,6 +6,7 @@ import { Param, ParseIntPipe, ParseArrayPipe, + Get, } from '@nestjs/common'; import { ApiBody } from '@nestjs/swagger'; import { Donation } from './donations.entity'; @@ -24,6 +25,11 @@ import { FoodManufacturer } from '../foodManufacturers/manufacturers.entity'; export class DonationsController { constructor(private donationService: DonationService) {} + @Get() + async getAllDonations(): Promise { + return this.donationService.getAll(); + } + @Post() @ApiBody({ description: 'Details for creating a donation', From 32c1b048c82f4a46eea07e03ce0e90389b59786e Mon Sep 17 00:00:00 2001 From: Dalton Burkhart Date: Mon, 25 May 2026 22:02:39 -0700 Subject: [PATCH 4/4] Added back endpoints --- .../donations/donations.controller.spec.ts | 11 ++ .../src/donations/donations.controller.ts | 8 ++ .../foodRequests/dtos/update-request.dto.ts | 25 ++++ .../foodRequests/request.controller.spec.ts | 30 +++++ .../src/foodRequests/request.controller.ts | 17 +++ .../src/foodRequests/request.service.spec.ts | 120 ++++++++++++++++++ .../src/foodRequests/request.service.ts | 67 ++++++++++ .../src/orders/order.controller.spec.ts | 23 ++++ apps/backend/src/orders/order.controller.ts | 12 ++ apps/backend/src/orders/order.service.spec.ts | 30 +++++ apps/backend/src/orders/order.service.ts | 16 +++ 11 files changed, 359 insertions(+) create mode 100644 apps/backend/src/foodRequests/dtos/update-request.dto.ts diff --git a/apps/backend/src/donations/donations.controller.spec.ts b/apps/backend/src/donations/donations.controller.spec.ts index 0a4bba6bb..554afe7d6 100644 --- a/apps/backend/src/donations/donations.controller.spec.ts +++ b/apps/backend/src/donations/donations.controller.spec.ts @@ -129,4 +129,15 @@ describe('DonationsController', () => { ).toHaveBeenCalledWith(donationId, body); }); }); + + describe('DELETE /:donationId', () => { + it('should call donationService.delete with the correct id', async () => { + const donationId = 1; + + await controller.deleteDonation(donationId); + + expect(mockDonationService.delete).toHaveBeenCalledWith(donationId); + expect(mockDonationService.delete).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/apps/backend/src/donations/donations.controller.ts b/apps/backend/src/donations/donations.controller.ts index 872815eaf..ab493edd5 100644 --- a/apps/backend/src/donations/donations.controller.ts +++ b/apps/backend/src/donations/donations.controller.ts @@ -7,6 +7,7 @@ import { ParseIntPipe, ParseArrayPipe, Get, + Delete, } from '@nestjs/common'; import { ApiBody } from '@nestjs/swagger'; import { Donation } from './donations.entity'; @@ -116,4 +117,11 @@ export class DonationsController { ): Promise { await this.donationService.updateDonationItemDetails(donationId, body); } + + @Delete('/:donationId') + async deleteDonation( + @Param('donationId', ParseIntPipe) donationId: number, + ): Promise { + return this.donationService.delete(donationId); + } } diff --git a/apps/backend/src/foodRequests/dtos/update-request.dto.ts b/apps/backend/src/foodRequests/dtos/update-request.dto.ts new file mode 100644 index 000000000..06c1fd277 --- /dev/null +++ b/apps/backend/src/foodRequests/dtos/update-request.dto.ts @@ -0,0 +1,25 @@ +import { + ArrayNotEmpty, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; +import { RequestSize } from '../types'; +import { FoodType } from '../../donationItems/types'; + +export class UpdateRequestDto { + @IsOptional() + @IsEnum(RequestSize) + requestedSize?: RequestSize; + + @IsOptional() + @ArrayNotEmpty() + @IsEnum(FoodType, { each: true }) + requestedFoodTypes?: FoodType[]; + + @IsOptional() + @IsString() + @IsNotEmpty() + additionalInformation?: string; +} diff --git a/apps/backend/src/foodRequests/request.controller.spec.ts b/apps/backend/src/foodRequests/request.controller.spec.ts index 0a2f7cfa1..6e77d179d 100644 --- a/apps/backend/src/foodRequests/request.controller.spec.ts +++ b/apps/backend/src/foodRequests/request.controller.spec.ts @@ -8,6 +8,7 @@ import { OrderStatus } from '../orders/types'; import { FoodType } from '../donationItems/types'; import { OrderDetailsDto } from '../orders/dtos/order-details.dto'; import { CreateRequestDto } from './dtos/create-request.dto'; +import { UpdateRequestDto } from './dtos/update-request.dto'; import { DonationItemDetailsDto, MatchingItemsDto, @@ -43,6 +44,8 @@ describe('RequestsController', () => { mockRequestsService.findOne.mockReset(); mockRequestsService.create.mockReset(); mockRequestsService.getOrderDetails.mockReset(); + mockRequestsService.update.mockReset(); + mockRequestsService.delete.mockReset(); const module: TestingModule = await Test.createTestingModule({ controllers: [RequestsController], @@ -218,6 +221,33 @@ describe('RequestsController', () => { }); }); + describe('PATCH /:requestId', () => { + it('should update request with valid information', async () => { + mockRequestsService.update.mockResolvedValue(undefined); + + const updateRequestDto: UpdateRequestDto = { + requestedSize: RequestSize.MEDIUM, + }; + await controller.updateRequest(1, updateRequestDto); + + expect(mockRequestsService.update).toHaveBeenCalledWith( + 1, + updateRequestDto, + ); + }); + }); + + describe('DELETE /:requestId', () => { + it('should delete a request by id', async () => { + mockRequestsService.delete.mockResolvedValue(undefined); + + const result = await controller.deleteRequest(1); + + expect(result).toBeUndefined(); + expect(mockRequestsService.delete).toHaveBeenCalledWith(1); + }); + }); + describe('GET /:requestId/matching-manufacturers/:foodManufacturerId/available-items', () => { it('should call requestsService.getAvailableItems and return grouped items', async () => { const requestId = 1; diff --git a/apps/backend/src/foodRequests/request.controller.ts b/apps/backend/src/foodRequests/request.controller.ts index ddb33938e..1a494d3cc 100644 --- a/apps/backend/src/foodRequests/request.controller.ts +++ b/apps/backend/src/foodRequests/request.controller.ts @@ -7,6 +7,7 @@ import { Body, ValidationPipe, Patch, + Delete, } from '@nestjs/common'; import { ApiBody } from '@nestjs/swagger'; import { RequestsService } from './request.service'; @@ -22,6 +23,7 @@ import { MatchingItemsDto, MatchingManufacturersDto, } from './dtos/matching.dto'; +import { UpdateRequestDto } from './dtos/update-request.dto'; @Controller('requests') export class RequestsController { @@ -103,6 +105,21 @@ export class RequestsController { ); } + @Patch('/:requestId') + async updateRequest( + @Param('requestId', ParseIntPipe) requestId: number, + @Body(new ValidationPipe()) body: UpdateRequestDto, + ): Promise { + await this.requestsService.update(requestId, body); + } + + @Delete('/:requestId') + async deleteRequest( + @Param('requestId', ParseIntPipe) requestId: number, + ): Promise { + return this.requestsService.delete(requestId); + } + @Roles(Role.VOLUNTEER) @Patch('/:requestId/close') async closeRequest( diff --git a/apps/backend/src/foodRequests/request.service.spec.ts b/apps/backend/src/foodRequests/request.service.spec.ts index 921c8370e..67f2ce891 100644 --- a/apps/backend/src/foodRequests/request.service.spec.ts +++ b/apps/backend/src/foodRequests/request.service.spec.ts @@ -615,6 +615,126 @@ describe('RequestsService', () => { }); }); + describe('update', () => { + it('should update request attributes', async () => { + await testDataSource.query( + `DELETE FROM allocations WHERE order_id IN (SELECT order_id FROM orders WHERE request_id = 1)`, + ); + await testDataSource.query(`DELETE FROM orders WHERE request_id = 1`); + + await service.update(1, { + requestedSize: RequestSize.MEDIUM, + }); + + const fromDb = await service.findOne(1); + expect(fromDb.requestedSize).toBe(RequestSize.MEDIUM); + expect(fromDb.requestedFoodTypes).toEqual([ + FoodType.SEED_BUTTERS, + FoodType.GLUTEN_FREE_BREAD, + FoodType.DRIED_BEANS, + FoodType.DAIRY_FREE_ALTERNATIVES, + ]); + }); + + it('should throw NotFoundException when request is not found', async () => { + await expect( + service.update(9999, { requestedSize: RequestSize.MEDIUM }), + ).rejects.toThrow(new NotFoundException('Request 9999 not found')); + }); + + it('should update all request attributes when all fields are provided', async () => { + await testDataSource.query( + `DELETE FROM allocations WHERE order_id IN (SELECT order_id FROM orders WHERE request_id = 1)`, + ); + await testDataSource.query(`DELETE FROM orders WHERE request_id = 1`); + + await service.update(1, { + requestedSize: RequestSize.SMALL, + requestedFoodTypes: [FoodType.GRANOLA], + additionalInformation: 'Updated information', + }); + + const fromDb = await service.findOne(1); + expect(fromDb.requestedSize).toBe(RequestSize.SMALL); + expect(fromDb.requestedFoodTypes).toEqual([FoodType.GRANOLA]); + expect(fromDb.additionalInformation).toBe('Updated information'); + }); + + it('should throw BadRequestException when request is not active', async () => { + await testDataSource.query( + `UPDATE food_requests SET status = 'closed' WHERE request_id = 1`, + ); + + await expect( + service.update(1, { requestedSize: RequestSize.MEDIUM }), + ).rejects.toThrow( + new BadRequestException( + `Request must be ${FoodRequestStatus.ACTIVE} in order to be updated`, + ), + ); + }); + + it('should throw BadRequestException when request has orders', async () => { + await expect( + service.update(2, { requestedSize: RequestSize.MEDIUM }), + ).rejects.toThrow( + new BadRequestException( + `Request 2 cannot be updated if it still has orders associated with it`, + ), + ); + }); + + it('should throw BadRequestException when all DTO fields are undefined', async () => { + await expect(service.update(1, {})).rejects.toThrow( + new BadRequestException( + 'At least one field must be provided to update request', + ), + ); + }); + }); + + describe('delete', () => { + it('should delete a request by id', async () => { + await testDataSource.query( + `DELETE FROM allocations WHERE order_id IN (SELECT order_id FROM orders WHERE request_id = 1)`, + ); + await testDataSource.query(`DELETE FROM orders WHERE request_id = 1`); + + await service.delete(1); + + const fromDb = await testDataSource + .getRepository(FoodRequest) + .findOneBy({ requestId: 1 }); + expect(fromDb).toBeNull(); + }); + + it('should throw BadRequestException when request is not active', async () => { + await testDataSource.query( + `UPDATE food_requests SET status = 'closed' WHERE request_id = 1`, + ); + + await expect(service.delete(1)).rejects.toThrow( + new BadRequestException( + `Request must be ${FoodRequestStatus.ACTIVE} in order to be deleted`, + ), + ); + }); + + it('should throw BadRequestException when request has orders', async () => { + await expect(service.delete(2)).rejects.toThrow( + new BadRequestException( + `Request 2 cannot be deleted if it still has orders associated with it`, + ), + ); + }); + + it('should throw NotFoundException when request is not found', async () => { + await expect(service.delete(9999)).rejects.toThrow( + new NotFoundException('Request 9999 not found'), + ); + }); + }); + describe('closeRequest', () => { it('should close an active request', async () => { await service.closeRequest(3); diff --git a/apps/backend/src/foodRequests/request.service.ts b/apps/backend/src/foodRequests/request.service.ts index 1dd3b02c6..12a4dd511 100644 --- a/apps/backend/src/foodRequests/request.service.ts +++ b/apps/backend/src/foodRequests/request.service.ts @@ -23,6 +23,7 @@ import { FoodType } from '../donationItems/types'; import { DonationItem } from '../donationItems/donationItems.entity'; import { EmailsService } from '../emails/email.service'; import { emailTemplates } from '../emails/emailTemplates'; +import { UpdateRequestDto } from './dtos/update-request.dto'; @Injectable() export class RequestsService { @@ -307,6 +308,72 @@ export class RequestsService { await this.repo.save(request); } + async update(requestId: number, dto: UpdateRequestDto): Promise { + validateId(requestId, 'Request'); + + if ( + dto.requestedSize == undefined && + dto.requestedFoodTypes == undefined && + dto.additionalInformation == undefined + ) { + throw new BadRequestException( + 'At least one field must be provided to update request', + ); + } + + const request = await this.repo.findOne({ + where: { requestId }, + relations: ['orders'], + }); + + if (!request) { + throw new NotFoundException(`Request ${requestId} not found`); + } + + if (request.status != FoodRequestStatus.ACTIVE) { + throw new BadRequestException( + `Request must be ${FoodRequestStatus.ACTIVE} in order to be updated`, + ); + } + + if (request.orders && request.orders.length > 0) { + throw new BadRequestException( + `Request ${requestId} cannot be updated if it still has orders associated with it`, + ); + } + + Object.assign(request, dto); + + await this.repo.save(request); + } + + async delete(requestId: number) { + validateId(requestId, 'Request'); + + const request = await this.repo.findOne({ + where: { requestId }, + relations: ['orders'], + }); + + if (!request) { + throw new NotFoundException(`Request ${requestId} not found`); + } + + if (request.status != FoodRequestStatus.ACTIVE) { + throw new BadRequestException( + `Request must be ${FoodRequestStatus.ACTIVE} in order to be deleted`, + ); + } + + if (request.orders && request.orders.length > 0) { + throw new BadRequestException( + `Request ${requestId} cannot be deleted if it still has orders associated with it`, + ); + } + + await this.repo.remove(request); + } + async closeRequest(requestId: number): Promise { validateId(requestId, 'Request'); diff --git a/apps/backend/src/orders/order.controller.spec.ts b/apps/backend/src/orders/order.controller.spec.ts index 5cec8d73c..da8b70c01 100644 --- a/apps/backend/src/orders/order.controller.spec.ts +++ b/apps/backend/src/orders/order.controller.spec.ts @@ -263,6 +263,29 @@ describe('OrdersController', () => { }); }); + describe('updateStatus', () => { + it('should call ordersService.updateStatus', async () => { + const status = OrderStatus.DELIVERED; + const orderId = 1; + + await controller.updateStatus(orderId, status); + + expect(mockOrdersService.updateStatus).toHaveBeenCalledWith( + orderId, + status, + ); + }); + + it('should throw with invalid status', async () => { + const invalidStatus = 'invalid status'; + const orderId = 1; + + await expect( + controller.updateStatus(orderId, invalidStatus), + ).rejects.toThrow(new BadRequestException('Invalid status')); + }); + }); + describe('bulkUpdateTrackingCostInfo', () => { it('should call ordersService.bulkUpdateTrackingCostInfo with correct parameters', async () => { const dto: BulkUpdateTrackingCostDto = { diff --git a/apps/backend/src/orders/order.controller.ts b/apps/backend/src/orders/order.controller.ts index 04def0c35..f53f25a5d 100644 --- a/apps/backend/src/orders/order.controller.ts +++ b/apps/backend/src/orders/order.controller.ts @@ -33,6 +33,7 @@ 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'; @Controller('orders') export class OrdersController { @@ -172,6 +173,17 @@ export class OrdersController { ); } + @Patch('/update-status/:orderId') + async updateStatus( + @Param('orderId', ParseIntPipe) orderId: number, + @Body('newStatus') newStatus: string, + ): Promise { + if (!Object.values(OrderStatus).includes(newStatus as OrderStatus)) { + throw new BadRequestException('Invalid status'); + } + return this.ordersService.updateStatus(orderId, newStatus as OrderStatus); + } + @Roles(Role.FOODMANUFACTURER) @Patch('/bulk-update-tracking-cost-info') async bulkUpdateTrackingCostInfo( diff --git a/apps/backend/src/orders/order.service.spec.ts b/apps/backend/src/orders/order.service.spec.ts index 403d4d74e..05a512a26 100644 --- a/apps/backend/src/orders/order.service.spec.ts +++ b/apps/backend/src/orders/order.service.spec.ts @@ -331,6 +331,36 @@ describe('OrdersService', () => { }); }); + describe('updateStatus', () => { + it('updates order status to delivered', async () => { + const orderId = 3; + const order = await service.findOne(orderId); + + expect(order.status).toEqual(OrderStatus.SHIPPED); + expect(order.shippedAt).toBeDefined(); + + await service.updateStatus(orderId, OrderStatus.DELIVERED); + const updatedOrder = await service.findOne(orderId); + + expect(updatedOrder.status).toEqual(OrderStatus.DELIVERED); + expect(updatedOrder.deliveredAt).toBeDefined(); + }); + + it('updates order status to shipped', async () => { + const orderId = 4; + const order = await service.findOne(orderId); + + expect(order.status).toEqual(OrderStatus.PENDING); + + await service.updateStatus(orderId, OrderStatus.SHIPPED); + const updatedOrder = await service.findOne(orderId); + + expect(updatedOrder.status).toEqual(OrderStatus.SHIPPED); + expect(updatedOrder.shippedAt).toBeDefined(); + expect(updatedOrder.deliveredAt).toBeNull(); + }); + }); + describe('getOrdersByPantry', () => { it('returns order from pantry ID', async () => { const pantryId = 1; diff --git a/apps/backend/src/orders/order.service.ts b/apps/backend/src/orders/order.service.ts index 9877d96aa..7dd95a21e 100644 --- a/apps/backend/src/orders/order.service.ts +++ b/apps/backend/src/orders/order.service.ts @@ -373,6 +373,22 @@ export class OrdersService { }; } + async updateStatus(orderId: number, newStatus: OrderStatus) { + validateId(orderId, 'Order'); + + await this.repo + .createQueryBuilder() + .update(Order) + .set({ + status: newStatus as OrderStatus, + shippedAt: newStatus === OrderStatus.SHIPPED ? new Date() : undefined, + deliveredAt: + newStatus === OrderStatus.DELIVERED ? new Date() : undefined, + }) + .where('order_id = :orderId', { orderId }) + .execute(); + } + async confirmDelivery( orderId: number, dto: ConfirmDeliveryDto,