feat(core): implement data retention, metrics, and enhanced media handling

This commit introduces several significant improvements across the backend and gateway services:

- **Data Retention**: Added an automated cleanup scheduler in `discord-gateway` to prune expired messages, attachments, and voice recordings based on configurable retention policies.
- **Observability**: Integrated `prom-client` in the `backend` service to expose Prometheus metrics via `/api/metrics` and added default Node.js runtime metrics.
- **Media Handling**: Enhanced `MediaHandler` in `discord-gateway` to support media URL resolution and improved playback status tracking.
- **API & Config**: Expanded the configuration endpoint to expose more system settings and reorganized `.env.example` for better readability.
- **Refactoring & Cleanup**:
    - Removed unused `better-sqlite3` dependency.
    - Refactored voice channel routing.
    - Improved error handling and testing coverage with comprehensive unit tests for shared utilities and error classes.
- **Documentation**: Added `MEMORY.md` for project context.
This commit is contained in:
MythEclipse
2026-06-10 20:56:16 +07:00
parent f04b0f0b42
commit 2557a07916
18 changed files with 1537 additions and 249 deletions
@@ -1,15 +1,23 @@
import { type CommandMessage, type CommandReply } from "@bete/shared";
import { createChildLogger } from "@bete/shared/logger";
import { StreamType } from "@discordjs/voice";
import { resolveMediaUrl } from "../voice-recording/mediaSource.js";
import { discordPlayer } from "../voice-recording/player.js";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
interface CurrentTrack {
title: string;
url: string;
duration?: number;
}
export interface MediaStatusPayload {
playing: boolean;
musicVolume: number;
current: unknown;
current: CurrentTrack | null;
queue: unknown[];
}
@@ -19,29 +27,84 @@ export interface MediaStatusPayload {
export class MediaHandler {
private logger = createChildLogger("media-handler");
private currentTrack: CurrentTrack | null = null;
getCurrentMediaStatus(): MediaStatusPayload {
return {
playing: discordPlayer.getStatus() === "playing",
musicVolume: discordPlayer.getMusicVolume(),
current: null,
current: this.currentTrack,
queue: [],
};
}
async handleMediaQueue(cmd: CommandMessage): Promise<CommandReply<unknown>> {
this.logger.info(
"media:queue received — media queueing is handled externally",
);
return {
id: cmd.id,
success: true,
data: this.getCurrentMediaStatus(),
};
const url = String(cmd.payload.url ?? "").trim();
if (!url) {
this.logger.warn("media:queue received without a URL");
return {
id: cmd.id,
success: false,
data: null,
error: "url is required",
};
}
if (!discordPlayer.isConnected()) {
this.logger.warn(
"media:queue attempted without an active voice connection",
);
return {
id: cmd.id,
success: false,
data: null,
error: "Not connected to a voice channel. Connect to voice first.",
};
}
try {
this.logger.info({ url }, "Resolving media URL");
const resolution = await resolveMediaUrl(url);
this.currentTrack = {
title: resolution.title ?? url,
url,
duration: resolution.duration,
};
discordPlayer.playStream(resolution.stream, "music", {
inputType: StreamType.Arbitrary,
inlineVolume: true,
volume: discordPlayer.getMusicVolume(),
});
this.logger.info(
{ url, title: resolution.title },
"Media queued and playback started",
);
return {
id: cmd.id,
success: true,
data: this.getCurrentMediaStatus(),
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.logger.error({ error: message, url }, "Failed to queue media");
return {
id: cmd.id,
success: false,
data: null,
error: message,
};
}
}
async handleMediaSkip(cmd: CommandMessage): Promise<CommandReply<unknown>> {
discordPlayer.stop("music");
this.currentTrack = null;
return {
id: cmd.id,
success: true,
@@ -51,6 +114,7 @@ export class MediaHandler {
async handleMediaStop(cmd: CommandMessage): Promise<CommandReply<unknown>> {
discordPlayer.stop("music");
this.currentTrack = null;
return {
id: cmd.id,
success: true,