Skip to content

Commit c965322

Browse files
committed
feat: Update document processing to use reportId instead of filePath and add report status checking
1 parent 354705e commit c965322

File tree

3 files changed

+182
-30
lines changed

3 files changed

+182
-30
lines changed

backend/src/document-processor/controllers/document-processor.controller.ts

Lines changed: 116 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class DocumentProcessorController {
3939

4040
@Post('upload')
4141
@UseInterceptors(FileInterceptor('file'))
42-
async processDocument(
42+
async uploadAndProcessReport(
4343
@UploadedFile() file: Express.Multer.File,
4444
@Body('userId') userId: string,
4545
@Body('debug') debug?: string,
@@ -115,12 +115,12 @@ export class DocumentProcessorController {
115115
}
116116

117117
@Post('process-file')
118-
async processFileFromPath(
119-
@Body('filePath') filePath: string,
118+
async processReport(
119+
@Body('reportId') reportId: string,
120120
@Req() request: RequestWithUser,
121-
): Promise<ProcessedDocumentResult | any> {
122-
if (!filePath) {
123-
throw new BadRequestException('No filePath provided');
121+
): Promise<any> {
122+
if (!reportId) {
123+
throw new BadRequestException('No reportId provided');
124124
}
125125

126126
// Extract userId from the request (attached by auth middleware)
@@ -129,21 +129,70 @@ export class DocumentProcessorController {
129129
throw new UnauthorizedException('User ID not found in request');
130130
}
131131

132-
this.logger.log(`Processing document from file path: ${filePath}`);
132+
this.logger.log(`Queueing document for processing, report ID: ${reportId}`);
133133

134134
try {
135-
// Fetch the associated report record from DynamoDB
136-
const report = await this.reportsService.findByFilePath(filePath, userId);
135+
// Fetch the associated report record from DynamoDB using findOne method
136+
const report = await this.reportsService.findOne(reportId, userId);
137137
if (!report) {
138-
throw new NotFoundException(`Report with filePath ${filePath} not found`);
138+
throw new NotFoundException(`Report with ID ${reportId} not found`);
139+
}
140+
141+
// Make sure we have a filePath to retrieve the file
142+
if (!report.filePath) {
143+
throw new BadRequestException(`Report with ID ${reportId} has no associated file`);
139144
}
140145

146+
// Update report status to IN_PROGRESS before starting async processing
147+
report.processingStatus = ProcessingStatus.IN_PROGRESS;
148+
report.updatedAt = new Date().toISOString();
149+
await this.reportsService.updateReport(report);
150+
151+
// Start async processing in background
152+
this.processReportAsync(reportId, userId, report.filePath).catch(error => {
153+
this.logger.error(`Async processing failed for report ${reportId}: ${error.message}`);
154+
});
155+
156+
return {
157+
success: true,
158+
reportId: report.id,
159+
status: ProcessingStatus.IN_PROGRESS,
160+
message: 'Document processing started. Check the report status to know when it completes.',
161+
};
162+
} catch (error: unknown) {
163+
this.logger.error(
164+
`Error queueing document for report ID ${reportId}: ${error instanceof Error ? error.message : 'Unknown error'}`,
165+
);
166+
throw error;
167+
}
168+
}
169+
170+
/**
171+
* Processes a report file asynchronously
172+
* @param reportId - ID of the report to process
173+
* @param userId - ID of the user who owns the report
174+
* @param filePath - S3 path to the file
175+
*/
176+
private async processReportAsync(
177+
reportId: string,
178+
userId: string,
179+
filePath: string,
180+
): Promise<void> {
181+
try {
182+
this.logger.log(`Started async processing for report: ${reportId}`);
183+
141184
// Get the file from S3
142185
const fileBuffer = await this.getFileFromS3(filePath);
143186

144187
// Process the document
145188
const result = await this.documentProcessorService.processDocument(fileBuffer, userId);
146189

190+
// Fetch the report again to ensure we have the latest version
191+
const report = await this.reportsService.findOne(reportId, userId);
192+
if (!report) {
193+
throw new Error(`Report ${reportId} not found during async processing`);
194+
}
195+
147196
// Update the report with analysis results
148197
report.title = result.analysis.title || 'Untitled Report';
149198
report.category = result.analysis.category || 'general';
@@ -163,13 +212,26 @@ export class DocumentProcessorController {
163212
// Update the report in DynamoDB
164213
await this.reportsService.updateReport(report);
165214

166-
return {
167-
success: true,
168-
reportId: report.id,
169-
};
170-
} catch (error: unknown) {
215+
this.logger.log(`Completed async processing for report: ${reportId}`);
216+
} catch (error) {
217+
// If processing fails, update the report status to indicate failure
218+
try {
219+
const report = await this.reportsService.findOne(reportId, userId);
220+
if (report) {
221+
report.processingStatus = ProcessingStatus.UNPROCESSED; // Could add a FAILED status in the enum if needed
222+
report.updatedAt = new Date().toISOString();
223+
await this.reportsService.updateReport(report);
224+
}
225+
} catch (updateError: unknown) {
226+
this.logger.error(
227+
`Failed to update report status after processing error: ${
228+
updateError instanceof Error ? updateError.message : 'Unknown error'
229+
}`,
230+
);
231+
}
232+
171233
this.logger.error(
172-
`Error processing document from path ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`,
234+
`Error during async processing for report ${reportId}: ${error instanceof Error ? error.message : 'Unknown error'}`,
173235
);
174236
throw error;
175237
}
@@ -607,4 +669,42 @@ export class DocumentProcessorController {
607669

608670
res.type('text/html').send(html);
609671
}
672+
673+
@Get('report-status/:reportId')
674+
async getReportStatus(
675+
@Req() request: RequestWithUser,
676+
@Body('reportId') idFromBody: string,
677+
): Promise<any> {
678+
// Get reportId from path parameter or body
679+
const reportId = request.params.reportId || idFromBody;
680+
681+
if (!reportId) {
682+
throw new BadRequestException('No reportId provided');
683+
}
684+
685+
// Extract userId from the request (attached by auth middleware)
686+
const userId = request.user?.sub;
687+
if (!userId) {
688+
throw new UnauthorizedException('User ID not found in request');
689+
}
690+
691+
try {
692+
// Fetch the associated report record from DynamoDB
693+
const report = await this.reportsService.findOne(reportId, userId);
694+
if (!report) {
695+
throw new NotFoundException(`Report with ID ${reportId} not found`);
696+
}
697+
698+
return {
699+
reportId: report.id,
700+
status: report.processingStatus,
701+
isComplete: report.processingStatus === ProcessingStatus.PROCESSED,
702+
};
703+
} catch (error: unknown) {
704+
this.logger.error(
705+
`Error fetching report status for ${reportId}: ${error instanceof Error ? error.message : 'Unknown error'}`,
706+
);
707+
throw error;
708+
}
709+
}
610710
}

frontend/src/common/components/Upload/UploadModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
6363
if (onUploadComplete) {
6464
onUploadComplete(result);
6565
}
66-
// Navigate to the processing tab with filePath in state
66+
// Navigate to the processing tab with reportId in state
6767
if (file) {
6868
history.push('/tabs/processing', {
69-
filePath: result.filePath,
69+
reportId: result.id,
7070
});
7171
} else {
7272
history.push('/tabs/processing');

frontend/src/pages/Processing/Processing.tsx

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { IonContent, IonPage } from '@ionic/react';
22
import { useCurrentUser } from '../../common/hooks/useAuth';
33
import Avatar from '../../common/components/Icon/Avatar';
44
import { useLocation, useHistory } from 'react-router-dom';
5-
import { useEffect, useState } from 'react';
5+
import { useEffect, useState, useRef } from 'react';
66
import { useAxios } from '../../common/hooks/useAxios';
77
import './Processing.scss';
88
import { getAuthConfig } from 'common/api/reportService';
@@ -21,22 +21,62 @@ const Processing: React.FC = () => {
2121
// States to track processing
2222
const [isProcessing, setIsProcessing] = useState(true);
2323
const [processingError, setProcessingError] = useState<string | null>(null);
24+
const statusCheckIntervalRef = useRef<number | null>(null);
2425

25-
// Get the location state which may contain the filePath
26-
const location = useLocation<{ filePath: string }>();
27-
const filePath = location.state?.filePath;
28-
const [reportId, setReportId] = useState(null);
26+
// Get the location state which may contain the reportId (previously filePath)
27+
const location = useLocation<{ reportId: string }>();
28+
const reportId = location.state?.reportId;
2929
const [isFetching, setIsFetching] = useState(false);
3030

31+
// Check the status of the report processing
32+
const checkReportStatus = async () => {
33+
if (!reportId) return;
34+
35+
try {
36+
const response = await axios.get(
37+
`${API_URL}/api/document-processor/report-status/${reportId}`,
38+
await getAuthConfig(),
39+
);
40+
41+
console.log('Report status:', response.data);
42+
43+
// If processing is complete, clear the interval and redirect to the report page
44+
if (response.data.isComplete) {
45+
setIsProcessing(false);
46+
47+
// Clear the interval
48+
if (statusCheckIntervalRef.current) {
49+
window.clearInterval(statusCheckIntervalRef.current);
50+
statusCheckIntervalRef.current = null;
51+
}
52+
53+
console.log('Processing complete');
54+
55+
// Redirect to report detail page
56+
history.push(`/tabs/reports/${reportId}`);
57+
}
58+
} catch (error) {
59+
console.error('Error checking report status:', error);
60+
setProcessingError('Failed to check the status of the report. Please try again.');
61+
setIsProcessing(false);
62+
63+
// Clear the interval on error
64+
if (statusCheckIntervalRef.current) {
65+
window.clearInterval(statusCheckIntervalRef.current);
66+
statusCheckIntervalRef.current = null;
67+
}
68+
}
69+
};
70+
3171
// Send the API request when component mounts
3272
useEffect(() => {
33-
if (!filePath) {
34-
setProcessingError('No file path provided');
73+
if (!reportId) {
74+
setProcessingError('No report ID provided');
3575
setIsProcessing(false);
3676
return;
3777
}
3878

39-
if (reportId && isFetching) {
79+
if (isFetching) {
4080
return;
4181
}
4282

@@ -47,12 +87,17 @@ const Processing: React.FC = () => {
4787
// Send POST request to backend API
4888
const response = await axios.post(
4989
`${API_URL}/api/document-processor/process-file`,
50-
{ filePath },
90+
{ reportId },
5191
await getAuthConfig(),
5292
);
53-
setReportId(response.data.reportId);
5493

55-
console.log('File processed successfully:', response.data);
94+
console.log('File processing started:', response.data);
95+
96+
// Start checking the status every 5 seconds
97+
statusCheckIntervalRef.current = window.setInterval(checkReportStatus, 5000);
98+
99+
// Run the first status check immediately
100+
checkReportStatus();
56101
} catch (error) {
57102
console.error('Error processing file:', error);
58103
setProcessingError('Failed to process the file. Please try again.');
@@ -61,7 +106,14 @@ const Processing: React.FC = () => {
61106
};
62107

63108
processFile();
64-
}, [filePath, axios, history]);
109+
110+
// Clean up the interval when the component unmounts
111+
return () => {
112+
if (statusCheckIntervalRef.current) {
113+
window.clearInterval(statusCheckIntervalRef.current);
114+
}
115+
};
116+
}, [reportId, axios, history]);
65117

66118
return (
67119
<IonPage className="processing-page">

0 commit comments

Comments
 (0)