Initial miaojingAI project with image resolution guard

This commit is contained in:
FengLee
2026-05-09 11:32:34 +08:00
commit d499020d4e
264 changed files with 54160 additions and 0 deletions

View 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);
});