feat: skript pro import dat z Redis databáze
This commit is contained in:
+2
-1
@@ -8,7 +8,8 @@
|
||||
"start": "ts-node src/index.ts",
|
||||
"startReload": "nodemon --watch src src/index.ts",
|
||||
"build": "tsc -p .",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"export:redis": "ts-node scripts/exportRedisToJson.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.5",
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Vývojový skript pro export dat z Redisu do JSON souboru (data/db.json).
|
||||
*
|
||||
* Použití: typicky proti produkčnímu Redisu zpřístupněnému přes dočasný SSH tunel:
|
||||
* ssh -L 6379:localhost:6379 user@prod-host
|
||||
* cd server && yarn export:redis
|
||||
*
|
||||
* Skript jde přes stejné StorageInterface jako aplikace (RedisStorage), takže
|
||||
* korektně přečte i hodnoty uložené přes RedisJSON modul a zapíše je ve tvaru,
|
||||
* který očekává JsonStorage (simple-json-db) — tedy plochý objekt { klíč: hodnota }.
|
||||
*
|
||||
* Volitelné parametry (CLI nebo env):
|
||||
* --host <host> (REDIS_HOST) výchozí: localhost
|
||||
* --port <port> (REDIS_PORT) výchozí: 6379
|
||||
* --out <cesta> výchozí: server/data/db.json
|
||||
* --filter <text> exportovat jen klíče obsahující daný podřetězec
|
||||
* --yes přeskočit interaktivní potvrzení přepisu
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as readline from 'readline';
|
||||
import RedisStorage, { shutdownRedisStorage } from '../src/storage/redis';
|
||||
|
||||
// Bezpečnostní pojistka — skript nikdy nepouštět v produkčním režimu.
|
||||
if ((process.env.NODE_ENV ?? 'development') === 'production') {
|
||||
console.error('Tento skript nelze spustit s NODE_ENV=production.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/** Jednoduché parsování CLI argumentů typu --klíč hodnota a --flag. */
|
||||
function parseArgs(argv: string[]): Record<string, string | boolean> {
|
||||
const out: Record<string, string | boolean> = {};
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
if (!arg.startsWith('--')) continue;
|
||||
const key = arg.slice(2);
|
||||
const next = argv[i + 1];
|
||||
if (next === undefined || next.startsWith('--')) {
|
||||
out[key] = true;
|
||||
} else {
|
||||
out[key] = next;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Dotaz na potvrzení (y/n) ve stdin. */
|
||||
function confirm(question: string): Promise<boolean> {
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
return new Promise(resolve => {
|
||||
rl.question(question, answer => {
|
||||
rl.close();
|
||||
resolve(/^(y|a|ano|yes)$/i.test(answer.trim()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
|
||||
// Host/port nastavíme do env PŘED načtením RedisStorage — jeho konstruktor je čte z env.
|
||||
if (typeof args.host === 'string') process.env.REDIS_HOST = args.host;
|
||||
if (typeof args.port === 'string') process.env.REDIS_PORT = args.port;
|
||||
|
||||
const host = process.env.REDIS_HOST ?? 'localhost';
|
||||
const port = process.env.REDIS_PORT ?? '6379';
|
||||
|
||||
const outPath = typeof args.out === 'string'
|
||||
? path.resolve(process.cwd(), args.out)
|
||||
: path.resolve(__dirname, '../data/db.json');
|
||||
|
||||
const filter = typeof args.filter === 'string' ? args.filter : undefined;
|
||||
|
||||
// RedisStorage čte REDIS_HOST/PORT z env až ve svém konstruktoru (ne při importu),
|
||||
// proto stačí je nastavit výše a teprve teď vytvořit instanci.
|
||||
console.log(`Připojuji se k Redisu na ${host}:${port} ...`);
|
||||
const storage = new RedisStorage();
|
||||
await storage.initialize!();
|
||||
|
||||
const keys = await storage.listKeys(filter);
|
||||
console.log(`Nalezeno ${keys.length} klíčů${filter ? ` (filtr: "${filter}")` : ''}.`);
|
||||
|
||||
if (keys.length === 0) {
|
||||
console.warn('Žádná data k exportu — končím bez zápisu.');
|
||||
await shutdownRedisStorage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bezpečné načtení jednoho klíče — getData jde přes json.get, takže ne-JSON klíče
|
||||
// (např. lease připomínkovače uložené jako plain string přes SET NX EX) vyhodí chybu.
|
||||
// Takové klíče nejsou aplikační data a do db.json nepatří, proto je přeskočíme.
|
||||
const skipped: string[] = [];
|
||||
async function readSafe(key: string): Promise<unknown> {
|
||||
try {
|
||||
return await storage.getData(key);
|
||||
} catch {
|
||||
skipped.push(key);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Načtení hodnot po dávkách, ať zbytečně nezahltíme spojení.
|
||||
const BATCH = 20;
|
||||
const result: Record<string, unknown> = {};
|
||||
for (let i = 0; i < keys.length; i += BATCH) {
|
||||
const batch = keys.slice(i, i + BATCH);
|
||||
const values = await Promise.all(batch.map(k => readSafe(k)));
|
||||
batch.forEach((k, idx) => {
|
||||
const value = values[idx];
|
||||
if (value !== undefined && value !== null) {
|
||||
result[k] = value;
|
||||
}
|
||||
});
|
||||
console.log(`Načteno ${Math.min(i + BATCH, keys.length)}/${keys.length} klíčů ...`);
|
||||
}
|
||||
|
||||
if (skipped.length > 0) {
|
||||
console.warn(`Přeskočeno ${skipped.length} ne-JSON klíčů: ${skipped.join(', ')}`);
|
||||
}
|
||||
|
||||
await shutdownRedisStorage();
|
||||
|
||||
// Potvrzení přepisu existujícího souboru (pokud není --yes) + záloha.
|
||||
if (fs.existsSync(outPath) && args.yes !== true) {
|
||||
const ok = await confirm(`Soubor ${outPath} už existuje a bude přepsán. Pokračovat? [a/N] `);
|
||||
if (!ok) {
|
||||
console.log('Zrušeno uživatelem.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (fs.existsSync(outPath)) {
|
||||
const backupPath = `${outPath}.bak`;
|
||||
fs.copyFileSync(outPath, backupPath);
|
||||
console.log(`Záloha původního souboru: ${backupPath}`);
|
||||
}
|
||||
|
||||
const dir = path.dirname(outPath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
// simple-json-db ukládá data jako plochý JSON objekt { klíč: hodnota }.
|
||||
fs.writeFileSync(outPath, JSON.stringify(result), 'utf-8');
|
||||
console.log(`Hotovo — ${Object.keys(result).length} klíčů zapsáno do ${outPath}.`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Export selhal:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user