feat(system): implement graceful shutdown and expand websocket events

Improve system reliability and real-time capabilities by implementing a robust lifecycle management system and adding new broadcast events for attachments and voice recordings.

- Implement asynchronous graceful shutdown in backend to close HTTP, WebSocket, Redis, and database connections.
- Add new WebSocket broadcast events: `attachment_created`, `voice_recording_started`, `voice_recording_stopped`, `voice_recording_uploaded`, and `analysis_queue_status`.
- Refactor media status handling to use boolean `playing` state instead of string-based status.
- Centralize `PageResult` and `VoiceRecording` types to improve consistency between frontend and backend.
- Update frontend API client to include `listRecordings` and handle new WebSocket event types.
- Fix type mismatches in voice command handling and media status reporting.
This commit is contained in:
MythEclipse
2026-06-09 16:56:44 +07:00
parent 3a7b005d95
commit 7108f6bb47
14 changed files with 161 additions and 90 deletions
@@ -35,7 +35,7 @@ interface VoiceStatusPayload {
}
interface MediaStatusPayload {
playing: string;
playing: boolean;
musicVolume: number;
current: unknown;
queue: unknown[];
@@ -341,25 +341,34 @@ export class CommandHandler {
}
}
private async handleMediaQueue(_cmd: BackendCommand): Promise<CommandReply> {
private getCurrentMediaStatus(): MediaStatusPayload {
return {
playing: discordPlayer.getStatus() === "playing",
musicVolume: discordPlayer.getMusicVolume(),
current: null,
queue: [],
};
}
private async handleMediaQueue(cmd: BackendCommand): Promise<CommandReply> {
// Media queueing is handled at a higher level (frontend / backend streams
// audio directly). Log the request for now.
logger.info("media:queue received — media queueing is handled externally");
return {
id: _cmd.id,
id: cmd.id,
success: true,
data: { note: "media queueing handled externally" },
data: this.getCurrentMediaStatus(),
};
}
private async handleMediaSkip(cmd: BackendCommand): Promise<CommandReply> {
discordPlayer.stop("music");
return { id: cmd.id, success: true, data: { action: "skipped" } };
return { id: cmd.id, success: true, data: this.getCurrentMediaStatus() };
}
private async handleMediaStop(cmd: BackendCommand): Promise<CommandReply> {
discordPlayer.stop("music");
return { id: cmd.id, success: true, data: { action: "stopped" } };
return { id: cmd.id, success: true, data: this.getCurrentMediaStatus() };
}
private async handleMediaVolume(cmd: BackendCommand): Promise<CommandReply> {
@@ -376,7 +385,7 @@ export class CommandHandler {
return {
id: cmd.id,
success: true,
data: { volume: discordPlayer.getMusicVolume() },
data: this.getCurrentMediaStatus(),
};
}
@@ -579,14 +588,7 @@ export class CommandHandler {
}
private publishMediaStatus(): void {
const status: MediaStatusPayload = {
playing: String(discordPlayer.getStatus()),
musicVolume: discordPlayer.getMusicVolume(),
current: null,
queue: [],
};
this.setKey(MEDIA_STATUS_KEY, JSON.stringify(status));
this.setKey(MEDIA_STATUS_KEY, JSON.stringify(this.getCurrentMediaStatus()));
}
/**