const fs = require('fs'); const path = require('path'); const { categoryDir, latestPath } = require('./output-paths'); function devicesDir() { return categoryDir('devices'); } function deviceFilePath(guid) { return path.join(devicesDir(), `${guid}.json`); } function safeReadJson(filePath, fallback = null) { try { if (!filePath || !fs.existsSync(filePath)) return fallback; return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return fallback; } } function atomicWriteJson(filePath, data) { const dir = path.dirname(filePath); fs.mkdirSync(dir, { recursive: true }); const tmpPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`; try { fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf8'); fs.renameSync(tmpPath, filePath); } catch (err) { try { fs.unlinkSync(tmpPath); } catch {} throw err; } return filePath; } function readDeviceBrokerMapping() { const dir = devicesDir(); if (!fs.existsSync(dir)) return {}; const mapping = {}; for (const file of fs.readdirSync(dir)) { if (!file.endsWith('.json')) continue; const guid = file.replace(/\.json$/, ''); const data = safeReadJson(path.join(dir, file)); if (data && data.guid) { mapping[data.guid] = data; } else if (data) { mapping[guid] = { ...data, guid }; } } return mapping; } function writeDeviceBrokerMapping(mapping) { const dir = devicesDir(); fs.mkdirSync(dir, { recursive: true }); for (const [guid, record] of Object.entries(mapping)) { atomicWriteJson(deviceFilePath(guid), { ...record, guid }); } return dir; } function resolveBrokerIdForGuid(guid) { if (!guid) return null; const record = safeReadJson(deviceFilePath(guid)); return record && record.brokerId ? record.brokerId : null; } function resolveWecomUserIdForGuid(guid) { if (!guid) return null; const record = safeReadJson(deviceFilePath(guid)); return record && record.wecomUserId ? record.wecomUserId : null; } function resolveBrokerIdForWecomUserId(wecomUserId) { if (!wecomUserId) return null; const mapping = readDeviceBrokerMapping(); for (const record of Object.values(mapping)) { if (record.wecomUserId === wecomUserId && record.brokerId) { return record.brokerId; } } return null; } function resolveWecomUserIdForBrokerId(brokerId) { if (!brokerId) return null; const mapping = readDeviceBrokerMapping(); for (const record of Object.values(mapping)) { if (record.brokerId === brokerId && record.wecomUserId) { return record.wecomUserId; } } return null; } function recordDeviceGuid(guid, detail = {}) { if (!guid) return null; const filePath = deviceFilePath(guid); const existing = safeReadJson(filePath) || {}; const record = { ...existing, guid, wecomUserId: detail.wecomUserId || existing.wecomUserId || undefined, nickname: detail.nickname || existing.nickname || undefined, brokerId: detail.brokerId || existing.brokerId || undefined, recordedAt: existing.recordedAt || new Date().toISOString() }; if (detail.wecomUserId || detail.nickname || detail.brokerId) { record.updatedAt = new Date().toISOString(); } atomicWriteJson(filePath, record); return record; } function bindBrokerToGuid(guid, brokerId) { if (!guid) return null; return recordDeviceGuid(guid, { brokerId }); } module.exports = { readDeviceBrokerMapping, writeDeviceBrokerMapping, resolveBrokerIdForGuid, resolveWecomUserIdForGuid, resolveBrokerIdForWecomUserId, resolveWecomUserIdForBrokerId, recordDeviceGuid, bindBrokerToGuid, deviceFilePath };