feat(profile): add password change and activity filters

Implement password change API and UI in the Account tab
Add secondary filters for contributions, reactions, and comments
Display referral summary in the profile header
This commit is contained in:
2026-05-03 13:52:35 +08:00
parent 0e835f9c03
commit 282481bbcc
8 changed files with 453 additions and 23 deletions

View File

@@ -2205,6 +2205,28 @@ function cleanLifeReactionType(value: unknown): LifeReactionType {
return value;
}
function cleanLifeReactionFilter(value: QueryValue): LifeReactionType | null {
const reactionType = asString(value);
if (!reactionType) {
return null;
}
return cleanLifeReactionType(reactionType);
}
function cleanUserCommentActivitySourceFilter(value: QueryValue): UserCommentActivitySource | null {
const source = asString(value);
if (!source) {
return null;
}
if (source !== 'life' && source !== 'discussion') {
throw validationError('server.validation.invalidField');
}
return source;
}
function lifePostProjection(locale = defaultLocale): string {
const tagName = localizedName('life-tags', 'lt', locale);
@@ -2691,9 +2713,15 @@ export async function listUserReactionActivities(
const cursor = decodeLifePostCursor(paramsQuery.cursor);
const limit = cleanLifePostLimit(paramsQuery.limit);
const reactionType = cleanLifeReactionFilter(paramsQuery.reactionType);
const params: unknown[] = [user.id];
const conditions = ['lpr.user_id = $1', 'lp.deleted_at IS NULL'];
if (reactionType) {
params.push(reactionType);
conditions.push(`lpr.reaction_type = $${params.length}`);
}
if (cursor) {
params.push(cursor.createdAt, cursor.id);
conditions.push(`(lpr.updated_at, lpr.post_id) < ($${params.length - 1}::timestamptz, $${params.length}::integer)`);
@@ -2765,19 +2793,28 @@ export async function listUserCommentActivities(
const cursor = decodeUserCommentActivityCursor(paramsQuery.cursor);
const limit = cleanLifePostLimit(paramsQuery.limit);
const sourceFilter = cleanUserCommentActivitySourceFilter(paramsQuery.source);
const pokemonName = localizedName('pokemon', 'p', locale);
const itemName = localizedName('items', 'i', locale);
const recipeItemName = localizedName('items', 'recipe_item', locale);
const habitatName = localizedName('habitats', 'h', locale);
const params: unknown[] = [user.id];
let cursorClause = '';
const outerConditions: string[] = [];
if (sourceFilter) {
params.push(sourceFilter);
outerConditions.push(`source = $${params.length}`);
}
if (cursor) {
params.push(cursor.createdAt, cursor.source, cursor.id);
cursorClause = `WHERE (created_at, source, id) < ($${params.length - 2}::timestamptz, $${params.length - 1}::text, $${params.length}::integer)`;
outerConditions.push(
`(created_at, source, id) < ($${params.length - 2}::timestamptz, $${params.length - 1}::text, $${params.length}::integer)`
);
}
params.push(limit + 1);
const outerWhere = outerConditions.length ? `WHERE ${outerConditions.join(' AND ')}` : '';
const rows = await query<{
id: number;
source: UserCommentActivitySource;
@@ -2849,7 +2886,7 @@ export async function listUserCommentActivities(
target_title AS "targetTitle",
target_excerpt AS "targetExcerpt"
FROM activity
${cursorClause}
${outerWhere}
ORDER BY created_at DESC, source DESC, id DESC
LIMIT $${params.length}
`,