Skip to content

Commit 6382be2

Browse files
authored
Merge pull request #85 from ModusCreateOrg/ADE-36
[ADE-36] Bookmark
2 parents 4606170 + ab7f71d commit 6382be2

File tree

4 files changed

+139
-48
lines changed

4 files changed

+139
-48
lines changed

backend/src/iac/backend-stack.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ export class BackendStack extends cdk.Stack {
397397
// Create the 'status' resource under ':id'
398398
const reportStatusResource = reportIdResource.addResource('status');
399399

400+
// Create the 'bookmark' resource under ':id'
401+
const reportBookmarkResource = reportIdResource.addResource('bookmark');
402+
400403
// Create the 'status' resource under ':id'
401404
const documentProcessorResource = apiResource.addResource('document-processor');
402405
const processFileResource = documentProcessorResource.addResource('process-file');
@@ -463,6 +466,18 @@ export class BackendStack extends cdk.Stack {
463466
},
464467
});
465468

469+
const patchReportBookmarkIntegration = new apigateway.Integration({
470+
type: apigateway.IntegrationType.HTTP_PROXY,
471+
integrationHttpMethod: 'PATCH',
472+
uri: `${serviceUrl}/api/reports/{id}/bookmark`,
473+
options: {
474+
...integrationOptions,
475+
requestParameters: {
476+
'integration.request.path.id': 'method.request.path.id',
477+
},
478+
},
479+
});
480+
466481
const processFileIntegration = new apigateway.Integration({
467482
type: apigateway.IntegrationType.HTTP_PROXY,
468483
integrationHttpMethod: 'POST',
@@ -509,6 +524,14 @@ export class BackendStack extends cdk.Stack {
509524
},
510525
});
511526

