refactor: remove voice, recording, and music/media features (gateway + backend)

- Gateway: delete voice-recording/, voice-pcm-ws/, voice/video/media handlers,
  vendor/discord-voice-fork/, voice DB repos, 2 voice migrations
- Gateway: cut voice wiring from bootstrap/shutdown/commandHandler/handler-registry,
  eventBroadcaster/eventTypes, redis-channels, moderation-types, message-capture,
  config keys, and deps (@discordjs/voice, opus, libsodium, prism-media, davey)
- Backend: delete voice/, recordings/, media/ modules + @discordjs/voice dep
- Backend: cut voice/media/recordings oRPC routers, WS gateway-PCM auth + voice
  handlers, Redis bridge voice aggregation, redis-channels voice/media constants,
  config keys, moderation-types voice items, e2e voice/recordings suites
- Keep voice DB tables (destructive migration avoided); chatbot voiceRecordings
  tool + dashboard count remain as read-only historical data access
This commit is contained in:
asepharyana
2026-09-23 19:31:06 +07:00
parent b5b370e6eb
commit 33013697e0
79 changed files with 27 additions and 20185 deletions
@@ -1,9 +0,0 @@
import { pgVoiceRecordingsTable } from "../../../shared/index.js";
// Re-export shared table
export { pgVoiceRecordingsTable };
export const voiceRecordingsTable = pgVoiceRecordingsTable;
// Types
export type VoiceRecording = typeof voiceRecordingsTable.$inferSelect;
export type VoiceRecordingInsert = typeof voiceRecordingsTable.$inferInsert;
@@ -1,59 +0,0 @@
import { eq } from "drizzle-orm";
import { getDatabase } from "./drizzle.js";
import {
type VoiceAutoReconnect,
type VoiceAutoReconnectInsert,
voiceAutoReconnectTable,
} from "./schema.js";
/**
* Upsert a guild's desired voice state.
* Called after a successful connect so the gateway can rejoin after a
* restart/reboot or unexpected drop.
*/
export async function upsertVoiceAutoReconnect(
record: VoiceAutoReconnectInsert,
): Promise<VoiceAutoReconnect | undefined> {
const db = getDatabase();
const [row] = await db
.insert(voiceAutoReconnectTable)
.values(record)
.onConflictDoUpdate({
target: voiceAutoReconnectTable.guild_id,
set: {
channel_id: record.channel_id,
channel_name: record.channel_name ?? null,
connected_at: record.connected_at,
updated_at: record.updated_at,
},
})
.returning();
return row;
}
/** List all persisted desired voice states (for startup auto-reconnect). */
export async function listVoiceAutoReconnects(): Promise<VoiceAutoReconnect[]> {
const db = getDatabase();
return db.select().from(voiceAutoReconnectTable);
}
/** Clear a guild's persisted voice state (explicit manual leave). */
export async function deleteVoiceAutoReconnect(guildId: string): Promise<void> {
const db = getDatabase();
await db
.delete(voiceAutoReconnectTable)
.where(eq(voiceAutoReconnectTable.guild_id, guildId));
}
/** Fetch a single guild's persisted state, if any. */
export async function getVoiceAutoReconnect(
guildId: string,
): Promise<VoiceAutoReconnect | undefined> {
const db = getDatabase();
const rows = await db
.select()
.from(voiceAutoReconnectTable)
.where(eq(voiceAutoReconnectTable.guild_id, guildId))
.limit(1);
return rows[0];
}
@@ -1,162 +0,0 @@
import { desc, eq } from "drizzle-orm";
import { createChildLogger } from "@/shared/logger/index";
import { getDatabase } from "./drizzle.js";
import {
type VoiceRecording,
type VoiceRecordingInsert,
voiceRecordingsTable,
} from "./schema.js";
const logger = createChildLogger("voice-recording-repo");
interface QueryBuilder<T = unknown> extends PromiseLike<T> {
from(...args: unknown[]): QueryBuilder<T>;
where(...args: unknown[]): QueryBuilder<T>;
orderBy(...args: unknown[]): QueryBuilder<T>;
limit(...args: unknown[]): QueryBuilder<T>;
offset(...args: unknown[]): QueryBuilder<T>;
values(...args: unknown[]): QueryBuilder<T>;
onConflictDoNothing(...args: unknown[]): QueryBuilder<T>;
returning(...args: unknown[]): QueryBuilder<T>;
set(...args: unknown[]): QueryBuilder<T>;
}
interface RecordingDatabase {
select<T = unknown[]>(...args: unknown[]): QueryBuilder<T>;
insert<T = unknown>(...args: unknown[]): QueryBuilder<T>;
update(...args: unknown[]): QueryBuilder<unknown>;
}
function db(): RecordingDatabase {
return getDatabase() as unknown as RecordingDatabase;
}
export async function insertVoiceRecording(
recording: VoiceRecordingInsert,
): Promise<void> {
try {
await db()
.insert(voiceRecordingsTable)
.values(recording)
.onConflictDoNothing();
} catch (error) {
// Drizzle wraps PG errors — dig into cause + enumerable props for the real PG error
const err = error as Error & Record<string, unknown>;
const detail: Record<string, unknown> = {
message: err.message,
name: err.name,
};
// node-postgres native error props
for (const k of [
"code",
"detail",
"schema",
"table",
"constraint",
"severity",
]) {
if (err[k] !== undefined) detail[k] = err[k];
}
// drizzle may stash the original in .cause
const cause = err.cause;
if (cause instanceof Error) {
const causeErr = cause as Error & Record<string, unknown>;
detail.cause = {
message: causeErr.message,
name: causeErr.name,
code: causeErr.code,
detail: causeErr.detail,
constraint: causeErr.constraint,
};
}
logger.error(
{ id: recording.id, error: detail },
"Failed to insert voice recording",
);
throw error;
}
}
export async function updateVoiceRecordingAsUploaded(
id: string,
downloadUrl: string,
uploadedAt: number,
filename?: string,
sizeBytes?: number,
): Promise<void> {
try {
await db()
.update(voiceRecordingsTable)
.set({
download_url: downloadUrl,
upload_status: "uploaded",
uploaded_at: uploadedAt,
...(filename !== undefined ? { filename } : {}),
...(sizeBytes !== undefined ? { size_bytes: sizeBytes } : {}),
})
.where(eq(voiceRecordingsTable.id, id));
} catch (error) {
logger.error(
{ id, error: error instanceof Error ? error.message : String(error) },
"Failed to update voice recording status to uploaded",
);
throw error;
}
}
export async function updateVoiceRecordingAsFailed(
id: string,
error: string,
): Promise<void> {
try {
await db()
.update(voiceRecordingsTable)
.set({
upload_status: "failed",
upload_error: error,
})
.where(eq(voiceRecordingsTable.id, id));
} catch (error) {
logger.error(
{ id, error: error instanceof Error ? error.message : String(error) },
"Failed to update voice recording status to failed",
);
throw error;
}
}
export async function updateVoiceRecordingTranscription(
id: string,
transcription: string,
): Promise<void> {
try {
await db()
.update(voiceRecordingsTable)
.set({ transcription })
.where(eq(voiceRecordingsTable.id, id));
} catch (error) {
logger.error(
{ id, error: error instanceof Error ? error.message : String(error) },
"Failed to update voice recording transcription",
);
}
}
export async function listVoiceRecordings(
limit = 100,
): Promise<VoiceRecording[]> {
try {
const rows = await db()
.select()
.from(voiceRecordingsTable)
.orderBy(desc(voiceRecordingsTable.created_at))
.limit(limit);
return rows as VoiceRecording[];
} catch (error) {
logger.error(
{ error: error instanceof Error ? error.message : String(error) },
"Failed to list voice recordings",
);
throw error;
}
}