Initial miaojingAI project with image resolution guard
This commit is contained in:
206
audit_recovered_data.js
Normal file
206
audit_recovered_data.js
Normal file
@@ -0,0 +1,206 @@
|
||||
const { Pool } = require('pg');
|
||||
require('dotenv').config({ path: '.env.local' });
|
||||
|
||||
const SYSTEM_USER_ID = '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
function short(value, length = 160) {
|
||||
if (value == null) return value;
|
||||
const text = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
return text.length > length ? `${text.slice(0, length)}...` : text;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const pool = new Pool({ connectionString: process.env.LOCAL_DB_URL });
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const tableColumns = await client.query(`
|
||||
SELECT table_schema, table_name, column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE (table_schema = 'public' AND table_name IN ('profiles', 'works', 'user_api_keys', 'orders', 'credit_transactions'))
|
||||
OR (table_schema = 'auth' AND table_name = 'users')
|
||||
ORDER BY table_schema, table_name, ordinal_position
|
||||
`);
|
||||
|
||||
const userSummary = await client.query(`
|
||||
SELECT
|
||||
(SELECT COUNT(*)::int FROM auth.users) AS auth_users,
|
||||
(SELECT COUNT(*)::int FROM profiles) AS profiles,
|
||||
(SELECT COUNT(*)::int FROM profiles WHERE role = 'admin') AS admins,
|
||||
(SELECT COUNT(*)::int FROM profiles WHERE role <> 'admin') AS non_admin_profiles,
|
||||
(SELECT COUNT(*)::int FROM auth.users u LEFT JOIN profiles p ON p.id = u.id WHERE p.id IS NULL) AS auth_without_profile,
|
||||
(SELECT COUNT(*)::int FROM profiles p LEFT JOIN auth.users u ON u.id = p.id WHERE u.id IS NULL) AS profile_without_auth,
|
||||
(SELECT COUNT(*)::int FROM auth.users WHERE password_hash IS NULL OR password_hash = '') AS auth_without_password_hash,
|
||||
(SELECT COUNT(*)::int FROM auth.users WHERE password_hash IS NOT NULL AND password_hash <> '') AS auth_with_password_hash
|
||||
`);
|
||||
|
||||
const userSamples = await client.query(`
|
||||
SELECT p.id, p.email, p.nickname, p.role, p.membership_tier, p.is_active,
|
||||
u.id IS NOT NULL AS has_auth,
|
||||
(u.password_hash IS NOT NULL AND u.password_hash <> '') AS has_password_hash,
|
||||
p.created_at
|
||||
FROM profiles p
|
||||
LEFT JOIN auth.users u ON u.id = p.id
|
||||
ORDER BY p.created_at DESC NULLS LAST
|
||||
LIMIT 80
|
||||
`);
|
||||
|
||||
const workSummary = await client.query(`
|
||||
SELECT
|
||||
COUNT(*)::int AS total,
|
||||
COUNT(*) FILTER (WHERE status = 'completed')::int AS completed,
|
||||
COUNT(*) FILTER (WHERE is_public = true AND status = 'completed')::int AS public_completed,
|
||||
COUNT(*) FILTER (WHERE is_public = false AND status = 'completed')::int AS private_completed,
|
||||
COUNT(*) FILTER (WHERE user_id IS NULL)::int AS null_user_id,
|
||||
COUNT(*) FILTER (WHERE user_id = $1)::int AS system_user_id,
|
||||
COUNT(*) FILTER (WHERE p.id IS NULL)::int AS missing_profile,
|
||||
COUNT(*) FILTER (WHERE p.id IS NOT NULL)::int AS linked_profile
|
||||
FROM works w
|
||||
LEFT JOIN profiles p ON p.id = w.user_id
|
||||
`, [SYSTEM_USER_ID]);
|
||||
|
||||
const publicWorkSummary = await client.query(`
|
||||
SELECT
|
||||
COUNT(*)::int AS public_total,
|
||||
COUNT(*) FILTER (WHERE w.user_id IS NULL)::int AS null_user_id,
|
||||
COUNT(*) FILTER (WHERE w.user_id = $1)::int AS system_user_id,
|
||||
COUNT(*) FILTER (WHERE p.id IS NULL)::int AS missing_profile,
|
||||
COUNT(*) FILTER (WHERE p.id IS NOT NULL)::int AS linked_profile
|
||||
FROM works w
|
||||
LEFT JOIN profiles p ON p.id = w.user_id
|
||||
WHERE w.is_public = true AND w.status = 'completed'
|
||||
`, [SYSTEM_USER_ID]);
|
||||
|
||||
const workByUser = await client.query(`
|
||||
SELECT
|
||||
COALESCE(p.email, '[missing-profile]') AS email,
|
||||
COALESCE(p.nickname, '') AS nickname,
|
||||
COALESCE(p.role, '') AS role,
|
||||
w.user_id,
|
||||
COUNT(*)::int AS total_works,
|
||||
COUNT(*) FILTER (WHERE w.status = 'completed')::int AS completed_works,
|
||||
COUNT(*) FILTER (WHERE w.is_public = true AND w.status = 'completed')::int AS public_works,
|
||||
COUNT(*) FILTER (WHERE w.is_public = false AND w.status = 'completed')::int AS history_works
|
||||
FROM works w
|
||||
LEFT JOIN profiles p ON p.id = w.user_id
|
||||
GROUP BY w.user_id, p.email, p.nickname, p.role
|
||||
ORDER BY total_works DESC
|
||||
LIMIT 120
|
||||
`);
|
||||
|
||||
const orphanSamples = await client.query(`
|
||||
SELECT w.id, w.user_id, w.type, w.status, w.is_public, w.result_url,
|
||||
LEFT(COALESCE(w.prompt, ''), 140) AS prompt,
|
||||
w.params,
|
||||
w.created_at
|
||||
FROM works w
|
||||
LEFT JOIN profiles p ON p.id = w.user_id
|
||||
WHERE p.id IS NULL OR w.user_id = $1 OR w.user_id IS NULL
|
||||
ORDER BY w.created_at DESC NULLS LAST
|
||||
LIMIT 80
|
||||
`, [SYSTEM_USER_ID]);
|
||||
|
||||
const paramKeys = await client.query(`
|
||||
SELECT key, COUNT(*)::int AS count
|
||||
FROM works w
|
||||
CROSS JOIN LATERAL jsonb_object_keys(COALESCE(w.params, '{}'::jsonb)) AS key
|
||||
LEFT JOIN profiles p ON p.id = w.user_id
|
||||
WHERE w.is_public = true
|
||||
AND w.status = 'completed'
|
||||
AND (p.id IS NULL OR w.user_id = $1 OR w.user_id IS NULL)
|
||||
GROUP BY key
|
||||
ORDER BY count DESC, key
|
||||
LIMIT 80
|
||||
`, [SYSTEM_USER_ID]);
|
||||
|
||||
const possibleOwnerFields = await client.query(`
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
params->>'user_id' AS params_user_id,
|
||||
params->>'userId' AS params_user_id_camel,
|
||||
params->>'publisher_id' AS publisher_id,
|
||||
params->>'publisherId' AS publisher_id_camel,
|
||||
params->>'owner_id' AS owner_id,
|
||||
params->>'ownerId' AS owner_id_camel,
|
||||
params->>'created_by' AS created_by,
|
||||
params->>'createdBy' AS created_by_camel,
|
||||
params->>'email' AS params_email,
|
||||
params->>'userEmail' AS params_user_email,
|
||||
params->>'publisherEmail' AS params_publisher_email,
|
||||
params->>'nickname' AS params_nickname,
|
||||
params->>'userName' AS params_user_name,
|
||||
LEFT(COALESCE(prompt, ''), 120) AS prompt
|
||||
FROM works
|
||||
WHERE is_public = true
|
||||
AND status = 'completed'
|
||||
AND (user_id IS NULL OR user_id = $1 OR NOT EXISTS (SELECT 1 FROM profiles p WHERE p.id = works.user_id))
|
||||
ORDER BY created_at DESC NULLS LAST
|
||||
LIMIT 80
|
||||
`, [SYSTEM_USER_ID]);
|
||||
|
||||
const duplicateCandidates = await client.query(`
|
||||
SELECT
|
||||
public.id AS orphan_id,
|
||||
public.user_id AS orphan_user_id,
|
||||
private.id AS owned_id,
|
||||
private.user_id AS owner_user_id,
|
||||
p.email,
|
||||
p.nickname,
|
||||
CASE
|
||||
WHEN private.result_url = public.result_url THEN 'result_url'
|
||||
WHEN COALESCE(private.thumbnail_url, '') <> '' AND private.thumbnail_url = public.thumbnail_url THEN 'thumbnail_url'
|
||||
WHEN COALESCE(private.prompt, '') <> '' AND private.prompt = public.prompt THEN 'prompt_time'
|
||||
ELSE 'unknown'
|
||||
END AS match_type,
|
||||
ABS(EXTRACT(EPOCH FROM (private.created_at - public.created_at)))::int AS seconds_apart,
|
||||
LEFT(COALESCE(public.prompt, ''), 120) AS prompt
|
||||
FROM works public
|
||||
JOIN works private
|
||||
ON private.id <> public.id
|
||||
AND private.user_id IS NOT NULL
|
||||
AND private.user_id <> $1
|
||||
AND (
|
||||
private.result_url = public.result_url
|
||||
OR (
|
||||
COALESCE(public.thumbnail_url, '') <> ''
|
||||
AND private.thumbnail_url = public.thumbnail_url
|
||||
)
|
||||
OR (
|
||||
COALESCE(private.prompt, '') <> ''
|
||||
AND private.prompt = public.prompt
|
||||
AND private.created_at BETWEEN public.created_at - INTERVAL '30 minutes' AND public.created_at + INTERVAL '30 minutes'
|
||||
)
|
||||
)
|
||||
JOIN profiles p ON p.id = private.user_id
|
||||
LEFT JOIN profiles public_profile ON public_profile.id = public.user_id
|
||||
WHERE public.is_public = true
|
||||
AND public.status = 'completed'
|
||||
AND (public_profile.id IS NULL OR public.user_id = $1 OR public.user_id IS NULL)
|
||||
ORDER BY public.created_at DESC NULLS LAST, match_type, seconds_apart
|
||||
LIMIT 100
|
||||
`, [SYSTEM_USER_ID]);
|
||||
|
||||
const output = {
|
||||
columns: tableColumns.rows,
|
||||
userSummary: userSummary.rows[0],
|
||||
userSamples: userSamples.rows,
|
||||
workSummary: workSummary.rows[0],
|
||||
publicWorkSummary: publicWorkSummary.rows[0],
|
||||
workByUser: workByUser.rows,
|
||||
orphanSamples: orphanSamples.rows.map(row => ({ ...row, result_url: short(row.result_url, 120), params: short(row.params, 300) })),
|
||||
anonymousParamKeys: paramKeys.rows,
|
||||
possibleOwnerFields: possibleOwnerFields.rows,
|
||||
duplicateCandidates: duplicateCandidates.rows,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(output, null, 2));
|
||||
} finally {
|
||||
client.release();
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user