const { pool } = require('../db'); const SAMPLE_LIMIT = 50; const sampleSpecs = { catalog_categories: { columns: ['id', 'name', 'description', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] }, catalog_products: { columns: ['id', 'type', 'sku', 'name', 'category_id', 'composition', 'gramature', 'material_yield', 'width_cm', 'color', 'subcategory', 'sizes', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] }, consumption_references: { columns: ['id', 'product_id', 'material_product_id', 'color', 'general_yield', 'size_yields', 'size_areas', 'gramature', 'efficiency_percent', 'rib_g_per_piece', 'material_cost_per_kg', 'consumption_quantity', 'consumption_unit', 'source', 'last_production_order_id', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] }, cutting_family_rules: { columns: ['family_key', 'units_per_roll', 'updated_at'], orderBy: ['family_key'] }, cutting_product_overrides: { columns: ['product_id', 'family_key', 'color', 'size', 'product_type', 'planning_notes', 'updated_at'], orderBy: ['updated_at', 'product_id'] }, orders: { columns: ['id', 'pedido_id', 'data_pedido', 'data_pedido_date', 'valor_pedido', 'produto_id', 'produto_descricao', 'quantidade', 'valor_unitario', 'id_vendedor', 'nome_vendedor', 'marketplace', 'canal_venda', 'numero_ecommerce', 'created_at'], orderBy: ['data_pedido_date', 'created_at', 'id'] }, production_order_markers: { columns: ['id', 'production_order_id', 'label', 'color', 'created_at'], orderBy: ['created_at', 'id'] }, production_order_components: { columns: ['id', 'production_order_id', 'component_tiny_id', 'component_sku', 'component_name', 'quantity_per_unit', 'total_quantity', 'unit', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] }, production_order_steps: { columns: ['id', 'production_order_id', 'step_number', 'name', 'start_date', 'end_date', 'status', 'color', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] }, production_orders: { columns: ['id', 'tiny_id', 'number', 'status', 'order_reference', 'issue_date', 'expected_date', 'product_sku', 'product_description', 'quantity', 'unit', 'integration_status', 'notes', 'supplier', 'lot_code', 'roll_quantity', 'fabric_kg', 'rib_kg', 'yield_pieces_per_kg', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] }, stock: { columns: ['produto_id', 'nome', 'saldo', 'delta_estoque', 'updated_at'], orderBy: ['updated_at', 'produto_id'] }, stock_campaign_queue: { columns: ['id', 'base_product_name', 'produto_id', 'nome', 'saldo', 'delta_estoque', 'status', 'attempts', 'last_error', 'created_at', 'updated_at', 'sent_at'], orderBy: ['updated_at', 'created_at', 'id'] }, supply_fabric_plans: { columns: ['id', 'material', 'color', 'quantity_kg', 'supplier', 'priority', 'status', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] }, supply_movements: { columns: ['id', 'receipt_id', 'lot_id', 'type', 'category', 'product', 'quantity', 'unit', 'reason', 'created_at'], orderBy: ['created_at', 'id'] }, supply_receipts: { columns: ['id', 'category', 'product', 'quantity', 'unit', 'supplier', 'invoice', 'notes', 'status', 'created_at', 'updated_at', 'approved_at'], orderBy: ['updated_at', 'created_at', 'id'] }, supply_stock_lots: { columns: ['id', 'receipt_id', 'category', 'product', 'quantity', 'unit', 'supplier', 'invoice', 'status', 'created_at', 'updated_at'], orderBy: ['updated_at', 'created_at', 'id'] } }; const quoteIdentifier = (identifier) => { if (!/^[a-z_][a-z0-9_]*$/.test(identifier)) { throw new Error(`Unsafe database identifier: ${identifier}`); } return `"${identifier}"`; }; const listPublicTables = async () => { const result = await pool.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name `); return result.rows.map(row => row.table_name); }; const listPublicColumns = async () => { const result = await pool.query(` SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = 'public' ORDER BY table_name, ordinal_position `); return result.rows.reduce((columnsByTable, row) => { if (!columnsByTable[row.table_name]) columnsByTable[row.table_name] = []; columnsByTable[row.table_name].push(row.column_name); return columnsByTable; }, {}); }; const countTableRows = async (tableName) => { const result = await pool.query(`SELECT COUNT(*)::int AS count FROM ${quoteIdentifier(tableName)}`); return result.rows[0]?.count || 0; }; const buildOrderClause = (spec, availableColumns) => { const orderColumns = spec.orderBy.filter(column => availableColumns.includes(column)); if (!orderColumns.length) return ''; const clauses = orderColumns.map(column => { const direction = column === 'family_key' || column === 'product_id' || column === 'produto_id' ? 'ASC' : 'DESC'; return `${quoteIdentifier(column)} ${direction}`; }); return ` ORDER BY ${clauses.join(', ')}`; }; const sampleTableRows = async (tableName, availableColumns) => { const spec = sampleSpecs[tableName]; if (!spec) return null; const selectedColumns = spec.columns.filter(column => availableColumns.includes(column)); if (!selectedColumns.length) return null; const selectClause = selectedColumns.map(quoteIdentifier).join(', '); const orderClause = buildOrderClause(spec, availableColumns); const result = await pool.query( `SELECT ${selectClause} FROM ${quoteIdentifier(tableName)}${orderClause} LIMIT $1`, [SAMPLE_LIMIT] ); return result.rows; }; const buildDatabaseDiagnostic = async (user) => { const [tables, columnsByTable] = await Promise.all([ listPublicTables(), listPublicColumns() ]); const counts = {}; const samples = {}; for (const tableName of tables) { counts[tableName] = await countTableRows(tableName); const sampleRows = await sampleTableRows(tableName, columnsByTable[tableName] || []); if (sampleRows) samples[tableName] = sampleRows; } return { generatedAt: new Date().toISOString(), generatedBy: { role: user?.role || null, userId: user?.userId || null }, sampleLimit: SAMPLE_LIMIT, privacy: { countsIncludeAllPublicTables: true, samplesExcludeTables: ['app_users', 'client_identity_tokens'], ordersSampleExcludesCustomerNameAndPhone: true, productionOrdersSampleExcludesTinyPayload: true }, counts, samples }; }; module.exports = { buildDatabaseDiagnostic };