527+
// Add method for bookmark endpoint
528+
reportBookmarkResource.addMethod('PATCH', patchReportBookmarkIntegration, {
529+
...methodOptions,
530+
requestParameters: {
531+
'method.request.path.id': true,
532+
},
533+
});
534+
512535
// Add POST method to process file
513536
processFileResource.addMethod('POST', processFileIntegration, methodOptions);
514537
// Add GET method to document-processor/report-status/{reportId}
@@ -549,6 +572,10 @@ export class BackendStack extends cdk.Stack {
549572
...corsOptions,
550573
allowCredentials: false,
551574
});
575+
reportBookmarkResource.addCorsPreflight({
576+
...corsOptions,
577+
allowCredentials: false,
578+
});
552579
docsResource.addCorsPreflight({
553580
...corsOptions,
554581
allowCredentials: false,

backend/src/reports/reports.controller.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,42 @@ export class ReportsController {
107107
return this.reportsService.updateStatus(id, updateDto, userId);
108108
}
109109

110+
@ApiOperation({ summary: 'Toggle report bookmark status' })
111+
@ApiResponse({
112+
status: 200,
113+
description: 'Report bookmark status toggled successfully',
114+
type: Report,
115+
})
116+
@ApiResponse({
117+
status: 404,
118+
description: 'Report not found',
119+
})
120+
@ApiParam({
121+
name: 'id',
122+
description: 'Report ID',
123+
})
124+
@ApiBody({
125+
schema: {
126+
type: 'object',
127+
properties: {
128+
bookmarked: {
129+
type: 'boolean',
130+
description: 'New bookmark status',
131+
},
132+
},
133+
required: ['bookmarked'],
134+
},
135+
})
136+
@Patch(':id/bookmark')
137+
async toggleBookmark(
138+
@Param('id') id: string,
139+
@Body('bookmarked') bookmarked: boolean,
140+
@Req() request: RequestWithUser,
141+
): Promise<Report> {
142+
const userId = this.extractUserId(request);
143+
return this.reportsService.toggleBookmark(id, bookmarked, userId);
144+
}
145+
110146
@ApiOperation({ summary: 'Create a new report from S3 file' })
111147
@ApiResponse({
112148
status: 201,

backend/src/reports/reports.service.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,4 +379,77 @@ export class ReportsService {
379379
throw new InternalServerErrorException(`Failed to update report with ID ${report.id}`);
380380
}
381381
}
382+
383+
/**
384+
* Toggle the bookmark status of a report
385+
* @param id Report ID
386+
* @param bookmarked New bookmark status
387+
* @param userId User ID
388+
* @returns The updated report
389+
*/
390+
async toggleBookmark(id: string, bookmarked: boolean, userId: string): Promise<Report> {
391+
if (!id) {
392+
throw new NotFoundException('Report ID is required');
393+
}
394+
395+
if (bookmarked === undefined) {
396+
throw new InternalServerErrorException('Bookmark status is required');
397+
}
398+
399+
if (!userId) {
400+
throw new ForbiddenException('User ID is required');
401+
}
402+
403+
try {
404+
// First check if the report exists and belongs to the user
405+
const existingReport = await this.findOne(id, userId);
406+
407+
const command = new UpdateItemCommand({
408+
TableName: this.tableName,
409+
Key: marshall({
410+
userId,
411+
id,
412+
}),
413+
UpdateExpression: 'SET bookmarked = :bookmarked, updatedAt = :updatedAt',
414+
ExpressionAttributeValues: marshall({
415+
':bookmarked': bookmarked,
416+
':updatedAt': new Date().toISOString(),
417+
':userId': userId,
418+
}),
419+
ReturnValues: 'ALL_NEW',
420+
});
421+
422+
const response = await this.dynamoClient.send(command);
423+
424+
if (!response.Attributes) {
425+
// If for some reason Attributes is undefined, return the existing report with updated bookmark status
426+
return {
427+
...existingReport,
428+
bookmarked,
429+
updatedAt: new Date().toISOString(),
430+
};
431+
}
432+
433+
return unmarshall(response.Attributes) as Report;
434+
} catch (error: unknown) {
435+
if (error instanceof NotFoundException) {
436+
throw error;
437+
}
438+
439+
this.logger.error(`Error toggling bookmark for report ID ${id}:`);
440+
this.logger.error(error);
441+
442+
if (error instanceof DynamoDBServiceException) {
443+
if (error.name === 'ConditionalCheckFailedException') {
444+
throw new ForbiddenException('You do not have permission to update this report');
445+
} else if (error.name === 'ResourceNotFoundException') {
446+
throw new InternalServerErrorException(
447+
`Table "${this.tableName}" not found. Please check your database configuration.`,
448+
);
449+
}
450+
}
451+
452+
throw new InternalServerErrorException(`Failed to toggle bookmark for report ID ${id}`);
453+
}
454+
}
382455
}

frontend/src/common/api/reportService.ts

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,9 @@
11
import axios, { AxiosProgressEvent } from 'axios';
2-
import { MedicalReport, ReportCategory, ReportStatus, ProcessingStatus } from '../models/medicalReport';
2+
import { MedicalReport } from '../models/medicalReport';
33
import { fetchAuthSession } from '@aws-amplify/auth';
44
// Get the API URL from environment variables
55
const API_URL = import.meta.env.VITE_BASE_URL_API || '';
66

7-
// Mock data for testing and development
8-
const mockReports: MedicalReport[] = [
9-
{
10-
id: '1',
11-
userId: 'user1',
12-
title: 'Blood Test Report',
13-
category: ReportCategory.GENERAL,
14-
bookmarked: false,
15-
processingStatus: ProcessingStatus.PROCESSED,
16-
labValues: [],
17-
summary: 'Blood test results within normal range',
18-
status: ReportStatus.UNREAD,
19-
filePath: '/reports/blood-test.pdf',
20-
createdAt: '2023-04-15T12:30:00Z',
21-
updatedAt: '2023-04-15T12:30:00Z',
22-
},
23-
{
24-
id: '2',
25-
userId: 'user1',
26-
title: 'Heart Checkup',
27-
category: ReportCategory.HEART,
28-
bookmarked: true,
29-
processingStatus: ProcessingStatus.PROCESSED,
30-
labValues: [],
31-
summary: 'Heart functioning normally',
32-
status: ReportStatus.READ,
33-
filePath: '/reports/heart-checkup.pdf',
34-
createdAt: '2023-04-10T10:15:00Z',
35-
updatedAt: '2023-04-10T10:15:00Z',
36-
},
37-
];
38-
397
/**
408
* Interface for upload progress callback
419
*/
@@ -195,28 +163,15 @@ export const toggleReportBookmark = async (
195163
isBookmarked: boolean,
196164
): Promise<MedicalReport> => {
197165
try {
198-
await axios.patch(
166+
const response = await axios.patch(
199167
`${API_URL}/api/reports/${reportId}/bookmark`,
200168
{
201169
bookmarked: isBookmarked,
202170
},
203171
await getAuthConfig(),
204172
);
205173

206-
// In a real implementation, this would return the response from the API
207-
// return response.data;
208-
209-
// For now, we'll mock the response
210-
const report = mockReports.find((r) => r.id === reportId);
211-
212-
if (!report) {
213-
throw new Error(`Report with ID ${reportId} not found`);
214-
}
215-
216-
// Update the bookmark status
217-
report.bookmarked = isBookmarked;
218-
219-
return { ...report };
174+
return response.data;
220175
} catch (error) {
221176
if (axios.isAxiosError(error)) {
222177
throw new ReportError(`Failed to toggle bookmark status: ${error.message}`);

0 commit comments

Comments
 (0)