Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

알림 로직 전부 큐로 변경 #2733

Merged
merged 1 commit into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions apps/website/src/lib/notification/maker/comment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { and, eq } from 'drizzle-orm';
import { database, PostComments, PostRevisions, Posts, Profiles, Spaces } from '$lib/server/database';
import { createNotification, useFirstRow } from '$lib/server/utils';
import type { NotificationMaker } from './type';

export const commentNotificationMaker: NotificationMaker = async (commentId) => {
const comment = await database
.select({
id: PostComments.id,
content: PostComments.content,
commenterId: PostComments.userId,
profileId: PostComments.profileId,
profileName: Profiles.name,
spaceId: Posts.spaceId,
spaceSlug: Spaces.slug,
postPermalink: Posts.permalink,
postWriterId: Posts.userId,
postTitle: PostRevisions.title,
parentId: PostComments.parentId,
})
.from(PostComments)
.innerJoin(Posts, eq(PostComments.postId, Posts.id))
.innerJoin(Spaces, eq(Posts.spaceId, Spaces.id))
.innerJoin(PostRevisions, eq(Posts.publishedRevisionId, PostRevisions.id))
.innerJoin(Profiles, eq(PostComments.profileId, Profiles.id))
.where(and(eq(PostComments.id, commentId), eq(PostComments.state, 'ACTIVE')))
.then(useFirstRow);

if (!comment || !comment.spaceId) {
return;
}

let notifiedUserId = comment.postWriterId;

if (comment.parentId) {
const parentComment = await database
.select({
userId: PostComments.userId,
})
.from(PostComments)
.where(and(eq(PostComments.id, comment.parentId), eq(PostComments.state, 'ACTIVE')))
.then(useFirstRow);

if (parentComment) {
if (parentComment.userId === comment.commenterId) {
return;
}

notifiedUserId = parentComment.userId;
}
} else if (notifiedUserId === comment.commenterId) {
return;
}

await createNotification({
userId: notifiedUserId,
category: 'COMMENT',
actorId: comment.profileId,
data: {
commentId: comment.id,
},
pushTitle: comment.postTitle ?? '(제목 없음)',
pushBody: `${comment.profileName}님이 "${comment.content}" 댓글을 남겼어요.`,
pushPath: `/${comment.spaceSlug}/${comment.postPermalink}`,
});
};
43 changes: 43 additions & 0 deletions apps/website/src/lib/notification/maker/emoji-reaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { eq } from 'drizzle-orm';
import { database, PostReactions, PostRevisions, Posts, Spaces } from '$lib/server/database';
import { createNotification, getSpaceProfile, useFirstRow } from '$lib/server/utils';
import type { NotificationMaker } from './type';

export const emojiReactionNotificationMaker: NotificationMaker = async (reactionId) => {
const reaction = await database
.select({
emoji: PostReactions.emoji,
postId: Posts.id,
postPermalink: Posts.permalink,
title: PostRevisions.title,
postWriterId: Posts.userId,
spaceId: Posts.spaceId,
spaceSlug: Spaces.slug,
emojigazerId: PostReactions.userId,
})
.from(PostReactions)
.innerJoin(Posts, eq(PostReactions.postId, Posts.id))
.innerJoin(PostRevisions, eq(Posts.publishedRevisionId, PostRevisions.id))
.innerJoin(Spaces, eq(Posts.spaceId, Spaces.id))
.where(eq(PostReactions.id, reactionId))
.then(useFirstRow);

if (!reaction || !reaction.spaceId || reaction.postWriterId === reaction.emojigazerId) {
return;
}

const profile = await getSpaceProfile({ spaceId: reaction.spaceId, userId: reaction.emojigazerId });

await createNotification({
userId: reaction.postWriterId,
category: 'EMOJI_REACTION',
actorId: profile.id,
data: {
postId: reaction.postId,
emoji: reaction.emoji,
},
pushTitle: reaction.title ?? '(제목 없음)',
pushBody: `${profile.name}님이 이모지를 남겼어요.`,
pushPath: `/${reaction.spaceSlug}/${reaction.postPermalink}`,
});
};
11 changes: 11 additions & 0 deletions apps/website/src/lib/notification/maker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { commentNotificationMaker } from './comment';
import { emojiReactionNotificationMaker } from './emoji-reaction';
import { purchaseNotificationMaker } from './purchase';
import { subscribeNotificationMaker } from './subscribe';

