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
119 changes: 73 additions & 46 deletions web2/app/api/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { type NextRequest, NextResponse } from "next/server"

// This is a proxy API route that forwards requests to the backend
export async function GET(request: NextRequest, { params }: { params: { path: string[] } }) {
// const path = params.path.join("/")
const url = new URL(request.url);
const path = url.pathname.replace("/api/", "");
const { searchParams } = new URL(request.url)
const token = searchParams.get("token") || request.headers.get("Authorization")?.split(" ")[1]
const { searchParams } = new URL(request.url);
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")
const isSSE = path === "timeline" || path === "notifications" || path.includes("/comments");

if (isSSE) {
try {
Expand All @@ -19,45 +18,68 @@ export async function GET(request: NextRequest, { params }: { params: { path: st
Authorization: `Bearer ${token}`,
Accept: "text/event-stream",
},
})

if (response.ok) {
// Set up SSE response
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
const data = await response.json()

// Send the initial data
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",
},
})
});

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 {
// Fall back to regular response
return NextResponse.json(await response.json(), { status: response.status })
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 });
}
} catch (error) {
console.error("SSE error:", error)
return NextResponse.json({ error: "Failed to connect to SSE" }, { status: 500 })
}
}

Expand All @@ -68,13 +90,18 @@ export async function GET(request: NextRequest, { params }: { params: { path: st
Authorization: token ? `Bearer ${token}` : "",
"Content-Type": request.headers.get("Content-Type") || "application/json",
},
})
});

const data = await response.json()
return NextResponse.json(data, { status: response.status })
const text = await response.text(); // Read response as text

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

return NextResponse.json(JSON.parse(text), { status: response.status });
} catch (error) {
console.error("API error:", error)
return NextResponse.json({ error: "Failed to fetch data" }, { status: 500 })
console.error("API error:", error);
return NextResponse.json({ error: "Failed to fetch data" }, { status: 500 });
}
}

Expand Down
10 changes: 7 additions & 3 deletions web2/app/profile/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import { MainLayout } from "@/components/main-layout"
import { UserProfile } from "@/components/user-profile"
import { getUserProfile } from "@/lib/api"

