From 68ebd5311a042b1e29bfd4c2098caa088ed24518 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sat, 18 Apr 2026 21:38:27 +0800 Subject: [PATCH] feat(server/rss): public preview and paginated list by slug Made-with: Cursor --- server/api/public/profile/[publicSlug].get.ts | 6 +-- server/service/rss/index.ts | 67 ++++++++++++++++++++------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/server/api/public/profile/[publicSlug].get.ts b/server/api/public/profile/[publicSlug].get.ts index 36a6e08..eb08ed4 100644 --- a/server/api/public/profile/[publicSlug].get.ts +++ b/server/api/public/profile/[publicSlug].get.ts @@ -3,7 +3,7 @@ import { users } from "drizzle-pkg/lib/schema/auth"; import { and, eq } from "drizzle-orm"; import { getPublicPostsPreviewBySlug } from "#server/service/posts"; import { getPublicTimelinePreviewBySlug } from "#server/service/timeline"; -import { listPublicRssItemsBySlug } from "#server/service/rss"; +import { getPublicRssPreviewBySlug } from "#server/service/rss"; import { parseSocialLinksJson } from "#server/service/profile"; export default defineEventHandler(async (event) => { @@ -34,7 +34,7 @@ export default defineEventHandler(async (event) => { links: typeof links; posts: Awaited>; timeline: Awaited>; - rssItems: Awaited>; + rssItems: Awaited>; } = { user: { publicSlug: owner.publicSlug, @@ -45,7 +45,7 @@ export default defineEventHandler(async (event) => { links, posts: await getPublicPostsPreviewBySlug(publicSlug), timeline: await getPublicTimelinePreviewBySlug(publicSlug), - rssItems: await listPublicRssItemsBySlug(publicSlug), + rssItems: await getPublicRssPreviewBySlug(publicSlug), }; if (owner.bioVisibility === "public" && owner.bioMarkdown) { diff --git a/server/service/rss/index.ts b/server/service/rss/index.ts index 0b475d8..2c533b1 100644 --- a/server/service/rss/index.ts +++ b/server/service/rss/index.ts @@ -1,7 +1,9 @@ import { dbGlobal } from "drizzle-pkg/lib/db"; import { rssFeeds, rssItems } from "drizzle-pkg/lib/schema/rss"; import { users } from "drizzle-pkg/lib/schema/auth"; -import { and, desc, eq } from "drizzle-orm"; +import { PUBLIC_LIST_PAGE_SIZE, PUBLIC_PREVIEW_LIMIT } from "#server/constants/public-profile-lists"; +import { normalizePublicListPage } from "#server/utils/public-pagination"; +import { and, count, desc, eq } from "drizzle-orm"; import { XMLParser } from "fast-xml-parser"; import { assertSafeRssUrl } from "#server/utils/rss-url"; import { RssUrlUnsafeError } from "#server/utils/rss-url"; @@ -76,21 +78,54 @@ export async function updateItemVisibility(userId: number, itemId: number, visib return row ?? null; } -export async function listPublicRssItemsBySlug(publicSlug: string) { - const rows = await dbGlobal - .select({ it: rssItems }) - .from(rssItems) - .innerJoin(users, eq(rssItems.userId, users.id)) - .where( - and( - eq(users.publicSlug, publicSlug), - eq(users.status, "active"), - eq(rssItems.visibility, "public"), - ), - ) - .orderBy(desc(rssItems.publishedAt), desc(rssItems.id)) - .limit(200); - return rows.map((r) => r.it); +function publicRssListWhere(publicSlug: string) { + return and( + eq(users.publicSlug, publicSlug), + eq(users.status, "active"), + eq(rssItems.visibility, "public"), + ); +} + +export async function getPublicRssPreviewBySlug(publicSlug: string) { + const whereClause = publicRssListWhere(publicSlug); + const [countRows, rows] = await Promise.all([ + dbGlobal + .select({ total: count() }) + .from(rssItems) + .innerJoin(users, eq(rssItems.userId, users.id)) + .where(whereClause), + dbGlobal + .select({ it: rssItems }) + .from(rssItems) + .innerJoin(users, eq(rssItems.userId, users.id)) + .where(whereClause) + .orderBy(desc(rssItems.publishedAt), desc(rssItems.id)) + .limit(PUBLIC_PREVIEW_LIMIT), + ]); + return { items: rows.map((r) => r.it), total: countRows[0]?.total ?? 0 }; +} + +export async function getPublicRssPageBySlug(publicSlug: string, pageRaw: unknown) { + const page = normalizePublicListPage(pageRaw); + const pageSize = PUBLIC_LIST_PAGE_SIZE; + const offset = (page - 1) * pageSize; + const whereClause = publicRssListWhere(publicSlug); + const [countRows, rows] = await Promise.all([ + dbGlobal + .select({ total: count() }) + .from(rssItems) + .innerJoin(users, eq(rssItems.userId, users.id)) + .where(whereClause), + dbGlobal + .select({ it: rssItems }) + .from(rssItems) + .innerJoin(users, eq(rssItems.userId, users.id)) + .where(whereClause) + .orderBy(desc(rssItems.publishedAt), desc(rssItems.id)) + .limit(pageSize) + .offset(offset), + ]); + return { items: rows.map((r) => r.it), total: countRows[0]?.total ?? 0, page, pageSize }; } export async function getUnlistedRssItem(publicSlug: string, shareToken: string) {