export const notificationMaker = {
EMOJI_REACTION: emojiReactionNotificationMaker,
COMMENT: commentNotificationMaker,
PURCHASE: purchaseNotificationMaker,
SUBSCRIBE: subscribeNotificationMaker,
};
38 changes: 38 additions & 0 deletions apps/website/src/lib/notification/maker/purchase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { eq } from 'drizzle-orm';
import { database, PostPurchases, PostRevisions, Posts } from '$lib/server/database';
import { createNotification, getSpaceProfile, useFirstRow } from '$lib/server/utils';
import type { NotificationMaker } from './type';

export const purchaseNotificationMaker: NotificationMaker = async (purchaseId) => {
const purchase = await database
.select({
postId: PostPurchases.postId,
buyerId: PostPurchases.userId,
postWriterId: Posts.userId,
spaceId: Posts.spaceId,
postTitle: PostRevisions.title,
})
.from(PostPurchases)
.innerJoin(Posts, eq(PostPurchases.postId, Posts.id))
.innerJoin(PostRevisions, eq(Posts.publishedRevisionId, PostRevisions.id))
.where(eq(PostPurchases.id, purchaseId))
.then(useFirstRow);

if (!purchase || !purchase.spaceId) {
return;
}

const profile = await getSpaceProfile({ spaceId: purchase.spaceId, userId: purchase.buyerId });

await createNotification({
userId: purchase.postWriterId,
category: 'PURCHASE',
actorId: profile.id,
data: {
postId: purchase.postId,
},
pushTitle: purchase.postTitle ?? '(제목 없음)',
pushBody: `${profile.name}님이 포스트를 구매했어요.`,
pushPath: '/me/revenue',
});
};
48 changes: 48 additions & 0 deletions apps/website/src/lib/notification/maker/subscribe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { eq } from 'drizzle-orm';
import { database, SpaceFollows, SpaceMembers, Spaces } from '$lib/server/database';
import { createNotification, getSpaceProfile, useFirstRow } from '$lib/server/utils';
import type { NotificationMaker } from './type';

export const subscribeNotificationMaker: NotificationMaker = async (spaceFollowId) => {
const spaceFollow = await database
.select({
followerId: SpaceFollows.userId,
spaceId: SpaceFollows.spaceId,
spaceSlug: Spaces.slug,
spaceName: Spaces.name,
})
.from(SpaceFollows)
.innerJoin(Spaces, eq(SpaceFollows.spaceId, Spaces.id))
.where(eq(SpaceFollows.id, spaceFollowId))
.then(useFirstRow);

if (!spaceFollow) {
return;
}

const profile = await getSpaceProfile({ spaceId: spaceFollow.spaceId, userId: spaceFollow.followerId });

const spaceMemberIds = await database
.select({
userId: SpaceMembers.userId,
})
.from(SpaceMembers)
.where(eq(SpaceMembers.spaceId, spaceFollow.spaceId))
.then((rows) => rows.map((row) => row.userId));

await Promise.all(
spaceMemberIds.map(async (userId) => {
await createNotification({
userId,
category: 'SUBSCRIBE',
actorId: profile.id,
data: {
spaceId: spaceFollow.spaceId,
},
pushTitle: spaceFollow.spaceName,
pushBody: `${profile.name}님이 스페이스를 구독했어요.`,
pushPath: `/${spaceFollow.spaceSlug}`,
});
}),
);
};
3 changes: 3 additions & 0 deletions apps/website/src/lib/notification/maker/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { MaybePromise } from '$lib/types';

