Skip to content

Commit

Permalink
Merge pull request #905 from COS301-SE-2024/bugfix/comments
Browse files Browse the repository at this point in the history
fixed comments bugs
  • Loading branch information
Tinogwanz authored Oct 21, 2024
2 parents f292f5b + 29a4c51 commit c16ba89
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 60 deletions.
33 changes: 10 additions & 23 deletions frontend/src/components/Comments/comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getTicketComments,
getUserFirstLastName,
} from '@/services/tickets.service';
import { UserRole } from '@/types/custom.types';

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

const userPoolId = process.env.USER_POOL_ID;
if (!userPoolId) {
throw new Error("USER_POOL_ID is not defined");
}

const enrichedComments = await Promise.allSettled(commentsData.map(async (comment: any) => {
const userAttributes = await getUserFirstLastName(comment.user_id, userPoolId);
return {
...comment,
userName: `${userAttributes?.given_name} ${userAttributes?.family_name}`,
userImage: userAttributes?.picture || 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541',
time: new Date(comment.date)
};
}));

const fulfilledComments = enrichedComments.filter((comment: any) => comment.status === "fulfilled").map((comment: any) => comment.value);

setComments(fulfilledComments);
setComments(commentsData);
setLoading(false);
} catch (error) {
console.error("Error fetching comments:", error);
Expand All @@ -65,15 +49,18 @@ const Comments: React.FC<CommentsProps> = ({ onBack, isCitizen, ticketId }) => {
const user_picture = String(user_data.current?.picture);
const userSession = String(user_data.current?.session_token);
const user_email = String(user_data.current?.email);
const userRole = user_data.current?.user_role || UserRole.CITIZEN; // default to citizen if user_role is not set

const dateCommentCreated = new Date();

const newCommentData = {
userName,
userImage: user_picture || 'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541',
time: new Date(),
userImage: user_picture || "https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541",
time: dateCommentCreated,
commentText: newComment,
};

await addCommentWithoutImage(newCommentData.commentText, ticketId, user_email, userSession);
await addCommentWithoutImage(newCommentData.commentText, ticketId, user_email, dateCommentCreated, userRole, userSession);

setComments([...comments, newCommentData]);
setNewComment('');
Expand All @@ -100,8 +87,8 @@ const Comments: React.FC<CommentsProps> = ({ onBack, isCitizen, ticketId }) => {
key={index}
userName={comment.userName}
userImage={comment.userImage}
time={new Date(comment.time)}
commentText={comment.commentText} // Use commentText to ensure the actual comment is displayed
time={new Date(comment.date)}
commentText={comment.comment} // Use commentText to ensure the actual comment is displayed
/>
))
) : (
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/services/tickets.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DashboardTicket, FaultGeoData, FaultType, PaginatedResults, UnprocessedFaultGeoData } from "@/types/custom.types";
import { DashboardTicket, FaultGeoData, FaultType, PaginatedResults, UnprocessedFaultGeoData, UserRole } from "@/types/custom.types";
import { CognitoIdentityProviderClient, AdminGetUserCommand } from "@aws-sdk/client-cognito-identity-provider";

interface UserAttributes {
Expand Down Expand Up @@ -467,14 +467,17 @@ export async function addCommentWithImage(comment: string, ticket_id: string, im
}
}

