Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 75 additions & 74 deletions web2/app/api/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,80 +8,81 @@ export async function GET(request: NextRequest, { params }: { params: { path: st
const token = searchParams.get("token") || request.headers.get("Authorization")?.split(" ")[1];

// Check if this is an SSE endpoint
const isSSE = path === "timeline" || path === "notifications" || path.includes("/comments");

if (isSSE) {
try {
// Try SSE first
const response = await fetch(`${process.env.API_URL}/api/${path}`, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "text/event-stream",
},
});

const text = await response.text();

if (response.ok && text.trim()) {
try {
const data = JSON.parse(text);

// Set up SSE response
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));

// Keep the connection open
const interval = setInterval(() => {
controller.enqueue(encoder.encode(": keepalive\n\n"));
}, 30000);

// Clean up on close
request.signal.addEventListener("abort", () => {
clearInterval(interval);
controller.close();
});
},
});

return new NextResponse(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
} catch (jsonError) {
console.error("SSE JSON parse error:", jsonError);
return NextResponse.json({ error: "Invalid JSON response from SSE" }, { status: 502 });
}
} else {
console.log("SSE response empty, falling back to basic fetch...");

const fallbackResponse = await fetch(`${process.env.API_URL}/api/${path}`, {
headers: { Authorization: `Bearer ${token}` },
});

const fallbackText = await fallbackResponse.text();

if (!fallbackText.trim()) {
return NextResponse.json({ error: "Empty response from server" }, { status: 502 });
}

try {
return NextResponse.json(JSON.parse(fallbackText), { status: fallbackResponse.status });
} catch {
return NextResponse.json({ error: "Invalid JSON from fallback" }, { status: 502 });
}
}
} catch (error: any) {
console.error("SSE error:", error);
if (error.code !== "UND_ERR_HEADERS_TIMEOUT") {
return NextResponse.json({ error: "Failed to connect to SSE" }, { status: 500 });
}
}
}
// const isSSE = path === "timeline" || path === "notifications" ||path.includes("/comments");

// if (isSSE) {
// try {
// // Try SSE first
// const response = await fetch(`${process.env.API_URL}/api/${path}`, {
// headers: {
// Authorization: `Bearer ${token}`,
// Accept: "text/event-stream",
// },
// });
// console.log(response)

// const text = await response.text();

// if (response.ok && !text.trim()) {
// try {
// const data = JSON.parse(text);

// // Set up SSE response
// const encoder = new TextEncoder();
// const stream = new ReadableStream({
// async start(controller) {
// controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));

// // Keep the connection open
// const interval = setInterval(() => {
// controller.enqueue(encoder.encode(": keepalive\n\n"));
// }, 30000);

// // Clean up on close
// request.signal.addEventListener("abort", () => {
// clearInterval(interval);
// controller.close();
// });
// },
// });

// return new NextResponse(stream, {
// headers: {
// "Content-Type": "text/event-stream",
// "Cache-Control": "no-cache",
// Connection: "keep-alive",
// },
// });
// } catch (jsonError) {
// console.error("SSE JSON parse error:", jsonError);
// return NextResponse.json({ error: "Invalid JSON response from SSE" }, { status: 502 });
// }
// } else {
// console.log("SSE response empty, falling back to basic fetch...");

// const fallbackResponse = await fetch(`${process.env.API_URL}/api/${path}`, {
// headers: { Authorization: `Bearer ${token}` },
// });

// const fallbackText = await fallbackResponse.text();

// if (!fallbackText.trim()) {
// return NextResponse.json({ error: "Empty response from server" }, { status: 502 });
// }

// try {
// return NextResponse.json(JSON.parse(fallbackText), { status: fallbackResponse.status });
// } catch {
// return NextResponse.json({ error: "Invalid JSON from fallback" }, { status: 502 });
// }
// }
// } catch (error: any) {
// console.error("SSE error:", error);
// if (error.code !== "UND_ERR_HEADERS_TIMEOUT") {
// return NextResponse.json({ error: "Failed to connect to SSE" }, { status: 500 });
// }
// }
// }

// Regular API request
try {
Expand Down
38 changes: 38 additions & 0 deletions web2/app/api/sse/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// app/api/sse/[...path]/route.ts
import { type NextRequest } from "next/server";

export async function GET(request: NextRequest) {
const url = new URL(request.url);
const path = url.pathname.replace("/api/sse/", ""); // e.g., "timeline" or "comments/123"
const token =
url.searchParams.get("token") ||
request.headers.get("Authorization")?.split(" ")[1];

try {
const backendResponse = await fetch(
`${process.env.API_URL}/api/${path}?${url.searchParams.toString()}`,
{
method: "GET",
headers: {
Authorization: token ? `Bearer ${token}` : "",
Accept: "text/event-stream",
},
}
);

// Proxy the backend SSE stream directly to the client
return new Response(backendResponse.body, {
status: 200,
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
// Optional headers for CORS (if needed):
// "Access-Control-Allow-Origin": "*",
},
});
} catch (error) {
console.error("SSE proxy error:", error);
return new Response("Failed to connect to SSE", { status: 502 });
}
}
4 changes: 2 additions & 2 deletions web2/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { redirect } from "next/navigation"
import { cookies } from "next/headers"
import { Timeline } from "@/components/timeline"
import { MainLayout } from "@/components/main-layout"
import TimelinePage from "./timeline/page"

