const router = require('express').Router() const { ok, fail } = require('../lib/response') const { requireUser } = require('../middleware/auth') const { randomHex } = require('../lib/auth') const { getSettings } = require('../lib/settings-cache') const bindingDao = require('../dao/binding.dao') const deviceDao = require('../dao/device.dao') const commandDao = require('../dao/command.dao') const subscriptionDao = require('../dao/subscription.dao') const deviceEventDao = require('../dao/device-event.dao') const logDao = require('../dao/log.dao') const wrap = fn => (req, res, next) => fn(req, res, next).catch(next) router.post('/device/bind', requireUser, wrap(async (req, res) => { const settings = await getSettings() if (settings.enable_binding === false) return res.json(fail(2001, '设备绑定已关闭')) const deviceId = String(req.body.device_id || '').trim() if (!deviceId) return res.json(fail(2001, 'device_id required')) const active = await bindingDao.findActiveByUser(req.user.user_id) if (active) return res.json(fail(2001, '已绑定设备', { device_id: active.device_id })) const device = await bindingDao.findDeviceExists(deviceId) if (!device) return res.json(fail(1005, 'DEVICE_NOT_FOUND')) // Cancel any stale pending bindings for this user before creating a new one await bindingDao.cancelPending(req.user.user_id) const bindToken = randomHex(8) await bindingDao.createPending(req.user.user_id, deviceId, bindToken) await logDao.write({ user_id: req.user.user_id, action: 'device_bind_request', detail: '申请绑定设备: ' + deviceId, ip: req.ip }) const sub = await subscriptionDao.findActive(req.user.user_id) res.json(ok({ device_id: deviceId, bind_token: bindToken, subscription: sub ? { plan: sub.plan, remaining_days: sub.remaining_days } : { plan: 'none', remaining_days: 0 } })) })) router.post('/device/bind/confirm', requireUser, wrap(async (req, res) => { const deviceId = String(req.body.device_id || '').trim() const bindToken = String(req.body.bind_token || '').trim() if (!deviceId || !bindToken) return res.json(fail(2001, 'device_id and bind_token required')) const confirmed = await bindingDao.confirmBind(req.user.user_id, deviceId, bindToken) if (!confirmed) return res.json(fail(2001, 'bind_token invalid or expired')) await logDao.write({ user_id: req.user.user_id, action: 'device_bind_confirm', detail: '确认绑定设备: ' + deviceId, ip: req.ip }) const sub = await subscriptionDao.findActive(req.user.user_id) res.json(ok({ message: 'success', subscription: sub ? { plan: sub.plan, remaining_days: sub.remaining_days } : { plan: 'none', remaining_days: 0 } })) })) router.post('/device/unbind', requireUser, wrap(async (req, res) => { const deviceId = req.body.device_id || null await bindingDao.unbindByUser(req.user.user_id, deviceId) await logDao.write({ user_id: req.user.user_id, action: 'device_unbind', detail: '解绑设备: ' + (deviceId || 'current'), ip: req.ip }) res.json(ok({ message: 'success' })) })) router.get('/device/list', requireUser, wrap(async (req, res) => { const devices = await deviceDao.listByUser(req.user.user_id) res.json(ok({ devices, total: devices.length })) })) router.get('/device/command/pending', requireUser, wrap(async (req, res) => { const deviceId = String(req.query.device_id || '').trim() if (!deviceId) return res.json(fail(2001, 'device_id required')) const active = await bindingDao.findActiveByUser(req.user.user_id) if (!active || active.device_id !== deviceId) return res.json(fail(1006, 'DEVICE_NOT_BOUND')) const commands = await commandDao.getPending(deviceId) if (commands.length > 0) { await commandDao.markPulled(commands.map(c => c.command_id)) } res.json(ok({ commands: commands.map(c => ({ seq: c.command_id, opcode: c.opcode, payload: typeof c.payload_json === 'string' ? JSON.parse(c.payload_json) : c.payload_json || {} })) })) })) router.post('/device/command/result', requireUser, wrap(async (req, res) => { const commandId = parseInt(req.body.command_id || req.body.seq, 10) const success = req.body.success !== false if (!commandId) return res.json(fail(2001, 'command_id required')) const cmd = await commandDao.findByIdForUser(commandId, req.user.user_id) if (!cmd) return res.json(fail(1006, 'device_not_bound')) await commandDao.finish(commandId, success, req.body) res.json(ok({ message: 'success' })) })) router.post('/device/event', requireUser, wrap(async (req, res) => { const deviceId = String(req.body.device_id || '').trim() if (!deviceId) return res.json(fail(2001, 'device_id required')) const active = await bindingDao.findActiveByUser(req.user.user_id) if (!active || active.device_id !== deviceId) return res.json(fail(1006, 'device_not_bound')) await deviceEventDao.create({ device_id: deviceId, user_id: req.user.user_id, event_type: req.body.event_type || 'device_error', error_code: req.body.error_code || null, temperature: req.body.temperature || null, payload: req.body }) await logDao.write({ user_id: req.user.user_id, action: 'device_event', detail: '设备事件: ' + deviceId, ip: req.ip }) res.json(ok({ message: 'ok' })) })) router.get('/device/:device_id', requireUser, wrap(async (req, res) => { const device = await deviceDao.findBoundDevice(req.user.user_id, req.params.device_id) if (!device) return res.json(fail(1006, 'DEVICE_NOT_BOUND')) res.json(ok(device)) })) module.exports = router