Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
ArrayNotEmpty,
IsBoolean,
IsEmail,
IsEnum,
IsNotEmpty,
IsOptional,
IsPhoneNumber,
IsString,
Length,
MaxLength,
} from 'class-validator';
import { Allergen, DonateWastedFood, ManufacturerAttribute } from '../types';

export class UpdateFoodManufacturerApplicationDto {
@IsOptional()
@IsString()
@IsNotEmpty()
@MaxLength(255)
secondaryContactFirstName?: string;

@IsOptional()
@IsString()
@IsNotEmpty()
@MaxLength(255)
secondaryContactLastName?: string;

@IsOptional()
@IsEmail()
@IsNotEmpty()
@MaxLength(255)
secondaryContactEmail?: string;

@IsOptional()
@IsString()
@IsPhoneNumber('US', {
message:
'secondaryContactPhone must be a valid phone number (make sure all the digits are correct)',
})
@IsNotEmpty()
secondaryContactPhone?: string;

@IsString()
@IsNotEmpty()
@IsOptional()
@Length(1, 255)
foodManufacturerName?: string;

@IsString()
@IsNotEmpty()
@IsOptional()
@Length(1, 255)
foodManufacturerWebsite?: string;

@IsOptional()
@ArrayNotEmpty()
@IsEnum(Allergen, { each: true })
unlistedProductAllergens?: Allergen[];

@IsOptional()
@ArrayNotEmpty()
@IsEnum(Allergen, { each: true })
facilityFreeAllergens?: Allergen[];

@IsOptional()
@IsBoolean()
productsGlutenFree?: boolean;

@IsOptional()
@IsBoolean()
productsContainSulfites?: boolean;

@IsOptional()
@IsString()
@IsNotEmpty()
productsSustainableExplanation?: string;

@IsOptional()
@IsBoolean()
inKindDonations?: boolean;

@IsOptional()
@IsEnum(DonateWastedFood)
donateWastedFood?: DonateWastedFood;

@IsOptional()
@IsEnum(ManufacturerAttribute)
manufacturerAttribute?: ManufacturerAttribute;

@IsOptional()
@IsString()
@IsNotEmpty()
additionalComments?: string;

@IsOptional()
@IsBoolean()
newsletterSubscription?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { Allergen, DonateWastedFood } from './types';
import { ApplicationStatus } from '../shared/types';
import { FoodManufacturerApplicationDto } from './dtos/manufacturer-application.dto';
import { Donation } from '../donations/donations.entity';
import { UpdateFoodManufacturerApplicationDto } from './dtos/update-manufacturer-application.dto';
import { NotFoundException } from '@nestjs/common';
import { AuthenticatedRequest } from '../auth/authenticated-request';

const mockManufacturersService = mock<FoodManufacturersService>();

Expand Down Expand Up @@ -134,6 +137,60 @@ describe('FoodManufacturersController', () => {
});
});

describe('PATCH /:manufacturerId/application', () => {
const req = { user: { id: 1 } };

it('should update a food manufacturer application', async () => {
const manufacturerId = 1;
const mockUpdateData: UpdateFoodManufacturerApplicationDto = {
secondaryContactFirstName: 'Bob',
secondaryContactLastName: 'Smith',
secondaryContactEmail: 'bob.smith@example.com',
productsGlutenFree: false,
inKindDonations: true,
donateWastedFood: DonateWastedFood.ALWAYS,
additionalComments: 'Updated application notes.',
};

mockManufacturersService.updateFoodManufacturerApplication.mockResolvedValue(
mockManufacturer1 as FoodManufacturer,
);

const result = await controller.updateFoodManufacturerApplication(
req as AuthenticatedRequest,
manufacturerId,
mockUpdateData,
);

expect(
mockManufacturersService.updateFoodManufacturerApplication,
).toHaveBeenCalledWith(manufacturerId, mockUpdateData, 1);

expect(result).toEqual(mockManufacturer1);
});

it('should throw error if manufacturer does not exist', async () => {
const mockUpdateData: UpdateFoodManufacturerApplicationDto = {
secondaryContactFirstName: 'John',
};

mockManufacturersService.updateFoodManufacturerApplication.mockRejectedValueOnce(
new NotFoundException('Food Manufacturer 999 not found'),
);

await expect(
controller.updateFoodManufacturerApplication(
req as AuthenticatedRequest,
999,
mockUpdateData,
),
).rejects.toThrow();
expect(
mockManufacturersService.updateFoodManufacturerApplication,
).toHaveBeenCalledWith(999, mockUpdateData, 1);
});
});

