Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
09f77a7
Initial commit for this branch
dburkhart07 Jan 20, 2025
4d303ea
food request modal and button, updated form contents
Juwang110 Jan 21, 2025
28951ee
Updated design for request forms page
dburkhart07 Jan 22, 2025
0fc781d
remove unecessary form data, made component into a button to be used …
Juwang110 Jan 22, 2025
fb3fc5f
Merge branch 'main' into food-request-form
Juwang110 Jan 22, 2025
d6ccd89
backend post form route working, created food requests backend folder…
Juwang110 Jan 22, 2025
11730b3
Renamed frontend component to RequestForms (Admin dashboard of all ac…
dburkhart07 Jan 24, 2025
6c78018
Completed frontend, just need to incorporate backend APIs into it
dburkhart07 Jan 24, 2025
22b7000
fixed submit button orientation, added description to form, deleted o…
Juwang110 Jan 24, 2025
ffae96e
Updated dashboard to properly get the requests by pantryId in database
dburkhart07 Jan 24, 2025
dc088a8
remove old form route
Juwang110 Jan 24, 2025
bac85b2
Updated API endpoint for confirm delivery and added previous order fu…
dburkhart07 Jan 25, 2025
d3e98c2
fix form submission bug
Juwang110 Jan 25, 2025
3fcced8
Fixed confirming delivery dates, previous requests, and smaller featu…
dburkhart07 Jan 26, 2025
5ce06a1
Final push for branch
dburkhart07 Jan 26, 2025
2389fb3
Fixed final aspects of deliveryConfirmation
dburkhart07 Jan 26, 2025
bd48e43
adding api body for documentation in swagger
Juwang110 Jan 26, 2025
067c220
Pulled changes from food-request
dburkhart07 Jan 27, 2025
9e93647
Created base64 encoding for uploaded photos
dburkhart07 Jan 27, 2025
e14c427
Logic for AWS S3 photo uploads and paths saved to database
dburkhart07 Jan 29, 2025
70c240b
Fixed PR suggested changes
dburkhart07 Feb 1, 2025
3c9498d
Finished PR changes
dburkhart07 Feb 1, 2025
5f229b1
Finalized AWS photo uploads
dburkhart07 Feb 2, 2025
549f512
Cleaned up code for consistency
dburkhart07 Feb 2, 2025
f16ad86
Fixing styling of request form page
dburkhart07 Feb 2, 2025
a1ae283
Fixed formattings
dburkhart07 Feb 2, 2025
b285c5e
Fixed code format
dburkhart07 Feb 2, 2025
c2c433c
Fixed backend alerting
dburkhart07 Feb 3, 2025
806ea34
Removed unwanted binary files
dburkhart07 Feb 3, 2025
6f0e232
Removed unwanted binary files
dburkhart07 Feb 3, 2025
9361a32
Fixed optional photo uploading
dburkhart07 Feb 5, 2025
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
4 changes: 2 additions & 2 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { RequestsModule } from './foodRequests/request.module';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
Expand All @@ -14,14 +14,14 @@ import typeorm from './config/typeorm';
isGlobal: true,
load: [typeorm],
}),
// Load TypeORM config async so we can target the config file (config/typeorm.ts) for migrations
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) =>
configService.get('typeorm'),
}),
UsersModule,
AuthModule,
RequestsModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
10 changes: 1 addition & 9 deletions apps/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
BadRequestException,
Body,
Controller,
Post,
Request,
UseGuards,
} from '@nestjs/common';
import { BadRequestException, Body, Controller, Post } from '@nestjs/common';

import { SignInDto } from './dtos/sign-in.dto';
import { SignUpDto } from './dtos/sign-up.dto';
Expand All @@ -16,7 +9,6 @@ import { DeleteUserDto } from './dtos/delete-user.dto';
import { User } from '../users/user.entity';
import { SignInResponseDto } from './dtos/sign-in-response.dto';
import { RefreshTokenDto } from './dtos/refresh-token.dto';
import { AuthGuard } from '@nestjs/passport';
import { ConfirmPasswordDto } from './dtos/confirm-password.dto';
import { ForgotPasswordDto } from './dtos/forgot-password.dto';

