191 lines
6.2 KiB
TypeScript
191 lines
6.2 KiB
TypeScript
import type { PostData, PostFilter } from './PostEngine';
|
|
|
|
export interface SharedSnapshotPostEngine {
|
|
getPostsFiltered: (filter: PostFilter) => Promise<PostData[]>;
|
|
getPost: (id: string) => Promise<PostData | null>;
|
|
getPublishedVersion: (id: string) => Promise<PostData | null>;
|
|
findPublishedBySlug?: (slug: string, dateFilter?: { year: number; month: number }) => Promise<PostData | null>;
|
|
}
|
|
|
|
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<PostData | null> {
|
|
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<PostData[]> {
|
|
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<PostData | null> {
|
|
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<PostData | null> {
|
|
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);
|
|
}
|