Skip to content

Commit

Permalink
알림 로직 전부 큐로 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
robin-maki committed Jul 11, 2024
1 parent 854d802 commit 48eec32
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 241 deletions.
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 @@ -1847,7 +1846,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 @@ -1873,19 +1872,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 @@ -1946,29 +1939,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

0 comments on commit 48eec32

Please sign in to comment.