Skip to content

Commit d61fe55

Browse files
committed
derive createNotification api from web api contract
1 parent e85fed9 commit d61fe55

File tree

4 files changed

+41
-58
lines changed

4 files changed

+41
-58
lines changed

apps/web/global.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
declare type MaybePromise<T> = T | Promise<T>;
2+
3+
declare type DistributiveOmit<T, K extends keyof any> = T extends any
4+
? Omit<T, K>
5+
: never;

apps/web/lib/Notification.ts

Lines changed: 28 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,33 @@
1-
import { z } from "zod";
2-
import { getCurrentUser } from "@cap/database/auth/session";
1+
// Ideally all the Notification-related types would be in @cap/web-domain
2+
// but @cap/web-api-contract is the closest we have right now
3+
34
import { notifications, videos, users } from "@cap/database/schema";
45
import { db } from "@cap/database";
56
import { and, eq, sql } from "drizzle-orm";
67
import { nanoId } from "@cap/database/helpers";
78
import { UserPreferences } from "@/app/(org)/dashboard/dashboard-data";
89
import { revalidatePath } from "next/cache";
9-
10-
const buildNotification = <TType extends string, TFields extends z.ZodRawShape>(
11-
type: TType,
12-
fields: TFields
13-
) => z.object({ ...fields, type: z.literal(type) });
14-
15-
export const Notification = z.union([
16-
buildNotification("view", { videoId: z.string(), authorId: z.string() }),
17-
buildNotification("comment", {
18-
videoId: z.string(),
19-
authorId: z.string(),
20-
comment: z.object({
21-
id: z.string(),
22-
content: z.string(),
23-
}),
24-
}),
25-
buildNotification("reaction", { videoId: z.string(), authorId: z.string() }),
26-
// buildNotification("mention", {
27-
// videoId: z.string(),
28-
// authorId: z.string(),
29-
// comment: z.object({
30-
// id: z.string(),
31-
// content: z.string(),
32-
// }),
33-
// }),
34-
buildNotification("reply", {
35-
videoId: z.string(),
36-
authorId: z.string(),
37-
comment: z.object({
38-
id: z.string(),
39-
content: z.string(),
40-
}),
41-
}),
42-
]);
43-
44-
export type RawNotification = z.infer<typeof Notification>;
45-
export type NotificationType = z.infer<typeof Notification>["type"];
46-
47-
export type HydratedNotification =
48-
| Extract<RawNotification, { type: "view" }>
49-
| (Extract<RawNotification, { type: "comment" }> & { content: string });
50-
51-
export async function createNotification(notification: RawNotification) {
10+
import { Notification, NotificationBase } from "@cap/web-api-contract";
11+
12+
// Notification daata without id, readTime, etc
13+
type NotificationSpecificData = DistributiveOmit<
14+
Notification,
15+
keyof NotificationBase
16+
>;
17+
18+
// Replaces author object with authorId since we query for that info.
19+
// If we add more notifications this would probably be better done manually
20+
// Type is weird since we need to operate on each member of the NotificationSpecificData union
21+
type CreateNotificationInput<D = NotificationSpecificData> =
22+
D extends NotificationSpecificData
23+
? D["author"] extends never
24+
? D
25+
: Omit<D, "author"> & { authorId: string }
26+
: never;
27+
28+
export async function createNotification(
29+
notification: CreateNotificationInput
30+
) {
5231
try {
5332
// First, get the video and owner data
5433
const [videoResult] = await db()
@@ -79,10 +58,9 @@ export async function createNotification(notification: RawNotification) {
7958

8059
const shouldSkipNotification =
8160
(notification.type === "comment" && notificationPrefs.pauseComments) ||
82-
(notification.type === "view" && notificationPrefs.pauseViews);
83-
// ||
84-
// (variant === "reply" && notificationPrefs.pauseReplies) ||
85-
// (variant === "reaction" && notificationPrefs.pauseReactions);
61+
(notification.type === "view" && notificationPrefs.pauseViews) ||
62+
(notification.type === "reply" && notificationPrefs.pauseReplies) ||
63+
(notification.type === "reaction" && notificationPrefs.pauseReactions);
8664

8765
if (shouldSkipNotification) {
8866
return;
@@ -115,7 +93,7 @@ export async function createNotification(notification: RawNotification) {
11593
and(
11694
eq(notifications.type, "comment"),
11795
eq(notifications.recipientId, videoResult.ownerId),
118-
sql`JSON_EXTRACT(${notifications.data}, '$.commentId') = ${notification.comment.id}`
96+
sql`JSON_EXTRACT(${notifications.data}, '$.comment.id') = ${notification.comment.id}`
11997
)
12098
)
12199
.limit(1);

packages/database/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ export const notifications = mysqlTable(
318318
recipientId: nanoId("recipientId").notNull(),
319319
type: varchar("type", { length: 10 })
320320
.notNull()
321-
.$type<"view" | "comment" | "reply" | "reaction" /* | "mention"*/>(),
321+
.$type<"view" | "comment" | "reply" | "reaction" | "mention">(),
322322
data: json("data")
323323
.$type<{
324324
videoId?: string;

packages/web-api-contract/src/index.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ export const NotificationAuthor = z.object({
88
avatar: z.string().nullable(),
99
});
1010

11+
export const NotificationBase = z.object({
12+
id: z.string(),
13+
readAt: z.coerce.date().nullable(),
14+
createdAt: z.coerce.date(),
15+
});
16+
export type NotificationBase = z.infer<typeof NotificationBase>;
17+
1118
export const Notification = z
1219
.union([
1320
z.object({
@@ -52,13 +59,7 @@ export const Notification = z
5259
// }),
5360
// }),
5461
])
55-
.and(
56-
z.object({
57-
id: z.string(),
58-
readAt: z.coerce.date().nullable(),
59-
createdAt: z.coerce.date(),
60-
})
61-
);
62+
.and(NotificationBase);
6263
export type Notification = z.infer<typeof Notification>;
6364

6465
export const contract = c.router({

0 commit comments

Comments
 (0)