fix(users): drop dead user_reputations JOIN that broke Members page + audit

Root cause: gateway migration 0016 removed the per-user reputation feature
(DROP TABLE user_reputations), but the backend still LEFT JOINed
pgUserReputationsTable in dashboard listUsers/getUserDetail and the chatbot
get_user_reputation tool. Against the live DB these queries raised 42P01
(undefined_table) -> the new /users page could never load (oRPC WS error).

Fix:
- dashboard.repository.ts: remove reputation columns/join from listUsers and
  getUserDetail (data sourced only from messages + user_profiles).
- chatbot.tools.ts: get_user_reputation now returns honest 'unavailable'
  (feature removed) instead of querying the dropped table.
- chatbot.toolDefs.ts: update tool description so the LLM doesn't advertise
  a dead feature.
- frontend types/users view: drop trust_score/clean_message_streak/
  total_infractions/last_infraction_at; derive risk label from real flagged%
  and add warn_count to the inspector (real available data).

Verified: backend+frontend tsc clean, biome clean, 37 unit tests pass,
query tested against live DB (returns real members), build green.
This commit is contained in:
asepharyana
2026-09-02 00:12:52 +07:00
parent 7122258d7c
commit d0453881b8
5 changed files with 56 additions and 94 deletions
@@ -5,7 +5,6 @@ import {
pgChannelCulturesTable,
pgMessagesTable,
pgUserProfilesTable,
pgUserReputationsTable,
pgVoiceRecordingsTable,
} from "../../shared/index.js";
import type { ListUsersQuery } from "./dashboard.service.js";
@@ -154,10 +153,6 @@ export class DashboardRepository {
m.username,
m.avatar_url,
p.profile_summary,
r.trust_score,
r.clean_message_streak,
r.total_infractions,
r.last_infraction_at,
m.total_messages,
m.flagged_count,
m.clean_count,
@@ -177,7 +172,6 @@ export class DashboardRepository {
GROUP BY user_id, username, avatar_url
) m
LEFT JOIN ${pgUserProfilesTable} p ON p.user_id = m.user_id
LEFT JOIN ${pgUserReputationsTable} r ON r.user_id = m.user_id
${whereClause}
ORDER BY m.last_message_at DESC NULLS LAST
LIMIT ${limit + 1}
@@ -190,16 +184,6 @@ export class DashboardRepository {
username: r.username as string | null,
avatar_url: r.avatar_url as string | null,
profile_summary: r.profile_summary as string | null,
trust_score: r.trust_score != null ? Number(r.trust_score) : null,
clean_message_streak:
r.clean_message_streak != null
? Number(r.clean_message_streak)
: null,
total_infractions:
r.total_infractions != null ? Number(r.total_infractions) : null,
last_infraction_at: r.last_infraction_at
? Number(r.last_infraction_at)
: null,
total_messages: Number(r.total_messages),
flagged_count: Number(r.flagged_count),
clean_count: Number(r.clean_count),
@@ -453,11 +437,7 @@ export class DashboardRepository {
m.clean_count,
m.warn_count,
p.profile_summary,
p.last_analyzed_at,
r.trust_score,
r.clean_message_streak,
r.total_infractions,
r.last_infraction_at
p.last_analyzed_at
FROM (
SELECT
user_id,
@@ -472,7 +452,6 @@ export class DashboardRepository {
GROUP BY user_id, username, avatar_url
) m
LEFT JOIN ${pgUserProfilesTable} p ON p.user_id = m.user_id
LEFT JOIN ${pgUserReputationsTable} r ON r.user_id = m.user_id
`);
const row = userResult.rows[0] as Record<string, unknown> | undefined;
@@ -500,16 +479,6 @@ export class DashboardRepository {
last_analyzed_at: row.last_analyzed_at
? Number(row.last_analyzed_at)
: null,
trust_score: row.trust_score != null ? Number(row.trust_score) : null,
clean_message_streak:
row.clean_message_streak != null
? Number(row.clean_message_streak)
: null,
total_infractions:
row.total_infractions != null ? Number(row.total_infractions) : null,
last_infraction_at: row.last_infraction_at
? Number(row.last_infraction_at)
: null,
recent_messages: (recent.rows as Record<string, unknown>[]).map((r) => ({
id: String(r.id),
content: String(r.content),