Skip to content
2 changes: 2 additions & 0 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './auth/roles.guard';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { ScheduleModule } from '@nestjs/schedule';
import { VolunteersModule } from './volunteers/volunteers.module';

@Module({
imports: [
Expand All @@ -43,6 +44,7 @@ import { ScheduleModule } from '@nestjs/schedule';
OrdersModule,
ManufacturerModule,
AllocationModule,
VolunteersModule,
],
controllers: [AppController],
providers: [
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/orders/order.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('OrdersService', () => {
}

// Clean database at the start
await testDataSource.query(`DROP SCHEMA public CASCADE`);
await testDataSource.query(`DROP SCHEMA IF EXISTS public CASCADE`);
await testDataSource.query(`CREATE SCHEMA public`);

const module: TestingModule = await Test.createTestingModule({
Expand Down
134 changes: 0 additions & 134 deletions apps/backend/src/users/users.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { userSchemaDto } from './dtos/userSchema.dto';

import { Test, TestingModule } from '@nestjs/testing';
import { mock } from 'jest-mock-extended';
import { Pantry } from '../pantries/pantries.entity';

const mockUserService = mock<UsersService>();

Expand All @@ -20,35 +19,6 @@ const mockUser1: Partial<User> = {
role: Role.VOLUNTEER,
};

const mockUser2: Partial<User> = {
id: 2543210,
email: 'bobsmith@example.com',
firstName: 'Bob',
lastName: 'Smith',
phone: '9876',
role: Role.VOLUNTEER,
};

const mockUser3: Partial<User> = {
id: 3,
role: Role.VOLUNTEER,
};

const mockPantries: Partial<Pantry>[] = [
{
pantryId: 1,
pantryUser: mockUser1 as User,
},
{
pantryId: 2,
pantryUser: mockUser1 as User,
},
{
pantryId: 3,
pantryUser: mockUser2 as User,
},
];

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

Expand Down Expand Up @@ -76,45 +46,6 @@ describe('UsersController', () => {
expect(controller).toBeDefined();
});

describe('GET /volunteers', () => {
it('should return all volunteers', async () => {
const users: (Omit<Partial<User>, 'pantries'> & {
pantryIds: number[];
})[] = [
{
id: 1,
role: Role.VOLUNTEER,
pantryIds: [1],
},
{
id: 2,
role: Role.VOLUNTEER,
pantryIds: [2],
},
{
id: 3,
role: Role.ADMIN,
pantryIds: [3],
},
];

const volunteers = users.slice(0, 2);

mockUserService.getVolunteersAndPantryAssignments.mockResolvedValue(
volunteers as (Omit<User, 'pantries'> & { pantryIds: number[] })[],
);

const result = await controller.getAllVolunteers();

expect(result).toEqual(volunteers);
expect(result.length).toBe(2);
expect(result.every((u) => u.role === Role.VOLUNTEER)).toBe(true);
expect(
mockUserService.getVolunteersAndPantryAssignments,
).toHaveBeenCalled();
});
});

describe('GET /:id', () => {
it('should return a user by id', async () => {
mockUserService.findOne.mockResolvedValue(mockUser1 as User);
Expand Down Expand Up @@ -200,69 +131,4 @@ describe('UsersController', () => {
);
});
});

describe('GET /volunteers', () => {
it('should return all volunteers with their pantry assignments', async () => {
const assignments: (User & { pantryIds: number[] })[] = [
{ ...(mockUser1 as User), pantryIds: [1, 2] },
{ ...(mockUser2 as User), pantryIds: [1] },
{ ...(mockUser3 as User), pantryIds: [] },
];

mockUserService.getVolunteersAndPantryAssignments.mockResolvedValue(
assignments,
);

const result = await controller.getAllVolunteers();

expect(result).toEqual(assignments);
expect(result).toHaveLength(3);
expect(result[0].id).toBe(1);
expect(result[0].pantryIds).toEqual([1, 2]);
expect(result[1].id).toBe(2543210);
expect(result[1].pantryIds).toEqual([1]);
expect(result[2].id).toBe(3);
expect(result[2].pantryIds).toEqual([]);
expect(
mockUserService.getVolunteersAndPantryAssignments,
).toHaveBeenCalled();
});
});

describe('GET /:id/pantries', () => {
it('should return pantries assigned to a user', async () => {
mockUserService.getVolunteerPantries.mockResolvedValue(
mockPantries.slice(0, 2) as Pantry[],
);

const result = await controller.getVolunteerPantries(1);

expect(result).toHaveLength(2);
expect(result).toEqual(mockPantries.slice(0, 2));
expect(mockUserService.getVolunteerPantries).toHaveBeenCalledWith(1);
});
});

describe('POST /:id/pantries', () => {
it('should assign pantries to a volunteer and return result', async () => {
const pantryIds = [1, 3];
const updatedUser = {
...mockUser3,
pantries: [mockPantries[0] as Pantry, mockPantries[2] as Pantry],
} as User;

mockUserService.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(mockUserService.assignPantriesToVolunteer).toHaveBeenCalledWith(
3,
pantryIds,
);
});
});
});
23 changes: 0 additions & 23 deletions apps/backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,16 @@ import { UsersService } from './users.service';
import { User } from './user.entity';
import { Role } from './types';
import { userSchemaDto } from './dtos/userSchema.dto';
import { Pantry } from '../pantries/pantries.entity';

@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}

@Get('/volunteers')
async getAllVolunteers(): Promise<
(Omit<User, 'pantries'> & { pantryIds: number[] })[]
> {
return this.usersService.getVolunteersAndPantryAssignments();
}

@Get('/:id')
async getUser(@Param('id', ParseIntPipe) userId: number): Promise<User> {
return this.usersService.findOne(userId);
}

@Get('/:id/pantries')
async getVolunteerPantries(
@Param('id', ParseIntPipe) id: number,
): Promise<Pantry[]> {
return this.usersService.getVolunteerPantries(id);
}

@Delete('/:id')
removeUser(@Param('id', ParseIntPipe) userId: number): Promise<User> {
return this.usersService.remove(userId);
Expand All @@ -59,12 +44,4 @@ export class UsersController {
const { email, firstName, lastName, phone, role } = createUserDto;
return this.usersService.create(email, firstName, lastName, phone, role);
}

@Post('/:id/pantries')
async assignPantries(
@Param('id', ParseIntPipe) id: number,
@Body('pantryIds') pantryIds: number[],
): Promise<User> {
return this.usersService.assignPantriesToVolunteer(id, pantryIds);
}
}
13 changes: 0 additions & 13 deletions apps/backend/src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,6 @@ describe('UsersService', () => {
});
});

describe('findByEmail', () => {
it('should return user by email', async () => {
mockUserRepository.findOneBy.mockResolvedValue(mockUser as User);

const result = await service.findByEmail('test@example.com');

expect(result).toEqual(mockUser);
expect(mockUserRepository.findOneBy).toHaveBeenCalledWith({
email: 'test@example.com',
});
});
});

describe('update', () => {
it('should update user attributes', async () => {
const updateData = { firstName: 'Updated', role: Role.ADMIN };
Expand Down
71 changes: 1 addition & 70 deletions apps/backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { User } from './user.entity';
import { Role } from './types';
import { validateId } from '../utils/validation.utils';
import { Pantry } from '../pantries/pantries.entity';
import { PantriesService } from '../pantries/pantries.service';

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

private pantriesService: PantriesService,
) {}

async create(
Expand Down Expand Up @@ -49,30 +41,6 @@ export class UsersService {
return user;
}

async findVolunteer(volunteerId: number): Promise<User> {
validateId(volunteerId, 'Volunteer');

const volunteer = await this.repo.findOne({
where: { id: volunteerId },
relations: ['pantries'],
});

if (!volunteer)
throw new NotFoundException(`User ${volunteerId} not found`);
if (volunteer.role !== Role.VOLUNTEER) {
throw new BadRequestException(`User ${volunteerId} is not a volunteer`);
}
return volunteer;
}

async findByEmail(email: string): Promise<User> {
const user = await this.repo.findOneBy({ email });
if (!user) {
throw new NotFoundException(`User with email ${email} not found`);
}
return user;
}

async update(id: number, attrs: Partial<User>) {
validateId(id, 'User');

Expand Down Expand Up @@ -106,43 +74,6 @@ export class UsersService {
});
}

async getVolunteersAndPantryAssignments(): Promise<
(Omit<User, 'pantries'> & { pantryIds: number[] })[]
> {
const volunteers = await this.findUsersByRoles([Role.VOLUNTEER]);

return volunteers.map((v) => {
const { pantries, ...volunteerWithoutPantries } = v;
return {
...volunteerWithoutPantries,
pantryIds: pantries.map((p) => p.pantryId),
};
});
}

async getVolunteerPantries(volunteerId: number): Promise<Pantry[]> {
const volunteer = await this.findVolunteer(volunteerId);
return volunteer.pantries;
}

async assignPantriesToVolunteer(
volunteerId: number,
pantryIds: number[],
): Promise<User> {
pantryIds.forEach((id) => validateId(id, 'Pantry'));

const volunteer = await this.findVolunteer(volunteerId);

const pantries = await this.pantriesService.findByIds(pantryIds);
const existingPantryIds = volunteer.pantries.map((p) => p.pantryId);
const newPantries = pantries.filter(
(p) => !existingPantryIds.includes(p.pantryId),
);

volunteer.pantries = [...volunteer.pantries, ...newPantries];
return this.repo.save(volunteer);
}

async findUserByCognitoId(cognitoId: string): Promise<User> {
const user = await this.repo.findOneBy({ userCognitoSub: cognitoId });
if (!user) {
Expand Down
Loading