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
11 changes: 3 additions & 8 deletions apps/backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PassportModule } from '@nestjs/passport';

import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UsersService } from '../users/users.service';
import { User } from '../users/user.entity';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';

@Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule.register({ defaultStrategy: 'jwt' }),
],
imports: [UsersModule, PassportModule.register({ defaultStrategy: 'jwt' })],
controllers: [AuthController],
providers: [AuthService, UsersService, JwtStrategy],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
2 changes: 2 additions & 0 deletions apps/backend/src/config/typeorm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { UpdateFoodRequests1744051370129 } from '../migrations/1744051370129-upd
import { UpdateRequestTable1741571847063 } from '../migrations/1741571847063-updateRequestTable';
import { RemoveOrderIdFromRequests1744133526650 } from '../migrations/1744133526650-removeOrderIdFromRequests.ts';
import { AddOrders1739496585940 } from '../migrations/1739496585940-addOrders';
import { AddVolunteerPantryUniqueConstraint1760033134668 } from '../migrations/1760033134668-AddVolunteerPantryUniqueConstraint';

const config = {
type: 'postgres',
Expand Down Expand Up @@ -45,6 +46,7 @@ const config = {
UpdateRequestTable1741571847063,
UpdateFoodRequests1744051370129,
RemoveOrderIdFromRequests1744133526650,
AddVolunteerPantryUniqueConstraint1760033134668,
],
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddVolunteerPantryUniqueConstraint1760033134668
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE volunteer_assignments DROP COLUMN assignment_id;

ALTER TABLE volunteer_assignments
ADD PRIMARY KEY (volunteer_id, pantry_id);

ALTER TABLE volunteer_assignments DROP CONSTRAINT IF EXISTS fk_volunteer_id;

ALTER TABLE volunteer_assignments DROP CONSTRAINT IF EXISTS fk_pantry_id;

ALTER TABLE volunteer_assignments
ADD CONSTRAINT fk_volunteer_id FOREIGN KEY (volunteer_id) REFERENCES users(user_id) ON DELETE CASCADE,
ADD CONSTRAINT fk_pantry_id FOREIGN KEY (pantry_id) REFERENCES pantries(pantry_id) ON DELETE CASCADE;
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE volunteer_assignments DROP CONSTRAINT fk_volunteer_id;

ALTER TABLE volunteer_assignments DROP CONSTRAINT fk_pantry_id;

ALTER TABLE volunteer_assignments DROP CONSTRAINT volunteer_assignments_pkey;

ALTER TABLE volunteer_assignments ADD COLUMN assignment_id SERIAL PRIMARY KEY;
`);
}
}
17 changes: 15 additions & 2 deletions apps/backend/src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Param,
ParseIntPipe,
Put,
Patch,
BadRequestException,
Body,
//UseGuards,
Expand All @@ -14,7 +15,6 @@ import { UsersService } from './users.service';
//import { AuthGuard } from '@nestjs/passport';
import { User } from './user.entity';
import { Role } from './types';
import { VOLUNTEER_ROLES } from './types';
//import { CurrentUserInterceptor } from '../interceptors/current-user.interceptor';

@Controller('users')
Expand All @@ -24,7 +24,7 @@ export class UsersController {

@Get('/volunteers')
async getAllVolunteers(): Promise<User[]> {
return this.usersService.findUsersByRoles(VOLUNTEER_ROLES);
return this.usersService.getVolunteersAndPantryAssignments();
}

// @UseGuards(AuthGuard('jwt'))
Expand All @@ -48,4 +48,17 @@ export class UsersController {
}
return this.usersService.update(id, { role: role as Role });
}

@Get('/:id/pantries')
async getVolunteerPantries(@Param('id', ParseIntPipe) id: number) {
return this.usersService.getVolunteerPantries(id);
}

@Patch(':id/pantries')
async assignPantry(
@Param('id', ParseIntPipe) id: number,
@Body('pantryIds') pantryIds: number[],
) {
return this.usersService.assignPantriesToVolunteer(id, pantryIds);
}
}
6 changes: 4 additions & 2 deletions apps/backend/src/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { User } from './user.entity';
import { JwtStrategy } from '../auth/jwt.strategy';
import { CurrentUserInterceptor } from '../interceptors/current-user.interceptor';
import { AuthService } from '../auth/auth.service';
import { Assignments } from '../volunteerAssignments/volunteerAssignments.entity';
import { Pantry } from '../pantries/pantries.entity';

@Module({
imports: [TypeOrmModule.forFeature([User])],
exports: [UsersService],
imports: [TypeOrmModule.forFeature([User, Assignments, Pantry])],
exports: [UsersService, TypeOrmModule],
controllers: [UsersController],
providers: [UsersService, AuthService, JwtStrategy, CurrentUserInterceptor],
})
Expand Down
69 changes: 66 additions & 3 deletions apps/backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import {
BadRequestException,
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 { Role, VOLUNTEER_ROLES } from './types';
import { validateId } from '../utils/validation.utils';
import { Assignments } from '../volunteerAssignments/volunteerAssignments.entity';
import { Pantry } from '../pantries/pantries.entity';

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

@InjectRepository(Assignments)
private assignmentsRepo: Repository<Assignments>,

@InjectRepository(Pantry)
private pantryRepo: Repository<Pantry>,
) {}

async create(
email: string,
Expand Down Expand Up @@ -72,4 +87,52 @@ export class UsersService {
async findUsersByRoles(roles: Role[]): Promise<User[]> {
return this.repo.find({ where: { role: In(roles) } });
}

async getVolunteersAndPantryAssignments() {
const volunteers = await this.findUsersByRoles(VOLUNTEER_ROLES);

const assignments = await this.assignmentsRepo.find({
relations: ['pantry', 'volunteer'],
});

return volunteers.map((v) => {
const assigned = assignments
.filter((a) => a.volunteer.id == v.id)
.map((a) => a.pantry.pantryId);
return { ...v, pantryIds: assigned };
});
}

async getVolunteerPantries(volunteerId: number) {
validateId(volunteerId, 'Volunteer');
const assignments = await this.assignmentsRepo.find({
where: { volunteer: { id: volunteerId } },
relations: ['pantry'],
});

return assignments.map((a) => a.pantry);
}

async assignPantriesToVolunteer(volunteerId: number, pantryIds: number[]) {
validateId(volunteerId, 'Volunteer');
for (const pantryId of pantryIds) {
validateId(pantryId, 'Pantry');
}

const volunteer = await this.repo.findOne({ where: { id: volunteerId } });
if (!volunteer)
throw new NotFoundException(`Volunteer ${volunteerId} not found`);

const pantries = await this.pantryRepo.findBy({ pantryId: In(pantryIds) });

if (pantries.length !== pantryIds.length) {
throw new BadRequestException('One or more pantries not found');
}

const assignments = pantries.map((pantry) =>
this.assignmentsRepo.create({ volunteer, pantry }),
);

return this.assignmentsRepo.save(assignments);
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
import {
Entity,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
ManyToOne,
Column,
} from 'typeorm';
import { Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
import { User } from '../users/user.entity';
import { Pantry } from '../pantries/pantries.entity';

@Entity('volunteer_assignments')
export class Assignments {
@PrimaryGeneratedColumn({ name: 'assignment_id' })
assignmentId: number;

@Column({ name: 'volunteer_id' })
@PrimaryColumn({ name: 'volunteer_id' })
volunteerId: number;

@ManyToOne(() => User, { nullable: false })
@PrimaryColumn({ name: 'pantry_id' })
pantryId: number;

@ManyToOne(() => User, { nullable: false, onDelete: 'CASCADE' })

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for @sam-schu : I did not know about this onDelete: 'CASCADE' feature. For future work, should look into other entities to see where we may want to add this, as its quite useful.

@JoinColumn({
name: 'volunteer_id',
referencedColumnName: 'id',
})
volunteer: User;

@OneToOne(() => Pantry, { nullable: true })
@ManyToOne(() => Pantry, { nullable: false, onDelete: 'CASCADE' })
@JoinColumn({
name: 'pantry_id',
referencedColumnName: 'pantryId',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ export class AssignmentsService {
private usersService: UsersService,
) {}

// Gets the assignment id, volunteer details and the corresponding pantry
// Gets the volunteer details and the corresponding pantry
async getAssignments() {
const results = await this.repo.find({
relations: ['volunteer', 'pantry'],
select: {
assignmentId: true,
volunteer: {
id: true,
firstName: true,
Expand Down
1 change: 0 additions & 1 deletion apps/frontend/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ export enum VolunteerType {
}

export interface VolunteerPantryAssignment {
assignmentId: number;
volunteer: {
id: number;
firstName: string;
Expand Down