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
16 changes: 9 additions & 7 deletions apps/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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 {
Expand All @@ -29,13 +30,14 @@ export class AuthController {
throw new BadRequestException(e.message);
}

const user = await this.usersService.create(
signUpDto.email,
signUpDto.firstName,
signUpDto.lastName,
signUpDto.phone,
Role.VOLUNTEER,
);
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;
}
Expand Down
39 changes: 38 additions & 1 deletion apps/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common';
import {
ConflictException,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import {
AdminDeleteUserCommand,
AdminInitiateAuthCommand,
Expand All @@ -7,6 +11,8 @@ import {
ConfirmSignUpCommand,
ForgotPasswordCommand,
SignUpCommand,
AdminCreateUserCommand,
AdminGetUserCommand,
} from '@aws-sdk/client-cognito-identity-provider';

import CognitoAuthConfig from './aws-exports';
Expand Down Expand Up @@ -73,6 +79,37 @@ export class AuthService {
return response.UserConfirmed;
}

async adminCreateUser({
firstName,
lastName,
email,
}: Omit<SignUpDto, 'password'>): Promise<string> {
const createUserCommand = new AdminCreateUserCommand({
UserPoolId: CognitoAuthConfig.userPoolId,
Username: email,
UserAttributes: [
{ Name: 'name', Value: `${firstName} ${lastName}` },
{ Name: 'email', Value: email },
{ Name: 'email_verified', Value: 'true' },
],
DesiredDeliveryMediums: ['EMAIL'],
});

try {
const response = await this.providerClient.send(createUserCommand);
const sub = response.User?.Attributes?.find(
(attr) => attr.Name === 'sub',
)?.Value;
return sub;
} catch (error) {
if (error.name == 'UsernameExistsException') {
throw new ConflictException('A user with this email already exists');
} else {
throw new InternalServerErrorException('Failed to create user');
}
}
}

async verifyUser(email: string, verificationCode: string): Promise<void> {
const confirmCommand = new ConfirmSignUpCommand({
ClientId: CognitoAuthConfig.userPoolClientId,
Expand Down
8 changes: 6 additions & 2 deletions apps/backend/src/foodManufacturers/manufacturers.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { Module } from '@nestjs/common';
import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FoodManufacturer } from './manufacturers.entity';
import { FoodManufacturersController } from './manufacturers.controller';
import { FoodManufacturersService } from './manufacturers.service';
import { UsersModule } from '../users/users.module';

@Module({
imports: [TypeOrmModule.forFeature([FoodManufacturer])],
imports: [
TypeOrmModule.forFeature([FoodManufacturer]),
forwardRef(() => UsersModule),
],
controllers: [FoodManufacturersController],
providers: [FoodManufacturersService],
})
Expand Down
19 changes: 18 additions & 1 deletion apps/backend/src/foodManufacturers/manufacturers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import { FoodManufacturerApplicationDto } from './dtos/manufacturer-application.
import { User } from '../users/user.entity';
import { Role } from '../users/types';
import { ApplicationStatus } from '../shared/types';
import { userSchemaDto } from '../users/dtos/userSchema.dto';
import { UsersService } from '../users/users.service';

@Injectable()
export class FoodManufacturersService {
constructor(
@InjectRepository(FoodManufacturer)
private repo: Repository<FoodManufacturer>,

private usersService: UsersService,
) {}

async findOne(foodManufacturerId: number): Promise<FoodManufacturer> {
Expand Down Expand Up @@ -99,7 +103,20 @@ export class FoodManufacturersService {
throw new NotFoundException(`Food Manufacturer ${id} not found`);
}

await this.repo.update(id, { status: ApplicationStatus.APPROVED });
const createUserDto: userSchemaDto = {
email: foodManufacturer.foodManufacturerRepresentative.email,
firstName: foodManufacturer.foodManufacturerRepresentative.firstName,
lastName: foodManufacturer.foodManufacturerRepresentative.lastName,
phone: foodManufacturer.foodManufacturerRepresentative.phone,
role: Role.FOODMANUFACTURER,
};

const newFoodManufacturer = await this.usersService.create(createUserDto);

await this.repo.update(id, {
status: ApplicationStatus.APPROVED,
foodManufacturerRepresentative: newFoodManufacturer,
});
}

async deny(id: number) {
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/pantries/pantries.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { Pantry } from './pantries.entity';
import { AuthModule } from '../auth/auth.module';
import { OrdersModule } from '../orders/order.module';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';

@Module({
imports: [
TypeOrmModule.forFeature([Pantry, User]),
OrdersModule,
forwardRef(() => UsersModule),
forwardRef(() => AuthModule),
],
controllers: [PantriesController],
Expand Down
20 changes: 6 additions & 14 deletions apps/backend/src/pantries/pantries.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import {
AllergensConfidence,
} from './types';
import { ApplicationStatus } from '../shared/types';
import { UsersService } from '../users/users.service';

const mockRepository = mock<Repository<Pantry>>();
const mockUsersService = mock<UsersService>();

describe('PantriesService', () => {
let service: PantriesService;
Expand Down Expand Up @@ -79,6 +81,10 @@ describe('PantriesService', () => {
provide: getRepositoryToken(Pantry),
useValue: mockRepository,
},
{
provide: UsersService,
useValue: mockUsersService,
},
],
}).compile();

Expand Down Expand Up @@ -162,20 +168,6 @@ describe('PantriesService', () => {

// Approve pantry by ID (status = approved)
describe('approve', () => {
it('should approve a pantry', async () => {
mockRepository.findOne.mockResolvedValueOnce(mockPendingPantry);
mockRepository.update.mockResolvedValueOnce(undefined);

await service.approve(1);

expect(mockRepository.findOne).toHaveBeenCalledWith({
where: { pantryId: 1 },
});
expect(mockRepository.update).toHaveBeenCalledWith(1, {
status: 'approved',
});
});

it('should throw NotFoundException if pantry not found', async () => {
mockRepository.findOne.mockResolvedValueOnce(null);

Expand Down
37 changes: 33 additions & 4 deletions apps/backend/src/pantries/pantries.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import {
ConflictException,
forwardRef,
Inject,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { Pantry } from './pantries.entity';
Expand All @@ -7,10 +13,17 @@ import { validateId } from '../utils/validation.utils';
import { ApplicationStatus } from '../shared/types';
import { PantryApplicationDto } from './dtos/pantry-application.dto';
import { Role } from '../users/types';
import { userSchemaDto } from '../users/dtos/userSchema.dto';
import { UsersService } from '../users/users.service';

@Injectable()
export class PantriesService {
constructor(@InjectRepository(Pantry) private repo: Repository<Pantry>) {}
constructor(
@InjectRepository(Pantry) private repo: Repository<Pantry>,

@Inject(forwardRef(() => UsersService))
private usersService: UsersService,
) {}

async findOne(pantryId: number): Promise<Pantry> {
validateId(pantryId, 'Pantry');
Expand Down Expand Up @@ -94,12 +107,28 @@ export class PantriesService {
async approve(id: number) {
validateId(id, 'Pantry');

const pantry = await this.repo.findOne({ where: { pantryId: id } });
const pantry = await this.repo.findOne({
where: { pantryId: id },
relations: ['pantryUser'],
});
if (!pantry) {
throw new NotFoundException(`Pantry ${id} not found`);
}

await this.repo.update(id, { status: ApplicationStatus.APPROVED });
const createUserDto: userSchemaDto = {
email: pantry.pantryUser.email,
firstName: pantry.pantryUser.firstName,
lastName: pantry.pantryUser.lastName,
phone: pantry.pantryUser.phone,
role: Role.PANTRY,
};

const newPantryUser = await this.usersService.create(createUserDto);

await this.repo.update(id, {
status: ApplicationStatus.APPROVED,
pantryUser: newPantryUser,
});
}

async deny(id: number) {
Expand Down
8 changes: 1 addition & 7 deletions apps/backend/src/users/users.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,7 @@ describe('UsersController', () => {
const result = await controller.createUser(createUserSchema);

expect(result).toEqual(createdUser);
expect(mockUserService.create).toHaveBeenCalledWith(
createUserSchema.email,
createUserSchema.firstName,
createUserSchema.lastName,
createUserSchema.phone,
createUserSchema.role,
);
expect(mockUserService.create).toHaveBeenCalledWith(createUserSchema);
});

it('should handle service errors', async () => {
Expand Down
3 changes: 1 addition & 2 deletions apps/backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ export class UsersController {

@Post('/')
async createUser(@Body() createUserDto: userSchemaDto): Promise<User> {
const { email, firstName, lastName, phone, role } = createUserDto;
return this.usersService.create(email, firstName, lastName, phone, role);
return this.usersService.create(createUserDto);
}

@Post('/:id/pantries')
Expand Down
37 changes: 22 additions & 15 deletions apps/backend/src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import { mock } from 'jest-mock-extended';
import { In } from 'typeorm';
import { BadRequestException } from '@nestjs/common';
import { PantriesService } from '../pantries/pantries.service';
import { userSchemaDto } from './dtos/userSchema.dto';
import { AuthService } from '../auth/auth.service';

const mockUserRepository = mock<Repository<User>>();
const mockPantriesService = mock<PantriesService>();
const mockAuthService = mock<AuthService>();

const mockUser: Partial<User> = {
id: 1,
Expand All @@ -32,6 +35,7 @@ describe('UsersService', () => {
mockUserRepository.find.mockReset();
mockUserRepository.remove.mockReset();
mockPantriesService.findByIds.mockReset();
mockAuthService.adminCreateUser.mockResolvedValue('mock-sub');

const module = await Test.createTestingModule({
providers: [
Expand All @@ -44,6 +48,10 @@ describe('UsersService', () => {
provide: PantriesService,
useValue: mockPantriesService,
},
{
provide: AuthService,
useValue: mockAuthService,
},
],
}).compile();

Expand All @@ -69,33 +77,32 @@ describe('UsersService', () => {

describe('create', () => {
it('should create a new user with auto-generated ID', async () => {
const userData = {
const createUserDto: userSchemaDto = {
email: 'newuser@example.com',
firstName: 'Jane',
lastName: 'Smith',
phone: '9876543210',
role: Role.ADMIN,
} as User;
};

const createdUser = { ...userData, id: 1 };
const createdUser = {
...createUserDto,
id: 1,
userCognitoSub: 'mock-sub',
} as User;
mockUserRepository.create.mockReturnValue(createdUser);
mockUserRepository.save.mockResolvedValue(createdUser);

const result = await service.create(
userData.email,
userData.firstName,
userData.lastName,
userData.phone,
userData.role,
);
const result = await service.create(createUserDto);

expect(result).toEqual(createdUser);
expect(mockUserRepository.create).toHaveBeenCalledWith({
role: userData.role,
firstName: userData.firstName,
lastName: userData.lastName,
email: userData.email,
phone: userData.phone,
role: createUserDto.role,
firstName: createUserDto.firstName,
lastName: createUserDto.lastName,
email: createUserDto.email,
phone: createUserDto.phone,
userCognitoSub: 'mock-sub',
});
expect(mockUserRepository.save).toHaveBeenCalledWith(createdUser);
});
Expand Down
Loading