Skip to content

Commit e724faa

Browse files
authored
Merge pull request #21 from Code-4-Community/confirm-food-delivered
Confirm food delivered and Request Forms Dashboard
2 parents aea2c59 + 9361a32 commit e724faa

File tree

16 files changed

+33602
-2255
lines changed

16 files changed

+33602
-2255
lines changed

apps/backend/src/app.module.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import typeorm from './config/typeorm';
1414
isGlobal: true,
1515
load: [typeorm],
1616
}),
17-
// Load TypeORM config async so we can target the config file (config/typeorm.ts) for migrations
1817
TypeOrmModule.forRootAsync({
1918
inject: [ConfigService],
2019
useFactory: async (configService: ConfigService) =>

apps/backend/src/auth/auth.controller.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import {
2-
BadRequestException,
3-
Body,
4-
Controller,
5-
Post,
6-
Request,
7-
UseGuards,
8-
} from '@nestjs/common';
1+
import { BadRequestException, Body, Controller, Post } from '@nestjs/common';
92

103
import { SignInDto } from './dtos/sign-in.dto';
114
import { SignUpDto } from './dtos/sign-up.dto';
@@ -16,7 +9,6 @@ import { DeleteUserDto } from './dtos/delete-user.dto';
169
import { User } from '../users/user.entity';
1710
import { SignInResponseDto } from './dtos/sign-in-response.dto';
1811
import { RefreshTokenDto } from './dtos/refresh-token.dto';
19-
import { AuthGuard } from '@nestjs/passport';
2012
import { ConfirmPasswordDto } from './dtos/confirm-password.dto';
2113
import { ForgotPasswordDto } from './dtos/forgot-password.dto';
2214

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Global, Module } from '@nestjs/common';
2+
import { AWSS3Service } from './aws-s3.service';
3+
4+
@Global()
5+
@Module({
6+
imports: [],
7+
providers: [AWSS3Service],
8+
exports: [AWSS3Service],
9+
})
10+
export class AWSS3Module {}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
3+
4+
@Injectable()
5+
export class AWSS3Service {
6+
private client: S3Client;
7+
private readonly bucket: string;
8+
private readonly region: string;
9+
10+
constructor() {
11+
this.region = process.env.AWS_REGION || 'us-east-2';
12+
this.bucket = process.env.AWS_BUCKET_NAME;
13+
if (!this.bucket) {
14+
throw new Error('AWS_BUCKET_NAME is not defined');
15+
}
16+
this.client = new S3Client({
17+
region: this.region,
18+
credentials: {
19+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
20+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
21+
},
22+
});
23+
}
24+
25+
async upload(files: Express.Multer.File[]): Promise<string[]> {
26+
const uploadedFileUrls: string[] = [];
27+
try {
28+
for (const file of files) {
29+
const fileName = file.originalname;
30+
31+
const command = new PutObjectCommand({
32+
Bucket: this.bucket,
33+
Key: fileName,
34+
Body: file.buffer,
35+
ContentType: file.mimetype || 'application/octet-stream',
36+
});
37+
38+
await this.client.send(command);
39+
40+
const url = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${fileName}`;
41+
uploadedFileUrls.push(url);
42+
}
43+
return uploadedFileUrls;
44+
} catch (error) {
45+
throw new Error('File upload to AWS failed');
46+
}
47+
}
48+
}

apps/backend/src/foodRequests/request.controller.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ import {
55
ParseIntPipe,
66
Post,
77
Body,
8+
UploadedFiles,
9+
UseInterceptors,
810
} from '@nestjs/common';
911
import { ApiBody } from '@nestjs/swagger';
1012
import { RequestsService } from './request.service';
1113
import { FoodRequest } from './request.entity';
14+
import { AWSS3Service } from '../aws/aws-s3.service';
15+
import { FilesInterceptor } from '@nestjs/platform-express';
16+
import * as multer from 'multer';
1217

1318
@Controller('requests')
14-
//@UseInterceptors()
19+
// @UseInterceptors()
1520
export class FoodRequestsController {
16-
constructor(private requestsService: RequestsService) {}
21+
constructor(
22+
private requestsService: RequestsService,
23+
private awsS3Service: AWSS3Service,
24+
) {}
1725

1826
@Get('/:pantryId')
1927
async getAllPantryRequests(
@@ -84,4 +92,54 @@ export class FoodRequestsController {
8492
body.photos,
8593
);
8694
}
95+
96+
@Post('/:requestId/confirm-delivery')
97+
@ApiBody({
98+
description: 'Details for a confirmation form',
99+
schema: {
100+
type: 'object',
101+
properties: {
102+
dateReceived: {
103+
type: 'string',
104+
format: 'date-time',
105+
nullable: true,
106+
example: new Date().toISOString(),
107+
},
108+
feedback: {
109+
type: 'string',
110+
nullable: true,
111+
example: 'Wonderful shipment!',
112+
},
113+
photos: {
114+
type: 'array',
115+
items: { type: 'string' },
116+
nullable: true,
117+
example: [],
118+
},
119+
},
120+
},
121+
})
122+
@UseInterceptors(
123+
FilesInterceptor('photos', 10, { storage: multer.memoryStorage() }),
124+
)
125+
async confirmDelivery(
126+
@Param('requestId', ParseIntPipe) requestId: number,
127+
@Body() body: { dateReceived: string; feedback: string },
128+
@UploadedFiles() photos?: Express.Multer.File[],
129+
): Promise<FoodRequest> {
130+
const formattedDate = new Date(body.dateReceived);
131+
if (isNaN(formattedDate.getTime())) {
132+
throw new Error('Invalid date format for deliveryDate');
133+
}
134+
135+
const uploadedPhotoUrls =
136+
photos && photos.length > 0 ? await this.awsS3Service.upload(photos) : [];
137+
138+
return this.requestsService.updateDeliveryDetails(
139+
requestId,
140+
formattedDate,
141+
body.feedback,
142+
uploadedPhotoUrls,
143+
);
144+
}
87145
}

apps/backend/src/foodRequests/request.module.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ import { FoodRequest } from './request.entity';
55
import { RequestsService } from './request.service';
66
import { JwtStrategy } from '../auth/jwt.strategy';
77
import { AuthService } from '../auth/auth.service';
8+
import { AWSS3Module } from '../aws/aws-s3.module';
9+
import { MulterModule } from '@nestjs/platform-express';
810

911
@Module({
10-
imports: [TypeOrmModule.forFeature([FoodRequest])],
12+
imports: [
13+
AWSS3Module,
14+
MulterModule.register({ dest: './uploads' }),
15+
TypeOrmModule.forFeature([FoodRequest]),
16+
],
1117
controllers: [FoodRequestsController],
1218
providers: [RequestsService, AuthService, JwtStrategy],
1319
})

apps/backend/src/foodRequests/request.service.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Injectable, NotFoundException } from '@nestjs/common';
22
import { InjectRepository } from '@nestjs/typeorm';
33
import { Repository } from 'typeorm';
4-
54
import { FoodRequest } from './request.entity';
65

76
@Injectable()
@@ -33,13 +32,33 @@ export class RequestsService {
3332
photos,
3433
});
3534

36-
return this.repo.save(foodRequest);
35+
return await this.repo.save(foodRequest);
3736
}
3837

39-
find(pantryId: number) {
38+
async find(pantryId: number) {
4039
if (!pantryId || pantryId < 1) {
4140
throw new NotFoundException('Invalid pantry ID');
4241
}
43-
return this.repo.find({ where: { pantryId } });
42+
return await this.repo.find({ where: { pantryId } });
43+
}
44+
45+
async updateDeliveryDetails(
46+
requestId: number,
47+
deliveryDate: Date,
48+
feedback: string,
49+
photos: string[],
50+
): Promise<FoodRequest> {
51+
const request = await this.repo.findOne({ where: { requestId } });
52+
53+
if (!request) {
54+
throw new NotFoundException('Invalid request ID');
55+
}
56+
57+
request.feedback = feedback;
58+
request.dateReceived = deliveryDate;
59+
request.photos = photos;
60+
request.status = 'fulfilled';
61+
62+
return await this.repo.save(request);
4463
}
4564
}

apps/backend/tsconfig.spec.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"jest.config.ts",
1010
"src/**/*.test.ts",
1111
"src/**/*.spec.ts",
12-
"src/**/*.d.ts"
12+
"src/**/*.d.ts",
13+
"src/types/**/*.d.ts"
1314
]
1415
}

apps/frontend/src/app.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import PantryPastOrders from '@containers/pantryPastOrders';
1010
import Pantries from '@containers/pantries';
1111
import Orders from '@containers/orders';
1212
import { submitFoodRequestFormModal } from '@components/forms/requestFormModalButton';
13+
import { submitDeliveryConfirmationFormModal } from '@components/forms/deliveryConfirmationModalButton';
14+
import FormRequests from '@containers/FormRequests';
1315

1416
const router = createBrowserRouter([
1517
{
@@ -38,8 +40,16 @@ const router = createBrowserRouter([
3840
element: <Orders />,
3941
},
4042
{
41-
path: '/food-request', // The route to handle form submission
42-
action: submitFoodRequestFormModal, // Action function to handle the form data and redirection
43+
path: '/request-form/:pantryId',
44+
element: <FormRequests />,
45+
},
46+
{
47+
path: '/food-request',
48+
action: submitFoodRequestFormModal,
49+
},
50+
{
51+
path: '/confirm-delivery',
52+
action: submitDeliveryConfirmationFormModal,
4353
},
4454
],
4555
},

0 commit comments

Comments
 (0)