a tool for shared writing and social publishing
at test/unknown-marks 142 lines 4.2 kB view raw
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}