/** * One-off maintenance script: lists every glossary that currently exists on * your DeepL account and compares it against the glossaries this app * actually tracks in domains.json. Run this on the machine where server.js * runs (it needs the same DEEPL_API_KEY and network access to DeepL). * * Why you might need this: DeepL accounts have a cap on total glossary * count. If you ever hit a "Too many glossaries" (456) error when saving * terms, it means glossaries exist on your DeepL account that this app * isn't tracking anymore (e.g. left over from earlier testing/debugging, or * from a save that got interrupted). This script finds and removes those * orphans so you have room to save again. * * Usage: * node cleanup-glossaries.js # dry run - lists orphans only * node cleanup-glossaries.js --delete # actually deletes the orphans */ const fs = require('fs'); const path = require('path'); function loadEnv() { const envPath = path.join(__dirname, '.env'); if (!fs.existsSync(envPath)) return; for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) { const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/i); if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, ''); } } loadEnv(); const DEEPL_API_KEY = process.env.DEEPL_API_KEY; if (!DEEPL_API_KEY) { console.error('DEEPL_API_KEY not found (checked .env and environment).'); process.exit(1); } const base = DEEPL_API_KEY.endsWith(':fx') ? 'https://api-free.deepl.com' : 'https://api.deepl.com'; function loadDomainsJson() { const p = path.join(__dirname, 'domains.json'); if (!fs.existsSync(p)) return {}; try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return {}; } } async function main() { const domains = loadDomainsJson(); const trackedIds = new Set(); for (const d of Object.values(domains)) { if (!d || !d.glossaries) continue; for (const g of Object.values(d.glossaries)) { if (g && g.id) trackedIds.add(g.id); } } const resp = await fetch(`${base}/v2/glossaries`, { headers: { Authorization: `DeepL-Auth-Key ${DEEPL_API_KEY}` }, }); if (!resp.ok) { console.error(`Failed to list glossaries: ${resp.status} ${await resp.text()}`); process.exit(1); } const { glossaries } = await resp.json(); console.log(`Found ${glossaries.length} glossary/glossaries on the DeepL account. This app tracks ${trackedIds.size} of them in domains.json.\n`); const orphans = glossaries.filter((g) => !trackedIds.has(g.glossary_id)); if (orphans.length === 0) { console.log('No orphaned glossaries found. Nothing to clean up.'); console.log('(If you are still getting "Too many glossaries", the account limit may just need to be raised with DeepL, or every glossary on the account is legitimately in use.)'); return; } console.log(`Orphaned glossaries (exist on DeepL, not referenced by domains.json):\n`); for (const g of orphans) { console.log(` - ${g.glossary_id} "${g.name}" ${g.source_lang}->${g.target_lang} (${g.entry_count} entries, created ${g.creation_time})`); } const shouldDelete = process.argv.includes('--delete'); if (!shouldDelete) { console.log(`\nDry run only - nothing was deleted. Re-run with --delete to remove the ${orphans.length} orphan(s) above.`); return; } console.log('\nDeleting orphans...'); for (const g of orphans) { const delResp = await fetch(`${base}/v2/glossaries/${g.glossary_id}`, { method: 'DELETE', headers: { Authorization: `DeepL-Auth-Key ${DEEPL_API_KEY}` }, }); console.log(` - ${g.glossary_id}: ${delResp.ok ? 'deleted' : `failed (${delResp.status})`}`); } console.log('Done.'); } main().catch((e) => { console.error(e); process.exit(1); });