Expand Down
10 changes: 10 additions & 0 deletions apps/backend/src/aws/aws-s3.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Global, Module } from '@nestjs/common';
import { AWSS3Service } from './aws-s3.service';

@Global()
@Module({
imports: [],
providers: [AWSS3Service],
exports: [AWSS3Service],
})
export class AWSS3Module {}
48 changes: 48 additions & 0 deletions apps/backend/src/aws/aws-s3.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Injectable } from '@nestjs/common';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

@Injectable()
export class AWSS3Service {
private client: S3Client;
private readonly bucket: string;
private readonly region: string;

constructor() {
this.region = process.env.AWS_REGION || 'us-east-2';
this.bucket = process.env.AWS_BUCKET_NAME;
if (!this.bucket) {
throw new Error('AWS_BUCKET_NAME is not defined');
}
this.client = new S3Client({
region: this.region,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});
}

async upload(files: Express.Multer.File[]): Promise<string[]> {
const uploadedFileUrls: string[] = [];
try {
for (const file of files) {
const fileName = file.originalname;

const command = new PutObjectCommand({
Bucket: this.bucket,
Key: fileName,
Body: file.buffer,
ContentType: file.mimetype || 'application/octet-stream',
});

await this.client.send(command);

const url = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${fileName}`;
uploadedFileUrls.push(url);
}
return uploadedFileUrls;
} catch (error) {
throw new Error('File upload to AWS failed');
}
}
}
145 changes: 145 additions & 0 deletions apps/backend/src/foodRequests/request.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {
Controller,
Get,
Param,
ParseIntPipe,
Post,
Body,
UploadedFiles,
UseInterceptors,
} from '@nestjs/common';
import { ApiBody } from '@nestjs/swagger';
import { RequestsService } from './request.service';
import { FoodRequest } from './request.entity';
import { AWSS3Service } from '../aws/aws-s3.service';
import { FilesInterceptor } from '@nestjs/platform-express';
import * as multer from 'multer';

@Controller('requests')
// @UseInterceptors()
export class FoodRequestsController {
constructor(
private requestsService: RequestsService,
private awsS3Service: AWSS3Service,
) {}

@Get('/:pantryId')
async getAllPantryRequests(
@Param('pantryId', ParseIntPipe) pantryId: number,
): Promise<FoodRequest[]> {
return this.requestsService.find(pantryId);
}

@Post('/create')
@ApiBody({
description: 'Details for creating a food request',
schema: {
type: 'object',
properties: {
pantryId: { type: 'integer', example: 1 },
requestedSize: { type: 'string', example: 'Medium (5-10 boxes)' },
requestedItems: {
type: 'array',
items: { type: 'string' },
example: ['Rice Noodles', 'Quinoa'],
},
additionalInformation: {
type: 'string',
nullable: true,
example: 'Urgent request',
},
status: { type: 'string', example: 'pending' },
fulfilledBy: { type: 'integer', nullable: true, example: null },
dateReceived: {
type: 'string',
format: 'date-time',
nullable: true,
example: null,
},
feedback: { type: 'string', nullable: true, example: null },
photos: {
type: 'array',
items: { type: 'string' },
nullable: true,
example: [],
},
},
},
})
async createRequest(
@Body()
body: {
pantryId: number;
requestedSize: string;
requestedItems: string[];
additionalInformation: string;
status: string;
fulfilledBy: number;
dateReceived: Date;
feedback: string;
photos: string[];
},
): Promise<FoodRequest> {
return this.requestsService.create(
body.pantryId,
body.requestedSize,
body.requestedItems,
body.additionalInformation,
body.status,
body.fulfilledBy,
body.dateReceived,
body.feedback,
body.photos,
);
}

@Post('/:requestId/confirm-delivery')
@ApiBody({
description: 'Details for a confirmation form',
schema: {
type: 'object',
properties: {
dateReceived: {
type: 'string',
format: 'date-time',
nullable: true,
example: new Date().toISOString(),
},
feedback: {
type: 'string',
nullable: true,
example: 'Wonderful shipment!',
},
photos: {
type: 'array',
items: { type: 'string' },
nullable: true,
example: [],
},
},
},
})
@UseInterceptors(
FilesInterceptor('photos', 10, { storage: multer.memoryStorage() }),
)
async confirmDelivery(
@Param('requestId', ParseIntPipe) requestId: number,
@Body() body: { dateReceived: string; feedback: string },
@UploadedFiles() photos?: Express.Multer.File[],
): Promise<FoodRequest> {
const formattedDate = new Date(body.dateReceived);
if (isNaN(formattedDate.getTime())) {
throw new Error('Invalid date format for deliveryDate');
}

const uploadedPhotoUrls =
photos && photos.length > 0 ? await this.awsS3Service.upload(photos) : [];

return this.requestsService.updateDeliveryDetails(
requestId,
formattedDate,
body.feedback,
uploadedPhotoUrls,
);
}
}
46 changes: 46 additions & 0 deletions apps/backend/src/foodRequests/request.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
} from 'typeorm';

@Entity('food_requests')
export class FoodRequest {
@PrimaryGeneratedColumn({ name: 'request_id' })
requestId: number;

@Column({ name: 'pantry_id', type: 'int' })
pantryId: number;

@Column({ name: 'requested_size', type: 'varchar', length: 50 })
requestedSize: string;

@Column({ name: 'requested_items', type: 'text', array: true })
requestedItems: string[];

@Column({ name: 'additional_information', type: 'text', nullable: true })
additionalInformation: string;

@CreateDateColumn({
name: 'requested_at',
type: 'timestamp',
default: () => 'NOW()',
})
requestedAt: Date;

@Column({ name: 'status', type: 'varchar', length: 25, default: 'pending' })
status: string;

@Column({ name: 'fulfilled_by', type: 'int', nullable: true })
fulfilledBy: number;

@Column({ name: 'date_received', type: 'timestamp', nullable: true })
dateReceived: Date;

@Column({ name: 'feedback', type: 'text', nullable: true })
feedback: string;

@Column({ name: 'photos', type: 'text', array: true, nullable: true })
photos: string[];
}
20 changes: 20 additions & 0 deletions apps/backend/src/foodRequests/request.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FoodRequestsController } from './request.controller';
import { FoodRequest } from './request.entity';
import { RequestsService } from './request.service';
import { JwtStrategy } from '../auth/jwt.strategy';
import { AuthService } from '../auth/auth.service';
import { AWSS3Module } from '../aws/aws-s3.module';
import { MulterModule } from '@nestjs/platform-express';

@Module({
imports: [
AWSS3Module,
MulterModule.register({ dest: './uploads' }),
TypeOrmModule.forFeature([FoodRequest]),
],
controllers: [FoodRequestsController],
providers: [RequestsService, AuthService, JwtStrategy],
})
export class RequestsModule {}
64 changes: 64 additions & 0 deletions apps/backend/src/foodRequests/request.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FoodRequest } from './request.entity';

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

async create(
pantryId: number,
requestedSize: string,
requestedItems: string[],
additionalInformation: string | null,
status: string = 'pending',
fulfilledBy: number | null,
dateReceived: Date | null,
feedback: string | null,
photos: string[] | null,
) {
const foodRequest = this.repo.create({
pantryId,
requestedSize,
requestedItems,
additionalInformation,
status,
fulfilledBy,
dateReceived,
feedback,
photos,
});

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

async find(pantryId: number) {
if (!pantryId || pantryId < 1) {
throw new NotFoundException('Invalid pantry ID');
}
return await this.repo.find({ where: { pantryId } });
}

async updateDeliveryDetails(
requestId: number,
deliveryDate: Date,
feedback: string,
photos: string[],
): Promise<FoodRequest> {
const request = await this.repo.findOne({ where: { requestId } });

if (!request) {
throw new NotFoundException('Invalid request ID');
}

request.feedback = feedback;
request.dateReceived = deliveryDate;
request.photos = photos;
request.status = 'fulfilled';

return await this.repo.save(request);
}
}
3 changes: 2 additions & 1 deletion apps/backend/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
"src/**/*.d.ts",
"src/types/**/*.d.ts"
]
}
Loading