export async function addCommentWithoutImage(comment: string, ticket_id: string, user_id: string, user_session: string,) {
export async function addCommentWithoutImage(comment: string, ticket_id: string, user_id: string, dateCreated: Date, user_role: UserRole, user_session: string,) {
try {
const apiUrl = "/api/tickets/add-comment-without-image";
const data = {
comment,
ticket_id,
user_id
comment: comment,
ticket_id: ticket_id,
user_id: user_id,
date_created: dateCreated.toISOString(),
user_role: String(user_role)
};

const response = await fetch(apiUrl, {
method: "POST",
headers: {
Expand All @@ -498,13 +501,12 @@ export async function addCommentWithoutImage(comment: string, ticket_id: string,

export async function getTicketComments(ticket_id: string, user_session: string) {
try {
const apiUrl = `/api/tickets/comments`;
const apiUrl = `/api/tickets/comments?ticket_id=${encodeURIComponent(ticket_id)}`;
const response = await fetch(apiUrl, {
method: "GET",
headers: {
"Authorization": user_session,
"Content-Type": "application/json",
"X-Ticket-ID": ticket_id, // Add ticket_id in the headers
"Content-Type": "application/json"
},
});

Expand Down
16 changes: 8 additions & 8 deletions nodejs_api/__tests__/integration/tickets.route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,9 @@ describe("Integration Tests - /tickets", () => {
const mockRequestBody = {
comment: "This is a test comment without an image.",
ticket_id: "ticket123",
user_id: "user456"
user_id: "user456",
user_role: "CITIZEN",
date_created: "2022-01-01T12:00:00Z"
};

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

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

expect(ticketsService.addTicketCommentWithoutImage).toHaveBeenCalledWith(
mockRequestBody.comment,
mockRequestBody.ticket_id,
mockRequestBody.user_id
);
expect(ticketsService.addTicketCommentWithoutImage).toHaveBeenCalledWith(mockRequestBody);
});

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

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

expect(response.body.error).toBe("Missing parameter(s): comment");
expect(response.body.error).toBe("Missing parameter(s): comment, date_created, user_role");
});

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

jest.spyOn(ticketsService, "addTicketCommentWithoutImage").mockRejectedValue(new Error("Internal Server Error"));
Expand Down
4 changes: 2 additions & 2 deletions nodejs_api/src/controllers/giveaway.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NextFunction, Request, Response } from "express";
import * as giveawayService from "../services/giveaway.service";
import { cacheResponse } from "../config/redis.config";
import { cacheResponse, DEFAULT_CACHE_DURATION } from "../config/redis.config";

export const getParticipantCount = async (req: Request, res: Response, next:NextFunction) => {
try {
const response = await giveawayService.getParticipantCount();
cacheResponse(req.originalUrl, 30, response); // cache for 30 seconds
cacheResponse(req.originalUrl, DEFAULT_CACHE_DURATION, response);
return res.status(200).json(response);
} catch (error: any) {
next(error);
Expand Down
8 changes: 4 additions & 4 deletions nodejs_api/src/controllers/tickets.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export const addCommentWithImage = async (req: Request, res: Response) => {
};

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

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

try {
const { comment, ticket_id, user_id } = req.body;
const response = await ticketsService.addTicketCommentWithoutImage(comment, ticket_id, user_id);
const response = await ticketsService.addTicketCommentWithoutImage(req.body);
return res.status(200).json(response);
} catch (error: any) {
return res.status(500).json({ Error: error.message });
}
};

export const getTicketComments = async (req: Request, res: Response) => {
const ticketId = req.headers["X-Ticket-ID"] as string;
const ticketId = req.query["ticket_id"] as string;
if (!ticketId) {
return res.status(400).json({ Error: "Missing request header: X-Ticket-ID" });
return res.status(400).json({ Error: "Missing parameter: ticket_id" });
}
try {
const response = await ticketsService.getTicketComments(ticketId);
Expand Down
4 changes: 3 additions & 1 deletion nodejs_api/src/services/giveaway.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PutCommandInput, PutCommandOutput, QueryCommandInput, QueryCommandOutpu
import { GIVEAWAY_TABLE, TICKETS_TABLE } from "../config/dynamodb.config";
import { addJobToReadQueue, addJobToWriteQueue } from "./jobs.service";
import { JobData } from "../types/job.types";
import { DB_PUT, DB_QUERY, DB_SCAN } from "../config/redis.config";
import { DB_PUT, DB_QUERY, DB_SCAN, deleteCacheKey } from "../config/redis.config";
import { CustomError } from "../errors/CustomError";

export const getParticipantCount = async () => {
Expand All @@ -25,6 +25,8 @@ export const getParticipantCount = async () => {
};

export const addParticipant = async (formData: any) => {
await deleteCacheKey("/giveaway/participant/count");

// verify that provided ticketNumber exists
const params: QueryCommandInput = {
TableName: TICKETS_TABLE,
Expand Down
1 change: 1 addition & 0 deletions nodejs_api/src/services/tenders.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface AcceptOrRejectTenderData {

export const createTender = async (senderData: TenderData) => {
await deleteAllCache();

const companyPid = await getCompanyIDFromName(senderData.company_name);
if (!companyPid) {
throw new BadRequestError("Company Does not Exist");
Expand Down
83 changes: 69 additions & 14 deletions nodejs_api/src/services/tickets.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { QueryCommandInput, GetCommandInput, GetCommandOutput, PutCommandInput, QueryCommandOutput, ScanCommandInput, ScanCommandOutput, PutCommandOutput, UpdateCommandInput } from "@aws-sdk/lib-dynamodb";
import { BadRequestError, ClientError } from "../types/error.types";
import { ASSETS_TABLE, TENDERS_TABLE, TICKET_UPDATE_TABLE, TICKETS_TABLE, WATCHLIST_TABLE } from "../config/dynamodb.config";
import { ASSETS_TABLE, cognitoClient, TENDERS_TABLE, TICKET_UPDATE_TABLE, TICKETS_TABLE, WATCHLIST_TABLE } from "../config/dynamodb.config";
import { generateId, generateTicketNumber, getCompanyIDFromName, getMunicipality, getTicketDateOpened, getUserProfile, updateCommentCounts, updateTicketTable, validateTicketId } from "../utils/tickets.utils";
import { uploadFile } from "../config/s3bucket.config";
import WebSocket from "ws";
import { addJobToReadQueue, addJobToWriteQueue } from "./jobs.service";
import { JobData } from "../types/job.types";
import { deleteAllCache, DB_GET, DB_PUT, DB_QUERY, DB_SCAN, DB_UPDATE } from "../config/redis.config";
import { deleteAllCache, DB_GET, DB_PUT, DB_QUERY, DB_SCAN, DB_UPDATE, deleteCacheKey } from "../config/redis.config";
import { sendWebSocketMessage } from "../utils/tenders.utils";
import { AdminGetUserCommand, AdminGetUserCommandOutput } from "@aws-sdk/client-cognito-identity-provider";

interface Ticket {
dateClosed: string;
Expand Down Expand Up @@ -960,25 +961,23 @@ export const addTicketCommentWithImage = async (comment: string, ticket_id: stri
return response;
};

export const addTicketCommentWithoutImage = async (comment: string, ticket_id: string, user_id: string) => {
export const addTicketCommentWithoutImage = async (commentData: any) => {
await deleteAllCache();

// Validate ticket_id
validateTicketId(ticket_id);
validateTicketId(commentData.ticket_id);

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

// Get current date and time
const currentDatetime = new Date();
const formattedDatetime = currentDatetime.toISOString();

// Prepare comment item
const commentItem = {
ticketupdate_id: ticketupdate_id,
comment: comment,
date: formattedDatetime,
comment: commentData.comment,
date: commentData.date_created,
imageURL: "<empty>", // Set to <empty> if no image is provided
ticket_id: ticket_id,
user_id: user_id
ticket_id: commentData.ticket_id,
user_id: commentData.user_id
};

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

const response = {
message: "Comment added successfully",
ticketupdate_id: ticketupdate_id,
ticketupdate_id: ticketupdate_id
};

return response;
Expand All @@ -1010,11 +1009,16 @@ export const getTicketComments = async (currTicketId: string) => {
TableName: TICKET_UPDATE_TABLE,
IndexName: "ticket_id-index",
KeyConditionExpression: "ticket_id = :ticket_id",
ProjectionExpression: "ticketupdate_id, #comment, user_id",
ExpressionAttributeNames: {
"#comment": "comment" // alias the reserved keyword "comment"
},
ExpressionAttributeValues: {
":ticket_id": currTicketId
}
};


const jobData: JobData = {
type: DB_QUERY,
params: params
Expand All @@ -1023,7 +1027,58 @@ export const getTicketComments = async (currTicketId: string) => {
const job = await addJobToReadQueue(jobData);
const response = await job.finished() as QueryCommandOutput;
const items = response.Items || [];
return items;

if (items.length > 0) {
let cognitoUsername: string = "";
try {
const USER_POOL_ID = process.env.USER_POOL_ID;
for (let commentItem of items) {
cognitoUsername = (commentItem["user_id"] as string).toLowerCase();
const userResponse: AdminGetUserCommandOutput = await cognitoClient.send(
new AdminGetUserCommand({
UserPoolId: USER_POOL_ID,
Username: cognitoUsername
})
);

let userImage: string | null = null;
let userGivenName: string | null = null;
let userFamilyName: string | null = null;

if (userResponse.UserAttributes) {
for (let attr of userResponse.UserAttributes) {
if (attr.Name === "picture") {
userImage = attr.Value || "https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png?20150327203541";
}

if (attr.Name === "given_name") {
userGivenName = attr.Value!;
}

if (attr.Name === "family_name") {
userFamilyName = attr.Value!;
}

if (userImage && userGivenName && userFamilyName) {
break;
}
}
}

commentItem["userImage"] = userImage;
commentItem["userName"] = userGivenName + " " + userFamilyName;
}

return items;
} catch (error: any) {
if (error.name === "UserNotFoundException") {
console.error(`${error.message}: ${cognitoUsername}`);
} else {
console.error("An error occurred:", error);
}
}
}

} catch (e: any) {
if (e instanceof ClientError) {
throw new BadRequestError(`Failed to search for the ticket comments: ${e.response.Error.Message}`);
Expand Down

0 comments on commit c16ba89

Please sign in to comment.