summaryrefslogtreecommitdiff
path: root/apps/web/app/api/v1/entries/route.ts
blob: 47789f17387c9ddedb60595945b7204f0a3790ba (plain) (blame)
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 })
}