refactor: atomic, DRY, and logging improvements across codebase
- Split llmModerationClient.ts (2170 lines) into 5 focused sub-modules - Split aiAnalyzer.ts (1282 lines) into 4 modular pipelines - Split messages.db.ts (826 lines) into 5 domain-specific modules - Moved shared schema to @bete/shared, eliminated backend duplication - Added createChildLogger to all voice-recording and AI moderation modules - Extracted tryCommandThenFallback, normalizeMediaState, DEFAULT_VOICE_STATUS - Created shared pagination.ts utility, eliminated 5+ cursor-pagination duplications - Created shared messageMapper.ts for row mapping - Standardized backend error handling with asyncHandler - Added frontend createLogger utility and useAsyncAction hook - Added structured logging to frontend hooks, socket, and API client Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b68789fffc
commit
07032ab521
@@ -7,6 +7,10 @@ import {
|
||||
skipMedia,
|
||||
stopMedia,
|
||||
} from "../../../shared/api/client";
|
||||
import { useAsyncAction } from "../../../shared/hooks/useAsyncAction.js";
|
||||
import { createLogger } from "../../../shared/lib/logger.js";
|
||||
|
||||
const logger = createLogger("use-media-control");
|
||||
|
||||
const emptyMediaState: MediaState = {
|
||||
playing: false,
|
||||
@@ -17,8 +21,7 @@ const emptyMediaState: MediaState = {
|
||||
|
||||
export function useMediaControl() {
|
||||
const [mediaState, setMediaState] = useState<MediaState>(emptyMediaState);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { loading, error, execute, clearError } = useAsyncAction();
|
||||
|
||||
const refreshMedia = useCallback(async () => {
|
||||
const state = await getMediaStatus();
|
||||
@@ -28,71 +31,62 @@ export function useMediaControl() {
|
||||
|
||||
const enqueue = useCallback(
|
||||
async (source: string, mode: "music" | "screen") => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const state = await queueMedia(source, mode);
|
||||
setMediaState(state);
|
||||
return state;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const result = await execute(() => queueMedia(source, mode));
|
||||
if (result) {
|
||||
setMediaState(result);
|
||||
logger.info("Media queued", { source, mode });
|
||||
} else {
|
||||
logger.error("Failed to queue media", { source, mode });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[],
|
||||
[execute],
|
||||
);
|
||||
|
||||
const skip = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const state = await skipMedia();
|
||||
setMediaState(state);
|
||||
return state;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const result = await execute(() => skipMedia());
|
||||
if (result) {
|
||||
setMediaState(result);
|
||||
logger.info("Media skipped");
|
||||
} else {
|
||||
logger.error("Failed to skip media");
|
||||
}
|
||||
}, []);
|
||||
return result;
|
||||
}, [execute]);
|
||||
|
||||
const stop = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const state = await stopMedia();
|
||||
setMediaState(state);
|
||||
return state;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const result = await execute(() => stopMedia());
|
||||
if (result) {
|
||||
setMediaState(result);
|
||||
logger.info("Media stopped");
|
||||
} else {
|
||||
logger.error("Failed to stop media");
|
||||
}
|
||||
}, []);
|
||||
return result;
|
||||
}, [execute]);
|
||||
|
||||
const setVolume = useCallback(async (volume: number) => {
|
||||
setError(null);
|
||||
try {
|
||||
const state = await setMediaVolume(volume);
|
||||
setMediaState(state);
|
||||
return state;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
throw err;
|
||||
}
|
||||
}, []);
|
||||
const setVolume = useCallback(
|
||||
async (volume: number) => {
|
||||
clearError();
|
||||
try {
|
||||
const state = await setMediaVolume(volume);
|
||||
setMediaState(state);
|
||||
logger.info("Volume set", { volume });
|
||||
return state;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.error("Failed to set volume", { volume, error: message });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
[clearError],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
refreshMedia().catch((err) =>
|
||||
setError(err instanceof Error ? err.message : String(err)),
|
||||
logger.error("Failed to refresh media state on mount", {
|
||||
error: String(err),
|
||||
}),
|
||||
);
|
||||
}, [refreshMedia]);
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ import {
|
||||
getVoiceChannels,
|
||||
getVoiceStatus,
|
||||
} from "../../../shared/api/client";
|
||||
import { useAsyncAction } from "../../../shared/hooks/useAsyncAction.js";
|
||||
import { createLogger } from "../../../shared/lib/logger.js";
|
||||
|
||||
const logger = createLogger("use-voice-control");
|
||||
|
||||
export function useVoiceControl() {
|
||||
const [guilds, setGuilds] = useState<Guild[]>([]);
|
||||
@@ -19,15 +23,14 @@ export function useVoiceControl() {
|
||||
activeChannelId: null,
|
||||
activeChannelName: null,
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { loading, error, execute, clearError } = useAsyncAction();
|
||||
|
||||
const refreshGuilds = useCallback(async () => {
|
||||
setError(null);
|
||||
clearError();
|
||||
const nextGuilds = await getGuilds();
|
||||
setGuilds(nextGuilds);
|
||||
return nextGuilds;
|
||||
}, []);
|
||||
}, [clearError]);
|
||||
|
||||
const refreshVoiceStatus = useCallback(async () => {
|
||||
const status = await getVoiceStatus();
|
||||
@@ -55,44 +58,39 @@ export function useVoiceControl() {
|
||||
return channels;
|
||||
}, []);
|
||||
|
||||
const joinVoice = useCallback(async (guildId: string, channelId: string) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const status = await connectVoice(guildId, channelId);
|
||||
setVoiceStatus(status);
|
||||
return status;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
const joinVoice = useCallback(
|
||||
async (guildId: string, channelId: string) => {
|
||||
const result = await execute(() => connectVoice(guildId, channelId));
|
||||
if (result) {
|
||||
setVoiceStatus(result);
|
||||
logger.info("Connected to voice", { guildId, channelId });
|
||||
} else {
|
||||
logger.error("Failed to connect to voice", { guildId, channelId });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[execute],
|
||||
);
|
||||
|
||||
const leaveVoice = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const status = await disconnectVoice();
|
||||
setVoiceStatus(status);
|
||||
return status;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
const result = await execute(() => disconnectVoice());
|
||||
if (result) {
|
||||
setVoiceStatus(result);
|
||||
logger.info("Disconnected from voice");
|
||||
} else {
|
||||
logger.error("Failed to disconnect from voice");
|
||||
}
|
||||
}, []);
|
||||
return result;
|
||||
}, [execute]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshGuilds().catch((err) =>
|
||||
setError(err instanceof Error ? err.message : String(err)),
|
||||
logger.error("Failed to refresh guilds on mount", { error: String(err) }),
|
||||
);
|
||||
refreshVoiceStatus().catch((err) =>
|
||||
setError(err instanceof Error ? err.message : String(err)),
|
||||
logger.error("Failed to refresh voice status on mount", {
|
||||
error: String(err),
|
||||
}),
|
||||
);
|
||||
}, [refreshGuilds, refreshVoiceStatus]);
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import {
|
||||
reanalyzeErrorBatch,
|
||||
reanalyzeMessage,
|
||||
} from "../../../shared/api/client";
|
||||
import { createLogger } from "../../../shared/lib/logger.js";
|
||||
|
||||
const logger = createLogger("use-messages");
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
@@ -54,6 +57,7 @@ export function useMessages() {
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
logger.error("Failed to fetch messages", { guildId, error: message });
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -72,6 +76,9 @@ export function useMessages() {
|
||||
setMessages((prev) => [...prev, ...result.data]);
|
||||
setCursor(result.nextCursor);
|
||||
setHasMore(!!result.nextCursor);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.error("Failed to load more messages", { error: message });
|
||||
} finally {
|
||||
setLoadingMore(false);
|
||||
}
|
||||
@@ -106,6 +113,8 @@ export function useMessages() {
|
||||
prev.map((message) => (message.id === id ? snapshot : message)),
|
||||
);
|
||||
}
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.error("Failed to reanalyze message", { id, error: message });
|
||||
throw err;
|
||||
}
|
||||
}, []);
|
||||
@@ -124,10 +133,17 @@ export function useMessages() {
|
||||
: message,
|
||||
),
|
||||
);
|
||||
const { count } = await reanalyzeErrorBatch({
|
||||
guildId: currentGuild.current ?? undefined,
|
||||
});
|
||||
return count;
|
||||
try {
|
||||
const { count } = await reanalyzeErrorBatch({
|
||||
guildId: currentGuild.current ?? undefined,
|
||||
});
|
||||
logger.info("Reanalyze all errors complete", { count });
|
||||
return count;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.error("Failed to reanalyze error batch", { error: message });
|
||||
throw err;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user