@@ -8,11 +8,11 @@ import {
88import { ConfigService } from '@nestjs/config' ;
99import {
1010 DynamoDBClient ,
11- ScanCommand ,
1211 GetItemCommand ,
1312 UpdateItemCommand ,
1413 DynamoDBServiceException ,
1514 PutItemCommand ,
15+ QueryCommand ,
1616} from '@aws-sdk/client-dynamodb' ;
1717import { marshall , unmarshall } from '@aws-sdk/util-dynamodb' ;
1818import { Report , ReportStatus } from './models/report.model' ;
@@ -60,10 +60,10 @@ export class ReportsService {
6060 }
6161
6262 try {
63- // If the table has a GSI for userId, use QueryCommand instead
64- const command = new ScanCommand ( {
63+ // Use QueryCommand instead of ScanCommand since userId is the partition key
64+ const command = new QueryCommand ( {
6565 TableName : this . tableName ,
66- FilterExpression : 'userId = :userId' ,
66+ KeyConditionExpression : 'userId = :userId' ,
6767 ExpressionAttributeValues : marshall ( {
6868 ':userId' : userId ,
6969 } ) ,
@@ -105,23 +105,21 @@ export class ReportsService {
105105 typeof queryDto . limit === 'string' ? parseInt ( queryDto . limit , 10 ) : queryDto . limit || 10 ;
106106
107107 try {
108- // If the table has a GSI for userId, use QueryCommand instead
109- const command = new ScanCommand ( {
108+ // Use the GSI userIdCreatedAtIndex with QueryCommand for efficient retrieval
109+ // This is much more efficient than a ScanCommand
110+ const command = new QueryCommand ( {
110111 TableName : this . tableName ,
111- FilterExpression : 'userId = :userId' ,
112+ IndexName : 'userIdCreatedAtIndex' , // Use the GSI for efficient queries
113+ KeyConditionExpression : 'userId = :userId' ,
112114 ExpressionAttributeValues : marshall ( {
113115 ':userId' : userId ,
114116 } ) ,
115- Limit : limit * 5 , // Fetch more items since we'll filter by userId
117+ ScanIndexForward : false , // Get items in descending order (newest first)
118+ Limit : limit , // Only fetch the number of items we need
116119 } ) ;
117120
118121 const response = await this . dynamoClient . send ( command ) ;
119- const reports = ( response . Items || [ ] ) . map ( item => unmarshall ( item ) as Report ) ;
120-
121- // Sort by createdAt in descending order
122- return reports
123- . sort ( ( a , b ) => new Date ( b . createdAt ) . getTime ( ) - new Date ( a . createdAt ) . getTime ( ) )
124- . slice ( 0 , limit ) ;
122+ return ( response . Items || [ ] ) . map ( item => unmarshall ( item ) as Report ) ;
125123 } catch ( error : unknown ) {
126124 this . logger . error ( `Error fetching latest reports for user ${ userId } :` ) ;
127125 this . logger . error ( error ) ;
@@ -131,6 +129,26 @@ export class ReportsService {
131129 throw new InternalServerErrorException (
132130 `Table "${ this . tableName } " not found. Please check your database configuration.` ,
133131 ) ;
132+ } else if ( error . name === 'ValidationException' ) {
133+ // This could happen if the GSI doesn't exist
134+ this . logger . warn ( 'GSI validation error, falling back to standard query' ) ;
135+
136+ // Fallback to standard query and sort in memory if GSI has issues
137+ const fallbackCommand = new QueryCommand ( {
138+ TableName : this . tableName ,
139+ KeyConditionExpression : 'userId = :userId' ,
140+ ExpressionAttributeValues : marshall ( {
141+ ':userId' : userId ,
142+ } ) ,
143+ } ) ;
144+
145+ const fallbackResponse = await this . dynamoClient . send ( fallbackCommand ) ;
146+ const reports = ( fallbackResponse . Items || [ ] ) . map ( item => unmarshall ( item ) as Report ) ;
147+
148+ // Sort by createdAt in descending order
149+ return reports
150+ . sort ( ( a , b ) => new Date ( b . createdAt ) . getTime ( ) - new Date ( a . createdAt ) . getTime ( ) )
151+ . slice ( 0 , limit ) ;
134152 }
135153 }
136154
@@ -330,25 +348,75 @@ export class ReportsService {
330348 throw new ForbiddenException ( 'User ID is required' ) ;
331349 }
332350
351+ // Log the actual filePath being searched for debugging
352+ this . logger . log ( `Searching for report with filePath: "${ filePath } " for user ${ userId } ` ) ;
353+
333354 try {
334- // Since filePath isn't a key attribute, we need to scan with filter
335- const command = new ScanCommand ( {
355+ const command = new QueryCommand ( {
336356 TableName : this . tableName ,
337- FilterExpression : 'filePath = :filePath AND userId = :userId' ,
357+ KeyConditionExpression : 'userId = :userId' ,
358+ FilterExpression : 'filePath = :filePath' ,
338359 ExpressionAttributeValues : marshall ( {
339- ':filePath' : filePath ,
340360 ':userId' : userId ,
361+ ':filePath' : filePath ,
341362 } ) ,
342363 Limit : 1 , // We only want one record
343364 } ) ;
344365
366+ this . logger . log ( 'Executing QueryCommand with params:' , {
367+ TableName : this . tableName ,
368+ KeyConditionExpression : 'userId = :userId' ,
369+ FilterExpression : 'filePath = :filePath' ,
370+ Values : {
371+ userId,
372+ filePath,
373+ } ,
374+ } ) ;
375+
345376 const response = await this . dynamoClient . send ( command ) ;
346377
378+ this . logger . log ( `Query response received, found ${ response . Items ?. length || 0 } items` ) ;
379+
347380 if ( ! response . Items || response . Items . length === 0 ) {
381+ // If no exact match, try with case-insensitive comparison as a fallback
382+ this . logger . log ( 'No exact match found, trying with case-insensitive search' ) ;
383+
384+ // Get all items for the user and filter manually for case-insensitive match
385+ const allUserItemsCommand = new QueryCommand ( {
386+ TableName : this . tableName ,
387+ KeyConditionExpression : 'userId = :userId' ,
388+ ExpressionAttributeValues : marshall ( {
389+ ':userId' : userId ,
390+ } ) ,
391+ } ) ;
392+
393+ const allUserResponse = await this . dynamoClient . send ( allUserItemsCommand ) ;
394+
395+ if ( ! allUserResponse . Items || allUserResponse . Items . length === 0 ) {
396+ return null ;
397+ }
398+
399+ // Convert items and find case-insensitive match
400+ const allReports = allUserResponse . Items . map ( item => unmarshall ( item ) as Report ) ;
401+ const matchingReport = allReports . find (
402+ report => report . filePath . toLowerCase ( ) === filePath . toLowerCase ( ) ,
403+ ) ;
404+
405+ if ( matchingReport ) {
406+ this . logger . log (
407+ `Found case-insensitive match for ${ filePath } : ${ matchingReport . filePath } ` ,
408+ ) ;
409+
410+ return matchingReport ;
411+ }
412+
348413 return null ;
349414 }
350415
351- return unmarshall ( response . Items [ 0 ] ) as Report ;
416+ const result = unmarshall ( response . Items [ 0 ] ) as Report ;
417+ this . logger . log ( `Found report with ID ${ result . id } ` ) ;
418+
419+ return result ;
352420 } catch ( error : unknown ) {
353421 this . logger . error ( `Error finding report with filePath ${ filePath } :` ) ;
354422 this . logger . error ( error ) ;
0 commit comments