forked from
leaflet.pub/leaflet
a tool for shared writing and social publishing
1"use server";
2
3import { supabaseServerClient } from "supabase/serverClient";
4import { Tables, TablesInsert } from "supabase/database.types";
5
6type NotificationRow = Tables<"notifications">;
7
8export type Notification = Omit<TablesInsert<"notifications">, "data"> & {
9 data: NotificationData;
10};
11// Notification data types (for writing to the notifications table)
12export type NotificationData =
13 | { type: "comment"; comment_uri: string }
14 | { type: "subscribe"; subscription_uri: string };
15
16// Hydrated notification types
17export type HydratedCommentNotification = {
18 id: string;
19 recipient: string;
20 created_at: string;
21 type: "comment";
22 comment_uri: string;
23 commentData?: Tables<"comments_on_documents">;
24};
25
26export type HydratedSubscribeNotification = {
27 id: string;
28 recipient: string;
29 created_at: string;
30 type: "subscribe";
31 subscription_uri: string;
32 subscriptionData?: Tables<"publication_subscriptions">;
33};
34
35export type HydratedNotification =
36 | HydratedCommentNotification
37 | HydratedSubscribeNotification;
38
39// Type guard to extract notification type
40type ExtractNotificationType<T extends NotificationData["type"]> = Extract<
41 NotificationData,
42 { type: T }
43>;
44
45// Hydrator function type
46type NotificationHydrator<T extends NotificationData["type"]> = (
47 notifications: NotificationRow[],
48) => Promise<Array<HydratedNotification & { type: T }>>;
49
50/**
51 * Hydrates comment notifications
52 */
53async function hydrateCommentNotifications(
54 notifications: NotificationRow[],
55): Promise<HydratedCommentNotification[]> {
56 const commentNotifications = notifications.filter(
57 (n): n is NotificationRow & { data: ExtractNotificationType<"comment"> } =>
58 (n.data as NotificationData)?.type === "comment",
59 );
60
61 if (commentNotifications.length === 0) {
62 return [];
63 }
64
65 // Fetch comment data from the database
66 const commentUris = commentNotifications.map((n) => n.data.comment_uri);
67 const { data: comments } = await supabaseServerClient
68 .from("comments_on_documents")
69 .select("*")
70 .in("uri", commentUris);
71
72 return commentNotifications.map((notification) => ({
73 id: notification.id,
74 recipient: notification.recipient,
75 created_at: notification.created_at,
76 type: "comment" as const,
77 comment_uri: notification.data.comment_uri,
78 commentData: comments?.find((c) => c.uri === notification.data.comment_uri),
79 }));
80}
81
82/**
83 * Hydrates subscribe notifications
84 */
85async function hydrateSubscribeNotifications(
86 notifications: NotificationRow[],
87): Promise<HydratedSubscribeNotification[]> {
88 const subscribeNotifications = notifications.filter(
89 (
90 n,
91 ): n is NotificationRow & { data: ExtractNotificationType<"subscribe"> } =>
92 (n.data as NotificationData)?.type === "subscribe",
93 );
94
95 if (subscribeNotifications.length === 0) {
96 return [];
97 }
98
99 // Fetch subscription data from the database
100 const subscriptionUris = subscribeNotifications.map(
101 (n) => n.data.subscription_uri,
102 );
103 const { data: subscriptions } = await supabaseServerClient
104 .from("publication_subscriptions")
105 .select("*")
106 .in("uri", subscriptionUris);
107
108 return subscribeNotifications.map((notification) => ({
109 id: notification.id,
110 recipient: notification.recipient,
111 created_at: notification.created_at,
112 type: "subscribe" as const,
113 subscription_uri: notification.data.subscription_uri,
114 subscriptionData: subscriptions?.find(
115 (s) => s.uri === notification.data.subscription_uri,
116 ),
117 }));
118}
119
120/**
121 * Main hydration function that processes all notifications
122 */
123export async function hydrateNotifications(
124 notifications: NotificationRow[],
125): Promise<HydratedNotification[]> {
126 // Call all hydrators in parallel
127 const [commentNotifications, subscribeNotifications] = await Promise.all([
128 hydrateCommentNotifications(notifications),
129 hydrateSubscribeNotifications(notifications),
130 ]);
131
132 // Combine all hydrated notifications
133 const allHydrated = [...commentNotifications, ...subscribeNotifications];
134
135 // Sort by created_at to maintain order
136 allHydrated.sort(
137 (a, b) =>
138 new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
139 );
140
141 return allHydrated;
142}