Skip to content

Commit 63b85fa

Browse files
committed
알림 로직 전부 큐로 변경 (#2733)
1 parent 9c2bf94 commit 63b85fa

File tree

12 files changed

+361
-241
lines changed

12 files changed

+361
-241
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { and, eq } from 'drizzle-orm';
2+
import { database, PostComments, PostRevisions, Posts, Profiles, Spaces } from '$lib/server/database';
3+
import { createNotification, useFirstRow } from '$lib/server/utils';
4+
import type { NotificationMaker } from './type';
5+
6+
export const commentNotificationMaker: NotificationMaker = async (commentId) => {
7+
const comment = await database
8+
.select({
9+
id: PostComments.id,
10+
content: PostComments.content,
11+
commenterId: PostComments.userId,
12+
profileId: PostComments.profileId,
13+
profileName: Profiles.name,
14+
spaceId: Posts.spaceId,
15+
spaceSlug: Spaces.slug,
16+
postPermalink: Posts.permalink,
17+
postWriterId: Posts.userId,
18+
postTitle: PostRevisions.title,
19+
parentId: PostComments.parentId,
20+
})
21+
.from(PostComments)
22+
.innerJoin(Posts, eq(PostComments.postId, Posts.id))
23+
.innerJoin(Spaces, eq(Posts.spaceId, Spaces.id))
24+
.innerJoin(PostRevisions, eq(Posts.publishedRevisionId, PostRevisions.id))
25+
.innerJoin(Profiles, eq(PostComments.profileId, Profiles.id))
26+
.where(and(eq(PostComments.id, commentId), eq(PostComments.state, 'ACTIVE')))
27+
.then(useFirstRow);
28+
29+
if (!comment || !comment.spaceId) {
30+
return;
31+
}
32+
33+
let notifiedUserId = comment.postWriterId;
34+
35+
if (comment.parentId) {
36+
const parentComment = await database
37+
.select({
38+
userId: PostComments.userId,
39+
})
40+
.from(PostComments)
41+
.where(and(eq(PostComments.id, comment.parentId), eq(PostComments.state, 'ACTIVE')))
42+
.then(useFirstRow);
43+
44+
if (parentComment) {
45+
if (parentComment.userId === comment.commenterId) {
46+
return;
47+
}
48+
49+
notifiedUserId = parentComment.userId;
50+
}
51+
} else if (notifiedUserId === comment.commenterId) {
52+
return;
53+
}
54+
55+
await createNotification({
56+
userId: notifiedUserId,
57+
category: 'COMMENT',
58+
actorId: comment.profileId,
59+
data: {
60+
commentId: comment.id,
61+
},
62+
pushTitle: comment.postTitle ?? '(제목 없음)',
63+
pushBody: `${comment.profileName}님이 "${comment.content}" 댓글을 남겼어요.`,
64+
pushPath: `/${comment.spaceSlug}/${comment.postPermalink}`,
65+
});
66+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { eq } from 'drizzle-orm';
2+
import { database, PostReactions, PostRevisions, Posts, Spaces } from '$lib/server/database';
3+
import { createNotification, getSpaceProfile, useFirstRow } from '$lib/server/utils';
4+
import type { NotificationMaker } from './type';
5+
6+
export const emojiReactionNotificationMaker: NotificationMaker = async (reactionId) => {
7+
const reaction = await database
8+
.select({
9+
emoji: PostReactions.emoji,
10+
postId: Posts.id,
11+
postPermalink: Posts.permalink,
12+
title: PostRevisions.title,
13+
postWriterId: Posts.userId,
14+
spaceId: Posts.spaceId,
15+
spaceSlug: Spaces.slug,
16+
emojigazerId: PostReactions.userId,
17+
})
18+
.from(PostReactions)
19+
.innerJoin(Posts, eq(PostReactions.postId, Posts.id))
20+
.innerJoin(PostRevisions, eq(Posts.publishedRevisionId, PostRevisions.id))
21+
.innerJoin(Spaces, eq(Posts.spaceId, Spaces.id))
22+
.where(eq(PostReactions.id, reactionId))
23+
.then(useFirstRow);
24+
25+
if (!reaction || !reaction.spaceId || reaction.postWriterId === reaction.emojigazerId) {
26+
return;
27+
}
28+
29+
const profile = await getSpaceProfile({ spaceId: reaction.spaceId, userId: reaction.emojigazerId });
30+
31+
await createNotification({
32+
userId: reaction.postWriterId,
33+
category: 'EMOJI_REACTION',
34+
actorId: profile.id,
35+
data: {
36+
postId: reaction.postId,
37+
emoji: reaction.emoji,
38+
},
39+
pushTitle: reaction.title ?? '(제목 없음)',
40+
pushBody: `${profile.name}님이 이모지를 남겼어요.`,
41+
pushPath: `/${reaction.spaceSlug}/${reaction.postPermalink}`,
42+
});
43+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { commentNotificationMaker } from './comment';
2+
import { emojiReactionNotificationMaker } from './emoji-reaction';
3+
import { purchaseNotificationMaker } from './purchase';
4+
import { subscribeNotificationMaker } from './subscribe';
5+
6+
export const notificationMaker = {
7+
EMOJI_REACTION: emojiReactionNotificationMaker,
8+
COMMENT: commentNotificationMaker,
9+
PURCHASE: purchaseNotificationMaker,
10+
SUBSCRIBE: subscribeNotificationMaker,
11+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { eq } from 'drizzle-orm';
2+
import { database, PostPurchases, PostRevisions, Posts } from '$lib/server/database';
3+
import { createNotification, getSpaceProfile, useFirstRow } from '$lib/server/utils';
4+
import type { NotificationMaker } from './type';
5+
6+
export const purchaseNotificationMaker: NotificationMaker = async (purchaseId) => {
7+
const purchase = await database
8+
.select({
9+
postId: PostPurchases.postId,
10+
buyerId: PostPurchases.userId,
11+
postWriterId: Posts.userId,
12+
spaceId: Posts.spaceId,
13+
postTitle: PostRevisions.title,
14+
})
15+
.from(PostPurchases)
16+
.innerJoin(Posts, eq(PostPurchases.postId, Posts.id))
17+
.innerJoin(PostRevisions, eq(Posts.publishedRevisionId, PostRevisions.id))
18+
.where(eq(PostPurchases.id, purchaseId))
19+
.then(useFirstRow);
20+
21+
if (!purchase || !purchase.spaceId) {
22+
return;
23+
}
24+
25+
const profile = await getSpaceProfile({ spaceId: purchase.spaceId, userId: purchase.buyerId });
26+
27+
await createNotification({
28+
userId: purchase.postWriterId,
29+
category: 'PURCHASE',
30+
actorId: profile.id,
31+
data: {
32+
postId: purchase.postId,
33+
},
34+
pushTitle: purchase.postTitle ?? '(제목 없음)',
35+
pushBody: `${profile.name}님이 포스트를 구매했어요.`,
36+
pushPath: '/me/revenue',
37+
});
38+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { eq } from 'drizzle-orm';
2+
import { database, SpaceFollows, SpaceMembers, Spaces } from '$lib/server/database';
3+
import { createNotification, getSpaceProfile, useFirstRow } from '$lib/server/utils';
4+
import type { NotificationMaker } from './type';
5+
6+
export const subscribeNotificationMaker: NotificationMaker = async (spaceFollowId) => {
7+
const spaceFollow = await database
8+
.select({
9+
followerId: SpaceFollows.userId,
10+
spaceId: SpaceFollows.spaceId,
11+
spaceSlug: Spaces.slug,
12+
spaceName: Spaces.name,
13+
})
14+
.from(SpaceFollows)
15+
.innerJoin(Spaces, eq(SpaceFollows.spaceId, Spaces.id))
16+
.where(eq(SpaceFollows.id, spaceFollowId))
17+
.then(useFirstRow);
18+
19+
if (!spaceFollow) {
20+
return;
21+
}
22+
23+
const profile = await getSpaceProfile({ spaceId: spaceFollow.spaceId, userId: spaceFollow.followerId });
24+
25+
const spaceMemberIds = await database
26+
.select({
27+
userId: SpaceMembers.userId,
28+
})
29+
.from(SpaceMembers)
30+
.where(eq(SpaceMembers.spaceId, spaceFollow.spaceId))
31+
.then((rows) => rows.map((row) => row.userId));
32+
33+
await Promise.all(
34+
spaceMemberIds.map(async (userId) => {
35+
await createNotification({
36+
userId,
37+
category: 'SUBSCRIBE',
38+
actorId: profile.id,
39+
data: {
40+
spaceId: spaceFollow.spaceId,
41+
},
42+
pushTitle: spaceFollow.spaceName,
43+
pushBody: `${profile.name}님이 스페이스를 구독했어요.`,
44+
pushPath: `/${spaceFollow.spaceSlug}`,
45+
});
46+
}),
47+
);
48+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { MaybePromise } from '$lib/types';
2+
3+
export type NotificationMaker = (targetId: string) => MaybePromise<void>;

apps/website/src/lib/server/graphql/schemas/comment.ts

+21-38
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
UserPersonalIdentities,
1414
} from '$lib/server/database';
1515
import { enqueueJob } from '$lib/server/jobs';
16-
import { getSpaceMember, Loader, makeMasquerade } from '$lib/server/utils';
16+
import { getSpaceMember, Loader, makeMasquerade, useFirstRowOrThrow } from '$lib/server/utils';
1717
import { builder } from '../builder';
1818
import { createObjectRef } from '../utils';
1919
import { Post } from './post';
@@ -369,10 +369,8 @@ builder.mutationFields((t) => ({
369369
}
370370
}
371371

372-
let notificationTargetUserId;
373-
374372
if (input.parentId) {
375-
const rows = await database
373+
await database
376374
.select()
377375
.from(PostComments)
378376
.where(
@@ -381,15 +379,8 @@ builder.mutationFields((t) => ({
381379
eq(PostComments.postId, input.postId),
382380
eq(PostComments.state, 'ACTIVE'),
383381
),
384-
);
385-
386-
if (rows.length === 0) {
387-
throw new NotFoundError();
388-
}
389-
390-
notificationTargetUserId = rows[0].userId;
391-
} else {
392-
notificationTargetUserId = post.userId;
382+
)
383+
.then(useFirstRowOrThrow(new NotFoundError()));
393384
}
394385

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

415-
const commentId = await database.transaction(async (tx) => {
416-
const [comment] = await tx
417-
.insert(PostComments)
418-
.values({
419-
postId: input.postId,
420-
userId: context.session.userId,
421-
profileId,
422-
parentId: input.parentId,
423-
content: input.content,
424-
visibility: input.visibility,
425-
state: 'ACTIVE',
426-
})
427-
.returning({ id: PostComments.id });
428-
429-
return comment.id;
430-
});
406+
const commentId = await database
407+
.insert(PostComments)
408+
.values({
409+
postId: input.postId,
410+
userId: context.session.userId,
411+
profileId,
412+
parentId: input.parentId,
413+
content: input.content,
414+
visibility: input.visibility,
415+
state: 'ACTIVE',
416+
})
417+
.returning({ id: PostComments.id })
418+
.then((rows) => rows[0].id);
431419

432-
if (notificationTargetUserId !== context.session.userId) {
433-
await enqueueJob('createNotification', {
434-
userId: notificationTargetUserId,
435-
category: 'COMMENT',
436-
actorId: profileId,
437-
data: { commentId },
438-
origin: context.event.url.origin,
439-
});
440-
}
420+
await enqueueJob('createNotification', {
421+
category: 'COMMENT',
422+
targetId: commentId,
423+
});
441424

442425
return commentId;
443426
},

apps/website/src/lib/server/graphql/schemas/post.ts

+11-26
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import {
6262
getPostContentState,
6363
getPostViewCount,
6464
getSpaceMember,
65-
makeMasquerade,
6665
makePostContentId,
6766
makeQueryContainers,
6867
searchResultToIds,
@@ -1862,7 +1861,7 @@ builder.mutationFields((t) => ({
18621861
throw new IntentionalError('이미 구매한 포스트예요');
18631862
}
18641863

1865-
await database.transaction(async (tx) => {
1864+
const purchaseId = await database.transaction(async (tx) => {
18661865
const [purchase] = await tx
18671866
.insert(PostPurchases)
18681867
.values({
@@ -1888,19 +1887,13 @@ builder.mutationFields((t) => ({
18881887
kind: 'POST_PURCHASE',
18891888
state: 'PENDING',
18901889
});
1891-
});
18921890

1893-
const masquerade = await makeMasquerade({
1894-
userId: context.session.userId,
1895-
spaceId: post.space.id,
1891+
return purchase.id;
18961892
});
18971893

18981894
await enqueueJob('createNotification', {
1899-
userId: post.userId,
19001895
category: 'PURCHASE',
1901-
actorId: masquerade.profileId,
1902-
data: { postId: input.postId },
1903-
origin: context.event.url.origin,
1896+
targetId: purchaseId,
19041897
});
19051898

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

1964-
await database
1957+
const reaction = await database
19651958
.insert(PostReactions)
19661959
.values({
19671960
userId: context.session.userId,
19681961
postId: input.postId,
19691962
emoji: input.emoji,
19701963
})
1971-
.onConflictDoNothing();
1972-
1973-
if (post.userId !== context.session.userId && post.spaceId) {
1974-
const masquerade = await makeMasquerade({
1975-
userId: context.session.userId,
1976-
spaceId: post.spaceId,
1977-
});
1964+
.onConflictDoNothing()
1965+
.returning({ id: PostReactions.id })
1966+
.then(useFirstRowOrThrow());
19781967

1979-
await enqueueJob('createNotification', {
1980-
userId: post.userId,
1981-
category: 'EMOJI_REACTION',
1982-
actorId: masquerade.profileId,
1983-
data: { postId: input.postId, emoji: input.emoji },
1984-
origin: context.event.url.origin,
1985-
});
1986-
}
1968+
await enqueueJob('createNotification', {
1969+
category: 'EMOJI_REACTION',
1970+
targetId: reaction.id,
1971+
});
19871972

19881973
return input.postId;
19891974
},

0 commit comments

Comments
 (0)