feat(profile): add public user profiles with activity tabs and stats

Add API routes for user stats, posts, reactions, and comments
Implement profile view with Feeds, Contributions, Reactions tabs
Link to user profiles from edit history, discussions, and life posts
Add database indexes to optimize user-centric queries
This commit is contained in:
2026-05-03 13:14:29 +08:00
parent b9ec8076ac
commit 0e835f9c03
13 changed files with 1762 additions and 146 deletions

View File

@@ -61,6 +61,7 @@ import {
getItem,
getOptions,
getPokemon,
getPublicUserProfile,
getRecipe,
isConfigType,
listEntityDiscussionComments,
@@ -73,6 +74,9 @@ import {
listPokemon,
listPokemonFetchOptions,
listRecipes,
listUserCommentActivities,
listUserLifePosts,
listUserReactionActivities,
reorderConfig,
reorderDailyChecklistItems,
reorderHabitats,
@@ -406,6 +410,46 @@ app.get('/api/options', async (request) => getOptions(requestLocale(request)));
app.get('/api/daily-checklist', async (request) => listDailyChecklistItems(requestLocale(request)));
app.get('/api/users/:id/profile', async (request, reply) => {
const { id } = request.params as { id: string };
const profile = await getPublicUserProfile(Number(id));
return profile ? { profile } : notFound(reply, request);
});
app.get('/api/users/:id/life-posts', async (request, reply) => {
const { id } = request.params as { id: string };
const user = await optionalUser(request);
const posts = await listUserLifePosts(
Number(id),
request.query as Record<string, string | string[] | undefined>,
user?.id ?? null,
requestLocale(request)
);
return posts ? posts : notFound(reply, request);
});
app.get('/api/users/:id/reactions', async (request, reply) => {
const { id } = request.params as { id: string };
const user = await optionalUser(request);
const reactions = await listUserReactionActivities(
Number(id),
request.query as Record<string, string | string[] | undefined>,
user?.id ?? null,
requestLocale(request)
);
return reactions ? reactions : notFound(reply, request);
});
app.get('/api/users/:id/comments', async (request, reply) => {
const { id } = request.params as { id: string };
const comments = await listUserCommentActivities(
Number(id),
request.query as Record<string, string | string[] | undefined>,
requestLocale(request)
);
return comments ? comments : notFound(reply, request);
});
app.get('/api/life-posts', async (request) => {
const user = await optionalUser(request);
return listLifePosts(request.query as Record<string, string | string[] | undefined>, user?.id ?? null, requestLocale(request));