Skip to content

Commit c16ba89

Browse files
authored
Merge pull request #905 from COS301-SE-2024/bugfix/comments
fixed comments bugs
2 parents f292f5b + 29a4c51 commit c16ba89

File tree

8 files changed

+107
-60
lines changed

8 files changed

+107
-60
lines changed

frontend/src/components/Comments/comments.tsx

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getTicketComments,
1010
getUserFirstLastName,
1111
} from '@/services/tickets.service';
12+
import { UserRole } from '@/types/custom.types';
1213

1314
interface CommentsProps {
1415
onBack: () => void;
@@ -28,24 +29,7 @@ const Comments: React.FC<CommentsProps> = ({ onBack, isCitizen, ticketId }) => {
2829
const userSession = String(user_data.current?.session_token);
2930
const commentsData = await getTicketComments(ticketId, userSession);
3031

31-
const userPoolId = process.env.USER_POOL_ID;
32-
if (!userPoolId) {
33-
throw new Error("USER_POOL_ID is not defined");
34-
}
35-
36-
const enrichedComments = await Promise.allSettled(commentsData.map(async (comment: any) => {
37-
const userAttributes = await getUserFirstLastName(comment.user_id, userPoolId);
38-
return {
39-
...comment,
40-
userName: `${userAttributes?.given_name} ${userAttributes?.family_name}`,
41-
userImage: userAttributes?.picture || 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541',
42-
time: new Date(comment.date)
43-
};
44-
}));
45-
46-
const fulfilledComments = enrichedComments.filter((comment: any) => comment.status === "fulfilled").map((comment: any) => comment.value);
47-
48-
setComments(fulfilledComments);
32+
setComments(commentsData);
4933
setLoading(false);
5034
} catch (error) {
5135
console.error("Error fetching comments:", error);
@@ -65,15 +49,18 @@ const Comments: React.FC<CommentsProps> = ({ onBack, isCitizen, ticketId }) => {
6549
const user_picture = String(user_data.current?.picture);
6650
const userSession = String(user_data.current?.session_token);
6751
const user_email = String(user_data.current?.email);
52+
const userRole = user_data.current?.user_role || UserRole.CITIZEN; // default to citizen if user_role is not set
53+
54+
const dateCommentCreated = new Date();
6855

6956
const newCommentData = {
7057
userName,
71-
userImage: user_picture || 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541',
72-
time: new Date(),
58+
userImage: user_picture || "https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541",
59+
time: dateCommentCreated,
7360
commentText: newComment,
7461
};
7562

76-
await addCommentWithoutImage(newCommentData.commentText, ticketId, user_email, userSession);
63+
await addCommentWithoutImage(newCommentData.commentText, ticketId, user_email, dateCommentCreated, userRole, userSession);
7764

7865
setComments([...comments, newCommentData]);
7966
setNewComment('');
@@ -100,8 +87,8 @@ const Comments: React.FC<CommentsProps> = ({ onBack, isCitizen, ticketId }) => {
10087
key={index}
10188
userName={comment.userName}
10289
userImage={comment.userImage}
103-
time={new Date(comment.time)}
104-
commentText={comment.commentText} // Use commentText to ensure the actual comment is displayed
90+
time={new Date(comment.date)}
91+
commentText={comment.comment} // Use commentText to ensure the actual comment is displayed
10592
/>
10693
))
10794
) : (

frontend/src/services/tickets.service.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DashboardTicket, FaultGeoData, FaultType, PaginatedResults, UnprocessedFaultGeoData } from "@/types/custom.types";
1+
import { DashboardTicket, FaultGeoData, FaultType, PaginatedResults, UnprocessedFaultGeoData, UserRole } from "@/types/custom.types";
22
import { CognitoIdentityProviderClient, AdminGetUserCommand } from "@aws-sdk/client-cognito-identity-provider";
33

44
interface UserAttributes {
@@ -467,14 +467,17 @@ export async function addCommentWithImage(comment: string, ticket_id: string, im
467467
}
468468
}
469469

470-
export async function addCommentWithoutImage(comment: string, ticket_id: string, user_id: string, user_session: string,) {
470+
export async function addCommentWithoutImage(comment: string, ticket_id: string, user_id: string, dateCreated: Date, user_role: UserRole, user_session: string,) {
471471
try {
472472
const apiUrl = "/api/tickets/add-comment-without-image";
473473
const data = {
474-
comment,
475-
ticket_id,
476-
user_id
474+
comment: comment,
475+
ticket_id: ticket_id,
476+
user_id: user_id,
477+
date_created: dateCreated.toISOString(),
478+
user_role: String(user_role)
477479
};
480+
478481
const response = await fetch(apiUrl, {
479482
method: "POST",
480483
headers: {
@@ -498,13 +501,12 @@ export async function addCommentWithoutImage(comment: string, ticket_id: string,
498501

499502
export async function getTicketComments(ticket_id: string, user_session: string) {
500503
try {
501-
const apiUrl = `/api/tickets/comments`;
504+
const apiUrl = `/api/tickets/comments?ticket_id=${encodeURIComponent(ticket_id)}`;
502505
const response = await fetch(apiUrl, {
503506
method: "GET",
504507
headers: {
505508
"Authorization": user_session,
506-
"Content-Type": "application/json",
507-
"X-Ticket-ID": ticket_id, // Add ticket_id in the headers
509+
"Content-Type": "application/json"
508510
},
509511
});
510512

nodejs_api/__tests__/integration/tickets.route.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,9 @@ describe("Integration Tests - /tickets", () => {
666666
const mockRequestBody = {
667667
comment: "This is a test comment without an image.",
668668
ticket_id: "ticket123",
669-
user_id: "user456"
669+
user_id: "user456",
670+
user_role: "CITIZEN",
671+
date_created: "2022-01-01T12:00:00Z"
670672
};
671673

672674
const mockServiceResponse = {
@@ -684,11 +686,7 @@ describe("Integration Tests - /tickets", () => {
684686

685687
expect(response.body).toEqual(mockServiceResponse);
686688

687-
expect(ticketsService.addTicketCommentWithoutImage).toHaveBeenCalledWith(
688-
mockRequestBody.comment,
689-
mockRequestBody.ticket_id,
690-
mockRequestBody.user_id
691-
);
689+
expect(ticketsService.addTicketCommentWithoutImage).toHaveBeenCalledWith(mockRequestBody);
692690
});
693691

694692
test("should return 400 if required fields are missing", async () => {
@@ -703,14 +701,16 @@ describe("Integration Tests - /tickets", () => {
703701

704702
expect(response.statusCode).toBe(400);
705703

706-
expect(response.body.error).toBe("Missing parameter(s): comment");
704+
expect(response.body.error).toBe("Missing parameter(s): comment, date_created, user_role");
707705
});
708706

709707
test("should return 500 if there is an internal server error", async () => {
710708
const mockRequestBody = {
711709
comment: "This is a test comment without an image.",
712710
ticket_id: "ticket123",
713-
user_id: "user456"
711+
user_id: "user456",
712+
user_role: "CITIZEN",
713+
date_created: "2022-01-01T12:00:00Z"
714714
};
715715

716716
jest.spyOn(ticketsService, "addTicketCommentWithoutImage").mockRejectedValue(new Error("Internal Server Error"));

nodejs_api/src/controllers/giveaway.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { NextFunction, Request, Response } from "express";
22
import * as giveawayService from "../services/giveaway.service";
3-
import { cacheResponse } from "../config/redis.config";
3+
import { cacheResponse, DEFAULT_CACHE_DURATION } from "../config/redis.config";
44

55
export const getParticipantCount = async (req: Request, res: Response, next:NextFunction) => {
66
try {
77
const response = await giveawayService.getParticipantCount();
8-
cacheResponse(req.originalUrl, 30, response); // cache for 30 seconds
8+
cacheResponse(req.originalUrl, DEFAULT_CACHE_DURATION, response);
99
return res.status(200).json(response);
1010
} catch (error: any) {
1111
next(error);

nodejs_api/src/controllers/tickets.controller.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export const addCommentWithImage = async (req: Request, res: Response) => {
229229
};
230230

231231
export const addCommentWithoutImage = async (req: Request, res: Response) => {
232-
const requiredFields = ["comment", "ticket_id", "user_id"];
232+
const requiredFields = ["comment", "ticket_id", "user_id", "date_created", "user_role"];
233233
const missingFields = requiredFields.filter(field => !req.body[field]);
234234

235235
if (missingFields.length > 0) {
@@ -238,17 +238,17 @@ export const addCommentWithoutImage = async (req: Request, res: Response) => {
238238

239239
try {
240240
const { comment, ticket_id, user_id } = req.body;
241-
const response = await ticketsService.addTicketCommentWithoutImage(comment, ticket_id, user_id);
241+
const response = await ticketsService.addTicketCommentWithoutImage(req.body);
242242
return res.status(200).json(response);
243243
} catch (error: any) {
244244
return res.status(500).json({ Error: error.message });
245245
}
246246
};
247247

248248
export const getTicketComments = async (req: Request, res: Response) => {
249-
const ticketId = req.headers["X-Ticket-ID"] as string;
249+
const ticketId = req.query["ticket_id"] as string;
250250
if (!ticketId) {
251-
return res.status(400).json({ Error: "Missing request header: X-Ticket-ID" });
251+
return res.status(400).json({ Error: "Missing parameter: ticket_id" });
252252
}
253253
try {
254254
const response = await ticketsService.getTicketComments(ticketId);

nodejs_api/src/services/giveaway.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { PutCommandInput, PutCommandOutput, QueryCommandInput, QueryCommandOutpu
22
import { GIVEAWAY_TABLE, TICKETS_TABLE } from "../config/dynamodb.config";
33
import { addJobToReadQueue, addJobToWriteQueue } from "./jobs.service";
44
import { JobData } from "../types/job.types";
5-
import { DB_PUT, DB_QUERY, DB_SCAN } from "../config/redis.config";
5+
import { DB_PUT, DB_QUERY, DB_SCAN, deleteCacheKey } from "../config/redis.config";
66
import { CustomError } from "../errors/CustomError";
77

88
export const getParticipantCount = async () => {
@@ -25,6 +25,8 @@ export const getParticipantCount = async () => {
2525
};
2626

2727
export const addParticipant = async (formData: any) => {
28+
await deleteCacheKey("/giveaway/participant/count");
29+
2830
// verify that provided ticketNumber exists
2931
const params: QueryCommandInput = {
3032
TableName: TICKETS_TABLE,

nodejs_api/src/services/tenders.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface AcceptOrRejectTenderData {
2727

2828
export const createTender = async (senderData: TenderData) => {
2929
await deleteAllCache();
30+
3031
const companyPid = await getCompanyIDFromName(senderData.company_name);
3132
if (!companyPid) {
3233
throw new BadRequestError("Company Does not Exist");

nodejs_api/src/services/tickets.service.ts

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { QueryCommandInput, GetCommandInput, GetCommandOutput, PutCommandInput, QueryCommandOutput, ScanCommandInput, ScanCommandOutput, PutCommandOutput, UpdateCommandInput } from "@aws-sdk/lib-dynamodb";
22
import { BadRequestError, ClientError } from "../types/error.types";
3-
import { ASSETS_TABLE, TENDERS_TABLE, TICKET_UPDATE_TABLE, TICKETS_TABLE, WATCHLIST_TABLE } from "../config/dynamodb.config";
3+
import { ASSETS_TABLE, cognitoClient, TENDERS_TABLE, TICKET_UPDATE_TABLE, TICKETS_TABLE, WATCHLIST_TABLE } from "../config/dynamodb.config";
44
import { generateId, generateTicketNumber, getCompanyIDFromName, getMunicipality, getTicketDateOpened, getUserProfile, updateCommentCounts, updateTicketTable, validateTicketId } from "../utils/tickets.utils";
55
import { uploadFile } from "../config/s3bucket.config";
66
import WebSocket from "ws";
77
import { addJobToReadQueue, addJobToWriteQueue } from "./jobs.service";
88
import { JobData } from "../types/job.types";
9-
import { deleteAllCache, DB_GET, DB_PUT, DB_QUERY, DB_SCAN, DB_UPDATE } from "../config/redis.config";
9+
import { deleteAllCache, DB_GET, DB_PUT, DB_QUERY, DB_SCAN, DB_UPDATE, deleteCacheKey } from "../config/redis.config";
1010
import { sendWebSocketMessage } from "../utils/tenders.utils";
11+
import { AdminGetUserCommand, AdminGetUserCommandOutput } from "@aws-sdk/client-cognito-identity-provider";
1112

1213
interface Ticket {
1314
dateClosed: string;
@@ -960,25 +961,23 @@ export const addTicketCommentWithImage = async (comment: string, ticket_id: stri
960961
return response;
961962
};
962963

963-
export const addTicketCommentWithoutImage = async (comment: string, ticket_id: string, user_id: string) => {
964+
export const addTicketCommentWithoutImage = async (commentData: any) => {
965+
await deleteAllCache();
966+
964967
// Validate ticket_id
965-
validateTicketId(ticket_id);
968+
validateTicketId(commentData.ticket_id);
966969

967970
// Generate unique ticket update ID
968971
const ticketupdate_id = generateId();
969972

970-
// Get current date and time
971-
const currentDatetime = new Date();
972-
const formattedDatetime = currentDatetime.toISOString();
973-
974973
// Prepare comment item
975974
const commentItem = {
976975
ticketupdate_id: ticketupdate_id,
977-
comment: comment,
978-
date: formattedDatetime,
976+
comment: commentData.comment,
977+
date: commentData.date_created,
979978
imageURL: "<empty>", // Set to <empty> if no image is provided
980-
ticket_id: ticket_id,
981-
user_id: user_id
979+
ticket_id: commentData.ticket_id,
980+
user_id: commentData.user_id
982981
};
983982

984983
// Insert comment into ticket_updates table
@@ -997,7 +996,7 @@ export const addTicketCommentWithoutImage = async (comment: string, ticket_id: s
997996

998997
const response = {
999998
message: "Comment added successfully",
1000-
ticketupdate_id: ticketupdate_id,
999+
ticketupdate_id: ticketupdate_id
10011000
};
10021001

10031002
return response;
@@ -1010,11 +1009,16 @@ export const getTicketComments = async (currTicketId: string) => {
10101009
TableName: TICKET_UPDATE_TABLE,
10111010
IndexName: "ticket_id-index",
10121011
KeyConditionExpression: "ticket_id = :ticket_id",
1012+
ProjectionExpression: "ticketupdate_id, #comment, user_id",
1013+
ExpressionAttributeNames: {
1014+
"#comment": "comment" // alias the reserved keyword "comment"
1015+
},
10131016
ExpressionAttributeValues: {
10141017
":ticket_id": currTicketId
10151018
}
10161019
};
10171020

1021+
10181022
const jobData: JobData = {
10191023
type: DB_QUERY,
10201024
params: params
@@ -1023,7 +1027,58 @@ export const getTicketComments = async (currTicketId: string) => {
10231027
const job = await addJobToReadQueue(jobData);
10241028
const response = await job.finished() as QueryCommandOutput;
10251029
const items = response.Items || [];
1026-
return items;
1030+
1031+
if (items.length > 0) {
1032+
let cognitoUsername: string = "";
1033+
try {
1034+
const USER_POOL_ID = process.env.USER_POOL_ID;
1035+
for (let commentItem of items) {
1036+
cognitoUsername = (commentItem["user_id"] as string).toLowerCase();
1037+
const userResponse: AdminGetUserCommandOutput = await cognitoClient.send(
1038+
new AdminGetUserCommand({
1039+
UserPoolId: USER_POOL_ID,
1040+
Username: cognitoUsername
1041+
})
1042+
);
1043+
1044+
let userImage: string | null = null;
1045+
let userGivenName: string | null = null;
1046+
let userFamilyName: string | null = null;
1047+
1048+
if (userResponse.UserAttributes) {
1049+
for (let attr of userResponse.UserAttributes) {
1050+
if (attr.Name === "picture") {
1051+
userImage = attr.Value || "https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541";
1052+
}
1053+
1054+
if (attr.Name === "given_name") {
1055+
userGivenName = attr.Value!;
1056+
}
1057+
1058+
if (attr.Name === "family_name") {
1059+
userFamilyName = attr.Value!;
1060+
}
1061+
1062+
if (userImage && userGivenName && userFamilyName) {
1063+
break;
1064+
}
1065+
}
1066+
}
1067+
1068+
commentItem["userImage"] = userImage;
1069+
commentItem["userName"] = userGivenName + " " + userFamilyName;
1070+
}
1071+
1072+
return items;
1073+
} catch (error: any) {
1074+
if (error.name === "UserNotFoundException") {
1075+
console.error(`${error.message}: ${cognitoUsername}`);
1076+
} else {
1077+
console.error("An error occurred:", error);
1078+
}
1079+
}
1080+
}
1081+
10271082
} catch (e: any) {
10281083
if (e instanceof ClientError) {
10291084
throw new BadRequestError(`Failed to search for the ticket comments: ${e.response.Error.Message}`);

0 commit comments

Comments
 (0)