Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c1c0485
Created initial Api and frontend integration, awaiting some validatio…
dburkhart07 Oct 25, 2025
09d9357
Changes for setting upstream
dburkhart07 Oct 25, 2025
3d95bbd
Added tests for donation items controller only
dburkhart07 Oct 26, 2025
706b59f
Final commit
dburkhart07 Oct 26, 2025
9c47821
Final commit
dburkhart07 Nov 22, 2025
ea1d063
Resolved merge conflicts
dburkhart07 Nov 27, 2025
ec957b7
Final commit
dburkhart07 Nov 27, 2025
20b9bbe
Prettier
dburkhart07 Nov 30, 2025
8e773fe
Resolved merge conflicts
dburkhart07 Dec 6, 2025
98b5e25
prettier
dburkhart07 Dec 6, 2025
0c249bc
Made changes
dburkhart07 Dec 6, 2025
c971104
Changes with one multiple donation item saving
dburkhart07 Dec 6, 2025
8548fc4
Fixed DTO formatting
dburkhart07 Dec 6, 2025
9c36a1c
prettier
dburkhart07 Dec 6, 2025
cc585cf
Fixed changes and rewrote test
dburkhart07 Dec 7, 2025
9451c7b
prettier
dburkhart07 Dec 7, 2025
ee7ad64
Final commits
dburkhart07 Dec 8, 2025
1106308
prettier
dburkhart07 Dec 8, 2025
c0f4c1e
Resolved merge conficts
dburkhart07 Dec 8, 2025
79df911
Resolved merge conflicts
dburkhart07 Dec 10, 2025
bc0e307
Final commit
dburkhart07 Dec 10, 2025
2f7c61a
Final commit
dburkhart07 Dec 10, 2025
ccc073d
Merged main
dburkhart07 Jan 19, 2026
aa6a0ab
Merged main
dburkhart07 Jan 19, 2026
72e34dc
Resolved comments
dburkhart07 Jan 19, 2026
9ccc8df
prettier
dburkhart07 Jan 19, 2026
8656f5c
Final commit
dburkhart07 Jan 25, 2026
eba7e50
Merged main
dburkhart07 Jan 25, 2026
00a57f6
fixed comments
dburkhart07 Jan 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions apps/backend/src/donationItems/donationItems.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DonationItemsController } from './donationItems.controller';
import { DonationItemsService } from './donationItems.service';
import { DonationItem } from './donationItems.entity';
import { mock } from 'jest-mock-extended';
import { FoodType } from './types';
import { CreateMultipleDonationItemsDto } from './dtos/create-donation-items.dto';

const mockDonationItemsService = mock<DonationItemsService>();

describe('DonationItemsController', () => {
let controller: DonationItemsController;

const mockDonationItemsCreateData: Partial<DonationItem>[] = [
{
itemId: 1,
donationId: 1,
itemName: 'Canned Beans',
quantity: 100,
reservedQuantity: 0,
ozPerItem: 15,
estimatedValue: 200,
foodType: FoodType.DAIRY_FREE_ALTERNATIVES,
},
{
itemId: 2,
donationId: 1,
itemName: 'Rice',
quantity: 50,
reservedQuantity: 0,
ozPerItem: 20,
estimatedValue: 150,
foodType: FoodType.GLUTEN_FREE_BAKING_PANCAKE_MIXES,
},
];

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [DonationItemsController],
providers: [
{ provide: DonationItemsService, useValue: mockDonationItemsService },
],
}).compile();

controller = module.get<DonationItemsController>(DonationItemsController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});

describe('createMultipleDonationItems', () => {
it('should call donationItemsService.createMultipleDonationItems with donationId and items, and return the created donation items', async () => {
const mockBody: CreateMultipleDonationItemsDto = {
donationId: 1,
items: [
{
itemName: 'Rice Noodles',
quantity: 100,
reservedQuantity: 0,
ozPerItem: 5,
estimatedValue: 100,
foodType: FoodType.DAIRY_FREE_ALTERNATIVES,
},
{
itemName: 'Beans',
quantity: 50,
reservedQuantity: 0,
ozPerItem: 10,
estimatedValue: 80,
foodType: FoodType.GLUTEN_FREE_BAKING_PANCAKE_MIXES,
},
],
};

const mockCreatedItems: Partial<DonationItem>[] = [
{ itemId: 1, donationId: 1, ...mockBody.items[0] },
{ itemId: 2, donationId: 1, ...mockBody.items[1] },
];

mockDonationItemsService.createMultipleDonationItems.mockResolvedValue(
mockCreatedItems as DonationItem[],
);

const result = await controller.createMultipleDonationItems(mockBody);

expect(
mockDonationItemsService.createMultipleDonationItems,
).toHaveBeenCalledWith(mockBody.donationId, mockBody.items);
expect(result).toEqual(mockCreatedItems);
});
});
});
66 changes: 29 additions & 37 deletions apps/backend/src/donationItems/donationItems.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ApiBody } from '@nestjs/swagger';
import { DonationItemsService } from './donationItems.service';
import { DonationItem } from './donationItems.entity';
import { FoodType } from './types';
import { CreateMultipleDonationItemsDto } from './dtos/create-donation-items.dto';

@Controller('donation-items')
//@UseInterceptors()
Expand All @@ -25,52 +26,43 @@ export class DonationItemsController {
return this.donationItemsService.getAllDonationItems(donationId);
}

