import type { PostData, PostFilter } from './PostEngine'; export interface SharedSnapshotPostEngine { getPostsFiltered: (filter: PostFilter) => Promise; getPost: (id: string) => Promise; getPublishedVersion: (id: string) => Promise; findPublishedBySlug?: (slug: string, dateFilter?: { year: number; month: number }) => Promise; } function buildSnapshotBaseFilter(filter: PostFilter): PostFilter { const baseFilter: PostFilter = {}; if (filter.startDate) baseFilter.startDate = filter.startDate; if (filter.endDate) baseFilter.endDate = filter.endDate; if (filter.year !== undefined) baseFilter.year = filter.year; if (filter.month !== undefined) baseFilter.month = filter.month; return baseFilter; } async function toPublishedSnapshot(postEngine: SharedSnapshotPostEngine, post: PostData): Promise { if (post.status === 'published') { return post; } if (post.status === 'draft') { return await postEngine.getPublishedVersion(post.id); } return null; } function paginateSnapshots( snapshots: PostData[], pagination?: { maxPostsPerPage: number; page?: number; excludeCategories?: string[] }, ): { posts: PostData[]; totalPosts: number } { const totalPosts = snapshots.length; if (typeof pagination?.maxPostsPerPage !== 'number') { return { posts: snapshots, totalPosts }; } const maxPostsPerPage = pagination.maxPostsPerPage; const page = Number.isInteger(pagination.page) && (pagination.page ?? 0) > 0 ? (pagination.page as number) : 1; const offset = (page - 1) * maxPostsPerPage; return { posts: snapshots.slice(offset, offset + maxPostsPerPage), totalPosts, }; } export async function loadPublishedSnapshotsPage( postEngine: SharedSnapshotPostEngine, filter: PostFilter, pagination?: { maxPostsPerPage: number; page?: number; excludeCategories?: string[] }, ): Promise<{ posts: PostData[]; totalPosts: number }> { if (filter.status && filter.status !== 'published') { return { posts: [], totalPosts: 0 }; } const baseFilter = buildSnapshotBaseFilter(filter); const publishedCandidates = await postEngine.getPostsFiltered({ ...baseFilter, status: 'published', excludeCategories: filter.excludeCategories, }); const draftCandidates = await postEngine.getPostsFiltered({ ...baseFilter, status: 'draft', excludeCategories: filter.excludeCategories, }); const snapshotCandidates = await Promise.all([ ...publishedCandidates.map((post) => toPublishedSnapshot(postEngine, post)), ...draftCandidates.map((post) => toPublishedSnapshot(postEngine, post)), ]); let snapshots = snapshotCandidates.filter((post): post is PostData => post !== null); if (filter.tags && filter.tags.length > 0) { snapshots = snapshots.filter((post) => filter.tags!.every((tag) => post.tags.includes(tag))); } if (filter.categories && filter.categories.length > 0) { snapshots = snapshots.filter((post) => filter.categories!.some((category) => post.categories.includes(category))); } snapshots.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); return paginateSnapshots(snapshots, pagination); } export async function loadPublishedSnapshots( postEngine: SharedSnapshotPostEngine, filter: PostFilter, pagination?: { maxPostsPerPage: number; page?: number; excludeCategories?: string[] }, ): Promise { const result = await loadPublishedSnapshotsPage(postEngine, filter, pagination); return result.posts; } export async function loadPostsForDayPage( postEngine: SharedSnapshotPostEngine, year: number, month: number, day: number, pagination?: { maxPostsPerPage: number; page?: number; excludeCategories?: string[] }, ): Promise<{ posts: PostData[]; totalPosts: number }> { if (month < 1 || month > 12 || day < 1 || day > 31) { return { posts: [], totalPosts: 0 }; } const startDate = new Date(year, month - 1, day, 0, 0, 0, 0); const endDate = new Date(year, month - 1, day, 23, 59, 59, 999); const result = await loadPublishedSnapshotsPage( postEngine, { status: 'published', excludeCategories: pagination?.excludeCategories, startDate, endDate, }, pagination, ); const posts = result.posts.filter((post) => { const createdAt = post.createdAt; return createdAt.getFullYear() === year && createdAt.getMonth() === month - 1 && createdAt.getDate() === day; }); return { posts, totalPosts: result.totalPosts, }; } export async function findPublishedPostBySlug( postEngine: SharedSnapshotPostEngine, slug: string, dateFilter?: { year: number; month: number }, ): Promise { if (!slug) return null; if (postEngine.findPublishedBySlug) { const directMatch = await postEngine.findPublishedBySlug(slug, dateFilter); if (directMatch) { return directMatch; } } const filter: PostFilter = { ...(dateFilter ? { year: dateFilter.year, month: dateFilter.month } : {}), }; const candidates = await loadPublishedSnapshots(postEngine, filter); const match = candidates.find((candidate) => candidate.slug === slug); return match ?? null; } export async function findSinglePostBySlug( postEngine: SharedSnapshotPostEngine, slug: string, singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string }, dateFilter?: { year: number; month: number; day?: number }, ): Promise { if (singlePostOptions?.useDraftContent && singlePostOptions.draftPostId) { const draftCandidate = await postEngine.getPost(singlePostOptions.draftPostId); if (draftCandidate && draftCandidate.status === 'draft' && draftCandidate.slug === slug) { if (!dateFilter) { return draftCandidate; } const sameYear = draftCandidate.createdAt.getFullYear() === dateFilter.year; const sameMonth = draftCandidate.createdAt.getMonth() === dateFilter.month; const sameDay = dateFilter.day === undefined || draftCandidate.createdAt.getDate() === dateFilter.day; if (sameYear && sameMonth && sameDay) { return draftCandidate; } } } const fallbackDateFilter = dateFilter ? { year: dateFilter.year, month: dateFilter.month } : undefined; return findPublishedPostBySlug(postEngine, slug, fallbackDateFilter); }