fix(moderation): remove manual reanalyze triggers — auto-recovery only

Manual per-message and batch reanalyze buttons/endpoints let anyone
re-queue arbitrary messages for LLM analysis, burning AI credits on
spam. Removed:
- FE: Reanalyze buttons in message list, search panel, and messages page
- FE: useReanalyze/useReanalyzeBatch hooks + messagesApi methods
- BE: POST /api/messages/:id/reanalyze and /reanalyze-batch endpoints
- BE: markForReanalysis/reanalyzeErrorBatch service+repository methods

Recovery of failed messages is fully automatic: the discord-gateway
startPendingAIAnalysisWorker retries 'pending' (batch path) and
'error/analysis_incomplete' (individual path) messages on
AI_ANALYSIS_RECOVERY_INTERVAL_MS.
This commit is contained in:
asepharyana
2026-08-04 15:37:56 +07:00
parent 88484f12a9
commit 2f51f94610
11 changed files with 6 additions and 243 deletions
@@ -6,7 +6,6 @@ import {
isNull,
like,
lt,
ne,
notInArray,
or,
type SQL,
@@ -223,58 +222,6 @@ export class MessagesRepository {
return mapMessageRow(row as Record<string, unknown>);
}
/**
* Bulk-reset ai_status from 'error' to 'pending' so the DG recovery worker
* picks them up on its next poll cycle.
*
* Accepts optional scope filters (guildId, channelId) or a list of explicit
* message IDs. Returns the count of rows that were actually updated.
*/
async reanalyzeErrorBatch(opts: {
guildId?: string;
channelId?: string;
messageIds?: string[];
}): Promise<number> {
const db = getDatabase();
const conditions: SQL[] = [eq(pgMessagesTable.ai_status, "error")];
if (opts.messageIds && opts.messageIds.length > 0) {
conditions.push(inArray(pgMessagesTable.id, opts.messageIds));
}
if (opts.guildId) {
conditions.push(eq(pgMessagesTable.guild_id, opts.guildId));
}
if (opts.channelId) {
conditions.push(eq(pgMessagesTable.channel_id, opts.channelId));
}
const result = await db
.update(pgMessagesTable)
.set({ ai_status: "pending" })
.where(and(...conditions));
const count = result.rowCount ?? 0;
logger.info({ count, ...opts }, "Batch reanalyze triggered");
return count;
}
/**
* Mark a single message for re-analysis by resetting ai_status to 'pending'.
* Skips messages already in 'pending' state to avoid write amplification.
*/
async markForReanalysis(id: string): Promise<void> {
const db = getDatabase();
await db
.update(pgMessagesTable)
.set({ ai_status: "pending" })
.where(
and(
eq(pgMessagesTable.id, id),
ne(pgMessagesTable.ai_status, "pending"),
),
);
}
/**
* Retrieve messages flagged for review (ai_status IN ('warn', 'flagged')).
* Optionally filtered by channelId, with configurable limit.