From b0ee829b013aaccf99d2097bdc6c88a612953940 Mon Sep 17 00:00:00 2001 From: Nadine Blaas <122269231+naadnn@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:00:18 +0200 Subject: [PATCH 1/6] refactor: logging related to dbErrors --- .../collaboration/comments/actions.ts | 254 +++++++++++------- app/components/newsPage/threads/actions.ts | 28 +- .../project-details/comments/actions.ts | 100 ++++--- .../api/notification/update-subscription.ts | 48 ++-- app/repository/db/posts.ts | 38 ++- app/repository/db/statistics.ts | 5 +- .../collaborationCommentResponseService.ts | 38 ++- app/services/collaborationCommentService.ts | 79 ++++-- app/services/commentService.ts | 87 +++++- app/services/followService.ts | 33 ++- app/services/postService.ts | 51 +++- app/services/reactionService.ts | 111 +++++--- app/services/surveyQuestionService.ts | 107 +++++--- app/services/updateService.ts | 21 +- app/utils/logger.ts | 2 +- app/utils/newsFeed/newsFeedSync.ts | 6 + app/utils/notification/notificationSender.ts | 31 ++- app/utils/notification/pushNotification.ts | 49 +++- .../collaborationComments/requests.ts | 28 +- .../collaborationQuestions/requests.ts | 2 +- app/utils/requests/comments/requests.ts | 40 ++- app/utils/requests/events/requests.ts | 48 ++-- app/utils/requests/statistics/requests.ts | 102 ++++--- app/utils/requests/updates/requests.ts | 110 ++++---- .../collaborationQuestionLifecycle.ts | 41 +-- .../entityLifecycles/eventLifecycle.ts | 31 ++- .../entityLifecycles/opportunityLifecycle.ts | 41 +-- .../entityLifecycles/projectLifecycle.ts | 41 +-- .../surveyQuestionLifecycle.ts | 41 +-- .../entityLifecycles/updateLifecycle.ts | 71 +++-- 30 files changed, 1117 insertions(+), 567 deletions(-) diff --git a/app/components/collaboration/comments/actions.ts b/app/components/collaboration/comments/actions.ts index c8c0a4b0..b9227be3 100644 --- a/app/components/collaboration/comments/actions.ts +++ b/app/components/collaboration/comments/actions.ts @@ -85,42 +85,8 @@ export const addProjectCollaborationComment = withAuth( ); export const deleteProjectCollaborationComment = withAuth(async (user: UserSession, body: { commentId: string }) => { - const validatedParams = validateParams(deleteCollaborationCommentSchema, body); - - if (validatedParams.status !== StatusCodes.OK) { - return { - status: validatedParams.status, - errors: validatedParams.errors, - message: validatedParams.message, - }; - } - - const comment = await getCollaborationCommentById(dbClient, body.commentId); - - if (comment === null) { - return { - status: StatusCodes.BAD_REQUEST, - message: 'No collaboration comment with the specified ID exists', - }; - } - - if (comment.author !== user.providerId) { - return { - status: StatusCodes.BAD_REQUEST, - message: 'A collaboration comment can only be deleted by its author', - }; - } - - await deleteCollaborationComment(body.commentId); - - return { - status: StatusCodes.OK, - }; -}); - -export const updateProjectCollaborationComment = withAuth( - async (user: UserSession, body: { commentId: string; updatedText: string }) => { - const validatedParams = validateParams(updateCollaborationCommentSchema, body); + try { + const validatedParams = validateParams(deleteCollaborationCommentSchema, body); if (validatedParams.status !== StatusCodes.OK) { return { @@ -142,22 +108,76 @@ export const updateProjectCollaborationComment = withAuth( if (comment.author !== user.providerId) { return { status: StatusCodes.BAD_REQUEST, - message: 'A collaboration comment can only be edited by its author', + message: 'A collaboration comment can only be deleted by its author', }; } - const updatedComment = await updateCollaborationComment({ - user, - comment: { - id: body.commentId, - comment: body.updatedText, - }, - }); + await deleteCollaborationComment(body.commentId); return { status: StatusCodes.OK, - comment: updatedComment, }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Deleting Collaboration Comment with id: ${body.commentId} by user ${user.providerId}`, + err as Error, + body.commentId, + ); + logger.error(error); + throw err; + } +}); + +export const updateProjectCollaborationComment = withAuth( + async (user: UserSession, body: { commentId: string; updatedText: string }) => { + try { + const validatedParams = validateParams(updateCollaborationCommentSchema, body); + + if (validatedParams.status !== StatusCodes.OK) { + return { + status: validatedParams.status, + errors: validatedParams.errors, + message: validatedParams.message, + }; + } + + const comment = await getCollaborationCommentById(dbClient, body.commentId); + + if (comment === null) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'No collaboration comment with the specified ID exists', + }; + } + + if (comment.author !== user.providerId) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'A collaboration comment can only be edited by its author', + }; + } + + const updatedComment = await updateCollaborationComment({ + user, + comment: { + id: body.commentId, + comment: body.updatedText, + }, + }); + + return { + status: StatusCodes.OK, + comment: updatedComment, + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Updating Collaboration Comment with id: ${body.commentId} by user ${user.providerId}`, + err as Error, + body.commentId, + ); + logger.error(error); + throw err; + } }, ); @@ -221,87 +241,117 @@ export const addProjectCollaborationCommentResponse = withAuth( export const deleteProjectCollaborationCommentResponse = withAuth( async (user: UserSession, body: { responseId: string }) => { - const validatedParams = validateParams(deleteCollaborationCommentResponseSchema, body); + try { + const validatedParams = validateParams(deleteCollaborationCommentResponseSchema, body); - if (validatedParams.status !== StatusCodes.OK) { - return { - status: validatedParams.status, - errors: validatedParams.errors, - message: validatedParams.message, - }; - } + if (validatedParams.status !== StatusCodes.OK) { + return { + status: validatedParams.status, + errors: validatedParams.errors, + message: validatedParams.message, + }; + } - const response = await getCollaborationCommentResponseById(dbClient, body.responseId); + const response = await getCollaborationCommentResponseById(dbClient, body.responseId); - if (response === null) { - return { - status: StatusCodes.BAD_REQUEST, - message: 'No collaboration comment response with the specified ID exists', - }; - } + if (response === null) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'No collaboration comment response with the specified ID exists', + }; + } + + if (response.author !== user.providerId) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'A collaboration comment response can only be deleted by its author', + }; + } + + await deleteCollaborationCommentResponse({ user, response: { id: body.responseId } }); - if (response.author !== user.providerId) { return { - status: StatusCodes.BAD_REQUEST, - message: 'A collaboration comment response can only be deleted by its author', + status: StatusCodes.OK, }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Deleting a Collaboration Comment Response with id: ${body.responseId} by user ${user.providerId}`, + err as Error, + body.responseId, + ); + logger.error(error); + throw err; } - - await deleteCollaborationCommentResponse({ user, response: { id: body.responseId } }); - - return { - status: StatusCodes.OK, - }; }, ); export const updateProjectCollaborationCommentResponse = withAuth( async (user: UserSession, body: { responseId: string; updatedText: string }) => { - const validatedParams = validateParams(updateCollaborationCommentResponseSchema, body); + try { + const validatedParams = validateParams(updateCollaborationCommentResponseSchema, body); - if (validatedParams.status !== StatusCodes.OK) { - return { - status: validatedParams.status, - errors: validatedParams.errors, - message: validatedParams.message, - }; - } + if (validatedParams.status !== StatusCodes.OK) { + return { + status: validatedParams.status, + errors: validatedParams.errors, + message: validatedParams.message, + }; + } - const response = await getCollaborationCommentResponseById(dbClient, body.responseId); + const response = await getCollaborationCommentResponseById(dbClient, body.responseId); - if (response === null) { - return { - status: StatusCodes.BAD_REQUEST, - message: 'No collaboration comment response with the specified ID exists', - }; - } + if (response === null) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'No collaboration comment response with the specified ID exists', + }; + } + + if (response.author !== user.providerId) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'A collaboration comment response can only be edited by its author', + }; + } + + const updatedResponse = await updateCollaborationCommentResponseInDb(dbClient, body.responseId, body.updatedText); - if (response.author !== user.providerId) { return { - status: StatusCodes.BAD_REQUEST, - message: 'A collaboration comment response can only be edited by its author', + status: StatusCodes.OK, + comment: updatedResponse, }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Updating a Collaboration Comment Response with id: ${body.responseId} by user ${user.providerId}`, + err as Error, + body.responseId, + ); + logger.error(error); + throw err; } - - const updatedResponse = await updateCollaborationCommentResponseInDb(dbClient, body.responseId, body.updatedText); - - return { - status: StatusCodes.OK, - comment: updatedResponse, - }; }, ); export const handleProjectCollaborationCommentResponseUpvotedBy = withAuth( async (user: UserSession, body: { responseId: string }) => { - const validatedParams = validateParams(collaborationCommentResponseUpvotedBySchema, body); - if (validatedParams.status === StatusCodes.OK) { - await handleCollaborationCommentResponseUpvotedByInDb(dbClient, body.responseId, user.providerId); - return { status: StatusCodes.OK }; + try { + const validatedParams = validateParams(collaborationCommentResponseUpvotedBySchema, body); + if (validatedParams.status === StatusCodes.OK) { + await handleCollaborationCommentResponseUpvotedByInDb(dbClient, body.responseId, user.providerId); + return { status: StatusCodes.OK }; + } + return { + status: validatedParams.status, + errors: validatedParams.errors, + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Adding an upvote to a Collaboration Comment Response with id: ${body.responseId} by user ${user.providerId}`, + err as Error, + body.responseId, + ); + logger.error(error); + throw err; } - return { - status: validatedParams.status, - errors: validatedParams.errors, - }; }, ); diff --git a/app/components/newsPage/threads/actions.ts b/app/components/newsPage/threads/actions.ts index a17c0c51..39b0f5da 100644 --- a/app/components/newsPage/threads/actions.ts +++ b/app/components/newsPage/threads/actions.ts @@ -6,6 +6,10 @@ import { StatusCodes } from 'http-status-codes'; import { UserSession } from '@/common/types'; import { addComment } from '@/services/commentService'; import { withAuth } from '@/utils/auth'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; + +const logger = getLogger(); interface AddUserComment { objectId: string; @@ -15,13 +19,23 @@ interface AddUserComment { } export const addUserComment = withAuth(async (user: UserSession, body: AddUserComment) => { - const { comment, commentType, objectId, parentCommentId } = body; - const author = user; + try { + const { comment, commentType, objectId, parentCommentId } = body; + const author = user; - const createdComment = await addComment({ author, comment, commentType, objectId, parentCommentId }); + const createdComment = await addComment({ author, comment, commentType, objectId, parentCommentId }); - return { - status: StatusCodes.OK, - data: createdComment, - }; + return { + status: StatusCodes.OK, + data: createdComment, + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Adding a ${CommentType} by user ${user.providerId}`, + err as Error, + user.providerId, + ); + logger.error(error); + throw err; + } }); diff --git a/app/components/project-details/comments/actions.ts b/app/components/project-details/comments/actions.ts index 3ee598df..b229b936 100644 --- a/app/components/project-details/comments/actions.ts +++ b/app/components/project-details/comments/actions.ts @@ -51,7 +51,7 @@ export const addProjectComment = withAuth(async (user: UserSession, body: { proj }; } catch (err) { const error: InnoPlatformError = dbError( - `Adding a Comment to a Project ${body.projectId} from user ${user.providerId}`, + `Adding a Comment to a Project ${body.projectId} by user ${user.providerId}`, err as Error, body.projectId, ); @@ -93,42 +93,8 @@ export const handleProjectCommentUpvoteBy = withAuth(async (user: UserSession, b }); export const deleteProjectComment = withAuth(async (user: UserSession, body: { commentId: string }) => { - const validatedParams = validateParams(deleteCommentSchema, body); - - if (validatedParams.status !== StatusCodes.OK) { - return { - status: validatedParams.status, - errors: validatedParams.errors, - message: validatedParams.message, - }; - } - - const comment = await getCommentbyId(dbClient, body.commentId); - - if (comment === null) { - return { - status: StatusCodes.BAD_REQUEST, - message: 'No comment with the specified ID exists', - }; - } - - if (comment.author !== user.providerId) { - return { - status: StatusCodes.BAD_REQUEST, - message: 'A comment can only be deleted by its author', - }; - } - - await deleteComment(dbClient, body.commentId); - - return { - status: StatusCodes.OK, - }; -}); - -export const updateProjectComment = withAuth( - async (user: UserSession, body: { commentId: string; updatedText: string }) => { - const validatedParams = validateParams(updateCommentSchema, body); + try { + const validatedParams = validateParams(deleteCommentSchema, body); if (validatedParams.status !== StatusCodes.OK) { return { @@ -150,15 +116,69 @@ export const updateProjectComment = withAuth( if (comment.author !== user.providerId) { return { status: StatusCodes.BAD_REQUEST, - message: 'A comment can only be edited by its author', + message: 'A comment can only be deleted by its author', }; } - const updatedComment = await updateComment(dbClient, body.commentId, body.updatedText); + await deleteComment(dbClient, body.commentId); return { status: StatusCodes.OK, - comment: updatedComment, }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Deleting a Comment ${body.commentId} from user ${user.providerId}`, + err as Error, + body.commentId, + ); + logger.error(error); + throw err; + } +}); + +export const updateProjectComment = withAuth( + async (user: UserSession, body: { commentId: string; updatedText: string }) => { + try { + const validatedParams = validateParams(updateCommentSchema, body); + + if (validatedParams.status !== StatusCodes.OK) { + return { + status: validatedParams.status, + errors: validatedParams.errors, + message: validatedParams.message, + }; + } + + const comment = await getCommentbyId(dbClient, body.commentId); + + if (comment === null) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'No comment with the specified ID exists', + }; + } + + if (comment.author !== user.providerId) { + return { + status: StatusCodes.BAD_REQUEST, + message: 'A comment can only be edited by its author', + }; + } + + const updatedComment = await updateComment(dbClient, body.commentId, body.updatedText); + + return { + status: StatusCodes.OK, + comment: updatedComment, + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Updating a Comment ${body.commentId} by user ${user.providerId}`, + err as Error, + body.commentId, + ); + logger.error(error); + throw err; + } }, ); diff --git a/app/pages/api/notification/update-subscription.ts b/app/pages/api/notification/update-subscription.ts index 1795e395..7f3880be 100644 --- a/app/pages/api/notification/update-subscription.ts +++ b/app/pages/api/notification/update-subscription.ts @@ -5,31 +5,45 @@ import { string, z } from 'zod'; import { options } from '@/pages/api/auth/[...nextauth]'; import { updatePushSubscriptionForUser } from '@/repository/db/push_subscriptions'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; import dbClient from './../../../repository/db/prisma/prisma'; +const logger = getLogger(); + const bodySchema = z.object({ oldSubscription: string(), newSubscription: string(), }); export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { method, body } = req; - const session = await getServerSession(options); - if (method !== 'POST') return res.status(StatusCodes.METHOD_NOT_ALLOWED); - if (session == undefined) { - return { - status: StatusCodes.UNAUTHORIZED, - errors: new Error('User is not authenticated'), - }; - } + try { + const { method, body } = req; + const session = await getServerSession(options); + if (method !== 'POST') return res.status(StatusCodes.METHOD_NOT_ALLOWED); + if (session == undefined) { + return { + status: StatusCodes.UNAUTHORIZED, + errors: new Error('User is not authenticated'), + }; + } - const { oldSubscription, newSubscription } = bodySchema.parse(body); - await updatePushSubscriptionForUser( - dbClient, - session.user.providerId, - JSON.parse(oldSubscription), - JSON.parse(newSubscription), - ); - return res.status(StatusCodes.OK); + const { oldSubscription, newSubscription } = bodySchema.parse(body); + await updatePushSubscriptionForUser( + dbClient, + session.user.providerId, + JSON.parse(oldSubscription), + JSON.parse(newSubscription), + ); + return res.status(StatusCodes.OK); + } catch (err) { + const session = await getServerSession(options); + const error: InnoPlatformError = dbError( + `Update push subscription for user ${session?.user.providerId}`, + err as Error, + ); + logger.error(error); + throw err; + } } diff --git a/app/repository/db/posts.ts b/app/repository/db/posts.ts index 42e3e2af..fec70d3e 100644 --- a/app/repository/db/posts.ts +++ b/app/repository/db/posts.ts @@ -1,25 +1,37 @@ import { PrismaClient } from '@prisma/client'; -import { getPostCommentsStartingFrom } from './post_comment'; + +import { dbError, InnoPlatformError } from '@/utils/errors'; import { getUniqueValues } from '@/utils/helpers'; +import getLogger from '@/utils/logger'; + +import { getPostCommentsStartingFrom } from './post_comment'; + +const logger = getLogger(); export async function getPostById(client: PrismaClient, id: string) { return await client.post.findFirst({ where: { id: id } }); } export async function getPostsStartingFrom(client: PrismaClient, from: Date) { - const [posts, postsComments] = await Promise.all([ - getPostsFromDbStartingFrom(client, from), - getPostCommentsStartingFrom(client, from), - ]); + try { + const [posts, postsComments] = await Promise.all([ + getPostsFromDbStartingFrom(client, from), + getPostCommentsStartingFrom(client, from), + ]); - // Get unique ids of posts - const postIds = getUniqueValues( - postsComments.map((comment) => comment.postComment?.postId).filter((id): id is string => id !== undefined), - ); - const postsWithComments = await getPostsByIds(client, postIds); - const allPosts = [...posts, ...postsWithComments]; - const uniquePosts = allPosts.filter((post, index, self) => index === self.findIndex((t) => t.id === post.id)); - return uniquePosts; + // Get unique ids of posts + const postIds = getUniqueValues( + postsComments.map((comment) => comment.postComment?.postId).filter((id): id is string => id !== undefined), + ); + const postsWithComments = await getPostsByIds(client, postIds); + const allPosts = [...posts, ...postsWithComments]; + const uniquePosts = allPosts.filter((post, index, self) => index === self.findIndex((t) => t.id === post.id)); + return uniquePosts; + } catch (err) { + const error: InnoPlatformError = dbError(`Getting post comments starting from ${Date}`, err as Error); + logger.error(error); + throw err; + } } export async function getPostsByIds(client: PrismaClient, ids: string[]) { diff --git a/app/repository/db/statistics.ts b/app/repository/db/statistics.ts index 163d8ef2..15643491 100644 --- a/app/repository/db/statistics.ts +++ b/app/repository/db/statistics.ts @@ -1,6 +1,7 @@ -import { ObjectType } from '@/common/types'; -import { PrismaClient } from '@prisma/client'; import type { ObjectType as PrismaObjectType } from '@prisma/client'; +import { PrismaClient } from '@prisma/client'; + +import { ObjectType } from '@/common/types'; export async function getTotalReactions(client: PrismaClient, type: ObjectType) { return client.reaction.groupBy({ diff --git a/app/services/collaborationCommentResponseService.ts b/app/services/collaborationCommentResponseService.ts index 7e50e284..e2da246c 100644 --- a/app/services/collaborationCommentResponseService.ts +++ b/app/services/collaborationCommentResponseService.ts @@ -8,6 +8,10 @@ import { } from '@/repository/db/collaboration_comment_response'; import dbClient from '@/repository/db/prisma/prisma'; import { updateCollaborationCommentInCache } from '@/services/collaborationCommentService'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; + +const logger = getLogger(); type AddResponse = { user: UserSession; @@ -25,14 +29,34 @@ type DeleteResponse = { }; export const addCollaborationCommentResponse = async ({ user, response, comment }: AddResponse) => { - const createdResponse = await addCollaborationCommentResponseToDb(dbClient, user.providerId, response, comment.id); - const responseCount = await getCollaborationCommentResponseCount(dbClient, comment.id); - await updateCollaborationCommentInCache({ user, comment: { id: comment.id, responseCount } }); - return createdResponse; + try { + const createdResponse = await addCollaborationCommentResponseToDb(dbClient, user.providerId, response, comment.id); + const responseCount = await getCollaborationCommentResponseCount(dbClient, comment.id); + await updateCollaborationCommentInCache({ user, comment: { id: comment.id, responseCount } }); + return createdResponse; + } catch (err) { + const error: InnoPlatformError = dbError( + `Add collaboration comment response for comment with id: ${comment.id} by user ${user.providerId}`, + err as Error, + comment.id, + ); + logger.error(error); + throw err; + } }; export const deleteCollaborationCommentResponse = async ({ user, response }: DeleteResponse) => { - const deletedResponse = await deleteCollaborationCommentResponseInDb(dbClient, response.id); - const responseCount = await getCollaborationCommentResponseCount(dbClient, deletedResponse.commentId); - await updateCollaborationCommentInCache({ user, comment: { id: deletedResponse.commentId, responseCount } }); + try { + const deletedResponse = await deleteCollaborationCommentResponseInDb(dbClient, response.id); + const responseCount = await getCollaborationCommentResponseCount(dbClient, deletedResponse.commentId); + await updateCollaborationCommentInCache({ user, comment: { id: deletedResponse.commentId, responseCount } }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Delete collaboration comment response with id: ${response.id} by user ${user.providerId}`, + err as Error, + response.id, + ); + logger.error(error); + throw err; + } }; diff --git a/app/services/collaborationCommentService.ts b/app/services/collaborationCommentService.ts index 3d65a4bc..fc0fdc3d 100644 --- a/app/services/collaborationCommentService.ts +++ b/app/services/collaborationCommentService.ts @@ -15,6 +15,7 @@ import { getCollaborationCommentResponseCount } from '@/repository/db/collaborat import { getFollowedByForEntity } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; import { getReactionsForEntity } from '@/repository/db/reaction'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { mapCollaborationCommentToRedisNewsFeedEntry, @@ -63,35 +64,75 @@ type UpvoteCollaborationComment = { }; export const addCollaborationComment = async ({ user, comment }: AddCollaborationComment) => { - const createdComment = await addCollaborationCommentToDb( - dbClient, - comment.projectId, - comment.questionId, - user.providerId, - comment.comment, - comment.visible, - ); + try { + const createdComment = await addCollaborationCommentToDb( + dbClient, + comment.projectId, + comment.questionId, + user.providerId, + comment.comment, + comment.visible, + ); - await addCollaborationCommentToCache(user, createdComment); - return createdComment; + await addCollaborationCommentToCache(user, createdComment); + return createdComment; + } catch (err) { + const error: InnoPlatformError = dbError( + `Add collaboration comment to project with id: ${comment.projectId} and question with id: ${comment.questionId} by user ${user.providerId}`, + err as Error, + comment.projectId, + ); + logger.error(error); + throw err; + } }; export const deleteCollaborationComment = async (commentId: string) => { - await deleteCollaborationCommentInDb(dbClient, commentId); - await deleteCollaborationCommentInCache(commentId); + try { + await deleteCollaborationCommentInDb(dbClient, commentId); + await deleteCollaborationCommentInCache(commentId); + } catch (err) { + const error: InnoPlatformError = dbError( + `Delete collaboration comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } }; export const updateCollaborationComment = async ({ user, comment }: UpdateCollaborationComment) => { - const updatedComment = await updateCollaborationCommentInDb(dbClient, comment.id, comment.comment); - await updateCollaborationCommentInCache({ user, comment }); - return updatedComment; + try { + const updatedComment = await updateCollaborationCommentInDb(dbClient, comment.id, comment.comment); + await updateCollaborationCommentInCache({ user, comment }); + return updatedComment; + } catch (err) { + const error: InnoPlatformError = dbError( + `Update collaboration comment with id: ${comment.id}`, + err as Error, + comment.id, + ); + logger.error(error); + throw err; + } }; export const handleCollaborationCommentUpvote = async ({ user, commentId }: UpvoteCollaborationComment) => { - const updatedComment = await handleCollaborationCommentUpvoteInDb(dbClient, commentId, user.providerId); - - if (updatedComment) { - await updateCollaborationCommentInCache({ user, comment: updatedComment }); + try { + const updatedComment = await handleCollaborationCommentUpvoteInDb(dbClient, commentId, user.providerId); + + if (updatedComment) { + await updateCollaborationCommentInCache({ user, comment: updatedComment }); + } + } catch (err) { + const error: InnoPlatformError = dbError( + `Handle upvote for collaboration comment with id: ${commentId} by user ${user.providerId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; } }; diff --git a/app/services/commentService.ts b/app/services/commentService.ts index 504585a3..272ef7af 100644 --- a/app/services/commentService.ts +++ b/app/services/commentService.ts @@ -19,9 +19,13 @@ import { import dbClient from '@/repository/db/prisma/prisma'; import { updatePostInCache } from '@/services/postService'; import { updateProjectUpdateInCache } from '@/services/updateService'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; import { notifyFollowers } from '@/utils/notification/notificationSender'; import { mapToNewsComment, mapToPostComment } from '@/utils/requests/comments/mapping'; +const logger = getLogger(); + interface AddComment { author: UserSession; objectId: string; @@ -101,35 +105,90 @@ export const removeComment = async ({ user, commentId, commentType }: RemoveComm }; const updatePostCommentInCache = async (postComment: { postId: string }, author: UserSession) => { - const postResponseCount = await countPostResponses(dbClient, postComment.postId); - return await updatePostInCache({ post: { id: postComment.postId, responseCount: postResponseCount }, user: author }); + try { + const postResponseCount = await countPostResponses(dbClient, postComment.postId); + return await updatePostInCache({ + post: { id: postComment.postId, responseCount: postResponseCount }, + user: author, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Update post comment in cache with id: ${postComment.postId}`, + err as Error, + postComment.postId, + ); + logger.error(error); + throw err; + } }; const updateNewsCommentInCache = async (newsComment: { newsId: string }) => { - const newsResponseCount = await countNewsResponses(dbClient, newsComment.newsId); - await updateProjectUpdateInCache({ update: { id: newsComment.newsId, responseCount: newsResponseCount } }); + try { + const newsResponseCount = await countNewsResponses(dbClient, newsComment.newsId); + await updateProjectUpdateInCache({ update: { id: newsComment.newsId, responseCount: newsResponseCount } }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Update news comment in cache with id: ${newsComment.newsId}`, + err as Error, + newsComment.newsId, + ); + logger.error(error); + throw err; + } }; const removePostCommentInCache = async (postId: string, user: UserSession) => { - if (postId) { - const responseCount = await countPostResponses(dbClient, postId); - await updatePostInCache({ post: { id: postId, responseCount }, user }); + try { + if (postId) { + const responseCount = await countPostResponses(dbClient, postId); + await updatePostInCache({ post: { id: postId, responseCount }, user }); + } + } catch (err) { + const error: InnoPlatformError = dbError(`Remove post comment in cache with id: ${postId}`, err as Error, postId); + logger.error(error); + throw err; } }; const removeNewsCommenInCache = async (newsId: string) => { - if (newsId) { - const responseCount = await countNewsResponses(dbClient, newsId); - await updateProjectUpdateInCache({ update: { id: newsId, responseCount } }); + try { + if (newsId) { + const responseCount = await countNewsResponses(dbClient, newsId); + await updateProjectUpdateInCache({ update: { id: newsId, responseCount } }); + } + } catch (err) { + const error: InnoPlatformError = dbError(`Remove news comment in cache with id: ${newsId}`, err as Error, newsId); + logger.error(error); + throw err; } }; const notifyUpdateFollowers = async (updateId: string) => { - const follows = await getFollowers(dbClient, ObjectType.UPDATE, updateId); - await notifyFollowers(follows, 'update', 'Jemand hat auf einen Post, dem du folgst, kommentiert.', '/news'); + try { + const follows = await getFollowers(dbClient, ObjectType.UPDATE, updateId); + await notifyFollowers(follows, 'update', 'Jemand hat auf einen Post, dem du folgst, kommentiert.', '/news'); + } catch (err) { + const error: InnoPlatformError = dbError( + `Notify followers about updated update with id: ${updateId}`, + err as Error, + updateId, + ); + logger.error(error); + throw err; + } }; const notifyPostFollowers = async (postId: string) => { - const follows = await getFollowers(dbClient, ObjectType.POST, postId); - await notifyFollowers(follows, 'post', 'Jemand hat auf einen Post, dem du folgst, kommentiert.', '/news'); + try { + const follows = await getFollowers(dbClient, ObjectType.POST, postId); + await notifyFollowers(follows, 'post', 'Jemand hat auf einen Post, dem du folgst, kommentiert.', '/news'); + } catch (err) { + const error: InnoPlatformError = dbError( + `Notify followers about updated post with id: ${postId}`, + err as Error, + postId, + ); + logger.error(error); + throw err; + } }; diff --git a/app/services/followService.ts b/app/services/followService.ts index b6272001..7a0dd91b 100644 --- a/app/services/followService.ts +++ b/app/services/followService.ts @@ -1,6 +1,7 @@ import { Follow, ObjectType, User } from '@/common/types'; import { addFollowToDb, removeFollowFromDb } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { RedisNewsFeedEntry } from '@/utils/newsFeed/redis/models'; import { getRedisClient, RedisClient } from '@/utils/newsFeed/redis/redisClient'; @@ -34,15 +35,35 @@ type RemoveFollow = { }; export const addFollow = async ({ user, object }: AddFollow) => { - const createdObject = await addFollowToDb(dbClient, object.followedBy, object.objectType, object.objectId); - await addFollowToCache({ ...createdObject, objectType: createdObject.objectType as ObjectType }, user); - return object; + try { + const createdObject = await addFollowToDb(dbClient, object.followedBy, object.objectType, object.objectId); + await addFollowToCache({ ...createdObject, objectType: createdObject.objectType as ObjectType }, user); + return object; + } catch (err) { + const error: InnoPlatformError = dbError( + `Add follow for user ${user.providerId} on ${object.objectType} object with id: ${object.objectId}`, + err as Error, + object.objectId, + ); + logger.error(error); + throw err; + } }; export const removeFollow = async ({ user, object }: RemoveFollow) => { - const removedObject = await removeFollowFromDb(dbClient, object.followedBy, object.objectType, object.objectId); - await removeFollowFromCache({ ...removedObject, objectType: removedObject.objectType as ObjectType }, user); - return object; + try { + const removedObject = await removeFollowFromDb(dbClient, object.followedBy, object.objectType, object.objectId); + await removeFollowFromCache({ ...removedObject, objectType: removedObject.objectType as ObjectType }, user); + return object; + } catch (err) { + const error: InnoPlatformError = dbError( + `Remove follow for user ${user.providerId} on ${object.objectType} object with id: ${object.objectId}`, + err as Error, + object.objectId, + ); + logger.error(error); + throw err; + } }; export const addFollowToCache = async (follow: Follow, user: User) => { diff --git a/app/services/postService.ts b/app/services/postService.ts index fa94df5d..11b46584 100644 --- a/app/services/postService.ts +++ b/app/services/postService.ts @@ -14,6 +14,7 @@ import { } from '@/repository/db/posts'; import dbClient from '@/repository/db/prisma/prisma'; import { getReactionsForEntity } from '@/repository/db/reaction'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import { getUnixTimestamp } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapPostToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; @@ -38,29 +39,53 @@ type UpvotePost = { postId: string; user: UserSession }; type DeletePost = { postId: string }; export const addPost = async ({ content, user, anonymous }: AddPost) => { - const createdPost = await addPostToDb(dbClient, content, user.providerId, anonymous ?? false); - await addPostToCache({ ...createdPost, author: user, responseCount: 0 }); - return createdPost; + try { + const createdPost = await addPostToDb(dbClient, content, user.providerId, anonymous ?? false); + await addPostToCache({ ...createdPost, author: user, responseCount: 0 }); + return createdPost; + } catch (err) { + const error: InnoPlatformError = dbError(`Add post by user ${user.providerId}`, err as Error); + logger.error(error); + throw err; + } }; export const updatePost = async ({ postId, content, user }: UpdatePost) => { - const updatedPost = await updatePostInDb(dbClient, postId, content); - await updatePostInCache({ post: updatedPost, user }); - return updatedPost; + try { + const updatedPost = await updatePostInDb(dbClient, postId, content); + await updatePostInCache({ post: updatedPost, user }); + return updatedPost; + } catch (err) { + const error: InnoPlatformError = dbError(`Update post with id: ${postId} by user ${user.providerId}`, err as Error); + logger.error(error); + throw err; + } }; export const deletePost = async ({ postId }: DeletePost) => { - const deletedPost = await deletePostFromDb(dbClient, postId); - await deletePostFromCache(postId); - return deletedPost; + try { + const deletedPost = await deletePostFromDb(dbClient, postId); + await deletePostFromCache(postId); + return deletedPost; + } catch (err) { + const error: InnoPlatformError = dbError(`Delete post with id: ${postId}`, err as Error); + logger.error(error); + throw err; + } }; export const handleUpvotePost = async ({ postId, user }: UpvotePost) => { - const updatedPost = await handlePostUpvoteInDb(dbClient, postId, user.providerId); - if (updatedPost) { - await updatePostInCache({ post: updatedPost, user }); + try { + const updatedPost = await handlePostUpvoteInDb(dbClient, postId, user.providerId); + if (updatedPost) { + await updatePostInCache({ post: updatedPost, user }); + } + return updatedPost; + } catch (err) { + const error: InnoPlatformError = dbError(`Upvote post with id: ${postId} by user ${user.providerId}`, err as Error); + logger.error(error); + throw err; } - return updatedPost; }; export const addPostToCache = async (post: Post) => { diff --git a/app/services/reactionService.ts b/app/services/reactionService.ts index 09008bd4..4e0d8f02 100644 --- a/app/services/reactionService.ts +++ b/app/services/reactionService.ts @@ -6,11 +6,14 @@ import { getNewsFeedEntryForCollaborationQuestion } from '@/services/collaborati import { getNewsFeedEntryForProjectEvent } from '@/services/eventService'; import { getNewsFeedEntryForPost } from '@/services/postService'; import { getNewsFeedEntryForProjectUpdate } from '@/services/updateService'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { RedisReaction } from '@/utils/newsFeed/redis/models'; import { getRedisClient } from '@/utils/newsFeed/redis/redisClient'; import { saveNewsFeedEntry } from '@/utils/newsFeed/redis/redisService'; +import { redisError } from '../utils/errors'; + import { getNewsFeedEntryForProject } from './projectService'; import { getNewsFeedEntryForSurveyQuestion } from './surveyQuestionService'; @@ -37,57 +40,89 @@ type RemoveReaction = { }; export const addReaction = async ({ user, reaction }: AddReaction) => { - const createdReaction = await addReactionToDb( - dbClient, - reaction.reactedBy, - reaction.objectType, - reaction.objectId, - reaction.shortCode, - reaction.nativeSymbol, - ); - addReactionToCache({ ...createdReaction, objectType: createdReaction.objectType as ObjectType }, user); - return createdReaction; + try { + const createdReaction = await addReactionToDb( + dbClient, + reaction.reactedBy, + reaction.objectType, + reaction.objectId, + reaction.shortCode, + reaction.nativeSymbol, + ); + addReactionToCache({ ...createdReaction, objectType: createdReaction.objectType as ObjectType }, user); + return createdReaction; + } catch (err) { + const error: InnoPlatformError = dbError( + `Adding Reaction by user ${reaction.reactedBy} on type ${reaction.objectType} with id: ${reaction.objectId}`, + err as Error, + reaction.objectId, + ); + logger.error(error); + throw err; + } }; export const removeReaction = async ({ user, reaction }: RemoveReaction) => { - const removedReaction = await removeReactionFromDb( - dbClient, - reaction.reactedBy, - reaction.objectType, - reaction.objectId, - ); - removeReactionFromCache({ - reaction: { ...removedReaction, objectType: removedReaction.objectType as ObjectType }, - user, - }); - return removedReaction; + try { + const removedReaction = await removeReactionFromDb( + dbClient, + reaction.reactedBy, + reaction.objectType, + reaction.objectId, + ); + removeReactionFromCache({ + reaction: { ...removedReaction, objectType: removedReaction.objectType as ObjectType }, + user, + }); + return removedReaction; + } catch (err) { + const error: InnoPlatformError = dbError( + `Remove Reaction by user ${reaction.reactedBy} on type ${reaction.objectType} with id: ${reaction.objectId}`, + err as Error, + reaction.objectId, + ); + logger.error(error); + throw err; + } }; export const addReactionToCache = async (reaction: Reaction, user: User) => { - const redisClient = await getRedisClient(); - const newsFeedEntry = await getItemForEntityWithReactions(reaction, user); + try { + const redisClient = await getRedisClient(); + const newsFeedEntry = await getItemForEntityWithReactions(reaction, user); - if (!newsFeedEntry) { - return; - } + if (!newsFeedEntry) { + return; + } - const updatedReactions = newsFeedEntry.item.reactions.filter((r: RedisReaction) => r.reactedBy !== user.providerId); - updatedReactions.push(reaction); - newsFeedEntry.item.reactions = updatedReactions; - await saveNewsFeedEntry(redisClient, newsFeedEntry); + const updatedReactions = newsFeedEntry.item.reactions.filter((r: RedisReaction) => r.reactedBy !== user.providerId); + updatedReactions.push(reaction); + newsFeedEntry.item.reactions = updatedReactions; + await saveNewsFeedEntry(redisClient, newsFeedEntry); + } catch (err) { + const error: InnoPlatformError = redisError(`Adding reaction to cache by user ${user.providerId}`, err as Error); + logger.error(error); + throw err; + } }; export const removeReactionFromCache = async ({ reaction, user }: RemoveReaction) => { - const redisClient = await getRedisClient(); - const newsFeedEntry = await getItemForEntityWithReactions(reaction, user); + try { + const redisClient = await getRedisClient(); + const newsFeedEntry = await getItemForEntityWithReactions(reaction, user); - if (!newsFeedEntry) { - return; - } + if (!newsFeedEntry) { + return; + } - const updatedReactions = newsFeedEntry.item.reactions.filter((r: RedisReaction) => r.reactedBy !== user.providerId); - newsFeedEntry.item.reactions = updatedReactions; - await saveNewsFeedEntry(redisClient, newsFeedEntry); + const updatedReactions = newsFeedEntry.item.reactions.filter((r: RedisReaction) => r.reactedBy !== user.providerId); + newsFeedEntry.item.reactions = updatedReactions; + await saveNewsFeedEntry(redisClient, newsFeedEntry); + } catch (err) { + const error: InnoPlatformError = redisError(`Removing reaction to cache by user ${user.providerId}`, err as Error); + logger.error(error); + throw err; + } }; export const getItemForEntityWithReactions = async ( diff --git a/app/services/surveyQuestionService.ts b/app/services/surveyQuestionService.ts index 84a8fc96..9b846310 100644 --- a/app/services/surveyQuestionService.ts +++ b/app/services/surveyQuestionService.ts @@ -5,6 +5,7 @@ import { getFollowedByForEntity, getFollowers } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; import { getReactionsForEntity } from '@/repository/db/reaction'; import { handleSurveyQuestionVoteInDb } from '@/repository/db/survey_votes'; +import { dbError, InnoPlatformError, redisError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { mapSurveyQuestionToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; import { NewsType, RedisSurveyQuestion, RedisSurveyVote } from '@/utils/newsFeed/redis/models'; @@ -27,44 +28,60 @@ export const handleSurveyQuestionVote = async (surveyQuestion: { providerId: string; vote: string; }) => { - const { operation, vote: surveyVote } = await handleSurveyQuestionVoteInDb( - dbClient, - surveyQuestion.projectId, - surveyQuestion.surveyQuestionId, - surveyQuestion.providerId, - surveyQuestion.vote, - ); - - handleSurveyVoteInCache(surveyVote); - - if (operation !== 'deleted') { - notifySurveyFollowers(surveyQuestion.surveyQuestionId); - } + try { + const { operation, vote: surveyVote } = await handleSurveyQuestionVoteInDb( + dbClient, + surveyQuestion.projectId, + surveyQuestion.surveyQuestionId, + surveyQuestion.providerId, + surveyQuestion.vote, + ); + + handleSurveyVoteInCache(surveyVote); + + if (operation !== 'deleted') { + notifySurveyFollowers(surveyQuestion.surveyQuestionId); + } - return surveyVote; + return surveyVote; + } catch (err) { + const error: InnoPlatformError = dbError( + `Handle vote for survey question with id: ${surveyQuestion.surveyQuestionId} by user ${surveyQuestion.providerId}`, + err as Error, + surveyQuestion.surveyQuestionId, + ); + logger.error(error); + throw error; + } }; const handleSurveyVoteInCache = async (surveyQuestion: PrismaSurveyVote) => { - const { surveyQuestionId, votedBy, vote, id } = surveyQuestion; - const redisClient = await getRedisClient(); - const newsFeedEntry = await getNewsFeedEntryForSurveyQuestion(redisClient, { - surveyId: surveyQuestionId, - }); - - if (!newsFeedEntry) { - return; - } - const survey = newsFeedEntry.item as RedisSurveyQuestion; - await performRedisTransaction(redisClient, async (transactionClient) => { - const redisVote = survey.votes.find((vote) => vote.votedBy === votedBy); - if (redisVote) { - await removeVoteFromCache(transactionClient, survey, redisVote); + try { + const { surveyQuestionId, votedBy, vote, id } = surveyQuestion; + const redisClient = await getRedisClient(); + const newsFeedEntry = await getNewsFeedEntryForSurveyQuestion(redisClient, { + surveyId: surveyQuestionId, + }); + + if (!newsFeedEntry) { + return; } - if (redisVote?.vote !== vote) { - const newVote = { id, votedBy, vote }; - await addVoteToCache(transactionClient, survey, newVote); - } - }); + const survey = newsFeedEntry.item as RedisSurveyQuestion; + await performRedisTransaction(redisClient, async (transactionClient) => { + const redisVote = survey.votes.find((vote) => vote.votedBy === votedBy); + if (redisVote) { + await removeVoteFromCache(transactionClient, survey, redisVote); + } + if (redisVote?.vote !== vote) { + const newVote = { id, votedBy, vote }; + await addVoteToCache(transactionClient, survey, newVote); + } + }); + } catch (err) { + const error: InnoPlatformError = redisError(`Handle survey vote in cache`, err as Error); + logger.error(error); + throw error; + } }; const addVoteToCache = async ( @@ -116,13 +133,23 @@ export const createNewsFeedEntryForSurveyQuestion = async (survey: SurveyQuestio }; const notifySurveyFollowers = async (surveyQuestionId: string) => { - const followers = await getFollowers(dbClient, ObjectType.SURVEY_QUESTION, surveyQuestionId); - await notifyFollowers( - followers, - 'survey-question', - 'Auf eine Umfrage, der du folgst, wurde eine neue Stimme abgegeben.', - '/news', - ); + try { + const followers = await getFollowers(dbClient, ObjectType.SURVEY_QUESTION, surveyQuestionId); + await notifyFollowers( + followers, + 'survey-question', + 'Auf eine Umfrage, der du folgst, wurde eine neue Stimme abgegeben.', + '/news', + ); + } catch (err) { + const error: InnoPlatformError = dbError( + `Failed to notify followers about updated survey question with id: ${surveyQuestionId}`, + err as Error, + surveyQuestionId, + ); + logger.error(error); + throw err; + } }; const getRedisKey = (id: string) => `${NewsType.SURVEY_QUESTION}:${id}`; diff --git a/app/services/updateService.ts b/app/services/updateService.ts index 46b0df82..8494f760 100644 --- a/app/services/updateService.ts +++ b/app/services/updateService.ts @@ -5,6 +5,7 @@ import { getFollowedByForEntity } from '@/repository/db/follow'; import { countNewsResponses } from '@/repository/db/news_comment'; import dbClient from '@/repository/db/prisma/prisma'; import { getReactionsForEntity } from '@/repository/db/reaction'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import { getUnixTimestamp } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { @@ -102,11 +103,21 @@ const createNewsFeedEntryForProjectUpdateById = async (updateId: string) => { }; export const createNewsFeedEntryForProjectUpdate = async (update: ProjectUpdate) => { - const updateReactions = await getReactionsForEntity(dbClient, ObjectType.UPDATE, update.id); - const projectFollowedBy = await getFollowedByForEntity(dbClient, ObjectType.PROJECT, update.projectId); - const mappedUpdateFollowedBy = await mapToRedisUsers(projectFollowedBy); - const responseCount = await countNewsResponses(dbClient, update.id); - return mapUpdateToRedisNewsFeedEntry(update, updateReactions, mappedUpdateFollowedBy, responseCount); + try { + const updateReactions = await getReactionsForEntity(dbClient, ObjectType.UPDATE, update.id); + const projectFollowedBy = await getFollowedByForEntity(dbClient, ObjectType.PROJECT, update.projectId); + const mappedUpdateFollowedBy = await mapToRedisUsers(projectFollowedBy); + const responseCount = await countNewsResponses(dbClient, update.id); + return mapUpdateToRedisNewsFeedEntry(update, updateReactions, mappedUpdateFollowedBy, responseCount); + } catch (err) { + const error: InnoPlatformError = dbError( + `Creating news feed entry for project update with id: ${update.projectId}`, + err as Error, + update.projectId, + ); + logger.error(error); + throw err; + } }; const getRedisKey = (updateId: string) => `${NewsType.UPDATE}:${updateId}`; diff --git a/app/utils/logger.ts b/app/utils/logger.ts index e00f8a3d..2b10dba6 100644 --- a/app/utils/logger.ts +++ b/app/utils/logger.ts @@ -29,7 +29,7 @@ const getErrorLog = (info: winston.Logform.TransformableInfo) => { }; const createdLogger = winston.createLogger({ - level: 'info', + level: `${process.env.LOG_LEVEL}`, format: combine( colorize(), timestamp({ diff --git a/app/utils/newsFeed/newsFeedSync.ts b/app/utils/newsFeed/newsFeedSync.ts index d3891a0e..3d52e70a 100644 --- a/app/utils/newsFeed/newsFeedSync.ts +++ b/app/utils/newsFeed/newsFeedSync.ts @@ -153,6 +153,9 @@ const removeKeysAndSaveNewEntriesAsTransaction = async ( const aggregatePosts = async ({ from }: { from: Date }): Promise => { // posts fetched from prisma, hence no pagination required const posts = await getPostsStartingFrom(dbClient, from); + if (!posts || posts.length === 0) { + logger.warn('No posts found to sync'); + } const mapEntries = posts.map(async (post) => createNewsFeedEntryForPost(post)); const newsFeedEntries = await getPromiseResults(mapEntries); return newsFeedEntries.filter((entry): entry is RedisNewsFeedEntry => entry !== null); @@ -161,6 +164,9 @@ const aggregatePosts = async ({ from }: { from: Date }): Promise => { // collaboration comments fetched from prisma, hence no pagination required const comments = await getCollaborationCommentStartingFrom(dbClient, from); + if (!comments || comments.length === 0) { + logger.warn('No collaboration comments found to sync'); + } const mapToNewsFeedEntries = comments.map(async (comment) => createNewsFeedEntryForComment(comment)); const newsFeedEntries = await getPromiseResults(mapToNewsFeedEntries); return newsFeedEntries.filter((entry): entry is RedisNewsFeedEntry => entry !== null); diff --git a/app/utils/notification/notificationSender.ts b/app/utils/notification/notificationSender.ts index 930bbf40..273998bf 100644 --- a/app/utils/notification/notificationSender.ts +++ b/app/utils/notification/notificationSender.ts @@ -8,6 +8,8 @@ import { getPromiseResults } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { sendPushNotification } from '@/utils/notification/pushNotification'; +import { dbError, InnoPlatformError } from '../errors'; + const logger = getLogger(); export type NotificationTopic = @@ -71,15 +73,24 @@ const createNotificationForFollow = async ( text: string, url: string, ): Promise => { - const subscriptions = await getPushSubscriptionsForUser(dbClient, follow.followedBy); + try { + const subscriptions = await getPushSubscriptionsForUser(dbClient, follow.followedBy); - return { - subscriptions: subscriptions, - userId: follow.followedBy, - notification: { - topic, - body: text, - url, - }, - }; + return { + subscriptions: subscriptions, + userId: follow.followedBy, + notification: { + topic, + body: text, + url, + }, + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Create notification for follow by user ${follow.followedBy}`, + err as Error, + ); + logger.error(error); + throw err; + } }; diff --git a/app/utils/notification/pushNotification.ts b/app/utils/notification/pushNotification.ts index d0a46755..324ffbb3 100644 --- a/app/utils/notification/pushNotification.ts +++ b/app/utils/notification/pushNotification.ts @@ -9,6 +9,7 @@ import { serverConfig } from '@/config/server'; import { createPushSubscriptionForUser, removePushSubscriptionForUser } from '@/repository/db/push_subscriptions'; import { PushNotification } from '@/types/notification'; import { withAuth } from '@/utils/auth'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import dbClient from '../../repository/db/prisma/prisma'; import getLogger from '../logger'; @@ -50,19 +51,51 @@ export const sendPushNotification = async (subscription: WebPushSubscription, pu } } }; -const removeExpiredPushSubscriptions = async (userId: string, subscription: webpush.PushSubscription) => - await removePushSubscriptionForUser(dbClient, userId, subscription); + +const removeExpiredPushSubscriptions = async (userId: string, subscription: webpush.PushSubscription) => { + try { + await removePushSubscriptionForUser(dbClient, userId, subscription); + } catch (err) { + const error: InnoPlatformError = dbError( + `Remove expired push subscription for user ${userId}`, + err as Error, + userId, + ); + logger.error(error); + throw err; + } +}; export const subscribeToWebPush = withAuth( async (user: UserSession, body: { subscription: string; browserFingerprint: string }) => { - const subscription = JSON.parse(body.subscription); - await createPushSubscriptionForUser(dbClient, user.providerId, body.browserFingerprint, subscription); - return { status: 200 }; + try { + const subscription = JSON.parse(body.subscription); + await createPushSubscriptionForUser(dbClient, user.providerId, body.browserFingerprint, subscription); + return { status: 200 }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Create push subscription for user ${user.providerId}`, + err as Error, + user.providerId, + ); + logger.error(error); + throw err; + } }, ); export const unsubscribeFromWebPush = withAuth(async (user: UserSession, body: string) => { - const subscription: webpush.PushSubscription = JSON.parse(body); - await removePushSubscriptionForUser(dbClient, user.providerId, subscription); - return { status: 200 }; + try { + const subscription: webpush.PushSubscription = JSON.parse(body); + await removePushSubscriptionForUser(dbClient, user.providerId, subscription); + return { status: 200 }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Remove push subscription for user ${user.providerId}`, + err as Error, + user.providerId, + ); + logger.error(error); + throw err; + } }); diff --git a/app/utils/requests/collaborationComments/requests.ts b/app/utils/requests/collaborationComments/requests.ts index 970e0cec..35a7b45c 100644 --- a/app/utils/requests/collaborationComments/requests.ts +++ b/app/utils/requests/collaborationComments/requests.ts @@ -106,17 +106,27 @@ export const getProjectCollaborationCommentResponses = withAuth( export const isProjectCollaborationCommentResponseUpvotedBy = withAuth( async (user: UserSession, body: { responseId: string }) => { - const validatedParams = validateParams(collaborationCommentResponseUpvotedBySchema, body); - if (validatedParams.status === StatusCodes.OK) { - const result = await getCollaborationCommentResponseUpvotedBy(dbClient, body.responseId, user.providerId); + try { + const validatedParams = validateParams(collaborationCommentResponseUpvotedBySchema, body); + if (validatedParams.status === StatusCodes.OK) { + const result = await getCollaborationCommentResponseUpvotedBy(dbClient, body.responseId, user.providerId); + return { + status: StatusCodes.OK, + data: result.length > 0, + }; + } return { - status: StatusCodes.OK, - data: result.length > 0, + status: validatedParams.status, + errors: validatedParams.errors, }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Checking if user ${user.providerId} has upvoted response ${body.responseId}`, + err as Error, + user.providerId, + ); + logger.error(error); + throw err; } - return { - status: validatedParams.status, - errors: validatedParams.errors, - }; }, ); diff --git a/app/utils/requests/collaborationQuestions/requests.ts b/app/utils/requests/collaborationQuestions/requests.ts index ad80f73f..cd2039c0 100644 --- a/app/utils/requests/collaborationQuestions/requests.ts +++ b/app/utils/requests/collaborationQuestions/requests.ts @@ -137,7 +137,7 @@ export const isCollaborationCommentUpvotedByUser = withAuth(async (user: UserSes return { status: StatusCodes.OK, data: isUpvotedBy }; } catch (err) { const error: InnoPlatformError = strapiError( - `Find upvote for comment${body.commentId} by user ${user.providerId}`, + `Find upvote for comment with id: ${body.commentId} by user ${user.providerId}`, err as RequestError, body.commentId, ); diff --git a/app/utils/requests/comments/requests.ts b/app/utils/requests/comments/requests.ts index ded5eb93..6ecd4ac2 100644 --- a/app/utils/requests/comments/requests.ts +++ b/app/utils/requests/comments/requests.ts @@ -4,22 +4,42 @@ import { CommentWithResponses, CommonCommentProps } from '@/common/types'; import { getNewsCommentsByUpdateId } from '@/repository/db/news_comment'; import { getNewsCommentsByPostId } from '@/repository/db/post_comment'; import dbClient from '@/repository/db/prisma/prisma'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; import { mapToNewsComment, mapToPostComment } from '@/utils/requests/comments/mapping'; +const logger = getLogger(); + export const getNewsCommentProjectUpdateId = async (updateId: string) => { - const dbComments = await getNewsCommentsByUpdateId(dbClient, updateId); - const mapComments = dbComments.map(mapToNewsComment); - const comments = await Promise.all(mapComments); - const commensWithResponses = setResponses(comments); - return commensWithResponses; + try { + const dbComments = await getNewsCommentsByUpdateId(dbClient, updateId); + const mapComments = dbComments.map(mapToNewsComment); + const comments = await Promise.all(mapComments); + const commensWithResponses = setResponses(comments); + return commensWithResponses; + } catch (err) { + const error: InnoPlatformError = dbError( + `Getting news comments for project update with id: ${updateId}`, + err as Error, + updateId, + ); + logger.error(error); + throw err; + } }; export const getPostCommentByPostId = async (postId: string) => { - const dbComments = await getNewsCommentsByPostId(dbClient, postId); - const mapComments = dbComments.map(mapToPostComment); - const comments = await Promise.all(mapComments); - const commensWithResponses = setResponses(comments); - return commensWithResponses; + try { + const dbComments = await getNewsCommentsByPostId(dbClient, postId); + const mapComments = dbComments.map(mapToPostComment); + const comments = await Promise.all(mapComments); + const commensWithResponses = setResponses(comments); + return commensWithResponses; + } catch (err) { + const error: InnoPlatformError = dbError(`Getting news comments for post with id: ${postId}`, err as Error, postId); + logger.error(error); + throw err; + } }; const setResponses = (comments: CommonCommentProps[]) => { diff --git a/app/utils/requests/events/requests.ts b/app/utils/requests/events/requests.ts index 7bf39cff..84b3cd9f 100644 --- a/app/utils/requests/events/requests.ts +++ b/app/utils/requests/events/requests.ts @@ -8,7 +8,7 @@ import { RequestError } from '@/entities/error'; import dbClient from '@/repository/db/prisma/prisma'; import { countNumberOfReactions, getReactionsForEntity } from '@/repository/db/reaction'; import { withAuth } from '@/utils/auth'; -import { strapiError } from '@/utils/errors'; +import { dbError, InnoPlatformError, strapiError } from '@/utils/errors'; import { getPromiseResults } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapToEvent, mapToEvents } from '@/utils/requests/events/mappings'; @@ -127,24 +127,34 @@ export async function getEventsWithAdditionalData(events: Event[]) { export async function getEventWithAdditionalData( item: Event | EventWithAdditionalData, ): Promise { - const { data: reactionForUser } = await findReactionByUser({ objectType: ObjectType.EVENT, objectId: item.id }); - const reactionCountResult = await countNumberOfReactions(dbClient, ObjectType.EVENT, item.id); - - const reactionCount = reactionCountResult.map((r) => ({ - count: r._count.shortCode, - emoji: { - shortCode: r.shortCode, - nativeSymbol: r.nativeSymbol, - }, - })); - - return { - ...item, - reactionForUser: reactionForUser - ? { ...reactionForUser, objectType: reactionForUser.objectType as ObjectType } - : null, - reactionCount, - }; + try { + const { data: reactionForUser } = await findReactionByUser({ objectType: ObjectType.EVENT, objectId: item.id }); + const reactionCountResult = await countNumberOfReactions(dbClient, ObjectType.EVENT, item.id); + + const reactionCount = reactionCountResult.map((r) => ({ + count: r._count.shortCode, + emoji: { + shortCode: r.shortCode, + nativeSymbol: r.nativeSymbol, + }, + })); + + return { + ...item, + reactionForUser: reactionForUser + ? { ...reactionForUser, objectType: reactionForUser.objectType as ObjectType } + : null, + reactionCount, + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Getting additional data for event with id: ${item.id}`, + err as Error, + item.id, + ); + logger.error(error); + throw err; + } } export const countFutureEventsForProject = withAuth(async (user: UserSession, body: { projectId: string }) => { diff --git a/app/utils/requests/statistics/requests.ts b/app/utils/requests/statistics/requests.ts index a7de6043..3a49c142 100644 --- a/app/utils/requests/statistics/requests.ts +++ b/app/utils/requests/statistics/requests.ts @@ -3,73 +3,95 @@ import { StatusCodes } from 'http-status-codes'; import { json2csv } from 'json-2-csv'; +import { ObjectType } from '@/common/types'; import { serverConfig } from '@/config/server'; import { getCollaborationQuestionComments } from '@/repository/db/collaboration_comment'; import dbClient from '@/repository/db/prisma/prisma'; import { getTotalComments, getTotalProjectLikes, getTotalReactions } from '@/repository/db/statistics'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { getPlatformFeedbackCollaborationQuestion } from '../collaborationQuestions/requests'; -import { ObjectType } from '@/common/types'; const logger = getLogger(); const add = (acc: number, el: { _count: number }) => acc + el._count; export const getFeedback = async (body: { username: string; password: string }) => { - const { username, password } = body; - if (!checkCredentials(username, password)) { - logger.error('Invalid credentials for downloading feedback'); + try { + const { username, password } = body; + if (!checkCredentials(username, password)) { + logger.error('Invalid credentials for downloading feedback'); + return { + status: StatusCodes.UNAUTHORIZED, + data: 'Invalid credentials', + }; + } + const response = await getPlatformFeedbackCollaborationQuestion(); + + if (!response) { + return { + status: StatusCodes.BAD_REQUEST, + data: 'Could not find project collaboration question', + }; + } + + const feedback = await getCollaborationQuestionComments( + dbClient, + response.projectId, + response.collaborationQuestionId, + ); + return { - status: StatusCodes.UNAUTHORIZED, - data: 'Invalid credentials', + status: StatusCodes.OK, + data: json2csv(feedback), }; - } - const response = await getPlatformFeedbackCollaborationQuestion(); - - if (!response) { + } catch (err) { + logger.error('Error getting feedback', err); return { - status: StatusCodes.BAD_REQUEST, - data: 'Could not find project collaboration question', + status: StatusCodes.INTERNAL_SERVER_ERROR, + data: 'Error getting feedback', }; } - - const feedback = await getCollaborationQuestionComments( - dbClient, - response.projectId, - response.collaborationQuestionId, - ); - - return { - status: StatusCodes.OK, - data: json2csv(feedback), - }; }; export const getOverallStats = async () => { //returns overall stats for the platform such as: total reactions, total comments, total users, total projects - const totalReactionsForNews = await getTotalReactions(dbClient, ObjectType.UPDATE); - const totalReactionsForEvents = await getTotalReactions(dbClient, ObjectType.EVENT); - const totalComments = await getTotalComments(dbClient); - const totalProjectLikes = await getTotalProjectLikes(dbClient); + try { + const totalReactionsForNews = await getTotalReactions(dbClient, ObjectType.UPDATE); + const totalReactionsForEvents = await getTotalReactions(dbClient, ObjectType.EVENT); + const totalComments = await getTotalComments(dbClient); + const totalProjectLikes = await getTotalProjectLikes(dbClient); - return { - totalReactionsForNews: totalReactionsForNews.reduce(add, 0), - totalReactionsForEvents: totalReactionsForEvents.reduce(add, 0), - totalComments: totalComments.reduce(add, 0), - totalProjectLikes: totalProjectLikes.reduce(add, 0), - }; + return { + totalReactionsForNews: totalReactionsForNews.reduce(add, 0), + totalReactionsForEvents: totalReactionsForEvents.reduce(add, 0), + totalComments: totalComments.reduce(add, 0), + totalProjectLikes: totalProjectLikes.reduce(add, 0), + }; + } catch (err) { + const error: InnoPlatformError = dbError(`Getting overall stats`, err as Error); + logger.error(error); + throw err; + } }; export const getProjectsStats = async (projectId: string) => { - const projectComments = await getTotalComments(dbClient, projectId); - const projectLikes = await getTotalProjectLikes(dbClient, projectId); + try { + const projectComments = await getTotalComments(dbClient, projectId); + const projectLikes = await getTotalProjectLikes(dbClient, projectId); - return { - projectId, - projectComments, - projectLikes, - }; + return { + projectId, + projectComments, + projectLikes, + }; + } catch (err) { + const error: InnoPlatformError = dbError(`Getting stats for project with id: ${projectId} `, err as Error); + logger.error(error); + throw err; + } }; + export const checkCredentials = async (username: string, password: string) => { const [AUTH_USER, AUTH_PASS] = serverConfig.HTTP_BASIC_AUTH.split(':'); return username == AUTH_USER && password == AUTH_PASS; diff --git a/app/utils/requests/updates/requests.ts b/app/utils/requests/updates/requests.ts index 90862038..a1bc4530 100644 --- a/app/utils/requests/updates/requests.ts +++ b/app/utils/requests/updates/requests.ts @@ -15,10 +15,11 @@ import { import { handleProjectUpdatesSchema } from '@/components/updates/validationSchema'; import { RequestError } from '@/entities/error'; import { getFollowedByForEntity } from '@/repository/db/follow'; +import { getNewsCommentsStartingFrom } from '@/repository/db/news_comment'; import dbClient from '@/repository/db/prisma/prisma'; import { countNumberOfReactions, findReaction, getReactionsForEntity } from '@/repository/db/reaction'; import { withAuth } from '@/utils/auth'; -import { InnoPlatformError, strapiError } from '@/utils/errors'; +import { dbError, InnoPlatformError, strapiError } from '@/utils/errors'; import { getPromiseResults, getUniqueValues, getUnixTimestamp } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapReaction } from '@/utils/newsFeed/redis/redisMappings'; @@ -43,7 +44,6 @@ import { GetUpdatesStartingFromQuery, } from '@/utils/requests/updates/queries'; import { validateParams } from '@/utils/validationHelper'; -import { getNewsCommentsStartingFrom } from '@/repository/db/news_comment'; const logger = getLogger(); @@ -235,51 +235,71 @@ export const getUpdateWithReactions = withAuth(async (user: UserSession, body: { export async function getUpdateWithAdditionalData( update: ProjectUpdate | ProjectUpdateWithAdditionalData, ): Promise { - const { data: reactionForUser } = await findReactionByUser({ objectType: ObjectType.UPDATE, objectId: update.id }); - const reactionCountResult = await countNumberOfReactions(dbClient, ObjectType.UPDATE, update.id); - const { data: followedByUser } = await isProjectFollowedByUser({ projectId: update.projectId }); - - const reactionCount = reactionCountResult.map((r) => ({ - count: r._count.shortCode, - emoji: { - shortCode: r.shortCode, - nativeSymbol: r.nativeSymbol, - }, - })); - - return { - ...update, - reactionForUser: reactionForUser ? mapReaction(reactionForUser) : undefined, - followedByUser, - reactionCount, - }; -} - -export async function getNewsFeedEntry(entry: any): Promise { - const { data: reactionForUser } = await findReactionByUser({ - objectType: ObjectType.UPDATE, - objectId: entry.item.id, - }); - const reactionCountResult = await countNumberOfReactions(dbClient, ObjectType.UPDATE, entry.item.id); - const { data: followedByUser } = await isProjectFollowedByUser({ projectId: entry.item.projectId }); - - const reactionCount = reactionCountResult.map((r) => ({ - count: r._count.shortCode, - emoji: { - shortCode: r.shortCode, - nativeSymbol: r.nativeSymbol, - }, - })); - - return { - ...entry, - item: { - ...entry.item, + try { + const { data: reactionForUser } = await findReactionByUser({ objectType: ObjectType.UPDATE, objectId: update.id }); + const reactionCountResult = await countNumberOfReactions(dbClient, ObjectType.UPDATE, update.id); + const { data: followedByUser } = await isProjectFollowedByUser({ projectId: update.projectId }); + + const reactionCount = reactionCountResult.map((r) => ({ + count: r._count.shortCode, + emoji: { + shortCode: r.shortCode, + nativeSymbol: r.nativeSymbol, + }, + })); + + return { + ...update, reactionForUser: reactionForUser ? mapReaction(reactionForUser) : undefined, followedByUser, reactionCount, - }, - }; + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Getting additional data for update with id: ${update.id}`, + err as Error, + update.id, + ); + logger.error(error); + throw err; + } +} + +export async function getNewsFeedEntry(entry: any): Promise { + try { + const { data: reactionForUser } = await findReactionByUser({ + objectType: ObjectType.UPDATE, + objectId: entry.item.id, + }); + const reactionCountResult = await countNumberOfReactions(dbClient, ObjectType.UPDATE, entry.item.id); + const { data: followedByUser } = await isProjectFollowedByUser({ projectId: entry.item.projectId }); + + const reactionCount = reactionCountResult.map((r) => ({ + count: r._count.shortCode, + emoji: { + shortCode: r.shortCode, + nativeSymbol: r.nativeSymbol, + }, + })); + + return { + ...entry, + item: { + ...entry.item, + reactionForUser: reactionForUser ? mapReaction(reactionForUser) : undefined, + followedByUser, + reactionCount, + }, + }; + } catch (err) { + const error: InnoPlatformError = dbError( + `Getting news feed entry for update with id: ${entry.item.id}`, + err as Error, + entry.item.id, + ); + logger.error(error); + throw err; + } } export const findReactionByUser = withAuth( @@ -348,7 +368,7 @@ export async function getProjectUpdatesStartingFrom({ from, page, pageSize }: St return updates; } catch (err) { - const error = strapiError(`Getting updates starting from ${from}`, err as RequestError); + const error = strapiError(`Getting updates on news comments starting from ${from}`, err as RequestError); logger.error(error); } } diff --git a/app/utils/strapiEvents/entityLifecycles/collaborationQuestionLifecycle.ts b/app/utils/strapiEvents/entityLifecycles/collaborationQuestionLifecycle.ts index abcafb4f..e4d507f0 100644 --- a/app/utils/strapiEvents/entityLifecycles/collaborationQuestionLifecycle.ts +++ b/app/utils/strapiEvents/entityLifecycles/collaborationQuestionLifecycle.ts @@ -2,6 +2,7 @@ import { ObjectType } from '@/common/types'; import { getFollowedByForEntity, getProjectFollowers } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; import { getPushSubscriptionsForUser } from '@/repository/db/push_subscriptions'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { mapCollaborationQuestionToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; import { getRedisClient } from '@/utils/newsFeed/redis/redisClient'; @@ -40,21 +41,31 @@ export class CollaborationQuestionLifecycle extends StrapiEntityLifecycle { }; private buildNotifications = async (questionId: string): Promise => { - const followers = await getProjectFollowers(dbClient, questionId); - // Ignore possible erros... - return await Promise.all( - followers.map(async ({ followedBy }) => { - return { - subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), - userId: followedBy, - notification: { - topic: 'collaboration-question', - body: 'Eine neue Frage wurde kürzlich zu einem Projekt, dem du folgst, hinzugefügt.', - url: '/', - }, - }; - }), - ); + try { + const followers = await getProjectFollowers(dbClient, questionId); + // Ignore possible erros... + return await Promise.all( + followers.map(async ({ followedBy }) => { + return { + subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), + userId: followedBy, + notification: { + topic: 'collaboration-question', + body: 'Eine neue Frage wurde kürzlich zu einem Projekt, dem du folgst, hinzugefügt.', + url: '/', + }, + }; + }), + ); + } catch (err) { + const error: InnoPlatformError = dbError( + `Build notifications for collaboration question with id: ${questionId}`, + err as Error, + questionId, + ); + logger.error(error); + throw err; + } }; private saveQuestionToCache = async ( diff --git a/app/utils/strapiEvents/entityLifecycles/eventLifecycle.ts b/app/utils/strapiEvents/entityLifecycles/eventLifecycle.ts index 8de87325..6d25f2be 100644 --- a/app/utils/strapiEvents/entityLifecycles/eventLifecycle.ts +++ b/app/utils/strapiEvents/entityLifecycles/eventLifecycle.ts @@ -2,6 +2,7 @@ import { ObjectType } from '@/common/types'; import { getFollowedByForEntity } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; import { getAllPushSubscriptions } from '@/repository/db/push_subscriptions'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import { mapObjectWithReactions } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapEventToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; @@ -42,18 +43,24 @@ export class EventLifecycle extends StrapiEntityLifecycle { }; private buildNotifications = async (): Promise => { - const allSubscriptions = await getAllPushSubscriptions(dbClient); - return allSubscriptions.map((subscription) => { - return { - subscriptions: subscription.subscriptions, - userId: subscription.userId, - notification: { - topic: 'event', - body: `Ein neues Event wurde kürzlich hinzugefügt.`, - url: '/', - }, - }; - }); + try { + const allSubscriptions = await getAllPushSubscriptions(dbClient); + return allSubscriptions.map((subscription) => { + return { + subscriptions: subscription.subscriptions, + userId: subscription.userId, + notification: { + topic: 'event', + body: `Ein neues Event wurde kürzlich hinzugefügt.`, + url: '/', + }, + }; + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Build notifications`, err as Error); + logger.error(error); + throw err; + } }; private saveEventToCache = async (eventId: string, options: { createIfNew: boolean } = { createIfNew: true }) => { diff --git a/app/utils/strapiEvents/entityLifecycles/opportunityLifecycle.ts b/app/utils/strapiEvents/entityLifecycles/opportunityLifecycle.ts index 90861907..7eb1b697 100644 --- a/app/utils/strapiEvents/entityLifecycles/opportunityLifecycle.ts +++ b/app/utils/strapiEvents/entityLifecycles/opportunityLifecycle.ts @@ -2,6 +2,7 @@ import { getProjectFollowers } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; import { getPushSubscriptionsForUser } from '@/repository/db/push_subscriptions'; import { createProjectUpdate } from '@/services/updateService'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { NotificationRequest, sendPushNotifications } from '@/utils/notification/notificationSender'; import { getBasicOpportunityById } from '@/utils/requests/opportunities/requests'; @@ -22,21 +23,31 @@ export class OpportunityLifecycle extends StrapiEntityLifecycle { }; private buildNotifications = async (opportunityId: string): Promise => { - const followers = await getProjectFollowers(dbClient, opportunityId); - // Ignore possible erros... - return await Promise.all( - followers.map(async ({ followedBy }) => { - return { - subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), - userId: followedBy, - notification: { - topic: 'opportunity', - body: 'Eine neue Opportunity wurde kürzlich zu einem Projekt, dem du folgst, hinzugefügt.', - url: '/', - }, - }; - }), - ); + try { + const followers = await getProjectFollowers(dbClient, opportunityId); + // Ignore possible erros... + return await Promise.all( + followers.map(async ({ followedBy }) => { + return { + subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), + userId: followedBy, + notification: { + topic: 'opportunity', + body: 'Eine neue Opportunity wurde kürzlich zu einem Projekt, dem du folgst, hinzugefügt.', + url: '/', + }, + }; + }), + ); + } catch (err) { + const error: InnoPlatformError = dbError( + `Build notifications for followers of opportunity with id: ${opportunityId}`, + err as Error, + opportunityId, + ); + logger.error(error); + throw err; + } }; private createUpdateForNewOpportunity = async (opportunityId: string) => { diff --git a/app/utils/strapiEvents/entityLifecycles/projectLifecycle.ts b/app/utils/strapiEvents/entityLifecycles/projectLifecycle.ts index 41b15d6c..7e0368b0 100644 --- a/app/utils/strapiEvents/entityLifecycles/projectLifecycle.ts +++ b/app/utils/strapiEvents/entityLifecycles/projectLifecycle.ts @@ -2,6 +2,7 @@ import { ObjectType } from '@/common/types'; import { getFollowedByForEntity, getProjectFollowers } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; import { getPushSubscriptionsForUser } from '@/repository/db/push_subscriptions'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import { mapObjectWithReactions } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapProjectToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; @@ -41,22 +42,32 @@ export class ProjectLifecycle extends StrapiEntityLifecycle { }; private buildNotifications = async (projectId: string): Promise => { - const followers = await getProjectFollowers(dbClient, projectId); + try { + const followers = await getProjectFollowers(dbClient, projectId); - // Ignore possible erros... - return await Promise.all( - followers.map(async ({ followedBy }) => { - return { - subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), - userId: followedBy, - notification: { - topic: 'project', - body: 'Ein Projekt, dem du folgst, wurde kürzlich aktualisiert.', - url: `/projects/${projectId}`, - }, - }; - }), - ); + // Ignore possible erros... + return await Promise.all( + followers.map(async ({ followedBy }) => { + return { + subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), + userId: followedBy, + notification: { + topic: 'project', + body: 'Ein Projekt, dem du folgst, wurde kürzlich aktualisiert.', + url: `/projects/${projectId}`, + }, + }; + }), + ); + } catch (err) { + const error: InnoPlatformError = dbError( + `Build notifications for followers of project with id: ${projectId}`, + err as Error, + projectId, + ); + logger.error(error); + throw err; + } }; private saveProjectToCache = async (projectId: string, options: { createIfNew: boolean } = { createIfNew: true }) => { diff --git a/app/utils/strapiEvents/entityLifecycles/surveyQuestionLifecycle.ts b/app/utils/strapiEvents/entityLifecycles/surveyQuestionLifecycle.ts index d8ddc034..bc2f5a37 100644 --- a/app/utils/strapiEvents/entityLifecycles/surveyQuestionLifecycle.ts +++ b/app/utils/strapiEvents/entityLifecycles/surveyQuestionLifecycle.ts @@ -3,6 +3,7 @@ import { getFollowedByForEntity, getProjectFollowers } from '@/repository/db/fol import dbClient from '@/repository/db/prisma/prisma'; import { getPushSubscriptionsForUser } from '@/repository/db/push_subscriptions'; import { createProjectUpdate } from '@/services/updateService'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import { mapObjectWithReactions } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapSurveyQuestionToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; @@ -47,21 +48,31 @@ export class SurveyQuestionLifecycle extends StrapiEntityLifecycle { }; private buildNotifications = async (surveyId: string): Promise => { - const followers = await getProjectFollowers(dbClient, surveyId); - // Ignore possible erros... - return await Promise.all( - followers.map(async ({ followedBy }) => { - return { - subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), - userId: followedBy, - notification: { - topic: 'survey-question', - body: 'Eine neue Umfrage wurde kürzlich zu einem Projekt, dem du folgst, hinzugefügt.', - url: '/', - }, - }; - }), - ); + try { + const followers = await getProjectFollowers(dbClient, surveyId); + // Ignore possible erros... + return await Promise.all( + followers.map(async ({ followedBy }) => { + return { + subscriptions: await getPushSubscriptionsForUser(dbClient, followedBy), + userId: followedBy, + notification: { + topic: 'survey-question', + body: 'Eine neue Umfrage wurde kürzlich zu einem Projekt, dem du folgst, hinzugefügt.', + url: '/', + }, + }; + }), + ); + } catch (err) { + const error: InnoPlatformError = dbError( + `Build notifications for followers of survey question with id: ${surveyId}`, + err as Error, + surveyId, + ); + logger.error(error); + throw err; + } }; private createUpdateForNewSurvey = async (surveyId: string) => { diff --git a/app/utils/strapiEvents/entityLifecycles/updateLifecycle.ts b/app/utils/strapiEvents/entityLifecycles/updateLifecycle.ts index 7c52138a..1dc5ef93 100644 --- a/app/utils/strapiEvents/entityLifecycles/updateLifecycle.ts +++ b/app/utils/strapiEvents/entityLifecycles/updateLifecycle.ts @@ -3,6 +3,7 @@ import { getFollowedByForEntity } from '@/repository/db/follow'; import { countNewsResponses } from '@/repository/db/news_comment'; import dbClient from '@/repository/db/prisma/prisma'; import { getAllPushSubscriptions } from '@/repository/db/push_subscriptions'; +import { dbError, InnoPlatformError } from '@/utils/errors'; import { mapObjectWithReactions } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapToRedisUsers, mapUpdateToRedisNewsFeedEntry } from '@/utils/newsFeed/redis/mappings'; @@ -44,39 +45,51 @@ export class UpdateLifecycle extends StrapiEntityLifecycle { }; private buildNotifications = async (): Promise => { - const allSubscriptions = await getAllPushSubscriptions(dbClient); - return allSubscriptions.map((subscription) => { - return { - subscriptions: subscription.subscriptions, - userId: subscription.userId, - notification: { - topic: 'update', - body: `Ein neues Update wurde hinzugefügt.`, - url: '/news', - }, - }; - }); + try { + const allSubscriptions = await getAllPushSubscriptions(dbClient); + return allSubscriptions.map((subscription) => { + return { + subscriptions: subscription.subscriptions, + userId: subscription.userId, + notification: { + topic: 'update', + body: `Ein neues Update wurde hinzugefügt.`, + url: '/news', + }, + }; + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Build notifications`, err as Error); + logger.error(error); + throw err; + } }; private saveUpdateToCache = async (updateId: string) => { - const update = await getProjectUpdateByIdWithReactions(updateId); - if (!update) { - logger.warn(`Failed to save project update with id '${updateId}' to cache`); - return; - } + try { + const update = await getProjectUpdateByIdWithReactions(updateId); + if (!update) { + logger.warn(`Failed to save project update with id '${updateId}' to cache`); + return; + } - const redisClient = await getRedisClient(); - const followerIds = await getFollowedByForEntity(dbClient, ObjectType.UPDATE, updateId); - const followers = await mapToRedisUsers(followerIds); - const updateWithReactions = mapObjectWithReactions(update); - const responseCount = await countNewsResponses(dbClient, update.id); - const newsFeedEntry = mapUpdateToRedisNewsFeedEntry( - updateWithReactions, - update.reactions, - followers, - responseCount, - ); - await saveNewsFeedEntry(redisClient, newsFeedEntry); + const redisClient = await getRedisClient(); + const followerIds = await getFollowedByForEntity(dbClient, ObjectType.UPDATE, updateId); + const followers = await mapToRedisUsers(followerIds); + const updateWithReactions = mapObjectWithReactions(update); + const responseCount = await countNewsResponses(dbClient, update.id); + const newsFeedEntry = mapUpdateToRedisNewsFeedEntry( + updateWithReactions, + update.reactions, + followers, + responseCount, + ); + await saveNewsFeedEntry(redisClient, newsFeedEntry); + } catch (err) { + const error: InnoPlatformError = dbError(`Save update to cache with id: ${updateId}`, err as Error, updateId); + logger.error(error); + throw err; + } }; private deleteUpdateFromCache = async (updateId: string) => { From ca55aec72164472b6f02dda9f055a2bc2cb6f65b Mon Sep 17 00:00:00 2001 From: Nadine Blaas <122269231+naadnn@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:31:34 +0200 Subject: [PATCH 2/6] refactor: added dbErrors and redisErrors in different files related to services --- app/repository/db/collaboration_comment.ts | 143 +++++++---- .../db/collaboration_comment_response.ts | 223 ++++++++++++------ app/repository/db/follow.ts | 96 +++++--- app/repository/db/news_comment.ts | 20 +- app/repository/db/post_comment.ts | 20 +- app/repository/db/posts.ts | 86 ++++--- .../collaborationCommentResponseService.ts | 38 +-- app/services/collaborationCommentService.ts | 140 +++++------ app/services/collaborationQuestionService.ts | 16 +- app/services/commentService.ts | 58 +---- app/services/followService.ts | 152 +++++++----- app/services/postService.ts | 121 +++++----- app/services/updateService.ts | 26 +- app/utils/newsFeed/redis/redisService.ts | 9 +- 14 files changed, 673 insertions(+), 475 deletions(-) diff --git a/app/repository/db/collaboration_comment.ts b/app/repository/db/collaboration_comment.ts index cec912dc..80c2157a 100644 --- a/app/repository/db/collaboration_comment.ts +++ b/app/repository/db/collaboration_comment.ts @@ -1,5 +1,10 @@ import { PrismaClient } from '@prisma/client'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; + +const logger = getLogger(); + export async function getCollaborationCommentById(client: PrismaClient, commentId: string) { return client.collaborationComment.findFirst({ where: { @@ -53,66 +58,106 @@ export async function addCollaborationCommentToDb( comment: string, visible: boolean = true, ) { - return client.collaborationComment.create({ - data: { + try { + return client.collaborationComment.create({ + data: { + projectId, + questionId, + author, + comment, + visible, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Add collaboration comment to project with id: ${projectId} and question with id: ${questionId} by user: ${author}`, + err as Error, projectId, - questionId, - author, - comment, - visible, - }, - }); + ); + logger.error(error); + throw err; + } } export async function updateCollaborationCommentInDb(client: PrismaClient, commentId: string, updatedText: string) { - return client.collaborationComment.update({ - where: { - id: commentId, - }, - data: { - comment: updatedText, - }, - }); + try { + return client.collaborationComment.update({ + where: { + id: commentId, + }, + data: { + comment: updatedText, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Update collaboration comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } export async function deleteCollaborationCommentInDb(client: PrismaClient, commentId: string) { - return client.collaborationComment.delete({ - where: { - id: commentId, - }, - }); -} - -export async function handleCollaborationCommentUpvoteInDb(client: PrismaClient, commentId: string, upvotedBy: string) { - return client.$transaction(async (tx) => { - const result = await tx.collaborationComment.findFirst({ - where: { id: commentId }, - select: { - upvotedBy: true, + try { + return client.collaborationComment.delete({ + where: { + id: commentId, }, }); - const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); + } catch (err) { + const error: InnoPlatformError = dbError( + `Delete collaboration comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } +} - if (result?.upvotedBy.includes(upvotedBy)) { - return tx.collaborationComment.update({ - where: { - id: commentId, - }, - data: { - upvotedBy: upvotes, +export async function handleCollaborationCommentUpvoteInDb(client: PrismaClient, commentId: string, upvotedBy: string) { + try { + return client.$transaction(async (tx) => { + const result = await tx.collaborationComment.findFirst({ + where: { id: commentId }, + select: { + upvotedBy: true, }, }); - } + const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); - if (result) { - return tx.collaborationComment.update({ - where: { - id: commentId, - }, - data: { - upvotedBy: { push: upvotedBy }, - }, - }); - } - }); + if (result?.upvotedBy.includes(upvotedBy)) { + return tx.collaborationComment.update({ + where: { + id: commentId, + }, + data: { + upvotedBy: upvotes, + }, + }); + } + + if (result) { + return tx.collaborationComment.update({ + where: { + id: commentId, + }, + data: { + upvotedBy: { push: upvotedBy }, + }, + }); + } + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Handle upvote on collaboration comment with id: ${commentId} by user: ${upvotedBy}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } diff --git a/app/repository/db/collaboration_comment_response.ts b/app/repository/db/collaboration_comment_response.ts index a8898cf4..bef94eeb 100644 --- a/app/repository/db/collaboration_comment_response.ts +++ b/app/repository/db/collaboration_comment_response.ts @@ -1,27 +1,62 @@ import { PrismaClient } from '@prisma/client'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; + +const logger = getLogger(); + export async function getCollaborationCommentResponseById(client: PrismaClient, responseId: string) { - return client.collaborationCommentResponse.findFirst({ - where: { - id: responseId, - }, - }); + try { + return client.collaborationCommentResponse.findFirst({ + where: { + id: responseId, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Get collaboration comment response with id: ${responseId}`, + err as Error, + responseId, + ); + logger.error(error); + throw err; + } } export async function getCollaborationCommentResponsesByCommentId(client: PrismaClient, commentId: string) { - return client.collaborationCommentResponse.findMany({ - where: { - commentId: commentId, - }, - }); + try { + return client.collaborationCommentResponse.findMany({ + where: { + commentId: commentId, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Get collaboration comment responses for comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } export async function getCollaborationCommentResponseCount(client: PrismaClient, commentId: string) { - return client.collaborationCommentResponse.count({ - where: { - commentId: commentId, - }, - }); + try { + return client.collaborationCommentResponse.count({ + where: { + commentId: commentId, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Get collaboration comment response count for comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } export async function addCollaborationCommentResponseToDb( @@ -30,13 +65,23 @@ export async function addCollaborationCommentResponseToDb( response: string, commentId: string, ) { - return client.collaborationCommentResponse.create({ - data: { - author: author, - response: response, - commentId: commentId, - }, - }); + try { + return client.collaborationCommentResponse.create({ + data: { + author: author, + response: response, + commentId: commentId, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Add collaboration comment to comment with id: ${commentId} by user: ${author}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } export async function updateCollaborationCommentResponseInDb( @@ -44,22 +89,42 @@ export async function updateCollaborationCommentResponseInDb( responseId: string, updatedText: string, ) { - return client.collaborationCommentResponse.update({ - where: { - id: responseId, - }, - data: { - response: updatedText, - }, - }); + try { + return client.collaborationCommentResponse.update({ + where: { + id: responseId, + }, + data: { + response: updatedText, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Update collaboration comment response with id: ${responseId}`, + err as Error, + responseId, + ); + logger.error(error); + throw err; + } } export async function deleteCollaborationCommentResponseInDb(client: PrismaClient, responseId: string) { - return client.collaborationCommentResponse.delete({ - where: { - id: responseId, - }, - }); + try { + return client.collaborationCommentResponse.delete({ + where: { + id: responseId, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Delete collaboration comment response with id: ${responseId}`, + err as Error, + responseId, + ); + logger.error(error); + throw err; + } } export async function getCollaborationCommentResponseUpvotedBy( @@ -67,12 +132,22 @@ export async function getCollaborationCommentResponseUpvotedBy( responseId: string, upvotedBy: string, ) { - return client.collaborationCommentResponse.findMany({ - where: { - id: responseId, - upvotedBy: { has: upvotedBy }, - }, - }); + try { + return client.collaborationCommentResponse.findMany({ + where: { + id: responseId, + upvotedBy: { has: upvotedBy }, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Get collaboration comment upvotes for response with id: ${responseId}`, + err as Error, + responseId, + ); + logger.error(error); + throw err; + } } export async function handleCollaborationCommentResponseUpvotedByInDb( @@ -80,35 +155,45 @@ export async function handleCollaborationCommentResponseUpvotedByInDb( responseId: string, upvotedBy: string, ) { - return client.$transaction(async (tx) => { - const result = await tx.collaborationCommentResponse.findFirst({ - where: { id: responseId }, - select: { - upvotedBy: true, - }, - }); - const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); - - if (result?.upvotedBy.includes(upvotedBy)) { - return tx.collaborationCommentResponse.update({ - where: { - id: responseId, - }, - data: { - upvotedBy: upvotes, + try { + return client.$transaction(async (tx) => { + const result = await tx.collaborationCommentResponse.findFirst({ + where: { id: responseId }, + select: { + upvotedBy: true, }, }); - } + const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); - if (result) { - return tx.collaborationCommentResponse.update({ - where: { - id: responseId, - }, - data: { - upvotedBy: { push: upvotedBy }, - }, - }); - } - }); + if (result?.upvotedBy.includes(upvotedBy)) { + return tx.collaborationCommentResponse.update({ + where: { + id: responseId, + }, + data: { + upvotedBy: upvotes, + }, + }); + } + + if (result) { + return tx.collaborationCommentResponse.update({ + where: { + id: responseId, + }, + data: { + upvotedBy: { push: upvotedBy }, + }, + }); + } + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Handle collaboration comment upvotes for response with id: ${responseId}`, + err as Error, + responseId, + ); + logger.error(error); + throw err; + } } diff --git a/app/repository/db/follow.ts b/app/repository/db/follow.ts index b1f99d9d..bdaf5950 100644 --- a/app/repository/db/follow.ts +++ b/app/repository/db/follow.ts @@ -2,6 +2,10 @@ import type { Follow, ObjectType as PrismaObjectType } from '@prisma/client'; import { PrismaClient } from '@prisma/client'; import { ObjectType } from '@/common/types'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; + +const logger = getLogger(); export async function getFollowers(client: PrismaClient, objectType: ObjectType, objectId: string, limit?: number) { const query: any = { @@ -21,16 +25,26 @@ export async function getFollowedByForEntity( objectType: ObjectType, objectId: string, ): Promise { - const followedBy = await client.follow.findMany({ - where: { + try { + const followedBy = await client.follow.findMany({ + where: { + objectId, + objectType: objectType as PrismaObjectType, + }, + select: { + followedBy: true, + }, + }); + return followedBy.map((follow) => follow.followedBy); + } catch (err) { + const error: InnoPlatformError = dbError( + `Get follower for ${objectType} object with id: ${objectId}`, + err as Error, objectId, - objectType: objectType as PrismaObjectType, - }, - select: { - followedBy: true, - }, - }); - return followedBy.map((follow) => follow.followedBy); + ); + logger.error(error); + throw err; + } } export async function getProjectFollowers(client: PrismaClient, objectId: string, limit?: number) { @@ -106,15 +120,25 @@ export async function removeFollowFromDb( objectType: ObjectType, objectId: string, ): Promise { - return await client.follow.delete({ - where: { - objectId_objectType_followedBy: { - followedBy, - objectId, - objectType: objectType as PrismaObjectType, + try { + return await client.follow.delete({ + where: { + objectId_objectType_followedBy: { + followedBy, + objectId, + objectType: objectType as PrismaObjectType, + }, }, - }, - }); + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Remove follow to ${objectType} object with id: ${objectId}`, + err as Error, + objectId, + ); + logger.error(error); + throw err; + } } export async function addFollowToDb( @@ -123,23 +147,33 @@ export async function addFollowToDb( objectType: ObjectType, objectId: string, ) { - return client.follow.upsert({ - where: { - objectId_objectType_followedBy: { + try { + return client.follow.upsert({ + where: { + objectId_objectType_followedBy: { + objectId, + objectType: objectType as PrismaObjectType, + followedBy, + }, + }, + update: { objectId, objectType: objectType as PrismaObjectType, followedBy, }, - }, - update: { - objectId, - objectType: objectType as PrismaObjectType, - followedBy, - }, - create: { + create: { + objectId, + objectType: objectType as PrismaObjectType, + followedBy, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Add follow to ${objectType} object with id: ${objectId}`, + err as Error, objectId, - objectType: objectType as PrismaObjectType, - followedBy, - }, - }); + ); + logger.error(error); + throw err; + } } diff --git a/app/repository/db/news_comment.ts b/app/repository/db/news_comment.ts index 4e905be0..dad685f8 100644 --- a/app/repository/db/news_comment.ts +++ b/app/repository/db/news_comment.ts @@ -1,13 +1,23 @@ import { PrismaClient } from '@prisma/client'; import { defaultParamsComment as defaultParams } from '@/repository/db/utils/types'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; + +const logger = getLogger(); export async function countNewsResponses(client: PrismaClient, newsId: string) { - return await client.newsComment.count({ - where: { - newsId, - }, - }); + try { + return await client.newsComment.count({ + where: { + newsId, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Count responses to news item with id: ${newsId}`, err as Error, newsId); + logger.error(error); + throw err; + } } export async function getNewsCommentsByUpdateId(client: PrismaClient, newsId: string) { diff --git a/app/repository/db/post_comment.ts b/app/repository/db/post_comment.ts index 47031cb0..8650ad13 100644 --- a/app/repository/db/post_comment.ts +++ b/app/repository/db/post_comment.ts @@ -1,13 +1,23 @@ import { PrismaClient } from '@prisma/client'; import { defaultParamsComment as defaultParams } from '@/repository/db/utils/types'; +import { dbError, InnoPlatformError } from '@/utils/errors'; +import getLogger from '@/utils/logger'; + +const logger = getLogger(); export async function countPostResponses(client: PrismaClient, postId: string) { - return await client.postComment.count({ - where: { - postId, - }, - }); + try { + return await client.postComment.count({ + where: { + postId, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Counting reponses for post with id: ${postId}`, err as Error, postId); + logger.error(error); + throw err; + } } export async function getNewsCommentsByPostId(client: PrismaClient, postId: string) { diff --git a/app/repository/db/posts.ts b/app/repository/db/posts.ts index fec70d3e..d5dd5396 100644 --- a/app/repository/db/posts.ts +++ b/app/repository/db/posts.ts @@ -54,47 +54,71 @@ export async function getPostsFromDbStartingFrom(client: PrismaClient, from: Dat } export async function addPostToDb(client: PrismaClient, content: string, author: string, anonymous: boolean) { - return await client.post.create({ data: { author, content, anonymous } }); + try { + return await client.post.create({ data: { author, content, anonymous } }); + } catch (err) { + const error: InnoPlatformError = dbError(`Add post with by author: ${author}`, err as Error); + logger.error(error); + throw error; + } } export async function deletePostFromDb(client: PrismaClient, postId: string) { - return await client.post.delete({ where: { id: postId } }); + try { + return await client.post.delete({ where: { id: postId } }); + } catch (err) { + const error: InnoPlatformError = dbError(`Delete post with id: ${postId}`, err as Error); + logger.error(error); + throw error; + } } export async function updatePostInDb(client: PrismaClient, postId: string, content: string) { - return await client.post.update({ data: { content }, where: { id: postId } }); + try { + return await client.post.update({ data: { content }, where: { id: postId } }); + } catch (err) { + const error: InnoPlatformError = dbError(`Update post with id: ${postId}`, err as Error); + logger.error(error); + throw error; + } } export async function handlePostUpvoteInDb(client: PrismaClient, postId: string, upvotedBy: string) { - return client.$transaction(async (tx) => { - const result = await tx.post.findFirst({ - where: { id: postId }, - select: { - upvotedBy: true, - }, - }); - const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); - - if (result?.upvotedBy.includes(upvotedBy)) { - return tx.post.update({ - where: { - id: postId, - }, - data: { - upvotedBy: upvotes, + try { + return client.$transaction(async (tx) => { + const result = await tx.post.findFirst({ + where: { id: postId }, + select: { + upvotedBy: true, }, }); - } + const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); - if (result) { - return tx.post.update({ - where: { - id: postId, - }, - data: { - upvotedBy: { push: upvotedBy }, - }, - }); - } - }); + if (result?.upvotedBy.includes(upvotedBy)) { + return tx.post.update({ + where: { + id: postId, + }, + data: { + upvotedBy: upvotes, + }, + }); + } + + if (result) { + return tx.post.update({ + where: { + id: postId, + }, + data: { + upvotedBy: { push: upvotedBy }, + }, + }); + } + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Upvote post with id: ${postId} by user ${upvotedBy}`, err as Error); + logger.error(error); + throw error; + } } diff --git a/app/services/collaborationCommentResponseService.ts b/app/services/collaborationCommentResponseService.ts index e2da246c..7e50e284 100644 --- a/app/services/collaborationCommentResponseService.ts +++ b/app/services/collaborationCommentResponseService.ts @@ -8,10 +8,6 @@ import { } from '@/repository/db/collaboration_comment_response'; import dbClient from '@/repository/db/prisma/prisma'; import { updateCollaborationCommentInCache } from '@/services/collaborationCommentService'; -import { dbError, InnoPlatformError } from '@/utils/errors'; -import getLogger from '@/utils/logger'; - -const logger = getLogger(); type AddResponse = { user: UserSession; @@ -29,34 +25,14 @@ type DeleteResponse = { }; export const addCollaborationCommentResponse = async ({ user, response, comment }: AddResponse) => { - try { - const createdResponse = await addCollaborationCommentResponseToDb(dbClient, user.providerId, response, comment.id); - const responseCount = await getCollaborationCommentResponseCount(dbClient, comment.id); - await updateCollaborationCommentInCache({ user, comment: { id: comment.id, responseCount } }); - return createdResponse; - } catch (err) { - const error: InnoPlatformError = dbError( - `Add collaboration comment response for comment with id: ${comment.id} by user ${user.providerId}`, - err as Error, - comment.id, - ); - logger.error(error); - throw err; - } + const createdResponse = await addCollaborationCommentResponseToDb(dbClient, user.providerId, response, comment.id); + const responseCount = await getCollaborationCommentResponseCount(dbClient, comment.id); + await updateCollaborationCommentInCache({ user, comment: { id: comment.id, responseCount } }); + return createdResponse; }; export const deleteCollaborationCommentResponse = async ({ user, response }: DeleteResponse) => { - try { - const deletedResponse = await deleteCollaborationCommentResponseInDb(dbClient, response.id); - const responseCount = await getCollaborationCommentResponseCount(dbClient, deletedResponse.commentId); - await updateCollaborationCommentInCache({ user, comment: { id: deletedResponse.commentId, responseCount } }); - } catch (err) { - const error: InnoPlatformError = dbError( - `Delete collaboration comment response with id: ${response.id} by user ${user.providerId}`, - err as Error, - response.id, - ); - logger.error(error); - throw err; - } + const deletedResponse = await deleteCollaborationCommentResponseInDb(dbClient, response.id); + const responseCount = await getCollaborationCommentResponseCount(dbClient, deletedResponse.commentId); + await updateCollaborationCommentInCache({ user, comment: { id: deletedResponse.commentId, responseCount } }); }; diff --git a/app/services/collaborationCommentService.ts b/app/services/collaborationCommentService.ts index fc0fdc3d..7155dfb7 100644 --- a/app/services/collaborationCommentService.ts +++ b/app/services/collaborationCommentService.ts @@ -15,7 +15,7 @@ import { getCollaborationCommentResponseCount } from '@/repository/db/collaborat import { getFollowedByForEntity } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; import { getReactionsForEntity } from '@/repository/db/reaction'; -import { dbError, InnoPlatformError } from '@/utils/errors'; +import { InnoPlatformError, redisError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { mapCollaborationCommentToRedisNewsFeedEntry, @@ -64,114 +64,100 @@ type UpvoteCollaborationComment = { }; export const addCollaborationComment = async ({ user, comment }: AddCollaborationComment) => { - try { - const createdComment = await addCollaborationCommentToDb( - dbClient, - comment.projectId, - comment.questionId, - user.providerId, - comment.comment, - comment.visible, - ); - - await addCollaborationCommentToCache(user, createdComment); - return createdComment; - } catch (err) { - const error: InnoPlatformError = dbError( - `Add collaboration comment to project with id: ${comment.projectId} and question with id: ${comment.questionId} by user ${user.providerId}`, - err as Error, - comment.projectId, - ); - logger.error(error); - throw err; - } + const createdComment = await addCollaborationCommentToDb( + dbClient, + comment.projectId, + comment.questionId, + user.providerId, + comment.comment, + comment.visible, + ); + await addCollaborationCommentToCache(user, createdComment); + return createdComment; }; export const deleteCollaborationComment = async (commentId: string) => { - try { - await deleteCollaborationCommentInDb(dbClient, commentId); - await deleteCollaborationCommentInCache(commentId); - } catch (err) { - const error: InnoPlatformError = dbError( - `Delete collaboration comment with id: ${commentId}`, - err as Error, - commentId, - ); - logger.error(error); - throw err; - } + await deleteCollaborationCommentInDb(dbClient, commentId); + await deleteCollaborationCommentInCache(commentId); }; export const updateCollaborationComment = async ({ user, comment }: UpdateCollaborationComment) => { + const updatedComment = await updateCollaborationCommentInDb(dbClient, comment.id, comment.comment); + await updateCollaborationCommentInCache({ user, comment }); + return updatedComment; +}; + +export const handleCollaborationCommentUpvote = async ({ user, commentId }: UpvoteCollaborationComment) => { + const updatedComment = await handleCollaborationCommentUpvoteInDb(dbClient, commentId, user.providerId); + + if (updatedComment) { + await updateCollaborationCommentInCache({ user, comment: updatedComment }); + } +}; + +export const addCollaborationCommentToCache = async (user: User, comment: { id: string }) => { try { - const updatedComment = await updateCollaborationCommentInDb(dbClient, comment.id, comment.comment); - await updateCollaborationCommentInCache({ user, comment }); - return updatedComment; + const newsFeedEntry = await createNewsFeedEntryForCommentById(comment.id, user); + if (!newsFeedEntry) return; + const redisClient = await getRedisClient(); + await saveNewsFeedEntry(redisClient, newsFeedEntry); } catch (err) { - const error: InnoPlatformError = dbError( - `Update collaboration comment with id: ${comment.id}`, + const error: InnoPlatformError = redisError( + `Add collaboration comment with id: ${comment.id} by user ${user}`, err as Error, - comment.id, ); logger.error(error); throw err; } }; -export const handleCollaborationCommentUpvote = async ({ user, commentId }: UpvoteCollaborationComment) => { +export const deleteCollaborationCommentInCache = async (commentId: string) => { try { - const updatedComment = await handleCollaborationCommentUpvoteInDb(dbClient, commentId, user.providerId); - - if (updatedComment) { - await updateCollaborationCommentInCache({ user, comment: updatedComment }); - } + const redisClient = await getRedisClient(); + const redisKey = getRedisKey(commentId); + await deleteItemFromRedis(redisClient, redisKey); } catch (err) { - const error: InnoPlatformError = dbError( - `Handle upvote for collaboration comment with id: ${commentId} by user ${user.providerId}`, - err as Error, - commentId, - ); + const error: InnoPlatformError = redisError(`Delete collaboration comment with id: ${commentId}`, err as Error); logger.error(error); throw err; } }; -export const addCollaborationCommentToCache = async (user: User, comment: { id: string }) => { - const newsFeedEntry = await createNewsFeedEntryForCommentById(comment.id, user); - if (!newsFeedEntry) return; - const redisClient = await getRedisClient(); - await saveNewsFeedEntry(redisClient, newsFeedEntry); -}; - -export const deleteCollaborationCommentInCache = async (commentId: string) => { - const redisClient = await getRedisClient(); - const redisKey = getRedisKey(commentId); - await deleteItemFromRedis(redisClient, redisKey); -}; - export const updateCollaborationCommentInCache = async ({ user, comment }: UpdateCollaborationCommentInCache) => { - const redisClient = await getRedisClient(); - const newsFeedEntry = await getNewsFeedEntryForComment(redisClient, { user, commentId: comment.id }); + try { + const redisClient = await getRedisClient(); + const newsFeedEntry = await getNewsFeedEntryForComment(redisClient, { user, commentId: comment.id }); - if (!newsFeedEntry) return; + if (!newsFeedEntry) return; - const cachedItem = newsFeedEntry.item as RedisCollaborationComment; - cachedItem.comment = comment.comment ?? cachedItem.comment; - cachedItem.upvotedBy = comment.upvotedBy ?? cachedItem.upvotedBy; - cachedItem.responseCount = comment.responseCount ?? cachedItem.responseCount; - newsFeedEntry.item = cachedItem; + const cachedItem = newsFeedEntry.item as RedisCollaborationComment; + cachedItem.comment = comment.comment ?? cachedItem.comment; + cachedItem.upvotedBy = comment.upvotedBy ?? cachedItem.upvotedBy; + cachedItem.responseCount = comment.responseCount ?? cachedItem.responseCount; + newsFeedEntry.item = cachedItem; - await saveNewsFeedEntry(redisClient, newsFeedEntry); - return newsFeedEntry.item; + await saveNewsFeedEntry(redisClient, newsFeedEntry); + return newsFeedEntry.item; + } catch (err) { + const error: InnoPlatformError = redisError(`Delete collaboration comment with id: ${comment.id}`, err as Error); + logger.error(error); + throw err; + } }; export const getNewsFeedEntryForComment = async ( redisClient: RedisClient, { user, commentId }: { user: User; commentId: string }, ) => { - const redisKey = getRedisKey(commentId); - const cacheEntry = await getNewsFeedEntryByKey(redisClient, redisKey); - return cacheEntry ?? (await createNewsFeedEntryForCommentById(commentId, user)); + try { + const redisKey = getRedisKey(commentId); + const cacheEntry = await getNewsFeedEntryByKey(redisClient, redisKey); + return cacheEntry ?? (await createNewsFeedEntryForCommentById(commentId, user)); + } catch (err) { + const error: InnoPlatformError = redisError(`Get news feed entry for comment with id: ${commentId}`, err as Error); + logger.error(error); + throw err; + } }; export const createNewsFeedEntryForCommentById = async (commentId: string, user?: User) => { diff --git a/app/services/collaborationQuestionService.ts b/app/services/collaborationQuestionService.ts index 20707e02..646ab5e9 100644 --- a/app/services/collaborationQuestionService.ts +++ b/app/services/collaborationQuestionService.ts @@ -3,6 +3,7 @@ import { ObjectType } from '@/common/types'; import { getFollowedByForEntity } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; +import { InnoPlatformError, redisError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { mapCollaborationQuestionToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; import { NewsType } from '@/utils/newsFeed/redis/models'; @@ -13,9 +14,18 @@ import { getBasicCollaborationQuestionByIdWithAdditionalData } from '@/utils/req const logger = getLogger(); export const getNewsFeedEntryForCollaborationQuestion = async (redisClient: RedisClient, questionId: string) => { - const redisKey = getRedisKey(questionId); - const cacheEntry = await getNewsFeedEntryByKey(redisClient, redisKey); - return cacheEntry ?? (await createNewsFeedEntryForCollaborationQuestion(questionId)); + try { + const redisKey = getRedisKey(questionId); + const cacheEntry = await getNewsFeedEntryByKey(redisClient, redisKey); + return cacheEntry ?? (await createNewsFeedEntryForCollaborationQuestion(questionId)); + } catch (err) { + const error: InnoPlatformError = redisError( + `Get news feed entry for collaboration question with id: ${questionId}`, + err as Error, + ); + logger.error(error); + throw err; + } }; const createNewsFeedEntryForCollaborationQuestion = async (questionId: string) => { diff --git a/app/services/commentService.ts b/app/services/commentService.ts index 272ef7af..17bddb6c 100644 --- a/app/services/commentService.ts +++ b/app/services/commentService.ts @@ -105,61 +105,29 @@ export const removeComment = async ({ user, commentId, commentType }: RemoveComm }; const updatePostCommentInCache = async (postComment: { postId: string }, author: UserSession) => { - try { - const postResponseCount = await countPostResponses(dbClient, postComment.postId); - return await updatePostInCache({ - post: { id: postComment.postId, responseCount: postResponseCount }, - user: author, - }); - } catch (err) { - const error: InnoPlatformError = dbError( - `Update post comment in cache with id: ${postComment.postId}`, - err as Error, - postComment.postId, - ); - logger.error(error); - throw err; - } + const postResponseCount = await countPostResponses(dbClient, postComment.postId); + return await updatePostInCache({ + post: { id: postComment.postId, responseCount: postResponseCount }, + user: author, + }); }; const updateNewsCommentInCache = async (newsComment: { newsId: string }) => { - try { - const newsResponseCount = await countNewsResponses(dbClient, newsComment.newsId); - await updateProjectUpdateInCache({ update: { id: newsComment.newsId, responseCount: newsResponseCount } }); - } catch (err) { - const error: InnoPlatformError = dbError( - `Update news comment in cache with id: ${newsComment.newsId}`, - err as Error, - newsComment.newsId, - ); - logger.error(error); - throw err; - } + const newsResponseCount = await countNewsResponses(dbClient, newsComment.newsId); + await updateProjectUpdateInCache({ update: { id: newsComment.newsId, responseCount: newsResponseCount } }); }; const removePostCommentInCache = async (postId: string, user: UserSession) => { - try { - if (postId) { - const responseCount = await countPostResponses(dbClient, postId); - await updatePostInCache({ post: { id: postId, responseCount }, user }); - } - } catch (err) { - const error: InnoPlatformError = dbError(`Remove post comment in cache with id: ${postId}`, err as Error, postId); - logger.error(error); - throw err; + if (postId) { + const responseCount = await countPostResponses(dbClient, postId); + await updatePostInCache({ post: { id: postId, responseCount }, user }); } }; const removeNewsCommenInCache = async (newsId: string) => { - try { - if (newsId) { - const responseCount = await countNewsResponses(dbClient, newsId); - await updateProjectUpdateInCache({ update: { id: newsId, responseCount } }); - } - } catch (err) { - const error: InnoPlatformError = dbError(`Remove news comment in cache with id: ${newsId}`, err as Error, newsId); - logger.error(error); - throw err; + if (newsId) { + const responseCount = await countNewsResponses(dbClient, newsId); + await updateProjectUpdateInCache({ update: { id: newsId, responseCount } }); } }; diff --git a/app/services/followService.ts b/app/services/followService.ts index 7a0dd91b..7c86ae4c 100644 --- a/app/services/followService.ts +++ b/app/services/followService.ts @@ -1,7 +1,7 @@ import { Follow, ObjectType, User } from '@/common/types'; import { addFollowToDb, removeFollowFromDb } from '@/repository/db/follow'; import dbClient from '@/repository/db/prisma/prisma'; -import { dbError, InnoPlatformError } from '@/utils/errors'; +import { InnoPlatformError, redisError } from '@/utils/errors'; import getLogger from '@/utils/logger'; import { RedisNewsFeedEntry } from '@/utils/newsFeed/redis/models'; import { getRedisClient, RedisClient } from '@/utils/newsFeed/redis/redisClient'; @@ -35,67 +35,65 @@ type RemoveFollow = { }; export const addFollow = async ({ user, object }: AddFollow) => { + const createdObject = await addFollowToDb(dbClient, object.followedBy, object.objectType, object.objectId); + await addFollowToCache({ ...createdObject, objectType: createdObject.objectType as ObjectType }, user); + return object; +}; + +export const removeFollow = async ({ user, object }: RemoveFollow) => { + const removedObject = await removeFollowFromDb(dbClient, object.followedBy, object.objectType, object.objectId); + await removeFollowFromCache({ ...removedObject, objectType: removedObject.objectType as ObjectType }, user); + return object; +}; + +export const addFollowToCache = async (follow: Follow, user: User) => { try { - const createdObject = await addFollowToDb(dbClient, object.followedBy, object.objectType, object.objectId); - await addFollowToCache({ ...createdObject, objectType: createdObject.objectType as ObjectType }, user); - return object; + if (follow.objectType === ObjectType.PROJECT) { + const projectId = follow.objectId; + await followNewsFeedEntriesWithProject({ projectId, user }); + return; + } + + const newsFeedEntry = await getNewsFeedEntry(follow, user); + + if (newsFeedEntry) { + const redisClient = await getRedisClient(); + return await addFollowToCacheForNewsFeedEntry(redisClient, newsFeedEntry, user); + } } catch (err) { - const error: InnoPlatformError = dbError( - `Add follow for user ${user.providerId} on ${object.objectType} object with id: ${object.objectId}`, + const error: InnoPlatformError = redisError( + `Add follow to cache for object with id: ${follow.objectId} by user ${user}`, err as Error, - object.objectId, ); logger.error(error); throw err; } }; -export const removeFollow = async ({ user, object }: RemoveFollow) => { +export const removeFollowFromCache = async (follow: Follow, user: User) => { try { - const removedObject = await removeFollowFromDb(dbClient, object.followedBy, object.objectType, object.objectId); - await removeFollowFromCache({ ...removedObject, objectType: removedObject.objectType as ObjectType }, user); - return object; + if (follow.objectType === ObjectType.PROJECT) { + const projectId = follow.objectId; + await unfollowNewsFeedEntriesWithProject({ projectId, user }); + return; + } + + const newsFeedEntry = await getNewsFeedEntry(follow, user); + + if (newsFeedEntry) { + const redisClient = await getRedisClient(); + await removeFollowFromCacheForNewsFeedEntry(redisClient, newsFeedEntry, user); + } } catch (err) { - const error: InnoPlatformError = dbError( - `Remove follow for user ${user.providerId} on ${object.objectType} object with id: ${object.objectId}`, + const error: InnoPlatformError = redisError( + `Remove follow from cache for object with id: ${follow.objectId} by user ${user}`, err as Error, - object.objectId, ); logger.error(error); throw err; } }; -export const addFollowToCache = async (follow: Follow, user: User) => { - if (follow.objectType === ObjectType.PROJECT) { - const projectId = follow.objectId; - await followNewsFeedEntriesWithProject({ projectId, user }); - return; - } - - const newsFeedEntry = await getNewsFeedEntry(follow, user); - - if (newsFeedEntry) { - const redisClient = await getRedisClient(); - return await addFollowToCacheForNewsFeedEntry(redisClient, newsFeedEntry, user); - } -}; - -export const removeFollowFromCache = async (follow: Follow, user: User) => { - if (follow.objectType === ObjectType.PROJECT) { - const projectId = follow.objectId; - await unfollowNewsFeedEntriesWithProject({ projectId, user }); - return; - } - - const newsFeedEntry = await getNewsFeedEntry(follow, user); - - if (newsFeedEntry) { - const redisClient = await getRedisClient(); - await removeFollowFromCacheForNewsFeedEntry(redisClient, newsFeedEntry, user); - } -}; - export const getNewsFeedEntry = async (object: { objectId: string; objectType: ObjectType }, user: User) => { const redisClient = await getRedisClient(); switch (object.objectType) { @@ -118,17 +116,35 @@ export const getNewsFeedEntry = async (object: { objectId: string; objectType: O }; const followNewsFeedEntriesWithProject = async ({ projectId, user }: { projectId: string; user: User }) => { - const redisClient = await getRedisClient(); - const newsFeedEntries = await getNewsFeedEntriesForProject(redisClient, { projectId }); - newsFeedEntries?.forEach(async (entry) => await addFollowToCacheForNewsFeedEntry(redisClient, entry, user)); + try { + const redisClient = await getRedisClient(); + const newsFeedEntries = await getNewsFeedEntriesForProject(redisClient, { projectId }); + newsFeedEntries?.forEach(async (entry) => await addFollowToCacheForNewsFeedEntry(redisClient, entry, user)); + } catch (err) { + const error: InnoPlatformError = redisError( + `Add follow to project with id: ${projectId} by user ${user}`, + err as Error, + ); + logger.error(error); + throw err; + } }; const unfollowNewsFeedEntriesWithProject = async ({ projectId, user }: { projectId: string; user: User }) => { - const redisClient = await getRedisClient(); - const projectNewsFeedEntries = await getNewsFeedEntriesForProject(redisClient, { projectId }); - projectNewsFeedEntries.forEach( - async (entry) => await removeFollowFromCacheForNewsFeedEntry(redisClient, entry, user), - ); + try { + const redisClient = await getRedisClient(); + const projectNewsFeedEntries = await getNewsFeedEntriesForProject(redisClient, { projectId }); + projectNewsFeedEntries.forEach( + async (entry) => await removeFollowFromCacheForNewsFeedEntry(redisClient, entry, user), + ); + } catch (err) { + const error: InnoPlatformError = redisError( + `Remove follow to project with id: ${projectId} by user ${user}`, + err as Error, + ); + logger.error(error); + throw err; + } }; const addFollowToCacheForNewsFeedEntry = async ( @@ -136,12 +152,21 @@ const addFollowToCacheForNewsFeedEntry = async ( newsFeedEntry: RedisNewsFeedEntry, user: User, ) => { - const updatedFollows = - newsFeedEntry.item.followedBy?.filter((follower) => follower.providerId !== user.providerId) ?? []; + try { + const updatedFollows = + newsFeedEntry.item.followedBy?.filter((follower) => follower.providerId !== user.providerId) ?? []; - updatedFollows.push(user); - newsFeedEntry.item.followedBy = updatedFollows; - await saveNewsFeedEntry(redisClient, newsFeedEntry); + updatedFollows.push(user); + newsFeedEntry.item.followedBy = updatedFollows; + await saveNewsFeedEntry(redisClient, newsFeedEntry); + } catch (err) { + const error: InnoPlatformError = redisError( + `Add follow to news feed entry with id: ${newsFeedEntry.item.id} by user ${user}`, + err as Error, + ); + logger.error(error); + throw err; + } }; const removeFollowFromCacheForNewsFeedEntry = async ( @@ -149,6 +174,15 @@ const removeFollowFromCacheForNewsFeedEntry = async ( entry: RedisNewsFeedEntry, user: User, ) => { - entry.item.followedBy = entry.item.followedBy.filter((follower) => follower.providerId !== user.providerId); - await saveNewsFeedEntry(redisClient, entry); + try { + entry.item.followedBy = entry.item.followedBy.filter((follower) => follower.providerId !== user.providerId); + await saveNewsFeedEntry(redisClient, entry); + } catch (err) { + const error: InnoPlatformError = redisError( + `Remove follow from news feed entry with id: ${entry.item.id} by user ${user}`, + err as Error, + ); + logger.error(error); + throw err; + } }; diff --git a/app/services/postService.ts b/app/services/postService.ts index 11b46584..a2518321 100644 --- a/app/services/postService.ts +++ b/app/services/postService.ts @@ -14,7 +14,7 @@ import { } from '@/repository/db/posts'; import dbClient from '@/repository/db/prisma/prisma'; import { getReactionsForEntity } from '@/repository/db/reaction'; -import { dbError, InnoPlatformError } from '@/utils/errors'; +import { InnoPlatformError, redisError } from '@/utils/errors'; import { getUnixTimestamp } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { mapPostToRedisNewsFeedEntry, mapToRedisUsers } from '@/utils/newsFeed/redis/mappings'; @@ -39,80 +39,77 @@ type UpvotePost = { postId: string; user: UserSession }; type DeletePost = { postId: string }; export const addPost = async ({ content, user, anonymous }: AddPost) => { - try { - const createdPost = await addPostToDb(dbClient, content, user.providerId, anonymous ?? false); - await addPostToCache({ ...createdPost, author: user, responseCount: 0 }); - return createdPost; - } catch (err) { - const error: InnoPlatformError = dbError(`Add post by user ${user.providerId}`, err as Error); - logger.error(error); - throw err; - } + const createdPost = await addPostToDb(dbClient, content, user.providerId, anonymous ?? false); + await addPostToCache({ ...createdPost, author: user, responseCount: 0 }); + return createdPost; }; export const updatePost = async ({ postId, content, user }: UpdatePost) => { - try { - const updatedPost = await updatePostInDb(dbClient, postId, content); + const updatedPost = await updatePostInDb(dbClient, postId, content); + await updatePostInCache({ post: updatedPost, user }); + return updatedPost; +}; + +export const deletePost = async ({ postId }: DeletePost) => { + const deletedPost = await deletePostFromDb(dbClient, postId); + await deletePostFromCache(postId); + return deletedPost; +}; + +export const handleUpvotePost = async ({ postId, user }: UpvotePost) => { + const updatedPost = await handlePostUpvoteInDb(dbClient, postId, user.providerId); + if (updatedPost) { await updatePostInCache({ post: updatedPost, user }); - return updatedPost; - } catch (err) { - const error: InnoPlatformError = dbError(`Update post with id: ${postId} by user ${user.providerId}`, err as Error); - logger.error(error); - throw err; } + return updatedPost; }; -export const deletePost = async ({ postId }: DeletePost) => { +export const addPostToCache = async (post: Post) => { try { - const deletedPost = await deletePostFromDb(dbClient, postId); - await deletePostFromCache(postId); - return deletedPost; + const redisClient = await getRedisClient(); + const newsFeedEntry = mapPostToRedisNewsFeedEntry(post, [], []); + await saveNewsFeedEntry(redisClient, newsFeedEntry); } catch (err) { - const error: InnoPlatformError = dbError(`Delete post with id: ${postId}`, err as Error); + const error: InnoPlatformError = redisError(`Add post with id: ${post.id} to cache`, err as Error); logger.error(error); throw err; } }; -export const handleUpvotePost = async ({ postId, user }: UpvotePost) => { +export const updatePostInCache = async ({ post, user }: UpdatePostInCache) => { try { - const updatedPost = await handlePostUpvoteInDb(dbClient, postId, user.providerId); - if (updatedPost) { - await updatePostInCache({ post: updatedPost, user }); - } - return updatedPost; + const redisClient = await getRedisClient(); + const newsFeedEntry = await getNewsFeedEntryForPost(redisClient, { user, postId: post.id }); + + if (!newsFeedEntry) return; + const cachedItem = newsFeedEntry.item as RedisPost; + cachedItem.content = post.content ?? cachedItem.content; + cachedItem.upvotedBy = post.upvotedBy ?? cachedItem.upvotedBy; + cachedItem.responseCount = post.responseCount ?? cachedItem.responseCount; + newsFeedEntry.item = cachedItem; + newsFeedEntry.updatedAt = getUnixTimestamp(new Date()); + + await saveNewsFeedEntry(redisClient, newsFeedEntry); } catch (err) { - const error: InnoPlatformError = dbError(`Upvote post with id: ${postId} by user ${user.providerId}`, err as Error); + const error: InnoPlatformError = redisError( + `Update post with id: ${post.id} by user ${user.providerId} could not be saved in the cache`, + err as Error, + ); logger.error(error); throw err; } }; -export const addPostToCache = async (post: Post) => { - const redisClient = await getRedisClient(); - const newsFeedEntry = mapPostToRedisNewsFeedEntry(post, [], []); - await saveNewsFeedEntry(redisClient, newsFeedEntry); -}; - -export const updatePostInCache = async ({ post, user }: UpdatePostInCache) => { - const redisClient = await getRedisClient(); - const newsFeedEntry = await getNewsFeedEntryForPost(redisClient, { user, postId: post.id }); - - if (!newsFeedEntry) return; - const cachedItem = newsFeedEntry.item as RedisPost; - cachedItem.content = post.content ?? cachedItem.content; - cachedItem.upvotedBy = post.upvotedBy ?? cachedItem.upvotedBy; - cachedItem.responseCount = post.responseCount ?? cachedItem.responseCount; - newsFeedEntry.item = cachedItem; - newsFeedEntry.updatedAt = getUnixTimestamp(new Date()); - - await saveNewsFeedEntry(redisClient, newsFeedEntry); -}; - export const deletePostFromCache = async (postId: string) => { - const redisClient = await getRedisClient(); - const redisKey = getRedisKey(postId); - await deleteItemFromRedis(redisClient, redisKey); + try { + const redisClient = await getRedisClient(); + const redisKey = getRedisKey(postId); + await deleteItemFromRedis(redisClient, redisKey); + } catch (err) { + const error: InnoPlatformError = redisError(`Delete post with id: ${postId} from cache`, err as Error); + logger.error(error); + throw err; + } }; export const getNewsFeedEntryForPost = async ( @@ -125,14 +122,20 @@ export const getNewsFeedEntryForPost = async ( }; export const createNewsFeedEntryForPostById = async (postId: string, author?: User) => { - const post = await getPostById(dbClient, postId); + try { + const post = await getPostById(dbClient, postId); - if (!post) { - logger.warn(`Failed to create news feed cache entry for post with id ${postId}: Post not found`); - return null; - } + if (!post) { + logger.warn(`Failed to create news feed cache entry for post with id ${postId}: Post not found`); + return null; + } - return await createNewsFeedEntryForPost(post, author); + return await createNewsFeedEntryForPost(post, author); + } catch (err) { + const error: InnoPlatformError = redisError(`Create news feed entry for post with id: ${postId}`, err as Error); + logger.error(error); + throw err; + } }; export const createNewsFeedEntryForPost = async (post: PrismaPost, author?: User) => { diff --git a/app/services/updateService.ts b/app/services/updateService.ts index 8494f760..f7a8f0d9 100644 --- a/app/services/updateService.ts +++ b/app/services/updateService.ts @@ -5,7 +5,7 @@ import { getFollowedByForEntity } from '@/repository/db/follow'; import { countNewsResponses } from '@/repository/db/news_comment'; import dbClient from '@/repository/db/prisma/prisma'; import { getReactionsForEntity } from '@/repository/db/reaction'; -import { dbError, InnoPlatformError } from '@/utils/errors'; +import { dbError, InnoPlatformError, redisError } from '@/utils/errors'; import { getUnixTimestamp } from '@/utils/helpers'; import getLogger from '@/utils/logger'; import { @@ -72,17 +72,23 @@ export const deleteProjectUpdateInCache = async (updateId: string) => { }; export const updateProjectUpdateInCache = async ({ update }: UpdateUpdateInCache) => { - const redisClient = await getRedisClient(); - const newsFeedEntry = await getNewsFeedEntryForProjectUpdate(redisClient, update.id); + try { + const redisClient = await getRedisClient(); + const newsFeedEntry = await getNewsFeedEntryForProjectUpdate(redisClient, update.id); - if (!newsFeedEntry) return; - const cachedItem = newsFeedEntry.item as RedisProjectUpdate; - cachedItem.responseCount = update.responseCount ?? cachedItem.responseCount; - cachedItem.comment = update.comment ?? cachedItem.comment; - newsFeedEntry.item = cachedItem; - newsFeedEntry.updatedAt = getUnixTimestamp(new Date()); + if (!newsFeedEntry) return; + const cachedItem = newsFeedEntry.item as RedisProjectUpdate; + cachedItem.responseCount = update.responseCount ?? cachedItem.responseCount; + cachedItem.comment = update.comment ?? cachedItem.comment; + newsFeedEntry.item = cachedItem; + newsFeedEntry.updatedAt = getUnixTimestamp(new Date()); - await saveNewsFeedEntry(redisClient, newsFeedEntry); + await saveNewsFeedEntry(redisClient, newsFeedEntry); + } catch (err) { + const error: InnoPlatformError = redisError(`Update project update with id: ${update.id}`, err as Error); + logger.error(error); + throw err; + } }; export const getNewsFeedEntryForProjectUpdate = async (redisClient: RedisClient, updateId: string) => { diff --git a/app/utils/newsFeed/redis/redisService.ts b/app/utils/newsFeed/redis/redisService.ts index aa98a554..67c2143b 100644 --- a/app/utils/newsFeed/redis/redisService.ts +++ b/app/utils/newsFeed/redis/redisService.ts @@ -47,7 +47,14 @@ export const getLatestSuccessfulNewsFeedSync = async (client: RedisClient) => { }; export const getNewsFeedEntryByKey = async (client: RedisClient, key: string) => { - return (await client.json.get(key)) as RedisNewsFeedEntry | null; + try { + return (await client.json.get(key)) as RedisNewsFeedEntry | null; + } catch (err) { + const error = err as Error; + error.cause = error.message; + const extendedError = redisError(`Failed to get RedisNewsFeedEntry with key '${key}'`, err as Error); + throw extendedError; + } }; export const getNewsFeedEntries = async (client: RedisClient, options?: GetItemsOptions) => { From bc5e5b39d06dc313d574747614e6e4afca37c942 Mon Sep 17 00:00:00 2001 From: simonhrmo <68249308+simonhrmo@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:40:49 +0200 Subject: [PATCH 3/6] feat: highlight news feed search filter in results (#61) --- .../contexts/news-feed-highlight-context.tsx | 30 +++++++++++++++++ app/app/news/page.tsx | 9 ++++-- .../collaboration/survey/SurveyCard.tsx | 3 +- app/components/common/LinkString.tsx | 12 ++++--- .../newsFeed/NewsFeedSearchFilter.tsx | 4 +-- .../newsPage/cards/NewsCollabCommentCard.tsx | 3 +- .../newsPage/cards/NewsCollabQuestionCard.tsx | 3 +- .../newsPage/cards/NewsEventCard.tsx | 5 +-- .../newsPage/cards/NewsProjectCard.tsx | 5 +-- .../newsPage/cards/common/CommentOverview.tsx | 3 +- app/types/strapi-graphql-env.d.ts | 32 ++++++++++--------- app/utils/highlightText.tsx | 23 +++++++++++++ 12 files changed, 100 insertions(+), 32 deletions(-) create mode 100644 app/app/contexts/news-feed-highlight-context.tsx create mode 100644 app/utils/highlightText.tsx diff --git a/app/app/contexts/news-feed-highlight-context.tsx b/app/app/contexts/news-feed-highlight-context.tsx new file mode 100644 index 00000000..c853f59d --- /dev/null +++ b/app/app/contexts/news-feed-highlight-context.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'; + +import { useNewsFeed } from './news-feed-context'; + +interface HighlightContext { + highlightString?: string; +} + +const HighlightContext = createContext({}); + +export function NewsFeedHighlightContextProvider(props: PropsWithChildren) { + const [highlightString, setHighlightString] = useState(); + const { filters } = useNewsFeed(); + + useEffect( + function updateNewsFeedSearchTerm() { + setHighlightString(filters.searchString); + }, + [filters.searchString], + ); + const ctx: HighlightContext = { highlightString }; + return {props.children}; +} + +export function useHighlightContext() { + const ctx = useContext(HighlightContext); + return ctx; +} diff --git a/app/app/news/page.tsx b/app/app/news/page.tsx index 6bb62b60..992277d4 100644 --- a/app/app/news/page.tsx +++ b/app/app/news/page.tsx @@ -4,6 +4,7 @@ import Box from '@mui/material/Box'; import Container from '@mui/material/Container'; import Stack from '@mui/material/Stack'; +import { NewsFeedHighlightContextProvider } from '@/app/contexts/news-feed-highlight-context'; import BreadcrumbsNav from '@/components/common/BreadcrumbsNav'; import ErrorPage from '@/components/error/ErrorPage'; import NewsFeedContainer from '@/components/newsFeed/NewsFeedContainer'; @@ -50,9 +51,11 @@ async function NewsFeedPage() { projects={props.projects} types={props.types} > - - - + + + + + diff --git a/app/components/collaboration/survey/SurveyCard.tsx b/app/components/collaboration/survey/SurveyCard.tsx index 6c929bd7..2adbe88a 100644 --- a/app/components/collaboration/survey/SurveyCard.tsx +++ b/app/components/collaboration/survey/SurveyCard.tsx @@ -12,6 +12,7 @@ import { SurveyQuestion } from '@/common/types'; import { errorMessage } from '@/components/common/CustomToast'; import * as m from '@/src/paraglide/messages.js'; import theme from '@/styles/theme'; +import { HighlightText } from '@/utils/highlightText'; import { handleSurveyVote } from './actions'; import { SurveyResponsePicker } from './SurveyResponsePicker'; @@ -48,7 +49,7 @@ export const SurveyCard = (props: SurveyCardProps) => { - {surveyQuestion.question} + diff --git a/app/components/common/LinkString.tsx b/app/components/common/LinkString.tsx index 4389aa24..dc51ff4d 100644 --- a/app/components/common/LinkString.tsx +++ b/app/components/common/LinkString.tsx @@ -1,18 +1,22 @@ +import React from 'react'; + import Link from '@mui/material/Link'; -export const parseStringForLinks = (text: string): React.ReactNode | string => { +import { HighlightText } from '@/utils/highlightText'; + +export const parseStringForLinks = (text: string): React.ReactNode => { const urlRegex = /((?:https?:\/\/)?(?:www\.|localhost:\d{1,5})?[^\s]+\.[^\s]+|https?:\/\/localhost:\d{1,5}[^\s]*)/g; const parts = text.split(urlRegex); return parts.map((part, index) => { if ((part.match(urlRegex) && part.startsWith('http')) || part.startsWith('www')) { return ( - - {part} + + ); } else { - return part; + return ; } }); }; diff --git a/app/components/newsFeed/NewsFeedSearchFilter.tsx b/app/components/newsFeed/NewsFeedSearchFilter.tsx index 017802a9..4e743ed8 100644 --- a/app/components/newsFeed/NewsFeedSearchFilter.tsx +++ b/app/components/newsFeed/NewsFeedSearchFilter.tsx @@ -35,11 +35,11 @@ export default function NewsFeedSearchFilter(props: NewsFeedSearchFilterProps) { useEffect(() => { const timeoutId = setTimeout(() => { - updateFilters(inputValue); // Update filters with input value + updateFilters(inputValue); }, 300); return () => { - clearTimeout(timeoutId); // Clear the timeout if inputValue changes or component unmounts + clearTimeout(timeoutId); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [inputValue]); diff --git a/app/components/newsPage/cards/NewsCollabCommentCard.tsx b/app/components/newsPage/cards/NewsCollabCommentCard.tsx index a51f6d20..3911d868 100644 --- a/app/components/newsPage/cards/NewsCollabCommentCard.tsx +++ b/app/components/newsPage/cards/NewsCollabCommentCard.tsx @@ -22,6 +22,7 @@ import { import { NewsCardControls } from '@/components/newsPage/cards/common/NewsCardControls'; import { WriteCommentCard } from '@/components/newsPage/cards/common/WriteCommentCard'; import * as m from '@/src/paraglide/messages.js'; +import { HighlightText } from '@/utils/highlightText'; import { appInsights } from '@/utils/instrumentation/AppInsights'; import CommentOverview from './common/CommentOverview'; @@ -56,7 +57,7 @@ function NewsCollabCommentCard(props: NewsCollabCommentCardProps) { - {comment.comment} + diff --git a/app/components/newsPage/cards/NewsCollabQuestionCard.tsx b/app/components/newsPage/cards/NewsCollabQuestionCard.tsx index bf68dbf5..df793214 100644 --- a/app/components/newsPage/cards/NewsCollabQuestionCard.tsx +++ b/app/components/newsPage/cards/NewsCollabQuestionCard.tsx @@ -2,6 +2,7 @@ import Typography from '@mui/material/Typography'; import { CollaborationQuestion } from '@/common/types'; import { parseStringForLinks } from '@/components/common/LinkString'; +import { HighlightText } from '@/utils/highlightText'; interface NewsCollabQuestionCardProps { question: CollaborationQuestion; } @@ -13,7 +14,7 @@ function NewsCollabQuestionCard(props: NewsCollabQuestionCardProps) { return ( <> - {title} + diff --git a/app/components/newsPage/cards/NewsEventCard.tsx b/app/components/newsPage/cards/NewsEventCard.tsx index e3252f2b..c0e31d64 100644 --- a/app/components/newsPage/cards/NewsEventCard.tsx +++ b/app/components/newsPage/cards/NewsEventCard.tsx @@ -12,6 +12,7 @@ import EventCardHeader from '@/components/landing/eventsSection/EventCardHeader' import * as m from '@/src/paraglide/messages.js'; import theme from '@/styles/theme'; import { getImageByBreakpoint } from '@/utils/helpers'; +import { HighlightText } from '@/utils/highlightText'; interface NewsSurveyCardProps { event: Event; @@ -44,10 +45,10 @@ function NewsEventCard(props: NewsSurveyCardProps) { - {event.title} + - {event.description} + diff --git a/app/components/newsPage/cards/NewsProjectCard.tsx b/app/components/newsPage/cards/NewsProjectCard.tsx index 6c02e481..d62763a5 100644 --- a/app/components/newsPage/cards/NewsProjectCard.tsx +++ b/app/components/newsPage/cards/NewsProjectCard.tsx @@ -14,6 +14,7 @@ import VisibleContributors from '@/components/project-details/VisibleContributor import * as m from '@/src/paraglide/messages.js'; import theme from '@/styles/theme'; import { getImageByBreakpoint } from '@/utils/helpers'; +import { HighlightText } from '@/utils/highlightText'; import CommentOverview from './common/CommentOverview'; @@ -59,7 +60,7 @@ function NewsProjectCard(props: NewsProjectCardProps) { - {project.title} + - {project.summary} + diff --git a/app/components/newsPage/cards/common/CommentOverview.tsx b/app/components/newsPage/cards/common/CommentOverview.tsx index 6fa43829..71be2632 100644 --- a/app/components/newsPage/cards/common/CommentOverview.tsx +++ b/app/components/newsPage/cards/common/CommentOverview.tsx @@ -4,6 +4,7 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { parseStringForLinks } from '@/components/common/LinkString'; +import { HighlightText } from '@/utils/highlightText'; interface CommentOverviewProps { title: string; @@ -25,7 +26,7 @@ const CommentOverviewContent = (props: CommentOverviewProps) => { - {title} + diff --git a/app/types/strapi-graphql-env.d.ts b/app/types/strapi-graphql-env.d.ts index bf44ffd9..e77ef79a 100644 --- a/app/types/strapi-graphql-env.d.ts +++ b/app/types/strapi-graphql-env.d.ts @@ -1,20 +1,7 @@ /* eslint-disable */ /* prettier-ignore */ -/** An IntrospectionQuery representation of your schema. - * - * @remarks - * This is an introspection of your schema saved as a file by GraphQLSP. - * It will automatically be used by `gql.tada` to infer the types of your GraphQL documents. - * If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to - * instead save to a .ts instead of a .d.ts file. - */ -export type introspection = { - name: never; - query: 'Query'; - mutation: 'Mutation'; - subscription: never; - types: { +export type introspection_types = { 'Boolean': unknown; 'BooleanFilterInput': { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'between'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'contains'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'containsi'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'endsWith'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'eq'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'eqi'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'gt'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'gte'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'in'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'lt'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'lte'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'ne'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'nei'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'notContains'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'notContainsi'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'notIn'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'notNull'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'null'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; }; defaultValue: null }, { name: 'startsWith'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }]; }; 'CollaborationQuestion': { kind: 'OBJECT'; name: 'CollaborationQuestion'; fields: { 'authors': { name: 'authors'; type: { kind: 'OBJECT'; name: 'InnoUserRelationResponseCollection'; ofType: null; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'isPlatformFeedback': { name: 'isPlatformFeedback'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'project': { name: 'project'; type: { kind: 'OBJECT'; name: 'ProjectEntityResponse'; ofType: null; } }; 'publishedAt': { name: 'publishedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'title': { name: 'title'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; }; }; @@ -176,7 +163,22 @@ export type introspection = { 'UsersPermissionsUserFiltersInput': { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; isOneOf: false; inputFields: [{ name: 'and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'blocked'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'confirmationToken'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'confirmed'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanFilterInput'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'email'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDFilterInput'; ofType: null; }; defaultValue: null }, { name: 'not'; type: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserFiltersInput'; ofType: null; }; }; defaultValue: null }, { name: 'password'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'provider'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'resetPasswordToken'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }, { name: 'role'; type: { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsRoleFiltersInput'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateTimeFilterInput'; ofType: null; }; defaultValue: null }, { name: 'username'; type: { kind: 'INPUT_OBJECT'; name: 'StringFilterInput'; ofType: null; }; defaultValue: null }]; }; 'UsersPermissionsUserInput': { kind: 'INPUT_OBJECT'; name: 'UsersPermissionsUserInput'; isOneOf: false; inputFields: [{ name: 'blocked'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'confirmationToken'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'confirmed'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; defaultValue: null }, { name: 'email'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'password'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'provider'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'resetPasswordToken'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'role'; type: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; defaultValue: null }, { name: 'username'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; 'UsersPermissionsUserRelationResponseCollection': { kind: 'OBJECT'; name: 'UsersPermissionsUserRelationResponseCollection'; fields: { 'data': { name: 'data'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UsersPermissionsUserEntity'; ofType: null; }; }; }; } }; }; }; - }; +}; + +/** An IntrospectionQuery representation of your schema. + * + * @remarks + * This is an introspection of your schema saved as a file by GraphQLSP. + * It will automatically be used by `gql.tada` to infer the types of your GraphQL documents. + * If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to + * instead save to a .ts instead of a .d.ts file. + */ +export type introspection = { + name: never; + query: 'Query'; + mutation: 'Mutation'; + subscription: never; + types: introspection_types; }; import * as gqlTada from 'gql.tada'; diff --git a/app/utils/highlightText.tsx b/app/utils/highlightText.tsx new file mode 100644 index 00000000..cf0f5398 --- /dev/null +++ b/app/utils/highlightText.tsx @@ -0,0 +1,23 @@ +import { useHighlightContext } from '@/app/contexts/news-feed-highlight-context'; +import React, { useMemo } from 'react'; + +export function HighlightText({ text }: { text: string | undefined }) { + const { highlightString } = useHighlightContext(); + const regex = useMemo(() => new RegExp(`(${highlightString})`, 'gi'), [highlightString]); + + const renderText = (text: string | undefined) => { + if (!text) return; + const parts = text.split(regex); + return parts.map((part, index) => + part.toLowerCase() === highlightString?.toLowerCase() ? ( + + {part} + + ) : ( + part + ), + ); + }; + + return <>{renderText(text)}; +} From 92f82dd00436a3235bdb5998f2ba3a45c127d353 Mon Sep 17 00:00:00 2001 From: Nadine Blaas <122269231+naadnn@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:44:36 +0200 Subject: [PATCH 4/6] refactor: added logging to post_comments file and improved code according to feedback --- app/components/newsPage/threads/actions.ts | 28 +-- app/repository/db/post_comment.ts | 244 ++++++++++++------- app/utils/newsFeed/newsFeedSync.ts | 4 +- app/utils/notification/notificationSender.ts | 14 +- 4 files changed, 169 insertions(+), 121 deletions(-) diff --git a/app/components/newsPage/threads/actions.ts b/app/components/newsPage/threads/actions.ts index 39b0f5da..a17c0c51 100644 --- a/app/components/newsPage/threads/actions.ts +++ b/app/components/newsPage/threads/actions.ts @@ -6,10 +6,6 @@ import { StatusCodes } from 'http-status-codes'; import { UserSession } from '@/common/types'; import { addComment } from '@/services/commentService'; import { withAuth } from '@/utils/auth'; -import { dbError, InnoPlatformError } from '@/utils/errors'; -import getLogger from '@/utils/logger'; - -const logger = getLogger(); interface AddUserComment { objectId: string; @@ -19,23 +15,13 @@ interface AddUserComment { } export const addUserComment = withAuth(async (user: UserSession, body: AddUserComment) => { - try { - const { comment, commentType, objectId, parentCommentId } = body; - const author = user; + const { comment, commentType, objectId, parentCommentId } = body; + const author = user; - const createdComment = await addComment({ author, comment, commentType, objectId, parentCommentId }); + const createdComment = await addComment({ author, comment, commentType, objectId, parentCommentId }); - return { - status: StatusCodes.OK, - data: createdComment, - }; - } catch (err) { - const error: InnoPlatformError = dbError( - `Adding a ${CommentType} by user ${user.providerId}`, - err as Error, - user.providerId, - ); - logger.error(error); - throw err; - } + return { + status: StatusCodes.OK, + data: createdComment, + }; }); diff --git a/app/repository/db/post_comment.ts b/app/repository/db/post_comment.ts index 8650ad13..fe917265 100644 --- a/app/repository/db/post_comment.ts +++ b/app/repository/db/post_comment.ts @@ -21,32 +21,54 @@ export async function countPostResponses(client: PrismaClient, postId: string) { } export async function getNewsCommentsByPostId(client: PrismaClient, postId: string) { - return await client.postComment.findMany({ - where: { - postId, - }, - ...defaultParams, - }); + try { + return await client.postComment.findMany({ + where: { + postId, + }, + ...defaultParams, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Get news comments by post with id: ${postId}`, err as Error, postId); + logger.error(error); + throw err; + } } export async function getPostComments(client: PrismaClient, commentId: string) { - return await client.postComment.findMany({ - where: { - comment: { - parentId: commentId, + try { + return await client.postComment.findMany({ + where: { + comment: { + parentId: commentId, + }, }, - }, - ...defaultParams, - }); + ...defaultParams, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Get post for comment with id: ${commentId}`, err as Error, commentId); + logger.error(error); + throw err; + } } export async function getPostCommentById(client: PrismaClient, commentId: string) { - return await client.postComment.findUnique({ - where: { - id: commentId, - }, - ...defaultParams, - }); + try { + return await client.postComment.findUnique({ + where: { + id: commentId, + }, + ...defaultParams, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Get post by id for comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } export async function addPostCommentToDb( @@ -56,47 +78,69 @@ export async function addPostCommentToDb( comment: string, parentCommentId?: string, ) { - return await client.postComment.create({ - data: { - postId, - comment: { - create: { - author, - objectType: 'POST_COMMENT', - text: comment, - parentId: parentCommentId, + try { + return await client.postComment.create({ + data: { + postId, + comment: { + create: { + author, + objectType: 'POST_COMMENT', + text: comment, + parentId: parentCommentId, + }, }, }, - }, - ...defaultParams, - }); + ...defaultParams, + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Add comment to post with id: ${postId} by user ${author}`, + err as Error, + postId, + ); + logger.error(error); + throw err; + } } export async function updatePostCommentInDb(client: PrismaClient, commentId: string, updatedText: string) { - return await client.postComment.update({ - where: { - commentId, - }, - data: { - comment: { - update: { - text: updatedText, + try { + return await client.postComment.update({ + where: { + commentId, + }, + data: { + comment: { + update: { + text: updatedText, + }, }, }, - }, - ...defaultParams, - }); + ...defaultParams, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Update post comment with id: ${commentId}`, err as Error, commentId); + logger.error(error); + throw err; + } } export async function deletePostCommentInDb(client: PrismaClient, commentId: string) { - return client.comment.delete({ - where: { - id: commentId, - }, - include: { - postComment: true, - }, - }); + try { + return client.comment.delete({ + where: { + id: commentId, + }, + include: { + postComment: true, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Delete post for comment with id: ${commentId}`, err as Error, commentId); + logger.error(error); + throw err; + } } export async function isPostCommentUpvotedBy(client: PrismaClient, commentId: string, upvotedBy: string) { @@ -107,50 +151,76 @@ export async function isPostCommentUpvotedBy(client: PrismaClient, commentId: st upvotedBy: { has: upvotedBy }, }, }); - return likedCommentsCount > 0; + try { + return likedCommentsCount > 0; + } catch (err) { + const error: InnoPlatformError = dbError( + `Get upvote by user for comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } export async function handlePostCommentUpvotedByInDb(client: PrismaClient, commentId: string, upvotedBy: string) { - return client.$transaction(async (tx) => { - const result = await tx.comment.findFirst({ - where: { id: commentId, objectType: 'POST_COMMENT' }, - select: { - upvotedBy: true, - }, - }); - - const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); - if (result?.upvotedBy.includes(upvotedBy)) { - return tx.comment.update({ - where: { - id: commentId, - objectType: 'POST_COMMENT', - }, - data: { - upvotedBy: upvotes, + try { + return client.$transaction(async (tx) => { + const result = await tx.comment.findFirst({ + where: { id: commentId, objectType: 'POST_COMMENT' }, + select: { + upvotedBy: true, }, }); - } - if (result) { - return tx.comment.update({ - where: { - id: commentId, - objectType: 'POST_COMMENT', - }, - data: { - upvotedBy: { push: upvotedBy }, - }, - }); - } - }); + const upvotes = result?.upvotedBy.filter((upvote) => upvote !== upvotedBy); + if (result?.upvotedBy.includes(upvotedBy)) { + return tx.comment.update({ + where: { + id: commentId, + objectType: 'POST_COMMENT', + }, + data: { + upvotedBy: upvotes, + }, + }); + } + + if (result) { + return tx.comment.update({ + where: { + id: commentId, + objectType: 'POST_COMMENT', + }, + data: { + upvotedBy: { push: upvotedBy }, + }, + }); + } + }); + } catch (err) { + const error: InnoPlatformError = dbError( + `Handle post comment upvote for comment with id: ${commentId}`, + err as Error, + commentId, + ); + logger.error(error); + throw err; + } } export async function getPostCommentsStartingFrom(client: PrismaClient, from: Date) { - return await client.comment.findMany({ - where: { objectType: 'POST_COMMENT', updatedAt: { gte: from } }, - include: { - postComment: true, - }, - }); + try { + return await client.comment.findMany({ + where: { objectType: 'POST_COMMENT', updatedAt: { gte: from } }, + include: { + postComment: true, + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Get post comments starting from ${from}`, err as Error); + logger.error(error); + throw err; + } } diff --git a/app/utils/newsFeed/newsFeedSync.ts b/app/utils/newsFeed/newsFeedSync.ts index 3d52e70a..df5c1408 100644 --- a/app/utils/newsFeed/newsFeedSync.ts +++ b/app/utils/newsFeed/newsFeedSync.ts @@ -153,8 +153,8 @@ const removeKeysAndSaveNewEntriesAsTransaction = async ( const aggregatePosts = async ({ from }: { from: Date }): Promise => { // posts fetched from prisma, hence no pagination required const posts = await getPostsStartingFrom(dbClient, from); - if (!posts || posts.length === 0) { - logger.warn('No posts found to sync'); + if (posts.length === 0) { + logger.info('No posts found to sync'); } const mapEntries = posts.map(async (post) => createNewsFeedEntryForPost(post)); const newsFeedEntries = await getPromiseResults(mapEntries); diff --git a/app/utils/notification/notificationSender.ts b/app/utils/notification/notificationSender.ts index 0238a450..4543549b 100644 --- a/app/utils/notification/notificationSender.ts +++ b/app/utils/notification/notificationSender.ts @@ -32,17 +32,9 @@ export type NotificationRequest = { }; export const notifyFollowers = async (follows: PrismaFollow[], topic: NotificationTopic, text: string, url: string) => { - try { - const buildNotifications = follows.map((follow) => createNotificationForFollow(follow, topic, text, url)); - const notifications = await getPromiseResults(buildNotifications); - sendPushNotifications(notifications); - } catch (err) { - logger.error( - `Failed to notify followers with ids (Topic: '${topic}', text: '${text}', url: '${url}'): ${follows.map((follow) => follow.followedBy)}`, - follows, - err, - ); - } + const buildNotifications = follows.map((follow) => createNotificationForFollow(follow, topic, text, url)); + const notifications = await getPromiseResults(buildNotifications); + sendPushNotifications(notifications); }; export const sendPushNotifications = (notifications: NotificationRequest[]) => { From 269df19151a724cc3962046b54acab7fabd0ea11 Mon Sep 17 00:00:00 2001 From: Georgia Moldovan <48566702+georgimld@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:05:34 +0200 Subject: [PATCH 5/6] fix: add allowed origin and format env variable errors --- app/.env.example | 3 ++ app/config/client.js | 4 +-- app/config/helper.js | 5 +++ app/config/server.js | 73 ++++++++++++++++++++++++++++------------ app/next.config.js | 6 +--- app/package-lock.json | 4 +-- strapi/package-lock.json | 4 +-- 7 files changed, 67 insertions(+), 32 deletions(-) diff --git a/app/.env.example b/app/.env.example index 8a99c4e7..0de55574 100644 --- a/app/.env.example +++ b/app/.env.example @@ -45,3 +45,6 @@ VAPID_ADMIN_EMAIL= NEXT_PUBLIC_APP_INSIGHTS_CONNECTION_STRING= NEXT_PUBLIC_APP_INSIGHTS_INSTRUMENTATION_KEY= APP_INSIGHTS_SERVICE_NAME= + +# Allowed origin urls used for triggering server actions, separated by comma +ALLOWED_ORIGINS= diff --git a/app/config/client.js b/app/config/client.js index a35c8336..a628f301 100644 --- a/app/config/client.js +++ b/app/config/client.js @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ const { z } = require('zod'); -const { someOrAllNotSet } = require('./helper'); +const { someOrAllNotSet, formatErrors } = require('./helper'); const { env } = require('next-runtime-env'); const Config = z @@ -70,7 +70,7 @@ const clientConfig = Config.safeParse({ }); if (!clientConfig.success) { - console.error(clientConfig.error.issues); + console.error(formatErrors(clientConfig.error)); throw new Error('There is an error with the client environment variables'); } diff --git a/app/config/helper.js b/app/config/helper.js index b7936aaa..e1abb863 100644 --- a/app/config/helper.js +++ b/app/config/helper.js @@ -5,4 +5,9 @@ const someOrAllNotSet = (arr) => { return someUndefined && !allDefined && !allUndefined; }; +const formatErrors = (error) => { + return JSON.stringify(error.issues); +}; + module.exports.someOrAllNotSet = someOrAllNotSet; +module.exports.formatErrors = formatErrors; diff --git a/app/config/server.js b/app/config/server.js index 621f0d94..e4672f80 100644 --- a/app/config/server.js +++ b/app/config/server.js @@ -2,7 +2,7 @@ const { z } = require('zod'); const { clientConfig } = require('./client'); -const { someOrAllNotSet } = require('./helper'); +const { someOrAllNotSet, formatErrors } = require('./helper'); const { cond } = require('lodash'); const { env } = require('next-runtime-env'); @@ -14,10 +14,10 @@ const RequiredBuildTimeEnv = z .object({ STAGE: z.enum(['development', 'test', 'build', 'production', 'lint']).default('development'), NEXT_PUBLIC_BUILDTIMESTAMP: z - .string({ errorMap: () => ({ message: 'NEXT_PUBLIC_BUILDTIMESTAMP is not' }) }) + .string({ errorMap: () => ({ message: 'NEXT_PUBLIC_BUILDTIMESTAMP is not set' }) }) .default(''), NEXT_PUBLIC_CI_COMMIT_SHA: z - .string({ errorMap: () => ({ message: 'NEXT_PUBLIC_CI_COMMIT_SHA is not' }) }) + .string({ errorMap: () => ({ message: 'NEXT_PUBLIC_CI_COMMIT_SHA is not set' }) }) .default(''), }) .superRefine((values, ctx) => { @@ -73,15 +73,19 @@ const RequiredRunTimeEnv = z STAGE, REDIS_URL, NEWS_FEED_SYNC_SECRET, + POSTGRES_USER, + POSTGRES_PASSWORD, } = values; - const required = [ - DATABASE_URL, - NEXTAUTH_URL, - NEXTAUTH_SECRET, - STRAPI_TOKEN, - HTTP_BASIC_AUTH, - REDIS_URL, - NEWS_FEED_SYNC_SECRET, + const requiredKeys = [ + 'DATABASE_URL', + 'NEXTAUTH_URL', + 'NEXTAUTH_SECRET', + 'STRAPI_TOKEN', + 'HTTP_BASIC_AUTH', + 'REDIS_URL', + 'NEWS_FEED_SYNC_SECRET', + 'POSTGRES_USER', + 'POSTGRES_PASSWORD', ]; const checkAndAddIssue = (condition, message, ctx) => { @@ -93,9 +97,11 @@ const RequiredRunTimeEnv = z } }; - const validateEnvVariables = (required, DATABASE_URL, REDIS_URL, HTTP_BASIC_AUTH, ctx) => { + const validateEnvVariables = (ctx) => { + requiredKeys.forEach((env) => checkAndAddIssue(values[env].trim().length === 0, `${env} is not set`, ctx)); + checkAndAddIssue( - required.some((el) => el === ''), + requiredKeys.some((el) => el === ''), 'Not all required env variables are set!', ctx, ); @@ -115,7 +121,7 @@ const RequiredRunTimeEnv = z ); }; - validateEnvVariables(required, DATABASE_URL, REDIS_URL, HTTP_BASIC_AUTH, ctx); + validateEnvVariables(ctx); }); // Optional at run-time @@ -134,6 +140,10 @@ const OptionalRunTimeEnv = z STRAPI_PUSH_NOTIFICATION_SECRET: z.string().default(''), APP_INSIGHTS_SERVICE_NAME: z.string().default(''), ANALYZE: z.boolean().default(false), + ALLOWED_ORIGINS: z + .string() + .transform((origins) => origins.split(',')) + .optional(), }) .superRefine((values, ctx) => { //Ignore the validation at build stage @@ -211,7 +221,12 @@ const OptionalRunTimeEnv = z message: 'Looks like the required environment variables for push-notifications are not set in the UI but in the server (or vice versa)', code: z.ZodIssueCode.custom, - path: ['VAPID_PRIVATE_KEY', 'VAPID_ADMIN_EMAIL', 'NEXT_PUBLIC_VAPID_PUBLIC_KEY', 'STRAPI_PUSH_NOTIFICATION_SECRET'], + path: [ + 'VAPID_PRIVATE_KEY', + 'VAPID_ADMIN_EMAIL', + 'NEXT_PUBLIC_VAPID_PUBLIC_KEY', + 'STRAPI_PUSH_NOTIFICATION_SECRET', + ], }); } @@ -230,12 +245,28 @@ const OptionalRunTimeEnv = z // If we run 'next build' the required runtime env variables can be empty, at run-time checks will be applied... // NEXT_PUBLIC_* are checked in client.js -const requiredBuildEnv = RequiredBuildTimeEnv.parse(process.env); -const optionalRunTimeEnv = OptionalRunTimeEnv.parse(process.env); -const requiredRunTimeEnv = RequiredRunTimeEnv.parse(process.env); +const requiredBuildTimeEnv = RequiredBuildTimeEnv.safeParse(process.env); +const requiredRunTimeEnv = RequiredRunTimeEnv.safeParse(process.env); +const optionalRunTimeEnv = OptionalRunTimeEnv.safeParse(process.env); + +if (!requiredRunTimeEnv.success) { + console.error(`Required runtime variables are not set correctly: ${formatErrors(requiredRunTimeEnv.error)}`); +} + +if (!requiredBuildTimeEnv.success) { + console.error(`Required build variables are not set correctly: ${formatErrors(requiredBuildTimeEnv.error)}`); +} + +if (!optionalRunTimeEnv.success) { + console.warn(`Optional runtime variables are not set correctly: ${formatErrors(optionalRunTimeEnv.error)}`); + throw new Error('There is an error with the optional runtime environment variables'); +} +if (!requiredRunTimeEnv.success || !requiredBuildTimeEnv.success) { + process.exit(1); +} module.exports.serverConfig = { - ...requiredBuildEnv, - ...requiredRunTimeEnv, - ...optionalRunTimeEnv, + ...requiredBuildTimeEnv.data, + ...requiredRunTimeEnv.data, + ...optionalRunTimeEnv.data, }; diff --git a/app/next.config.js b/app/next.config.js index ef0776bf..ca0f509e 100644 --- a/app/next.config.js +++ b/app/next.config.js @@ -88,11 +88,7 @@ const nextConfig = { }, instrumentationHook: true, serverActions: { - allowedOrigins: [ - '***URL_REMOVED***', - '***URL_REMOVED***', - '***URL_REMOVED***', - ], + allowedOrigins: serverConfig.NEXT_PUBLIC_ALLOWED_ORIGINS }, }, i18n: { diff --git a/app/package-lock.json b/app/package-lock.json index 8b5e1f24..81f12091 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "app", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "app", - "version": "1.0.0", + "version": "1.0.1", "dependencies": { "@azure/monitor-opentelemetry-exporter": "1.0.0-beta.21", "@emoji-mart/data": "^1.1.2", diff --git a/strapi/package-lock.json b/strapi/package-lock.json index 0e38238d..538d59aa 100644 --- a/strapi/package-lock.json +++ b/strapi/package-lock.json @@ -1,12 +1,12 @@ { "name": "strapi-project", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "strapi-project", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "dependencies": { "@strapi/plugin-graphql": "4.25.9", From 7789c3d0c3f385633b210201300084bd5f8e568b Mon Sep 17 00:00:00 2001 From: "andrea.smiesna" <122895253+andrea-smiesna@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:14:06 +0200 Subject: [PATCH 6/6] refactor: add logging to posts and collaboration comments --- app/repository/db/collaboration_comment.ts | 8 ++- app/repository/db/posts.ts | 58 +++++++++++----------- app/utils/newsFeed/newsFeedSync.ts | 4 +- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/app/repository/db/collaboration_comment.ts b/app/repository/db/collaboration_comment.ts index 80c2157a..d15ab5aa 100644 --- a/app/repository/db/collaboration_comment.ts +++ b/app/repository/db/collaboration_comment.ts @@ -47,7 +47,13 @@ export async function getCollaborationCommentUpvotedBy(client: PrismaClient, com } export async function getCollaborationCommentStartingFrom(client: PrismaClient, from: Date) { - return await client.collaborationComment.findMany({ where: { createdAt: { gte: from } } }); + try { + return await client.collaborationComment.findMany({ where: { createdAt: { gte: from } } }); + } catch (err) { + const error: InnoPlatformError = dbError(`Getting collaboration comment starting from ${from}`, err as Error); + logger.error(error); + throw err; + } } export async function addCollaborationCommentToDb( diff --git a/app/repository/db/posts.ts b/app/repository/db/posts.ts index d5dd5396..18ad4168 100644 --- a/app/repository/db/posts.ts +++ b/app/repository/db/posts.ts @@ -13,25 +13,19 @@ export async function getPostById(client: PrismaClient, id: string) { } export async function getPostsStartingFrom(client: PrismaClient, from: Date) { - try { - const [posts, postsComments] = await Promise.all([ - getPostsFromDbStartingFrom(client, from), - getPostCommentsStartingFrom(client, from), - ]); + const [posts, postsComments] = await Promise.all([ + getPostsFromDbStartingFrom(client, from), + getPostCommentsStartingFrom(client, from), + ]); - // Get unique ids of posts - const postIds = getUniqueValues( - postsComments.map((comment) => comment.postComment?.postId).filter((id): id is string => id !== undefined), - ); - const postsWithComments = await getPostsByIds(client, postIds); - const allPosts = [...posts, ...postsWithComments]; - const uniquePosts = allPosts.filter((post, index, self) => index === self.findIndex((t) => t.id === post.id)); - return uniquePosts; - } catch (err) { - const error: InnoPlatformError = dbError(`Getting post comments starting from ${Date}`, err as Error); - logger.error(error); - throw err; - } + // Get unique ids of posts + const postIds = getUniqueValues( + postsComments.map((comment) => comment.postComment?.postId).filter((id): id is string => id !== undefined), + ); + const postsWithComments = await getPostsByIds(client, postIds); + const allPosts = [...posts, ...postsWithComments]; + const uniquePosts = allPosts.filter((post, index, self) => index === self.findIndex((t) => t.id === post.id)); + return uniquePosts; } export async function getPostsByIds(client: PrismaClient, ids: string[]) { @@ -39,18 +33,24 @@ export async function getPostsByIds(client: PrismaClient, ids: string[]) { } export async function getPostsFromDbStartingFrom(client: PrismaClient, from: Date) { - return await client.post.findMany({ - where: { - OR: [ - { - createdAt: { - gte: from, + try { + return await client.post.findMany({ + where: { + OR: [ + { + createdAt: { + gte: from, + }, }, - }, - { updatedAt: { gte: from } }, - ], - }, - }); + { updatedAt: { gte: from } }, + ], + }, + }); + } catch (err) { + const error: InnoPlatformError = dbError(`Getting posts starting from ${from}`, err as Error); + logger.error(error); + throw err; + } } export async function addPostToDb(client: PrismaClient, content: string, author: string, anonymous: boolean) { diff --git a/app/utils/newsFeed/newsFeedSync.ts b/app/utils/newsFeed/newsFeedSync.ts index df5c1408..7430171f 100644 --- a/app/utils/newsFeed/newsFeedSync.ts +++ b/app/utils/newsFeed/newsFeedSync.ts @@ -164,8 +164,8 @@ const aggregatePosts = async ({ from }: { from: Date }): Promise => { // collaboration comments fetched from prisma, hence no pagination required const comments = await getCollaborationCommentStartingFrom(dbClient, from); - if (!comments || comments.length === 0) { - logger.warn('No collaboration comments found to sync'); + if (comments.length === 0) { + logger.info('No collaboration comments found to sync'); } const mapToNewsFeedEntries = comments.map(async (comment) => createNewsFeedEntryForComment(comment)); const newsFeedEntries = await getPromiseResults(mapToNewsFeedEntries);