feat(dashboard): add moderation log page + message edit history
- Backend moderation module: GET /api/moderation/stats (per-status + failed rate) + GET /api/moderation/actions (filter by status/actionType, cursor paging), joins messages for target username + content - Message GET /api/messages/detail/:id now returns edit_count + edit_history (old_content snapshots from message_edits, newest first) - FE: new /moderation page — summary cards (total/executed/failed/pending + failed-rate), status+type filter chips, timeline rows with action icon, target user, reason, status badge, timestamps, error text - FE: message detail shows 'Riwayat edit' panel with previous versions
This commit is contained in:
@@ -4,6 +4,7 @@ export { configApi } from "./config";
|
||||
export { dashboardApi } from "./dashboard";
|
||||
export { mediaApi } from "./media";
|
||||
export { messagesApi } from "./messages";
|
||||
export { moderationApi } from "./moderation";
|
||||
export { recordingsApi } from "./recordings";
|
||||
export { uiStateApi } from "./ui-state";
|
||||
export { voiceApi } from "./voice";
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ModerationStats, PaginatedModerationActions } from "@/lib/types";
|
||||
import { api } from "./client";
|
||||
|
||||
export const moderationApi = {
|
||||
getStats: () => api.get<ModerationStats>("/api/moderation/stats"),
|
||||
|
||||
listActions: (
|
||||
limit?: number,
|
||||
status?: string,
|
||||
actionType?: string,
|
||||
cursor?: string,
|
||||
) => {
|
||||
const params = new URLSearchParams();
|
||||
if (limit) params.set("limit", String(limit));
|
||||
if (status) params.set("status", status);
|
||||
if (actionType) params.set("actionType", actionType);
|
||||
if (cursor) params.set("cursor", cursor);
|
||||
const qs = params.toString();
|
||||
return api.get<PaginatedModerationActions>(
|
||||
`/api/moderation/actions${qs ? `?${qs}` : ""}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Mic,
|
||||
Music,
|
||||
Search,
|
||||
Shield,
|
||||
} from "lucide-react";
|
||||
|
||||
export interface NavItem {
|
||||
@@ -50,6 +51,12 @@ export const navItems: NavItem[] = [
|
||||
icon: Headphones,
|
||||
matchPrefix: "/recordings",
|
||||
},
|
||||
{
|
||||
href: "/moderation",
|
||||
label: "Moderation",
|
||||
icon: Shield,
|
||||
matchPrefix: "/moderation",
|
||||
},
|
||||
{
|
||||
href: "/analysis",
|
||||
label: "Search",
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from "./dashboard";
|
||||
export * from "./guild";
|
||||
export * from "./media";
|
||||
export * from "./message";
|
||||
export * from "./moderation";
|
||||
export * from "./recording";
|
||||
export * from "./ui";
|
||||
export * from "./voice";
|
||||
|
||||
@@ -127,6 +127,10 @@ export interface MessageRecord {
|
||||
ai_recommended_action?: AiRecommendedAction | null;
|
||||
ai_error?: string | null;
|
||||
ai_analyzed_at?: number | null;
|
||||
/** Detail-only: number of past edits (message_edits snapshots) */
|
||||
edit_count?: number;
|
||||
/** Detail-only: previous content snapshots, newest first */
|
||||
edit_history?: Array<{ old_content: string; edited_at: number }>;
|
||||
}
|
||||
|
||||
// ── Pagination ──────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export type ModerationActionType =
|
||||
| "delete_message"
|
||||
| "mute_user"
|
||||
| "warn_user"
|
||||
| "kick_user"
|
||||
| "ban_user";
|
||||
|
||||
export type ModerationStatus = "pending" | "executed" | "failed";
|
||||
|
||||
export interface ModerationAction {
|
||||
id: string;
|
||||
message_id: string | null;
|
||||
user_id: string | null;
|
||||
guild_id: string;
|
||||
action_type: ModerationActionType;
|
||||
reason: string | null;
|
||||
executed_by: string | null;
|
||||
status: ModerationStatus;
|
||||
error: string | null;
|
||||
created_at: number | null;
|
||||
executed_at: number | null;
|
||||
username: string | null;
|
||||
content: string | null;
|
||||
}
|
||||
|
||||
export interface ModerationStats {
|
||||
total: number;
|
||||
executed: number;
|
||||
failed: number;
|
||||
pending: number;
|
||||
failed_rate: number;
|
||||
by_action: Record<string, ModerationActionType>;
|
||||
}
|
||||
|
||||
export interface PaginatedModerationActions {
|
||||
data: ModerationAction[];
|
||||
nextCursor: string | null;
|
||||
}
|
||||
Reference in New Issue
Block a user