describe('PATCH /:id/approve', () => {
it('should approve a food manufacturer', async () => {
mockManufacturersService.approve.mockResolvedValue();
Expand Down
20 changes: 20 additions & 0 deletions apps/backend/src/foodManufacturers/manufacturers.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ParseIntPipe,
Patch,
Post,
Req,
ValidationPipe,
} from '@nestjs/common';
import { FoodManufacturersService } from './manufacturers.service';
Expand All @@ -15,6 +16,10 @@ import { ApiBody } from '@nestjs/swagger';
import { Allergen, DonateWastedFood, ManufacturerAttribute } from './types';
import { Donation } from '../donations/donations.entity';
import { Public } from '../auth/public.decorator';
import { UpdateFoodManufacturerApplicationDto } from './dtos/update-manufacturer-application.dto';
import { Roles } from '../auth/roles.decorator';
import { Role } from '../users/types';
import { AuthenticatedRequest } from '../auth/authenticated-request';

@Controller('manufacturers')
export class FoodManufacturersController {
Expand Down Expand Up @@ -169,6 +174,21 @@ export class FoodManufacturersController {
);
}

@Roles(Role.FOODMANUFACTURER)
@Patch('/:manufacturerId/application')
async updateFoodManufacturerApplication(
@Req() req: AuthenticatedRequest,
@Param('manufacturerId', ParseIntPipe) manufacturerId: number,
@Body(new ValidationPipe())
foodManufacturerData: UpdateFoodManufacturerApplicationDto,
): Promise<FoodManufacturer> {
return this.foodManufacturersService.updateFoodManufacturerApplication(
manufacturerId,
foodManufacturerData,
req.user.id,
);
}

@Patch('/:manufacturerId/approve')
async approveManufacturer(
@Param('manufacturerId', ParseIntPipe) manufacturerId: number,
Expand Down
32 changes: 32 additions & 0 deletions apps/backend/src/foodManufacturers/manufacturers.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BadRequestException,
Injectable,
NotFoundException,
ConflictException,
Expand All @@ -14,6 +15,7 @@ import { ApplicationStatus } from '../shared/types';
import { userSchemaDto } from '../users/dtos/userSchema.dto';
import { UsersService } from '../users/users.service';
import { Donation } from '../donations/donations.entity';
import { UpdateFoodManufacturerApplicationDto } from './dtos/update-manufacturer-application.dto';

@Injectable()
export class FoodManufacturersService {
Expand Down Expand Up @@ -121,6 +123,36 @@ export class FoodManufacturersService {
await this.repo.save(foodManufacturer);
}

async updateFoodManufacturerApplication(
manufacturerId: number,
foodManufacturerData: UpdateFoodManufacturerApplicationDto,
currentUserId: number,
) {
validateId(manufacturerId, 'Food Manufacturer');
validateId(currentUserId, 'User');

const manufacturer = await this.repo.findOne({
where: { foodManufacturerId: manufacturerId },
relations: ['foodManufacturerRepresentative'],
});

if (!manufacturer) {
throw new NotFoundException(
`Food Manufacturer ${manufacturerId} not found`,
);
}

if (manufacturer.foodManufacturerRepresentative.id !== currentUserId) {
throw new BadRequestException(
`User ${currentUserId} is not allowed to edit application for Food Manufacturer ${manufacturerId}`,
);
}

Object.assign(manufacturer, foodManufacturerData);

return await this.repo.save(manufacturer);
}

async approve(id: number) {
validateId(id, 'Food Manufacturer');

Expand Down
Loading
Loading