export type NotificationMaker = (targetId: string) => MaybePromise<void>;
59 changes: 21 additions & 38 deletions apps/website/src/lib/server/graphql/schemas/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
UserPersonalIdentities,
} from '$lib/server/database';
import { enqueueJob } from '$lib/server/jobs';
import { getSpaceMember, Loader, makeMasquerade } from '$lib/server/utils';
import { getSpaceMember, Loader, makeMasquerade, useFirstRowOrThrow } from '$lib/server/utils';
import { builder } from '../builder';
import { createObjectRef } from '../utils';
import { Post } from './post';
Expand Down Expand Up @@ -369,10 +369,8 @@ builder.mutationFields((t) => ({
}
}

let notificationTargetUserId;

if (input.parentId) {
const rows = await database
await database
.select()
.from(PostComments)
.where(
Expand All @@ -381,15 +379,8 @@ builder.mutationFields((t) => ({
eq(PostComments.postId, input.postId),
eq(PostComments.state, 'ACTIVE'),
),
);

if (rows.length === 0) {
throw new NotFoundError();
}

notificationTargetUserId = rows[0].userId;
} else {
notificationTargetUserId = post.userId;
)
.then(useFirstRowOrThrow(new NotFoundError()));
}

let profileId: string;
Expand All @@ -412,32 +403,24 @@ builder.mutationFields((t) => ({
profileId = masquerade.profileId;
}

const commentId = await database.transaction(async (tx) => {
const [comment] = await tx
.insert(PostComments)
.values({
postId: input.postId,
userId: context.session.userId,
profileId,
parentId: input.parentId,
content: input.content,
visibility: input.visibility,
state: 'ACTIVE',
})
.returning({ id: PostComments.id });

return comment.id;
});
const commentId = await database
.insert(PostComments)
.values({
postId: input.postId,
userId: context.session.userId,
profileId,
parentId: input.parentId,
content: input.content,
visibility: input.visibility,
state: 'ACTIVE',
})
.returning({ id: PostComments.id })
.then((rows) => rows[0].id);

if (notificationTargetUserId !== context.session.userId) {
await enqueueJob('createNotification', {
userId: notificationTargetUserId,
category: 'COMMENT',
actorId: profileId,
data: { commentId },
origin: context.event.url.origin,
});
}
await enqueueJob('createNotification', {
category: 'COMMENT',
targetId: commentId,
});

return commentId;
},
Expand Down
37 changes: 11 additions & 26 deletions apps/website/src/lib/server/graphql/schemas/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ import {
getPostContentState,
getPostViewCount,
getSpaceMember,
makeMasquerade,
makePostContentId,
makeQueryContainers,
searchResultToIds,
Expand Down Expand Up @@ -1862,7 +1861,7 @@ builder.mutationFields((t) => ({
throw new IntentionalError('이미 구매한 포스트예요');
}

await database.transaction(async (tx) => {
const purchaseId = await database.transaction(async (tx) => {
const [purchase] = await tx
.insert(PostPurchases)
.values({
Expand All @@ -1888,19 +1887,13 @@ builder.mutationFields((t) => ({
kind: 'POST_PURCHASE',
state: 'PENDING',
});
});

const masquerade = await makeMasquerade({
userId: context.session.userId,
spaceId: post.space.id,
return purchase.id;
});

await enqueueJob('createNotification', {
userId: post.userId,
category: 'PURCHASE',
actorId: masquerade.profileId,
data: { postId: input.postId },
origin: context.event.url.origin,
targetId: purchaseId,
});

return input.postId;
Expand Down Expand Up @@ -1961,29 +1954,21 @@ builder.mutationFields((t) => ({
throw new IntentionalError('피드백을 받지 않는 포스트예요');
}

await database
const reaction = await database
.insert(PostReactions)
.values({
userId: context.session.userId,
postId: input.postId,
emoji: input.emoji,
})
.onConflictDoNothing();

if (post.userId !== context.session.userId && post.spaceId) {
const masquerade = await makeMasquerade({
userId: context.session.userId,
spaceId: post.spaceId,
});
.onConflictDoNothing()
.returning({ id: PostReactions.id })
.then(useFirstRowOrThrow());

await enqueueJob('createNotification', {
userId: post.userId,
category: 'EMOJI_REACTION',
actorId: masquerade.profileId,
data: { postId: input.postId, emoji: input.emoji },
origin: context.event.url.origin,
});
}
await enqueueJob('createNotification', {
category: 'EMOJI_REACTION',
targetId: reaction.id,
});

return input.postId;
},
Expand Down
Loading
Loading