Browse Source

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

Made-with: Cursor
main
npmrun 9 hours ago
parent
commit
b9b443f30c
  1. 6
      server/api/public/profile/[publicSlug].get.ts
  2. 66
      server/service/timeline/index.ts

6
server/api/public/profile/[publicSlug].get.ts

@ -2,7 +2,7 @@ import { dbGlobal } from "drizzle-pkg/lib/db";
import { users } from "drizzle-pkg/lib/schema/auth"; 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 { listPublicTimelineBySlug } from "#server/service/timeline"; import { getPublicTimelinePreviewBySlug } from "#server/service/timeline";
import { listPublicRssItemsBySlug } from "#server/service/rss"; import { listPublicRssItemsBySlug } from "#server/service/rss";
import { parseSocialLinksJson } from "#server/service/profile"; import { parseSocialLinksJson } from "#server/service/profile";
@ -33,7 +33,7 @@ export default defineEventHandler(async (event) => {
bio: { markdown: string | null } | null; bio: { markdown: string | null } | null;
links: typeof links; links: typeof links;
posts: Awaited<ReturnType<typeof getPublicPostsPreviewBySlug>>; posts: Awaited<ReturnType<typeof getPublicPostsPreviewBySlug>>;
timeline: Awaited<ReturnType<typeof listPublicTimelineBySlug>>; timeline: Awaited<ReturnType<typeof getPublicTimelinePreviewBySlug>>;
rssItems: Awaited<ReturnType<typeof listPublicRssItemsBySlug>>; rssItems: Awaited<ReturnType<typeof listPublicRssItemsBySlug>>;
} = { } = {
user: { user: {
@ -44,7 +44,7 @@ export default defineEventHandler(async (event) => {
bio: null, bio: null,
links, links,
posts: await getPublicPostsPreviewBySlug(publicSlug), posts: await getPublicPostsPreviewBySlug(publicSlug),
timeline: await listPublicTimelineBySlug(publicSlug), timeline: await getPublicTimelinePreviewBySlug(publicSlug),
rssItems: await listPublicRssItemsBySlug(publicSlug), rssItems: await listPublicRssItemsBySlug(publicSlug),
}; };

66
server/service/timeline/index.ts

@ -1,8 +1,10 @@
import { dbGlobal } from "drizzle-pkg/lib/db"; import { dbGlobal } from "drizzle-pkg/lib/db";
import { timelineEvents } from "drizzle-pkg/lib/schema/content"; import { timelineEvents } from "drizzle-pkg/lib/schema/content";
import { users } from "drizzle-pkg/lib/schema/auth"; import { users } from "drizzle-pkg/lib/schema/auth";
import { and, desc, eq } from "drizzle-orm"; import { and, count, desc, eq } from "drizzle-orm";
import { PUBLIC_LIST_PAGE_SIZE, PUBLIC_PREVIEW_LIMIT } from "#server/constants/public-profile-lists";
import { visibilitySchema, type Visibility } from "#server/constants/visibility"; import { visibilitySchema, type Visibility } from "#server/constants/visibility";
import { normalizePublicListPage } from "#server/utils/public-pagination";
import { visibilityShareToken } from "#server/utils/share-token"; import { visibilityShareToken } from "#server/utils/share-token";
import { nextIntegerId } from "#server/utils/sqlite-id"; import { nextIntegerId } from "#server/utils/sqlite-id";
@ -100,20 +102,54 @@ export async function deleteTimelineEvent(userId: number, id: number) {
return true; return true;
} }
export async function listPublicTimelineBySlug(publicSlug: string) { function publicTimelineListWhere(publicSlug: string) {
const rows = await dbGlobal return and(
.select({ ev: timelineEvents }) eq(users.publicSlug, publicSlug),
.from(timelineEvents) eq(users.status, "active"),
.innerJoin(users, eq(timelineEvents.userId, users.id)) eq(timelineEvents.visibility, "public"),
.where( );
and( }
eq(users.publicSlug, publicSlug),
eq(users.status, "active"), export async function getPublicTimelinePreviewBySlug(publicSlug: string) {
eq(timelineEvents.visibility, "public"), const whereClause = publicTimelineListWhere(publicSlug);
), const [countRows, rows] = await Promise.all([
) dbGlobal
.orderBy(desc(timelineEvents.occurredOn), desc(timelineEvents.id)); .select({ total: count() })
return rows.map((r) => r.ev); .from(timelineEvents)
.innerJoin(users, eq(timelineEvents.userId, users.id))
.where(whereClause),
dbGlobal
.select({ ev: timelineEvents })
.from(timelineEvents)
.innerJoin(users, eq(timelineEvents.userId, users.id))
.where(whereClause)
.orderBy(desc(timelineEvents.occurredOn), desc(timelineEvents.id))
.limit(PUBLIC_PREVIEW_LIMIT),
]);
return { items: rows.map((r) => r.ev), total: countRows[0]?.total ?? 0 };
}
export async function getPublicTimelinePageBySlug(publicSlug: string, pageRaw: unknown) {
const page = normalizePublicListPage(pageRaw);
const pageSize = PUBLIC_LIST_PAGE_SIZE;
const offset = (page - 1) * pageSize;
const whereClause = publicTimelineListWhere(publicSlug);
const [countRows, rows] = await Promise.all([
dbGlobal
.select({ total: count() })
.from(timelineEvents)
.innerJoin(users, eq(timelineEvents.userId, users.id))
.where(whereClause),
dbGlobal
.select({ ev: timelineEvents })
.from(timelineEvents)
.innerJoin(users, eq(timelineEvents.userId, users.id))
.where(whereClause)
.orderBy(desc(timelineEvents.occurredOn), desc(timelineEvents.id))
.limit(pageSize)
.offset(offset),
]);
return { items: rows.map((r) => r.ev), total: countRows[0]?.total ?? 0, page, pageSize };
} }
export async function getUnlistedTimeline(publicSlug: string, shareToken: string) { export async function getUnlistedTimeline(publicSlug: string, shareToken: string) {

Loading…
Cancel
Save