export default async function ProfilePage(props: { params: Promise<{ username: string }> }) {
const params = await props.params;
export default async function ProfilePage({
params,
}: {
params: Promise<{ username: string }>
}) {
const {username} = await params;
const cookieStore = await cookies()
const token = cookieStore.get("auth_token")

if (!token) {
redirect("/login")
}

const userData = await getUserProfile(params.username, token.value)
const userData = await getUserProfile(username, token.value)

return (
<MainLayout>
Expand Down
6 changes: 3 additions & 3 deletions web2/app/prose/[proseId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { MainLayout } from "@/components/main-layout"
import { ProseDetail } from "@/components/prose-detail"
import { getProseById } from "@/lib/api"

export default async function ProsePage(props: { params: Promise<{ proseId: string }> }) {
const params = await props.params;
export default async function ProsePage({params,}: { params: Promise<{ proseId: string }> }) {
const {proseId} = await params;
const cookieStore = await cookies()
const token = cookieStore.get("auth_token")

if (!token) {
redirect("/login")
}

const proseData = await getProseById(params.proseId, token.value)
const proseData = await getProseById(proseId, token.value)

return (
<MainLayout>
Expand Down
6 changes: 5 additions & 1 deletion web2/components/comments-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export function CommentsList({ proseId }: { proseId: string }) {
const { data, error: sseError } = useSSE<Comment[]>(`/api/${proseId}/comments`, token, {
onMessage: (data) => {
if (data) {
console.log("SSE CHECK FOR COMMENTS")
console.log(data)
setComments(data)
setIsLoading(false)
}
Expand Down Expand Up @@ -68,6 +70,7 @@ export function CommentsList({ proseId }: { proseId: string }) {
}

const data = await response.json()
console.log(data)
setComments(data)
} catch (err) {
toast({
Expand Down Expand Up @@ -116,7 +119,7 @@ export function CommentsList({ proseId }: { proseId: string }) {
})
}
}

console.log(comments)
if (isLoading) {
return (
<div className="space-y-4">
Expand Down Expand Up @@ -144,6 +147,7 @@ export function CommentsList({ proseId }: { proseId: string }) {
)
}


return (
<div className="space-y-4">
{comments.map((comment) => (
Expand Down
9 changes: 8 additions & 1 deletion web2/components/notification-indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ export function NotificationIndicator({ className = "" }: NotificationIndicatorP
})

if (response.ok) {
const data = await response.json()
const text = await response.text()
const data = JSON.parse(text)
if (data!=null){
setHasUnread(data.some((notification: any) => !notification.read))
} else {
setHasUnread(false)
console.log("EMPTY")
console.log(data)
}
}
} catch (err) {
console.error("Failed to check unread notifications", err)
Expand Down
67 changes: 42 additions & 25 deletions web2/components/notifications-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ export function NotificationsList() {
const { data, error: sseError } = useSSE<Notification[]>("/api/notifications", token, {
onMessage: (data) => {
if (data) {
setNotifications(data)
console.log("SSE CHECK FOR NOTIFS")
console.log(data)
if (data!=null){
setNotifications(data)} else {
setNotifications([])
}
setIsLoading(false)
}
},
Expand All @@ -40,7 +45,10 @@ export function NotificationsList() {

useEffect(() => {
if (data) {
setNotifications(data)
if (data!=null){
setNotifications(data)} else {
setNotifications([])
}
setIsLoading(false)
}

Expand All @@ -62,7 +70,10 @@ export function NotificationsList() {
}

const data = await response.json()
setNotifications(data)
if (data!=null){
setNotifications(data.filter((notification: Notification) => !notification.read));} else {
setNotifications([])
}
} catch (err) {
toast({
title: "Error",
Expand All @@ -74,32 +85,44 @@ export function NotificationsList() {
}
}

const removeNotification = (notificationId: string) => {
setNotifications((prev) => prev.filter((n) => n.id !== notificationId));
};

const markAllAsRead = async () => {
try {
const response = await fetch("/api/notifications/mark_as_read", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify("")
})

console.log(response)

if (!response.ok) {
throw new Error("Failed to mark notifications as read")
}

// Update all notifications as read
setNotifications(
notifications.map((notification) => ({
...notification,
read: true,
})),
)
// if (notifications!=null){
// setNotifications(
// notifications.map((notification) => ({
// ...notification,
// read: true,
// })),
// )} else {
setNotifications([])
// }


toast({
title: "Notifications marked as read",
description: "All notifications have been marked as read",
})
} catch (err) {
console.log(err)
toast({
title: "Error",
description: "Failed to mark notifications as read",
Expand All @@ -115,24 +138,15 @@ export function NotificationsList() {
headers: {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify("")
})

console.log(response)
if (!response.ok) {
throw new Error("Failed to mark notification as read")
}

// Update the notification as read
setNotifications(
notifications.map((notification) => {
if (notification.id === notificationId) {
return {
...notification,
read: true,
}
}
return notification
}),
)
removeNotification(notificationId)
} catch (err) {
toast({
title: "Error",
Expand Down Expand Up @@ -173,7 +187,7 @@ export function NotificationsList() {
return (
<>
<span className="font-semibold">{actorText}</span>
{" commented on your verse"}
{" commented on your prose"}
</>
)
case "follow":
Expand Down Expand Up @@ -215,18 +229,21 @@ export function NotificationsList() {
<RefreshCw className="mr-2 h-4 w-4" />
Refresh
</Button>
{ notifications != null? (
<Button variant="outline" size="sm" onClick={markAllAsRead} disabled={notifications.every((n) => n.read)}>

Mark all as read
</Button>
</Button>) : (null)}
</div>
</div>
</div>

{notifications.length === 0 ? (
{notifications!= null && notifications.length === 0 ? (
<div className="rounded-lg border bg-white dark:bg-slate-900 p-8 text-center">
<p className="text-muted-foreground">No notifications yet.</p>
</div>
) : (
notifications.map((notification) => (

<Card
key={notification.id}
className={`${!notification.read ? "bg-muted/50" : "bg-white dark:bg-slate-900"}`}
Expand Down
Loading
Loading