1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
import { NextResponse } from "next/server"
import { createSupabaseAdminClient } from "@/lib/supabase/admin"
import { authenticateApiRequest } from "@/lib/api-auth"
export async function GET(request: Request) {
const authResult = await authenticateApiRequest(request)
if (!authResult.authenticated) {
return NextResponse.json(
{ error: authResult.error },
{ status: authResult.status }
)
}
const { searchParams } = new URL(request.url)
const feedIdentifier = searchParams.get("feedIdentifier")
const isRead = searchParams.get("isRead")
const isSaved = searchParams.get("isSaved")
const cursor = searchParams.get("cursor")
const limitParameter = searchParams.get("limit")
const parsedLimit = Number(limitParameter)
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(Math.floor(parsedLimit), 100) : 50
const adminClient = createSupabaseAdminClient()
const { data: subscriptionRows } = await adminClient
.from("subscriptions")
.select("feed_id")
.eq("user_id", authResult.user.userIdentifier)
const subscribedFeedIdentifiers = (subscriptionRows ?? []).map(
(row) => row.feed_id
)
if (subscribedFeedIdentifiers.length === 0) {
return NextResponse.json({ entries: [], nextCursor: null })
}
let query = adminClient
.from("entries")
.select(
"id, feed_id, guid, title, url, author, summary, image_url, published_at, enclosure_url, enclosure_type, user_entry_states!left(read, saved)"
)
.in("feed_id", subscribedFeedIdentifiers)
.is("owner_id", null)
.eq("user_entry_states.user_id", authResult.user.userIdentifier)
.order("published_at", { ascending: false })
.limit(limit + 1)
if (feedIdentifier) {
query = query.eq("feed_id", feedIdentifier)
}
if (cursor) {
query = query.lt("published_at", cursor)
}
const { data, error } = await query
if (error) {
return NextResponse.json(
{ error: "failed to load entries" },
{ status: 500 }
)
}
interface EntryRow {
id: string
feed_id: string
guid: string | null
title: string | null
url: string | null
author: string | null
summary: string | null
image_url: string | null
published_at: string | null
enclosure_url: string | null
enclosure_type: string | null
user_entry_states: Array<{ read: boolean; saved: boolean }> | null
}
let entries = (data as unknown as EntryRow[]).map((row) => {
const state = row.user_entry_states?.[0]
return {
entryIdentifier: row.id,
feedIdentifier: row.feed_id,
guid: row.guid,
title: row.title,
url: row.url,
author: row.author,
summary: row.summary,
imageUrl: row.image_url,
publishedAt: row.published_at,
enclosureUrl: row.enclosure_url,
enclosureType: row.enclosure_type,
isRead: state?.read ?? false,
isSaved: state?.saved ?? false,
}
})
if (isRead === "true") entries = entries.filter((entry) => entry.isRead)
if (isRead === "false") entries = entries.filter((entry) => !entry.isRead)
if (isSaved === "true") entries = entries.filter((entry) => entry.isSaved)
if (isSaved === "false") entries = entries.filter((entry) => !entry.isSaved)
const hasMore = entries.length > limit
if (hasMore) entries = entries.slice(0, limit)
const nextCursor =
hasMore && entries.length > 0
? entries[entries.length - 1].publishedAt
: null
return NextResponse.json({ entries, nextCursor })
}
|