@Post('/create')
@Post('/create-multiple')
@ApiBody({
description: 'Details for creating a donation item',
description: 'Bulk create donation items for a single donation',
schema: {
type: 'object',
properties: {
donationId: { type: 'integer', example: 1 },
itemName: { type: 'string', example: 'Rice Noodles' },
quantity: { type: 'integer', example: 100 },
reservedQuantity: { type: 'integer', example: 0 },
ozPerItem: { type: 'integer', example: 5 },
estimatedValue: { type: 'integer', example: 100 },
foodType: {
type: 'string',
enum: Object.values(FoodType),
example: FoodType.DAIRY_FREE_ALTERNATIVES,
donationId: {
type: 'integer',
example: 1,
},
items: {
type: 'array',
items: {
type: 'object',
properties: {
itemName: { type: 'string', example: 'Rice Noodles' },
quantity: { type: 'integer', example: 100 },
reservedQuantity: { type: 'integer', example: 0 },
ozPerItem: { type: 'integer', example: 5 },
estimatedValue: { type: 'integer', example: 100 },
foodType: {
type: 'string',
enum: Object.values(FoodType),
example: FoodType.DAIRY_FREE_ALTERNATIVES,
},
},
},
},
},
},
})
async createDonationItem(
@Body()
body: {
donationId: number;
itemName: string;
quantity: number;
reservedQuantity: number;
ozPerItem: number;
estimatedValue: number;
foodType: FoodType;
},
): Promise<DonationItem> {
if (
body.foodType &&
!Object.values(FoodType).includes(body.foodType as FoodType)
) {
throw new BadRequestException('Invalid foodtype');
}
return this.donationItemsService.create(
async createMultipleDonationItems(
@Body() body: CreateMultipleDonationItemsDto,
): Promise<DonationItem[]> {
return this.donationItemsService.createMultipleDonationItems(
body.donationId,
body.itemName,
body.quantity,
body.reservedQuantity,
body.ozPerItem,
body.estimatedValue,
body.foodType,
body.items,
);
}

Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/donationItems/donationItems.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class DonationItem {
itemId: number;

@Column({ name: 'donation_id', type: 'int' })
donation_id: number;
donationId: number;

@ManyToOne(() => Donation, { nullable: false })
@JoinColumn({ name: 'donation_id', referencedColumnName: 'donationId' })
Expand Down
31 changes: 31 additions & 0 deletions apps/backend/src/donationItems/donationItems.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,37 @@ export class DonationItemsService {
return this.repo.save(donationItem);
}

async createMultipleDonationItems(
donationId: number,
items: {
itemName: string;
quantity: number;
reservedQuantity: number;
ozPerItem: number;
estimatedValue: number;
foodType: FoodType;
}[],
): Promise<DonationItem[]> {
validateId(donationId, 'Donation');

const donation = await this.donationRepo.findOneBy({ donationId });
if (!donation) throw new NotFoundException('Donation not found');

const donationItems = items.map((item) =>
this.repo.create({
donation,
itemName: item.itemName,
quantity: item.quantity,
reservedQuantity: item.reservedQuantity,
ozPerItem: item.ozPerItem,
estimatedValue: item.estimatedValue,
foodType: item.foodType,
}),
);

return this.repo.save(donationItems);
}

async updateDonationItemQuantity(itemId: number): Promise<DonationItem> {
validateId(itemId, 'Donation Item');

Expand Down
48 changes: 48 additions & 0 deletions apps/backend/src/donationItems/dtos/create-donation-items.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
IsNumber,
IsString,
IsArray,
ValidateNested,
Min,
IsEnum,
IsNotEmpty,
Length,
} from 'class-validator';
import { Type } from 'class-transformer';
import { FoodType } from '../types';

export class CreateDonationItemDto {
@IsString()
@IsNotEmpty()
@Length(1, 255)
itemName: string;

@IsNumber()
@Min(1)
quantity: number;

@IsNumber()
@Min(0)
reservedQuantity: number;

@IsNumber()
@Min(1)
ozPerItem: number;

@IsNumber()
@Min(1)
estimatedValue: number;

@IsEnum(FoodType)
foodType: FoodType;
}

export class CreateMultipleDonationItemsDto {
@IsNumber()
donationId: number;

@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateDonationItemDto)
items: CreateDonationItemDto[];
}
16 changes: 9 additions & 7 deletions apps/frontend/src/api/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CreateFoodRequestBody,
Pantry,
PantryApplicationDto,
CreateMultipleDonationItemsBody,
OrderSummary,
UserDto,
OrderDetails,
Expand Down Expand Up @@ -43,19 +44,20 @@ export class ApiClient {
return this.post('/api/donations/create', body) as Promise<Donation>;
}

public async postDonationItem(body: unknown): Promise<DonationItem> {
return this.post(
'/api/donation-items/create',
body,
) as Promise<DonationItem>;
}

public async createFoodRequest(
body: CreateFoodRequestBody,
): Promise<FoodRequest> {
return this.post('/api/requests/create', body) as Promise<FoodRequest>;
}

public async postMultipleDonationItems(
body: CreateMultipleDonationItemsBody,
): Promise<DonationItem[]> {
return this.post('/api/donation-items/create-multiple', body) as Promise<
DonationItem[]
>;
}

private async patch(path: string, body: unknown): Promise<unknown> {
return this.axiosInstance
.patch(path, body)
Expand Down
Loading
Loading