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:
@@ -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) {
|
||||
|
||||
@@ -68,6 +68,7 @@ import {
|
||||
getAncientArtifact,
|
||||
getHabitat,
|
||||
getItem,
|
||||
getLifePost,
|
||||
getOptions,
|
||||
getPokemon,
|
||||
getPublicUserProfile,
|
||||
@@ -1198,6 +1199,16 @@ app.get('/api/life-posts', async (request) => {
|
||||
);
|
||||
});
|
||||
|
||||
app.get('/api/life-posts/:id', async (request, reply) => {
|
||||
const { id } = request.params as { id: string };
|
||||
const user = await optionalUser(request);
|
||||
const canViewAll = user
|
||||
? userHasPermission(user, 'life.posts.update-any') || userHasPermission(user, 'life.posts.delete-any')
|
||||
: false;
|
||||
const post = await getLifePost(Number(id), user?.id ?? null, requestLocale(request), canViewAll);
|
||||
return post ? post : notFound(reply, request);
|
||||
});
|
||||
|
||||
app.get('/api/life-posts/:postId/comments', async (request, reply) => {
|
||||
const { postId } = request.params as { postId: string };
|
||||
const user = await optionalUser(request);
|
||||
|
||||
Reference in New Issue
Block a user