import { useEffect } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { supabase } from "../lib/supabase"; import type { RealtimeChannel } from "@supabase/supabase-js"; import type { CommunityPost } from "../stores/community"; /** * Realtime-Subscription für die Community-Feed-Page. * Lauscht auf: * - INSERT auf community_posts → invalidiert die Feed-Query (frischer Refetch) * - UPDATE auf community_posts → patcht likes/comments-Counts inline * - UPDATE auf domain_submissions → patcht domain_vote-Posts mit neuem Status * - UPDATE auf game_challenges → patcht challenge-Status * * Pendant zum Nuxt-`communityStore.startRealtime()` aus apps/rebreak/. */ export function useCommunityRealtime(enabled: boolean = true) { const queryClient = useQueryClient(); useEffect(() => { if (!enabled) return; let channel: RealtimeChannel | null = null; let cancelled = false; let reconnectTimer: ReturnType | null = null; async function subscribe() { const { data } = await supabase.auth.getSession(); const session = data.session; if (!session?.access_token) return; if (cancelled) return; supabase.realtime.setAuth(session.access_token); const myId = session.user.id; channel = supabase .channel(`community:posts:${Date.now()}`) .on( "postgres_changes", { event: "INSERT", schema: "rebreak", table: "community_posts" }, (payload: any) => { const r = payload.new; if (r.user_id === myId) return; // eigene Posts schon optimistisch hinzugefügt if (r.is_moderated) return; // Einfacher als Detail-Fetch: alle Feed-Queries invalidieren queryClient.invalidateQueries({ queryKey: ["community-posts"] }); }, ) .on( "postgres_changes", { event: "UPDATE", schema: "rebreak", table: "community_posts" }, (payload: any) => { const r = payload.new; patchPostInAllQueries(queryClient, r.id, (p) => ({ ...p, likesCount: r.likes_count ?? p.likesCount, dislikesCount: r.dislikes_count ?? p.dislikesCount, commentsCount: r.comments_count ?? p.commentsCount, repostsCount: r.reposts_count ?? p.repostsCount, })); }, ) .on( "postgres_changes", { event: "UPDATE", schema: "rebreak", table: "domain_submissions" }, (payload: any) => { const r = payload.new; if ( r.status !== "approved" && r.status !== "rejected" && r.status !== "in_review" ) { return; } patchPostInAllQueries(queryClient, null, (p) => { if (p.submission?.domain == null) return p; // Wir kennen die submissionId nicht direkt am Post, also matche per domain. // Realistisch unique pro user_id, aber Feed enthält Post mit submission-Objekt. // Wenn der Post diese submission referenziert, patchen. if (!p.submission || (p as any).submissionId !== r.id) { // Falls du submissionId an Post-Schema hängst, hier nutzen. // Solange nicht: invalidate fallback unten. return p; } return { ...p, submission: { ...p.submission, status: r.status, yesVotes: r.yes_votes ?? p.submission.yesVotes, noVotes: r.no_votes ?? p.submission.noVotes, reviewedAt: r.reviewed_at ?? p.submission.reviewedAt, }, }; }); // Sicherheitshalber auch invalidieren — domain_vote ist selten genug. queryClient.invalidateQueries({ queryKey: ["community-posts"] }); }, ) .on( "postgres_changes", { event: "UPDATE", schema: "rebreak", table: "game_challenges" }, (payload: any) => { const r = payload.new; patchPostInAllQueries(queryClient, null, (p) => p.challengeId === r.id ? { ...p, challengeStatus: r.status } : p, ); }, ) .subscribe((status) => { if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") { cleanup(); if (reconnectTimer) clearTimeout(reconnectTimer); reconnectTimer = setTimeout(() => { if (!cancelled) subscribe(); }, 3000); } }); } function cleanup() { if (channel) { supabase.removeChannel(channel); channel = null; } } subscribe(); return () => { cancelled = true; if (reconnectTimer) clearTimeout(reconnectTimer); cleanup(); }; }, [enabled, queryClient]); } function patchPostInAllQueries( queryClient: ReturnType, postId: string | null, patcher: (p: CommunityPost) => CommunityPost, ) { const queries = queryClient.getQueriesData({ queryKey: ["community-posts"], }); for (const [key, data] of queries) { if (!Array.isArray(data)) continue; const next = data.map((p) => { if (postId !== null && p.id !== postId) return p; return patcher(p); }); queryClient.setQueryData(key, next); } }