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:
@@ -152,7 +152,7 @@ export const tools: ToolDef[] = [
|
||||
function: {
|
||||
name: "get_user_reputation",
|
||||
description:
|
||||
"Ambil skor trust, jumlah infraction, dan streak pesan bersih seorang user dari user_reputations. Untuk 'berapa trust score si A?' / riwayat pelanggaran. Butuh user_id.",
|
||||
"Cek status skor reputasi per-user. CATATAN: fitur trust score per-user telah dihapus dari sistem — tool ini menjawab 'unavailable' dan menyarankan rasio flag vs total sebagai pengganti.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
pgMessageReviewsTable,
|
||||
pgMessagesTable,
|
||||
pgUserProfilesTable,
|
||||
pgUserReputationsTable,
|
||||
pgVoiceRecordingsTable,
|
||||
} from "../../shared/index.js";
|
||||
|
||||
@@ -282,26 +281,18 @@ async function userProfile(userId?: string, guildId?: string): Promise<string> {
|
||||
}
|
||||
|
||||
async function userReputation(
|
||||
userId?: string,
|
||||
guildId?: string,
|
||||
_userId?: string,
|
||||
_guildId?: string,
|
||||
): Promise<string> {
|
||||
const db = getDatabase();
|
||||
if (!userId) return JSON.stringify({ error: "userId wajib" });
|
||||
const conds = [eq(pgUserReputationsTable.user_id, userId)];
|
||||
if (guildId) conds.push(eq(pgUserReputationsTable.guild_id, guildId));
|
||||
const rows = await db
|
||||
.select({
|
||||
user_id: pgUserReputationsTable.user_id,
|
||||
guild_id: pgUserReputationsTable.guild_id,
|
||||
trust_score: pgUserReputationsTable.trust_score,
|
||||
clean_message_streak: pgUserReputationsTable.clean_message_streak,
|
||||
total_infractions: pgUserReputationsTable.total_infractions,
|
||||
last_infraction_at: pgUserReputationsTable.last_infraction_at,
|
||||
})
|
||||
.from(pgUserReputationsTable)
|
||||
.where(and(...conds))
|
||||
.limit(1);
|
||||
return JSON.stringify(rows[0] ?? { error: "reputasi tidak ditemukan" });
|
||||
// The per-user reputation feature (trust scores, streaks, infractions) was
|
||||
// removed from the gateway (migration 0016 drops user_reputations). Return
|
||||
// an honest "unavailable" answer from the derived moderation signals instead
|
||||
// of querying the now-dropped table.
|
||||
return JSON.stringify({
|
||||
available: false,
|
||||
message:
|
||||
"Skor trust/skala reputasi per-user telah dihapus dari sistem. Gunakan rasio pesan ter-flag vs total untuk menilai risiko (lihat dashboard Users).",
|
||||
});
|
||||
}
|
||||
|
||||
async function channelCulture(channelId?: string): Promise<string> {
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user