feat(life): add Life Post detail page and endpoint

Implement GET /api/life-posts/:id with moderation and visibility rules
Add /life/:id route and LifePostDetail view
Update feeds and user profiles to link to the new detail page
This commit is contained in:
2026-05-04 09:51:31 +08:00
parent bcff83a512
commit 7ff7e18b94
10 changed files with 1039 additions and 10 deletions

View File

@@ -3626,27 +3626,49 @@ export async function listUserCommentActivities(
};
}
async function getLifePostById(id: number, userId: number | null = null, locale = defaultLocale): Promise<LifePost | null> {
async function getLifePostById(
id: number,
userId: number | null = null,
locale = defaultLocale,
options: { enforceVisibility?: boolean; canViewAll?: boolean } = {}
): Promise<LifePost | null> {
const params: unknown[] = [id];
const conditions = ['lp.id = $1', 'lp.deleted_at IS NULL'];
if (options.enforceVisibility) {
addModerationVisibilityCondition(conditions, params, 'lp', 'lp.created_by_user_id', userId, options.canViewAll === true);
}
const post = await queryOne<LifePostRow>(
`
${lifePostProjection(locale)}
WHERE lp.id = $1
AND lp.deleted_at IS NULL
WHERE ${conditions.join(' AND ')}
`,
[id]
params
);
if (!post) {
return null;
}
const commentPreviewByPost = await lifeCommentPreviewForPosts([post.id], userId, false);
const commentCountsByPost = await lifeCommentCountsForPosts([post.id], userId, false);
const canViewAll = options.canViewAll === true;
const commentPreviewByPost = await lifeCommentPreviewForPosts([post.id], userId, canViewAll);
const commentCountsByPost = await lifeCommentCountsForPosts([post.id], userId, canViewAll);
const { countsByPost, myReactionsByPost } = await lifeReactionsForPosts([post.id], userId);
const myRatingsByPost = await lifeRatingsForPosts([post.id], userId);
return hydrateLifePost(post, commentPreviewByPost, commentCountsByPost, countsByPost, myReactionsByPost, myRatingsByPost);
}
export async function getLifePost(
idValue: number,
userId: number | null = null,
locale = defaultLocale,
canViewAll = false
): Promise<LifePost | null> {
const id = requirePositiveInteger(idValue, 'server.validation.recordInvalid');
return getLifePostById(id, userId, locale, { enforceVisibility: true, canViewAll });
}
async function ensureLifeCategory(client: DbClient, categoryId: number): Promise<void> {
const result = await client.query<{ id: number }>('SELECT id FROM life_tags WHERE id = $1', [categoryId]);
if (result.rowCount === 0) {