Skip to content

Commit 3e2ebbd

Browse files
committed
Add user to filter the request
1 parent c89bf1f commit 3e2ebbd

File tree

2 files changed

+98
-38
lines changed

2 files changed

+98
-38
lines changed

backend/src/reports/reports.controller.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Controller, Get, Patch, Param, Body, Query, ValidationPipe } from '@nestjs/common';
1+
import { Controller, Get, Patch, Param, Body, Query, ValidationPipe, Req } from '@nestjs/common';
22
import {
33
ApiTags,
44
ApiOperation,
@@ -11,6 +11,7 @@ import { ReportsService } from './reports.service';
1111
import { Report } from './models/report.model';
1212
import { GetReportsQueryDto } from './dto/get-reports.dto';
1313
import { UpdateReportStatusDto } from './dto/update-report-status.dto';
14+
import { Request } from 'express';
1415

1516
@ApiTags('reports')
1617
@Controller('reports')
@@ -25,8 +26,9 @@ export class ReportsController {
2526
type: [Report],
2627
})
2728
@Get()
28-
async findAll(): Promise<Report[]> {
29-
return this.reportsService.findAll();
29+
async findAll(@Req() request: Request): Promise<Report[]> {
30+
const userId = this.extractUserId(request);
31+
return this.reportsService.findAll(userId);
3032
}
3133

3234
@ApiOperation({ summary: 'Get latest reports' })
@@ -41,14 +43,18 @@ export class ReportsController {
4143
description: 'Maximum number of reports to return',
4244
})
4345
@Get('latest')
44-
async findLatest(@Query(ValidationPipe) queryDto: GetReportsQueryDto): Promise<Report[]> {
45-
return this.reportsService.findLatest(queryDto);
46+
async findLatest(
47+
@Query(ValidationPipe) queryDto: GetReportsQueryDto,
48+
@Req() request: Request,
49+
): Promise<Report[]> {
50+
const userId = this.extractUserId(request);
51+
return this.reportsService.findLatest(queryDto, userId);
4652
}
4753

4854
@ApiOperation({ summary: 'GET report' })
4955
@ApiResponse({
5056
status: 200,
51-
description: 'Report status updated successfully',
57+
description: 'Report details',
5258
type: Report,
5359
})
5460
@ApiResponse({
@@ -60,8 +66,9 @@ export class ReportsController {
6066
description: 'Report ID',
6167
})
6268
@Get(':id')
63-
async getReport(@Param('id') id: string): Promise<Report> {
64-
return this.reportsService.findOne(id);
69+
async getReport(@Param('id') id: string, @Req() request: Request): Promise<Report> {
70+
const userId = this.extractUserId(request);
71+
return this.reportsService.findOne(id, userId);
6572
}
6673

6774
@ApiOperation({ summary: 'Update report status' })
@@ -82,7 +89,21 @@ export class ReportsController {
8289
async updateStatus(
8390
@Param('id') id: string,
8491
@Body(ValidationPipe) updateDto: UpdateReportStatusDto,
92+
@Req() request: Request,
8593
): Promise<Report> {
86-
return this.reportsService.updateStatus(id, updateDto);
94+
const userId = this.extractUserId(request);
95+
return this.reportsService.updateStatus(id, updateDto, userId);
96+
}
97+
98+
private extractUserId(request: Request): string {
99+
console.log(request);
100+
// The user object is attached to the request by the AuthGuard
101+
const user = request.user as any;
102+
103+
if (!user || !user.sub) {
104+
throw new Error('User ID not found in token');
105+
}
106+
107+
return user.sub;
87108
}
88109
}

backend/src/reports/reports.service.ts

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
NotFoundException,
44
InternalServerErrorException,
55
Logger,
6+
ForbiddenException,
67
} from '@nestjs/common';
78
import { ConfigService } from '@nestjs/config';
89
import {
@@ -46,16 +47,26 @@ export class ReportsService {
4647
this.tableName = this.configService.get<string>('DYNAMODB_REPORTS_TABLE', 'reports');
4748
}
4849

49-
async findAll(): Promise<Report[]> {
50-
const command = new ScanCommand({
51-
TableName: this.tableName,
52-
});
50+
async findAll(userId: string): Promise<Report[]> {
51+
if (!userId) {
52+
throw new ForbiddenException('User ID is required');
53+
}
5354

5455
try {
56+
// If the table has a GSI for userId, use QueryCommand instead
57+
const command = new ScanCommand({
58+
TableName: this.tableName,
59+
FilterExpression: 'userId = :userId',
60+
ExpressionAttributeValues: marshall({
61+
':userId': userId,
62+
}),
63+
});
64+
5565
const response = await this.dynamoClient.send(command);
5666
return (response.Items || []).map(item => unmarshall(item) as Report);
5767
} catch (error: unknown) {
58-
this.logger.error(`Error fetching all reports: ${this.formatError(error)}`);
68+
this.logger.error(`Error fetching reports for user ${userId}:`);
69+
this.logger.error(error);
5970

6071
if (error instanceof DynamoDBServiceException) {
6172
if (error.name === 'UnrecognizedClientException') {
@@ -73,19 +84,30 @@ export class ReportsService {
7384
}
7485
}
7586

76-
async findLatest(queryDto: GetReportsQueryDto): Promise<Report[]> {
77-
this.logger.log(`Running findLatest with params: ${JSON.stringify(queryDto)}`);
87+
async findLatest(queryDto: GetReportsQueryDto, userId: string): Promise<Report[]> {
88+
this.logger.log(
89+
`Running findLatest with params: ${JSON.stringify(queryDto)} for user ${userId}`,
90+
);
91+
92+
if (!userId) {
93+
throw new ForbiddenException('User ID is required');
94+
}
7895

7996
// Convert limit to a number to avoid serialization errors
8097
const limit =
8198
typeof queryDto.limit === 'string' ? parseInt(queryDto.limit, 10) : queryDto.limit || 10;
8299

83-
const command = new ScanCommand({
84-
TableName: this.tableName,
85-
Limit: limit,
86-
});
87-
88100
try {
101+
// If the table has a GSI for userId, use QueryCommand instead
102+
const command = new ScanCommand({
103+
TableName: this.tableName,
104+
FilterExpression: 'userId = :userId',
105+
ExpressionAttributeValues: marshall({
106+
':userId': userId,
107+
}),
108+
Limit: limit * 5, // Fetch more items since we'll filter by userId
109+
});
110+
89111
const response = await this.dynamoClient.send(command);
90112
const reports = (response.Items || []).map(item => unmarshall(item) as Report);
91113

@@ -94,7 +116,8 @@ export class ReportsService {
94116
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
95117
.slice(0, limit);
96118
} catch (error: unknown) {
97-
this.logger.error(`Error fetching latest reports: ${this.formatError(error)}`);
119+
this.logger.error(`Error fetching latest reports for user ${userId}:`);
120+
this.logger.error(error);
98121

99122
if (error instanceof DynamoDBServiceException) {
100123
if (error.name === 'ResourceNotFoundException') {
@@ -108,11 +131,15 @@ export class ReportsService {
108131
}
109132
}
110133

111-
async findOne(id: string): Promise<Report> {
134+
async findOne(id: string, userId: string): Promise<Report> {
112135
if (!id) {
113136
throw new NotFoundException('Report ID is required');
114137
}
115138

139+
if (!userId) {
140+
throw new ForbiddenException('User ID is required');
141+
}
142+
116143
const command = new GetItemCommand({
117144
TableName: this.tableName,
118145
Key: marshall({ id }),
@@ -125,13 +152,21 @@ export class ReportsService {
125152
throw new NotFoundException(`Report with ID ${id} not found`);
126153
}
127154

128-
return unmarshall(response.Item) as Report;
155+
const report = unmarshall(response.Item) as Report;
156+
157+
// Verify the report belongs to the user
158+
if (report.userId !== userId) {
159+
throw new ForbiddenException('You do not have permission to access this report');
160+
}
161+
162+
return report;
129163
} catch (error: unknown) {
130164
if (error instanceof NotFoundException) {
131165
throw error;
132166
}
133167

134-
this.logger.error(`Error fetching report with ID ${id}: ${this.formatError(error)}`);
168+
this.logger.error(`Error fetching report with ID ${id}:`);
169+
this.logger.error(error);
135170

136171
if (error instanceof DynamoDBServiceException) {
137172
if (error.name === 'ResourceNotFoundException') {
@@ -145,7 +180,11 @@ export class ReportsService {
145180
}
146181
}
147182

148-
async updateStatus(id: string, updateDto: UpdateReportStatusDto): Promise<Report> {
183+
async updateStatus(
184+
id: string,
185+
updateDto: UpdateReportStatusDto,
186+
userId: string,
187+
): Promise<Report> {
149188
if (!id) {
150189
throw new NotFoundException('Report ID is required');
151190
}
@@ -154,20 +193,26 @@ export class ReportsService {
154193
throw new InternalServerErrorException('Status is required for update');
155194
}
156195

196+
if (!userId) {
197+
throw new ForbiddenException('User ID is required');
198+
}
199+
157200
try {
158-
// First check if the report exists
159-
const existingReport = await this.findOne(id);
201+
// First check if the report exists and belongs to the user
202+
const existingReport = await this.findOne(id, userId);
160203

161204
const command = new UpdateItemCommand({
162205
TableName: this.tableName,
163206
Key: marshall({ id }),
164207
UpdateExpression: 'SET #status = :status, updatedAt = :updatedAt',
208+
ConditionExpression: 'userId = :userId', // Ensure the report belongs to the user
165209
ExpressionAttributeNames: {
166210
'#status': 'status',
167211
},
168212
ExpressionAttributeValues: marshall({
169213
':status': updateDto.status,
170214
':updatedAt': new Date().toISOString(),
215+
':userId': userId,
171216
}),
172217
ReturnValues: 'ALL_NEW',
173218
});
@@ -189,26 +234,20 @@ export class ReportsService {
189234
throw error;
190235
}
191236

192-
this.logger.error(`Error updating report status for ID ${id}: ${this.formatError(error)}`);
237+
this.logger.error(`Error updating report status for ID ${id}:`);
238+
this.logger.error(error);
193239

194240
if (error instanceof DynamoDBServiceException) {
195241
if (error.name === 'ConditionalCheckFailedException') {
196-
throw new NotFoundException(`Report with ID ${id} not found`);
242+
throw new ForbiddenException('You do not have permission to update this report');
197243
} else if (error.name === 'ResourceNotFoundException') {
198244
throw new InternalServerErrorException(
199245
`Table "${this.tableName}" not found. Please check your database configuration.`,
200246
);
201247
}
202248
}
203249

204-
throw new InternalServerErrorException(`Failed to update status for report with ID ${id}`);
205-
}
206-
}
207-
208-
private formatError(error: unknown): string {
209-
if (error instanceof Error) {
210-
return `${error.name}: ${error.message}`;
250+
throw new InternalServerErrorException(`Failed to update report status for ID ${id}`);
211251
}
212-
return JSON.stringify(error, null, 2);
213252
}
214253
}

0 commit comments

Comments
 (0)