export default async function Home() {
const cookieStore = await cookies()
Expand All @@ -13,7 +13,7 @@ export default async function Home() {

return (
<MainLayout>
<Timeline />
<TimelinePage />
</MainLayout>
)
}
Expand Down
10 changes: 10 additions & 0 deletions web2/app/timeline/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Timeline } from "@/components/timeline";
import React from "react";

export default function TimelinePage(){
return (
<div>
<Timeline/>
</div>
)
}
2 changes: 1 addition & 1 deletion web2/components/comments-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function CommentsList({ proseId }: { proseId: string }) {
const { toast } = useToast()

// Try to use SSE for comments, fall back to regular fetch
const { data, error: sseError } = useSSE<Comment[]>(`/api/${proseId}/comments`, token, {
const { data, error: sseError } = useSSE<Comment[]>(`/api/sse/${proseId}/comments`, token, "comment", {
onMessage: (data) => {
if (data) {
console.log("SSE CHECK FOR COMMENTS")
Expand Down
57 changes: 52 additions & 5 deletions web2/components/notification-indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function NotificationIndicator({ className = "" }: NotificationIndicatorP
const { token } = useAuth()

// Use SSE to check for unread notifications
const { data } = useSSE<{ has_unread: boolean }>("/api/notifications", token, {
const { data } = useSSE<{ has_unread: boolean }>("/api/sse/notifications", token, "notification", {
onMessage: (data) => {
if (data && Array.isArray(data)) {
setHasUnread(data.some((notification) => !notification.read))
Expand All @@ -38,13 +38,14 @@ export function NotificationIndicator({ className = "" }: NotificationIndicatorP

if (response.ok) {
const text = await response.text()
const data = JSON.parse(text)
if (text.trim()){
const data = JSON.parse(text)
if (data!=null){
setHasUnread(data.some((notification: any) => !notification.read))
} else {
}
}
else {
setHasUnread(false)
console.log("EMPTY")
console.log(data)
}
}
} catch (err) {
Expand All @@ -60,3 +61,49 @@ export function NotificationIndicator({ className = "" }: NotificationIndicatorP
return <div className={`h-2 w-2 rounded-full bg-red-500 ${className}`} />
}

// "use client";

// import { useState, useEffect } from "react";
// import { useAuth } from "@/lib/auth-hooks";

// type NotificationIndicatorProps = {
// className?: string;
// };

// export function NotificationIndicator({ className = "" }: NotificationIndicatorProps) {
// const [hasUnread, setHasUnread] = useState(false);
// const { token } = useAuth();

// // Fallback: check for unread notifications on mount (SSE disabled)
// useEffect(() => {
// const checkUnread = async () => {
// try {
// const response = await fetch("/api/notifications", {
// headers: {
// Authorization: `Bearer ${token}`,
// },
// });

// if (response.ok) {
// const text = await response.text();
// if (text.trim()) {
// const data = JSON.parse(text);
// if (data != null) {
// setHasUnread(data.some((notification: any) => !notification.read));
// }
// } else {
// setHasUnread(false);
// }
// }
// } catch (err) {
// console.error("Failed to check unread notifications", err);
// }
// };

// checkUnread();
// }, [token]);

// if (!hasUnread) return null;

// return <div className={`h-2 w-2 rounded-full bg-red-500 ${className}`} />;
// }
2 changes: 1 addition & 1 deletion web2/components/notifications-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function NotificationsList() {
const { toast } = useToast()

// Try to use SSE for notifications, fall back to regular fetch
const { data, error: sseError } = useSSE<Notification[]>("/api/notifications", token, {
const { data, error: sseError } = useSSE<Notification[]>("/api/sse/notifications", token, "notification", {
onMessage: (data) => {
if (data) {
console.log("SSE CHECK FOR NOTIFS")
Expand Down
5 changes: 4 additions & 1 deletion web2/components/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function Timeline() {
const { toast } = useToast();

// Use SSE to get timeline updates
const { data, error: sseError } = useSSE<TimelineItem[]>("/api/timeline", token, {
const { data, error: sseError } = useSSE<TimelineItem[]>("/api/sse/timeline", token, "timeline",{
onMessage: (data) => {
if (data) {
console.log("SSE CHECK FOR TL")
Expand Down Expand Up @@ -192,3 +192,6 @@ export function Timeline() {
</div>
);
}



1 change: 1 addition & 0 deletions web2/components/users-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export function UsersList() {
);
}

// console.log(filteredUsers)
return (
<div className="space-y-4">
<h2 className="text-2xl font-serif font-bold">Find Users to Follow</h2>
Expand Down
Loading
Loading