33 NotFoundException ,
44 InternalServerErrorException ,
55 Logger ,
6+ ForbiddenException ,
67} from '@nestjs/common' ;
78import { ConfigService } from '@nestjs/config' ;
89import {
@@ -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