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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user