feat: init project with miniprogram, cloud functions and admin console
这个提交包含在:
@@ -0,0 +1,195 @@
|
||||
var db = require('../../common/db')
|
||||
var auth = require('../../common/auth')
|
||||
var log = require('../../common/log')
|
||||
var RESPONSE = require('../../common/response').RESPONSE
|
||||
var ERROR_CODES = require('../../common/response').ERROR_CODES
|
||||
var uuid = require('uuid')
|
||||
|
||||
exports.main_handler = async function (event) {
|
||||
try {
|
||||
var method = event.httpMethod || 'GET'
|
||||
var path = event.path || ''
|
||||
var pathParams = event.pathParameters || {}
|
||||
var body = parseBody(event)
|
||||
|
||||
if (method === 'POST' && path === '/api/v1/device/bind') {
|
||||
return await bindDevice(event, body)
|
||||
}
|
||||
if (method === 'POST' && path === '/api/v1/device/unbind') {
|
||||
return await unbindDevice(event, body)
|
||||
}
|
||||
if (method === 'GET' && path === '/api/v1/device/list') {
|
||||
return await listDevices(event)
|
||||
}
|
||||
if (method === 'GET' && pathParams.device_id) {
|
||||
return await getDeviceDetail(event, pathParams.device_id)
|
||||
}
|
||||
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'unknown route')
|
||||
} catch (err) {
|
||||
console.error('device error:', err)
|
||||
return RESPONSE.error(ERROR_CODES.SERVER_ERROR.code, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function bindDevice(event, body) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
if (!body.device_id) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing device_id')
|
||||
}
|
||||
|
||||
var devices = await db.query(
|
||||
'SELECT id, device_id, device_name, product_id, status FROM devices WHERE device_id = ? AND deleted_at IS NULL LIMIT 1',
|
||||
[body.device_id]
|
||||
)
|
||||
if (devices.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.DEVICE_NOT_FOUND.code, ERROR_CODES.DEVICE_NOT_FOUND.message)
|
||||
}
|
||||
var device = devices[0]
|
||||
|
||||
var existing = await db.query(
|
||||
'SELECT id FROM bindings WHERE device_id = ? AND status = 1',
|
||||
[device.id]
|
||||
)
|
||||
if (existing.length > 0) {
|
||||
var existingUser = await db.query('SELECT id FROM users WHERE id = ? AND deleted_at IS NULL', [existing[0].user_id])
|
||||
if (existingUser.length > 0 && String(existing[0].user_id) !== String(user.user_id)) {
|
||||
return RESPONSE.error(ERROR_CODES.PERMISSION_DENIED.code, 'device already bound')
|
||||
}
|
||||
if (existingUser.length > 0 && String(existing[0].user_id) === String(user.user_id)) {
|
||||
var bindToken = uuid.v4().replace(/-/g, '').substring(0, 16)
|
||||
await db.update(
|
||||
'UPDATE bindings SET bind_token = ?, updated_at = NOW() WHERE id = ?',
|
||||
[bindToken, existing[0].id]
|
||||
)
|
||||
return RESPONSE.success({ bind_token: bindToken, bind_expires: 300 })
|
||||
}
|
||||
}
|
||||
|
||||
var bindToken = uuid.v4().replace(/-/g, '').substring(0, 16)
|
||||
|
||||
await db.insert(
|
||||
'INSERT INTO bindings (user_id, device_id, bind_token, status, bound_at) VALUES (?, ?, ?, 1, NOW())',
|
||||
[user.user_id, device.id, bindToken]
|
||||
)
|
||||
|
||||
await db.update(
|
||||
'UPDATE devices SET status = 2, activated_at = COALESCE(activated_at, NOW()) WHERE id = ?',
|
||||
[device.id]
|
||||
)
|
||||
|
||||
await grantTrialIfNeeded(user.user_id)
|
||||
|
||||
await log.writeLog({
|
||||
operator_type: 2,
|
||||
operator_id: user.user_id,
|
||||
target_type: 'device',
|
||||
target_id: device.device_id,
|
||||
action: 'bind',
|
||||
ip_address: getClientIp(event)
|
||||
})
|
||||
|
||||
return RESPONSE.success({ bind_token: bindToken, bind_expires: 300 })
|
||||
}
|
||||
|
||||
async function unbindDevice(event, body) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
if (!body.device_id) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing device_id')
|
||||
}
|
||||
|
||||
var devices = await db.query(
|
||||
'SELECT id, device_id FROM devices WHERE device_id = ? AND deleted_at IS NULL LIMIT 1',
|
||||
[body.device_id]
|
||||
)
|
||||
if (devices.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.DEVICE_NOT_FOUND.code, ERROR_CODES.DEVICE_NOT_FOUND.message)
|
||||
}
|
||||
|
||||
var result = await db.update(
|
||||
'UPDATE bindings SET status = 2, unbound_at = NOW(), updated_at = NOW() WHERE user_id = ? AND device_id = ? AND status = 1',
|
||||
[user.user_id, devices[0].id]
|
||||
)
|
||||
if (result.affectedRows === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.DEVICE_NOT_BOUND.code, ERROR_CODES.DEVICE_NOT_BOUND.message)
|
||||
}
|
||||
|
||||
await log.writeLog({
|
||||
operator_type: 2,
|
||||
operator_id: user.user_id,
|
||||
target_type: 'device',
|
||||
target_id: body.device_id,
|
||||
action: 'unbind',
|
||||
ip_address: getClientIp(event)
|
||||
})
|
||||
|
||||
return RESPONSE.success({ message: 'success' })
|
||||
}
|
||||
|
||||
async function listDevices(event) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
var bindings = await db.query(
|
||||
'SELECT b.bound_at AS bind_time, b.bind_token, d.device_id, d.device_name, d.fw_version AS firmware_version, d.hw_version, d.status ' +
|
||||
'FROM bindings b ' +
|
||||
'JOIN devices d ON b.device_id = d.id ' +
|
||||
'WHERE b.user_id = ? AND b.status = 1 AND d.deleted_at IS NULL ' +
|
||||
'ORDER BY b.bound_at DESC',
|
||||
[user.user_id]
|
||||
)
|
||||
|
||||
return RESPONSE.success({ devices: bindings })
|
||||
}
|
||||
|
||||
async function getDeviceDetail(event, deviceId) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
var bindings = await db.query(
|
||||
'SELECT b.id, b.bound_at AS bind_time FROM bindings b ' +
|
||||
'JOIN devices d ON b.device_id = d.id ' +
|
||||
'WHERE b.user_id = ? AND b.status = 1 AND d.device_id = ? AND d.deleted_at IS NULL LIMIT 1',
|
||||
[user.user_id, deviceId]
|
||||
)
|
||||
if (bindings.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.DEVICE_NOT_FOUND.code, ERROR_CODES.DEVICE_NOT_FOUND.message)
|
||||
}
|
||||
|
||||
var devices = await db.query(
|
||||
'SELECT device_id, device_name, device_type, hw_version, fw_version AS firmware_version, status, activated_at FROM devices WHERE device_id = ? LIMIT 1',
|
||||
[deviceId]
|
||||
)
|
||||
|
||||
return RESPONSE.success(Object.assign({}, devices[0], { bind_time: bindings[0].bind_time }))
|
||||
}
|
||||
|
||||
async function grantTrialIfNeeded(userId) {
|
||||
var existing = await db.query(
|
||||
'SELECT id FROM subscriptions WHERE user_id = ? AND type = 1 LIMIT 1',
|
||||
[userId]
|
||||
)
|
||||
if (existing.length > 0) return
|
||||
|
||||
await db.insert(
|
||||
'INSERT INTO subscriptions (user_id, type, plan, status, trial_used, started_at, expired_at, source) ' +
|
||||
'VALUES (?, 1, NULL, 1, 1, NOW(), DATE_ADD(NOW(), INTERVAL 7 DAY), ?)',
|
||||
[userId, 'trial_grant']
|
||||
)
|
||||
}
|
||||
|
||||
function parseBody(event) {
|
||||
if (event.body) {
|
||||
try { return JSON.parse(event.body) } catch (e) { return {} }
|
||||
}
|
||||
return event.queryStringParameters || {}
|
||||
}
|
||||
|
||||
function getClientIp(event) {
|
||||
var headers = event.headers || {}
|
||||
return headers['x-forwarded-for'] || headers['X-Forwarded-For'] || ''
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户