const { Client } = require('pg'); const fs = require('fs'); const path = require('path'); require('dotenv').config({ path: '/root/miaojingAI/.env.local' }); const c = new Client({ connectionString: process.env.LOCAL_DB_URL }); const root = '/root/miaojingAI'; function storageKeyFromUrl(url) { if (!url || typeof url !== 'string') return null; const marker = '/api/local-storage/'; const idx = url.indexOf(marker); if (idx < 0) return null; try { return decodeURIComponent(url.slice(idx + marker.length).split('?')[0]); } catch { return null; } } async function query(name, sql, params=[]) { try { const r = await c.query(sql, params); return { name, rows: r.rows }; } catch (e) { return { name, error: e.message }; } } (async () => { await c.connect(); const out = {}; out.db = await query('db', `select current_database() as database, current_user as user, now() as checked_at`); out.tables = await query('tables', `select schemaname, tablename from pg_tables where schemaname in ('public','auth') order by schemaname, tablename`); const tableNames = out.tables.rows || []; out.counts = []; for (const t of tableNames) { const full = t.schemaname === 'public' ? t.tablename : `${t.schemaname}.${t.tablename}`; const r = await query(full, `select count(*)::int as count from ${full}`); out.counts.push({ table: full, count: r.rows?.[0]?.count ?? null, error: r.error }); } out.admins = await query('admins', `select id,email,nickname,role,membership_tier,is_active,created_at,updated_at from profiles where role='admin' or email in ('admin@example.com','admin@miaojing.ai') order by created_at`); out.authAdmins = await query('auth_admins', `select id,email,created_at, password_hash is not null as has_password from auth.users where email in ('admin@example.com','admin@miaojing.ai') order by created_at`); out.profileWithoutAuth = await query('profile_without_auth', `select p.id,p.email,p.role from profiles p left join auth.users u on u.id=p.id where u.id is null order by p.created_at limit 50`); out.authWithoutProfile = await query('auth_without_profile', `select u.id,u.email from auth.users u left join profiles p on p.id=u.id where p.id is null order by u.created_at limit 50`); out.worksMissingUser = await query('works_missing_user', `select w.id,w.user_id,w.title,w.type,w.status,w.created_at from works w left join profiles p on p.id=w.user_id where w.user_id is not null and p.id is null order by w.created_at desc limit 50`); out.worksNullUser = await query('works_null_user', `select id,title,type,status,created_at from works where user_id is null order by created_at desc limit 50`); out.worksByStatus = await query('works_by_status', `select status, count(*)::int from works group by status order by status`); out.worksByUser = await query('works_by_user', `select p.email,p.nickname,w.user_id,count(*)::int as works from works w left join profiles p on p.id=w.user_id group by p.email,p.nickname,w.user_id order by works desc nulls last limit 20`); out.workLikesMissing = await query('work_likes_missing_refs', `select wl.id,wl.user_id,wl.work_id from work_likes wl left join profiles p on p.id=wl.user_id left join works w on w.id=wl.work_id where p.id is null or w.id is null limit 50`); out.creditMissingUser = await query('credit_missing_user', `select ct.id,ct.user_id,ct.amount,ct.type,ct.created_at from credit_transactions ct left join profiles p on p.id=ct.user_id where p.id is null limit 50`); out.ordersMissingUser = await query('orders_missing_user', `select o.id,o.user_id,o.order_no,o.status,o.created_at from orders o left join profiles p on p.id=o.user_id where o.user_id is not null and p.id is null limit 50`); out.apiKeysMissingUser = await query('api_keys_missing_user', `select k.id,k.user_id,k.provider,k.type from user_api_keys k left join profiles p on p.id=k.user_id where p.id is null limit 50`); out.jobsMissingUser = await query('jobs_missing_user', `select j.id,j.user_id,j.type,j.status,j.created_at from generation_jobs j left join profiles p on p.id=j.user_id where j.user_id is not null and p.id is null order by j.created_at desc limit 50`); out.jobsNullUser = await query('jobs_null_user', `select id,type,status,created_at from generation_jobs where user_id is null order by created_at desc limit 50`); out.jobsByUser = await query('jobs_by_user', `select p.email,p.nickname,j.user_id,count(*)::int as jobs from generation_jobs j left join profiles p on p.id=j.user_id group by p.email,p.nickname,j.user_id order by jobs desc nulls last limit 20`); out.worksFileCheck = { totalLocalUrls: 0, missing: [] }; const works = await query('works_urls', `select id,result_url,thumbnail_url from works order by created_at desc`); for (const w of works.rows || []) { for (const field of ['result_url','thumbnail_url']) { const key = storageKeyFromUrl(w[field]); if (!key) continue; out.worksFileCheck.totalLocalUrls++; const abs = path.join(root, 'local-storage', key); if (!fs.existsSync(abs)) out.worksFileCheck.missing.push({ workId: w.id, field, key }); } } out.storage = { localStorageExists: fs.existsSync(path.join(root,'local-storage')), backupsExists: fs.existsSync(path.join(root,'backups')), localFiles: 0, galleryFiles: 0, }; function walk(dir) { if (!fs.existsSync(dir)) return 0; let n=0; for (const e of fs.readdirSync(dir,{withFileTypes:true})) { const p=path.join(dir,e.name); n += e.isDirectory()?walk(p):1; } return n; } out.storage.localFiles = walk(path.join(root,'local-storage')); out.storage.galleryFiles = walk(path.join(root,'local-storage','gallery')); console.log(JSON.stringify(out, null, 2)); await c.end(); })().catch(e => { console.error(e); process.exit(1); });