Browse Source

feat(server/rss): public preview and paginated list by slug

Made-with: Cursor
main
npmrun 10 hours ago
parent
commit
68ebd5311a
  1. 6
      server/api/public/profile/[publicSlug].get.ts
  2. 59
      server/service/rss/index.ts

6
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 { and, eq } from "drizzle-orm";
import { getPublicPostsPreviewBySlug } from "#server/service/posts"; import { getPublicPostsPreviewBySlug } from "#server/service/posts";
import { getPublicTimelinePreviewBySlug } from "#server/service/timeline"; import { getPublicTimelinePreviewBySlug } from "#server/service/timeline";
import { listPublicRssItemsBySlug } from "#server/service/rss"; import { getPublicRssPreviewBySlug } from "#server/service/rss";
import { parseSocialLinksJson } from "#server/service/profile"; import { parseSocialLinksJson } from "#server/service/profile";
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
@ -34,7 +34,7 @@ export default defineEventHandler(async (event) => {
links: typeof links; links: typeof links;
posts: Awaited<ReturnType<typeof getPublicPostsPreviewBySlug>>; posts: Awaited<ReturnType<typeof getPublicPostsPreviewBySlug>>;
timeline: Awaited<ReturnType<typeof getPublicTimelinePreviewBySlug>>; timeline: Awaited<ReturnType<typeof getPublicTimelinePreviewBySlug>>;
rssItems: Awaited<ReturnType<typeof listPublicRssItemsBySlug>>; rssItems: Awaited<ReturnType<typeof getPublicRssPreviewBySlug>>;
} = { } = {
user: { user: {
publicSlug: owner.publicSlug, publicSlug: owner.publicSlug,
@ -45,7 +45,7 @@ export default defineEventHandler(async (event) => {
links, links,
posts: await getPublicPostsPreviewBySlug(publicSlug), posts: await getPublicPostsPreviewBySlug(publicSlug),
timeline: await getPublicTimelinePreviewBySlug(publicSlug), timeline: await getPublicTimelinePreviewBySlug(publicSlug),
rssItems: await listPublicRssItemsBySlug(publicSlug), rssItems: await getPublicRssPreviewBySlug(publicSlug),
}; };
if (owner.bioVisibility === "public" && owner.bioMarkdown) { if (owner.bioVisibility === "public" && owner.bioMarkdown) {

59
server/service/rss/index.ts

@ -1,7 +1,9 @@
import { dbGlobal } from "drizzle-pkg/lib/db"; import { dbGlobal } from "drizzle-pkg/lib/db";
import { rssFeeds, rssItems } from "drizzle-pkg/lib/schema/rss"; import { rssFeeds, rssItems } from "drizzle-pkg/lib/schema/rss";
import { users } from "drizzle-pkg/lib/schema/auth"; 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 { XMLParser } from "fast-xml-parser";
import { assertSafeRssUrl } from "#server/utils/rss-url"; import { assertSafeRssUrl } from "#server/utils/rss-url";
import { RssUrlUnsafeError } 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; return row ?? null;
} }
export async function listPublicRssItemsBySlug(publicSlug: string) { function publicRssListWhere(publicSlug: string) {
const rows = await dbGlobal return and(
.select({ it: rssItems })
.from(rssItems)
.innerJoin(users, eq(rssItems.userId, users.id))
.where(
and(
eq(users.publicSlug, publicSlug), eq(users.publicSlug, publicSlug),
eq(users.status, "active"), eq(users.status, "active"),
eq(rssItems.visibility, "public"), 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)) .orderBy(desc(rssItems.publishedAt), desc(rssItems.id))
.limit(200); .limit(pageSize)
return rows.map((r) => r.it); .offset(offset),
]);
return { items: rows.map((r) => r.it), total: countRows[0]?.total ?? 0, page, pageSize };
} }
export async function getUnlistedRssItem(publicSlug: string, shareToken: string) { export async function getUnlistedRssItem(publicSlug: string, shareToken: string) {

Loading…
Cancel
Save