fp/services/pocketbase/utils/data_migrations/2025-10-30-v1-export.js
2025-11-05 20:49:00 -09:00

173 lines
5.1 KiB
JavaScript

/**
* Migration Script: V1 → json
* -----------------------------------
* This script migrates VTuber and VOD data from an old Postgres database (V1) into JSON files
* One JSON file per collection, suitable for importing using pocketbase import script
*
* Usage:
* - Ensure environment variables are configured for both databases:
* V1_DB_HOST Hostname of the V1 database (default: "localhost")
* V1_DB_PORT Port of the V1 database (default: "5444")
* V1_DB_USER Username for the V1 database (default: "postgres")
* V1_DB_PASS Password for the V1 database (default: "password")
* V1_DB_NAME Database name for V1 (default: "restoredb")
* DEFAULT_UPLOADER_ID
* An existing user ID in the V2 database that will be set as the uploader for all migrated records.
*
* Usage:
* Run with Node.js:
* $ npx @dotenvx/dotenvx run -f ./.env.local -- node ./2025-10-30-v1-export.js ../pb_data/
*
*/
import pg from 'pg';
import fs from 'fs';
import path from 'path';
const dataDir = process.argv[2]; // e.g., "../../pb_data/"
if (!dataDir) {
console.error("Please provide the output data directory as the first argument.");
process.exit(1);
}
const v1 = new pg.Pool({
host: process.env.V1_DB_HOST || 'localhost',
port: +(process.env.V1_DB_PORT || '5444'),
user: process.env.V1_DB_USER || 'postgres',
password: process.env.V1_DB_PASS || 'password',
database: process.env.V1_DB_NAME || 'restoredb'
});
async function migrateVtubers() {
console.log('Migrating vtubers...');
const res = await v1.query(`SELECT * FROM vtubers`);
const vtubers = res.rows.map(vt => ({
slug: vt.slug,
displayName: vt.display_name,
chaturbate: vt.chaturbate,
twitter: vt.twitter,
patreon: vt.patreon,
twitch: vt.twitch,
tiktok: vt.tiktok,
onlyfans: vt.onlyfans,
youtube: vt.youtube,
linktree: vt.linktree,
carrd: vt.carrd,
fansly: vt.fansly,
pornhub: vt.pornhub,
discord: vt.discord,
reddit: vt.reddit,
throne: vt.throne,
instagram: vt.instagram,
facebook: vt.facebook,
merch: vt.merch,
description: `${vt.description_1 ?? ''}\n${vt.description_2 ?? ''}`.trim() || null,
themeColor: vt.theme_color,
}));
console.log('vtubers sample', vtubers.at(0))
const outPath = path.join(dataDir, 'vtubers.json');
fs.writeFileSync(outPath, JSON.stringify(vtubers, null, 2), 'utf-8');
console.log(`Migrated ${res.rows.length} vtubers → ${outPath}`);
}
async function migrateVods() {
console.log('Migrating vods...');
const res = await v1.query(`SELECT * FROM vods`);
const vods = [];
for (const vod of res.rows) {
// Get linked vtubers
const vtuberLinks = await v1.query(
`SELECT vtuber_id FROM vods_vtuber_links WHERE vod_id = $1`,
[vod.id]
);
let vtuberIds = [];
if (vtuberLinks.rows.length > 0) {
const vtuberRes = await v1.query(
`SELECT id FROM vtubers WHERE id = ANY($1)`,
[vtuberLinks.rows.map(r => r.vtuber_id)]
);
vtuberIds = vtuberRes.rows.map(v => v.id).filter(Boolean);
}
// Get thumbnail
const thumbLink = await v1.query(
`SELECT b2.cdn_url, b2.url FROM vods_thumbnail_links vtl
JOIN b2_files b2 ON vtl.b_2_file_id = b2.id
WHERE vtl.vod_id = $1 LIMIT 1`,
[vod.id]
);
// Get source video
const videoSrcLink = await v1.query(
`SELECT b2.cdn_url, b2.url FROM vods_video_src_b_2_links vsl
JOIN b2_files b2 ON vsl.b_2_file_id = b2.id
WHERE vsl.vod_id = $1 LIMIT 1`,
[vod.id]
);
// Get Mux asset and playback ID
const muxLink = await v1.query(
`SELECT m.asset_id, m.playback_id
FROM vods_mux_asset_links vmal
JOIN mux_assets m ON vmal.mux_asset_id = m.id
WHERE vmal.vod_id = $1 LIMIT 1`,
[vod.id]
);
const muxAssetId = muxLink.rows[0]?.asset_id || null;
const muxPlaybackId = muxLink.rows[0]?.playback_id || null;
// @chatgpt we need one more
// we need to populate videoSrcB2
// we want the cdn_url from b2_files
// we have to get the link from the vods_video_src_b_2_links table
vods.push({
streamDate: vod.date ?? new Date(),
notes: vod.note,
sourceVideo: (() => {
const u = videoSrcLink.rows[0]?.cdn_url || videoSrcLink.rows[0]?.url || null;
return u ? 'content' + new URL(u).pathname : null;
})(),
thumbnail: (() => {
const u = thumbLink.rows[0]?.cdn_url || thumbLink.rows[0]?.url || null;
return u ? 'content' + new URL(u).pathname : null;
})(),
ipfsCid: vod.video_src_hash,
videoSrcB2: videoSrcLink.rows[0]?.cdn_url || null,
muxAssetId,
muxPlaybackId,
});
}
console.log(vods.at(0))
const outPath = path.join(dataDir, 'vods.json');
fs.writeFileSync(outPath, JSON.stringify(vods, null, 2), 'utf-8');
console.log(`Migrated ${res.rows.length} vods → ${outPath}`);
}
async function main() {
try {
await migrateVtubers();
await migrateVods();
} catch (err) {
console.error(err);
} finally {
await v1.end();
}
}
main();