// Publish the DSCA33/45 gap SAFELY: for each validated model's unuploaded PASS serial, // GET the Hoffman record; push ONLY those that are absent (404) so every push is a // Created — never an UPDATE that could overwrite a pristine original. (The inventory // file is stale, so we probe per-serial instead of trusting api_uploaded_at.) const fs = require('fs'), https = require('https'); const db = require('./database/db'); const { uploadBySerialNumbers } = require('./database/upload-to-api'); const tpl = require('./dsca33-45-templates.json'); const c = JSON.parse(fs.readFileSync('C:\\ProgramData\\dataforth-uploader\\credentials.json', 'utf8')); const DRY = !process.argv.includes('--push'); function req(m, uri, h, b) { return new Promise((res, rej) => { const u = new URL(uri); const r = https.request({ hostname: u.hostname, port: 443, path: u.pathname, method: m, headers: h, timeout: 30000 }, x => { let d = ''; x.on('data', c => d += c); x.on('end', () => res({ status: x.statusCode, body: d })); }); r.on('error', rej); r.on('timeout', () => r.destroy(new Error('timeout'))); if (b) r.write(b); r.end(); }); } async function token() { const form = Object.entries({ grant_type: 'client_credentials', client_id: c.CF_CLIENT_ID, client_secret: c.CF_CLIENT_SECRET, scope: c.CF_SCOPE }).map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&'); const r = await req('POST', c.CF_TOKEN_URL, { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(form) }, form); return JSON.parse(r.body).access_token; } (async () => { const validated = Object.keys(tpl).filter(m => tpl[m].validated); const ph = validated.map((_, i) => '$' + (i + 1)).join(','); const rows = await db.query(`SELECT serial_number FROM test_records WHERE overall_result='PASS' AND api_uploaded_at IS NULL AND model_number IN (${ph}) ORDER BY serial_number`, validated); const sns = rows.map(r => r.serial_number); console.log(`validated models: ${validated.length}; unuploaded PASS serials to probe: ${sns.length}`); const t = await token(); const absent = [], present = []; for (let i = 0; i < sns.length; i++) { const r = await req('GET', c.CF_API_BASE + '/api/v1/TestReportDataFiles/' + encodeURIComponent(sns[i]), { Authorization: 'Bearer ' + t }); if (r.status === 404 || (r.status === 200 && !/"Content"/.test(r.body))) absent.push(sns[i]); else if (r.status === 200) present.push(sns[i]); else absent.push(sns[i]); // treat unknown as absent? no — be safe: skip if ((i + 1) % 200 === 0) console.log(` probed ${i + 1}/${sns.length} absent=${absent.length} present=${present.length}`); } console.log(`\nPROBE DONE: absent(not on Hoffman)=${absent.length} present(already live)=${present.length}`); if (DRY) { console.log('\n(dry run — pass --push to Created-publish the absent set)'); await db.close(); return; } console.log('\nPublishing absent set (Created only)...'); const tot = { created: 0, updated: 0, unchanged: 0, errors: 0, skipped: 0 }; const CH = 500; for (let i = 0; i < absent.length; i += CH) { const r = await uploadBySerialNumbers(absent.slice(i, i + CH)); for (const k of Object.keys(tot)) tot[k] += r[k] || 0; console.log(` ${Math.min(i + CH, absent.length)}/${absent.length} cumulative ${JSON.stringify(tot)}`); } console.log('\nDONE ' + JSON.stringify(tot)); if (tot.updated > 0) console.log('[WARNING] ' + tot.updated + ' UPDATED — investigate (should be 0 for an absent-only push)'); await db.close(); })().catch(e => { console.error('ERR', e.message, e.stack); process.exit(1); });