No env vars needed; subtitle text is served from Master after download import. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
3 KiB
TypeScript
92 lines
3 KiB
TypeScript
import { prisma } from "../database/client";
|
|
import { decryptSecret, encryptSecret } from "../security/crypto";
|
|
import { AppError } from "../security/errors";
|
|
import { OpenSubtitlesClient } from "../opensubtitles/client";
|
|
|
|
const OS_ID = "opensubtitles";
|
|
|
|
export type OpenSubtitlesSettingsPublic = {
|
|
configured: boolean;
|
|
enabled: boolean;
|
|
username: string;
|
|
hasApiKey: boolean;
|
|
hasPassword: boolean;
|
|
};
|
|
|
|
export async function getOpenSubtitlesSettingsPublic(): Promise<OpenSubtitlesSettingsPublic> {
|
|
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
|
|
return {
|
|
configured: !!(row?.apiKeyEnc && row.enabled),
|
|
enabled: row?.enabled ?? true,
|
|
username: row?.username ?? "",
|
|
hasApiKey: !!row?.apiKeyEnc,
|
|
hasPassword: !!row?.passwordEnc,
|
|
};
|
|
}
|
|
|
|
export async function upsertOpenSubtitlesSettings(
|
|
sessionSecret: string,
|
|
input: {
|
|
apiKey?: string;
|
|
username?: string;
|
|
password?: string;
|
|
enabled?: boolean;
|
|
}
|
|
): Promise<OpenSubtitlesSettingsPublic> {
|
|
const existing = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
|
|
const apiKeyTrim = input.apiKey?.trim();
|
|
if (!apiKeyTrim && !existing?.apiKeyEnc) {
|
|
throw new AppError("INVALID_REQUEST", "API-key is verplicht", 400);
|
|
}
|
|
|
|
const apiKeyEnc = apiKeyTrim
|
|
? encryptSecret(apiKeyTrim, sessionSecret)
|
|
: existing!.apiKeyEnc;
|
|
const passwordEnc =
|
|
input.password !== undefined
|
|
? input.password.trim()
|
|
? encryptSecret(input.password.trim(), sessionSecret)
|
|
: null
|
|
: existing?.passwordEnc ?? null;
|
|
const username =
|
|
input.username !== undefined ? input.username.trim() || null : existing?.username ?? null;
|
|
|
|
await prisma.integrationSetting.upsert({
|
|
where: { id: OS_ID },
|
|
create: {
|
|
id: OS_ID,
|
|
apiKeyEnc,
|
|
username,
|
|
passwordEnc,
|
|
enabled: input.enabled ?? true,
|
|
},
|
|
update: {
|
|
apiKeyEnc,
|
|
username,
|
|
passwordEnc,
|
|
enabled: input.enabled ?? true,
|
|
},
|
|
});
|
|
|
|
// Validate by searching a known movie (optional light check: login if creds)
|
|
const client = await createOpenSubtitlesClientFromDb(sessionSecret);
|
|
if (!client) throw new AppError("OPENSUBTITLES", "Kon client niet maken", 400);
|
|
try {
|
|
await client.search({ imdbId: "tt1375666", languages: "nl" });
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : "OpenSubtitles test mislukt";
|
|
throw new AppError("OPENSUBTITLES", message, 400);
|
|
}
|
|
|
|
return getOpenSubtitlesSettingsPublic();
|
|
}
|
|
|
|
export async function createOpenSubtitlesClientFromDb(
|
|
sessionSecret: string
|
|
): Promise<OpenSubtitlesClient | null> {
|
|
const row = await prisma.integrationSetting.findUnique({ where: { id: OS_ID } });
|
|
if (!row?.enabled || !row.apiKeyEnc) return null;
|
|
const apiKey = decryptSecret(row.apiKeyEnc, sessionSecret);
|
|
const password = row.passwordEnc ? decryptSecret(row.passwordEnc, sessionSecret) : undefined;
|
|
return new OpenSubtitlesClient(apiKey, row.username ?? undefined, password);
|
|
}
|