Initial miaojingAI project with image resolution guard
This commit is contained in:
204
audit_current_persistence.js
Normal file
204
audit_current_persistence.js
Normal file
@@ -0,0 +1,204 @@
|
||||
const { Client } = require('pg');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: '/root/miaojingAI/.env.local' });
|
||||
|
||||
const root = '/root/miaojingAI';
|
||||
const client = new Client({ connectionString: process.env.LOCAL_DB_URL });
|
||||
|
||||
function storageKeyFromUrl(url) {
|
||||
if (!url || typeof url !== 'string') return null;
|
||||
const marker = '/api/local-storage/';
|
||||
const index = url.indexOf(marker);
|
||||
if (index < 0) return null;
|
||||
try {
|
||||
return decodeURIComponent(url.slice(index + marker.length).split('?')[0]);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function countFiles(dir) {
|
||||
if (!fs.existsSync(dir)) return 0;
|
||||
let count = 0;
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
count += entry.isDirectory() ? countFiles(full) : 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
async function q(name, sql) {
|
||||
const result = await client.query(sql);
|
||||
return { name, rows: result.rows };
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await client.connect();
|
||||
const checks = {};
|
||||
|
||||
checks.tableCounts = await q('table_counts', `
|
||||
select 'auth.users' as table_name, count(*)::int as count from auth.users
|
||||
union all select 'profiles', count(*)::int from profiles
|
||||
union all select 'user_api_keys', count(*)::int from user_api_keys
|
||||
union all select 'works', count(*)::int from works
|
||||
union all select 'generation_jobs', count(*)::int from generation_jobs
|
||||
union all select 'credit_transactions', count(*)::int from credit_transactions
|
||||
union all select 'orders', count(*)::int from orders
|
||||
union all select 'api_providers', count(*)::int from api_providers
|
||||
union all select 'model_recommendations', count(*)::int from model_recommendations
|
||||
`);
|
||||
|
||||
checks.profiles = await q('profiles', `
|
||||
select id, email, nickname, role, membership_tier, credits_balance, is_active,
|
||||
avatar_url is not null as has_avatar, created_at, updated_at
|
||||
from profiles
|
||||
order by created_at desc
|
||||
`);
|
||||
|
||||
checks.profileAuthIntegrity = await q('profile_auth_integrity', `
|
||||
select 'profile_without_auth' as issue, count(*)::int as count
|
||||
from profiles p left join auth.users u on u.id = p.id where u.id is null
|
||||
union all
|
||||
select 'auth_without_profile', count(*)::int
|
||||
from auth.users u left join profiles p on p.id = u.id where p.id is null
|
||||
`);
|
||||
|
||||
checks.admin = await q('admin', `
|
||||
select p.id, p.email, p.nickname, p.role, p.membership_tier, p.is_active,
|
||||
u.password_hash is not null as has_password_hash
|
||||
from profiles p
|
||||
left join auth.users u on u.id = p.id
|
||||
where p.role = 'admin' or p.email = 'admin@example.com'
|
||||
order by p.created_at
|
||||
`);
|
||||
|
||||
checks.apiKeys = await q('api_keys', `
|
||||
select id, user_id, provider, supplier_name, type, model_name, note,
|
||||
api_key_preview, length(api_key_encrypted) as encrypted_len,
|
||||
api_key_encrypted like 'mjenc:v1:%' as has_enc_prefix,
|
||||
api_key_encrypted = api_key_preview as encrypted_equals_preview,
|
||||
is_active, created_at
|
||||
from user_api_keys
|
||||
order by created_at desc
|
||||
`);
|
||||
|
||||
checks.apiKeyIntegrity = await q('api_key_integrity', `
|
||||
select count(*)::int as orphan_api_keys
|
||||
from user_api_keys k left join profiles p on p.id = k.user_id
|
||||
where p.id is null
|
||||
`);
|
||||
|
||||
checks.worksDistribution = await q('works_distribution', `
|
||||
select type, status, is_public, count(*)::int as count,
|
||||
count(*) filter (where result_url like '/api/local-storage/%')::int as local_urls,
|
||||
count(*) filter (where result_url like 'data:%')::int as data_urls,
|
||||
count(*) filter (where result_url like '[%')::int as placeholder_urls
|
||||
from works
|
||||
group by type, status, is_public
|
||||
order by type, status, is_public
|
||||
`);
|
||||
|
||||
checks.worksByUser = await q('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
|
||||
`);
|
||||
|
||||
checks.workIntegrity = await q('work_integrity', `
|
||||
select 'works_missing_user' as issue, count(*)::int as count
|
||||
from works w left join profiles p on p.id = w.user_id
|
||||
where p.id is null
|
||||
union all
|
||||
select 'works_null_user', count(*)::int from works where user_id is null
|
||||
union all
|
||||
select 'works_bad_url', count(*)::int
|
||||
from works
|
||||
where result_url is null or result_url = '' or result_url like 'data:%'
|
||||
union all
|
||||
select 'public_not_gallery_url', count(*)::int
|
||||
from works
|
||||
where is_public = true and result_url not like '/api/local-storage/gallery/%'
|
||||
`);
|
||||
|
||||
checks.reversePromptWorks = await q('reverse_prompt_works', `
|
||||
select id, user_id, type,
|
||||
left(result_url, 100) as result_url,
|
||||
left(coalesce(params->>'referenceImage', ''), 100) as reference_image,
|
||||
prompt <> '' as has_prompt,
|
||||
negative_prompt is not null as has_negative_prompt,
|
||||
created_at
|
||||
from works
|
||||
where type = 'reverse-prompt'
|
||||
order by created_at desc
|
||||
limit 20
|
||||
`);
|
||||
|
||||
checks.galleryWorks = await q('gallery_works', `
|
||||
select id, user_id, type, is_public, left(result_url, 100) as result_url, created_at
|
||||
from works
|
||||
where is_public = true
|
||||
order by created_at desc
|
||||
`);
|
||||
|
||||
checks.generationJobs = await q('generation_jobs', `
|
||||
select status, count(*)::int as count,
|
||||
count(*) filter (where user_id is null)::int as null_user_count
|
||||
from generation_jobs
|
||||
group by status
|
||||
order by status
|
||||
`);
|
||||
|
||||
checks.generationJobsByUser = await q('generation_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
|
||||
`);
|
||||
|
||||
const works = (await client.query(`
|
||||
select id, result_url, thumbnail_url, params
|
||||
from works
|
||||
order by created_at desc
|
||||
`)).rows;
|
||||
|
||||
const missingFiles = [];
|
||||
let referencedLocalUrls = 0;
|
||||
for (const work of works) {
|
||||
const urls = [
|
||||
['result_url', work.result_url],
|
||||
['thumbnail_url', work.thumbnail_url],
|
||||
['referenceImage', work.params && work.params.referenceImage],
|
||||
];
|
||||
if (work.params && Array.isArray(work.params.referenceImages)) {
|
||||
work.params.referenceImages.forEach((url, index) => urls.push([`referenceImages[${index}]`, url]));
|
||||
}
|
||||
for (const [field, url] of urls) {
|
||||
const key = storageKeyFromUrl(url);
|
||||
if (!key) continue;
|
||||
referencedLocalUrls++;
|
||||
const full = path.join(root, 'local-storage', key);
|
||||
if (!fs.existsSync(full)) missingFiles.push({ workId: work.id, field, key });
|
||||
}
|
||||
}
|
||||
|
||||
checks.storage = {
|
||||
localStorageExists: fs.existsSync(path.join(root, 'local-storage')),
|
||||
backupsExists: fs.existsSync(path.join(root, 'backups')),
|
||||
localFiles: countFiles(path.join(root, 'local-storage')),
|
||||
generatedFiles: countFiles(path.join(root, 'local-storage', 'generated')),
|
||||
galleryFiles: countFiles(path.join(root, 'local-storage', 'gallery')),
|
||||
reversePromptFiles: countFiles(path.join(root, 'local-storage', 'reverse-prompt')),
|
||||
referencedLocalUrls,
|
||||
missingFiles,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(checks, null, 2));
|
||||
await client.end();
|
||||
})().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user