feat: init project with miniprogram, cloud functions and admin console
这个提交包含在:
@@ -0,0 +1,308 @@
|
||||
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 bcrypt = require('bcryptjs')
|
||||
|
||||
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/admin/login') {
|
||||
return await adminLogin(body, event)
|
||||
}
|
||||
|
||||
var admin = auth.extractAdmin(event)
|
||||
if (!admin) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
if (method === 'GET' && path === '/api/v1/admin/dashboard') {
|
||||
return await getDashboard()
|
||||
}
|
||||
if (method === 'GET' && path === '/api/v1/admin/devices' && !pathParams.device_id) {
|
||||
return await listDevices(body)
|
||||
}
|
||||
if (method === 'GET' && pathParams.device_id && !path.match(/\/command$/)) {
|
||||
return await getDeviceDetail(pathParams.device_id)
|
||||
}
|
||||
if (method === 'POST' && path.match(/\/command$/)) {
|
||||
return await sendDeviceCommand(pathParams.device_id, body)
|
||||
}
|
||||
if (method === 'GET' && path === '/api/v1/admin/users' && !pathParams.user_id) {
|
||||
return await listUsers(body)
|
||||
}
|
||||
if (method === 'GET' && pathParams.user_id && path.indexOf('/admin/users/') !== -1) {
|
||||
return await getUserDetail(pathParams.user_id)
|
||||
}
|
||||
if (method === 'GET' && path === '/api/v1/admin/subscriptions') {
|
||||
return await listSubscriptions(body)
|
||||
}
|
||||
if (method === 'POST' && path === '/api/v1/admin/subscriptions') {
|
||||
return await createSubscription(event, body)
|
||||
}
|
||||
if (method === 'GET' && path === '/api/v1/admin/logs') {
|
||||
return await getLogs(body)
|
||||
}
|
||||
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'unknown route')
|
||||
} catch (err) {
|
||||
console.error('admin error:', err)
|
||||
return RESPONSE.error(ERROR_CODES.SERVER_ERROR.code, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function adminLogin(body, event) {
|
||||
if (!body.username || !body.password) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing username or password')
|
||||
}
|
||||
|
||||
var admins = await db.query(
|
||||
'SELECT id, username, password_hash, role, real_name FROM admins WHERE username = ? AND status = 1 AND deleted_at IS NULL LIMIT 1',
|
||||
[body.username]
|
||||
)
|
||||
if (admins.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.PERMISSION_DENIED.code, 'invalid credentials')
|
||||
}
|
||||
|
||||
var admin = admins[0]
|
||||
var valid = bcrypt.compareSync(body.password, admin.password_hash)
|
||||
if (!valid) {
|
||||
return RESPONSE.error(ERROR_CODES.PERMISSION_DENIED.code, 'invalid credentials')
|
||||
}
|
||||
|
||||
await db.update(
|
||||
'UPDATE admins SET last_login_at = NOW() WHERE id = ?',
|
||||
[admin.id]
|
||||
)
|
||||
|
||||
var token = auth.generateAdminToken(admin.id, admin.username, admin.role)
|
||||
|
||||
await log.writeLog({
|
||||
operator_type: 1,
|
||||
operator_id: admin.id,
|
||||
target_type: 'admin',
|
||||
target_id: String(admin.id),
|
||||
action: 'login',
|
||||
ip_address: getClientIp(event)
|
||||
})
|
||||
|
||||
return RESPONSE.success({
|
||||
token: token,
|
||||
admin_id: String(admin.id),
|
||||
username: admin.username,
|
||||
real_name: admin.real_name,
|
||||
role: admin.role
|
||||
})
|
||||
}
|
||||
|
||||
async function getDashboard() {
|
||||
var userCount = await db.query('SELECT COUNT(*) AS cnt FROM users WHERE deleted_at IS NULL')
|
||||
var deviceCount = await db.query('SELECT COUNT(*) AS cnt FROM devices WHERE deleted_at IS NULL')
|
||||
var activeDevices = await db.query('SELECT COUNT(*) AS cnt FROM devices WHERE status = 2 AND deleted_at IS NULL')
|
||||
var subCount = await db.query('SELECT COUNT(*) AS cnt FROM subscriptions WHERE status = 1 AND expired_at > NOW()')
|
||||
var trialCount = await db.query('SELECT COUNT(*) AS cnt FROM subscriptions WHERE type = 1')
|
||||
var treatmentCount = await db.query('SELECT COUNT(*) AS cnt FROM treatment_records')
|
||||
var todayTreatments = await db.query(
|
||||
"SELECT COUNT(*) AS cnt FROM treatment_records WHERE DATE(created_at) = CURDATE()"
|
||||
)
|
||||
|
||||
return RESPONSE.success({
|
||||
user_count: userCount[0].cnt,
|
||||
device_count: deviceCount[0].cnt,
|
||||
active_device_count: activeDevices[0].cnt,
|
||||
subscription_count: subCount[0].cnt,
|
||||
trial_count: trialCount[0].cnt,
|
||||
treatment_count: treatmentCount[0].cnt,
|
||||
today_treatment_count: todayTreatments[0].cnt
|
||||
})
|
||||
}
|
||||
|
||||
async function listDevices(body) {
|
||||
var page = parseInt(body.page) || 1
|
||||
var pageSize = parseInt(body.page_size) || 20
|
||||
var offset = (page - 1) * pageSize
|
||||
|
||||
var total = await db.query('SELECT COUNT(*) AS cnt FROM devices WHERE deleted_at IS NULL')
|
||||
var devices = await db.query(
|
||||
'SELECT d.id, d.device_id, d.device_name, d.device_type, d.hw_version, d.fw_version, d.status, d.activated_at, d.created_at, ' +
|
||||
'u.nickname AS bound_user, b.bound_at ' +
|
||||
'FROM devices d ' +
|
||||
'LEFT JOIN bindings b ON d.id = b.device_id AND b.status = 1 ' +
|
||||
'LEFT JOIN users u ON b.user_id = u.id AND u.deleted_at IS NULL ' +
|
||||
'WHERE d.deleted_at IS NULL ' +
|
||||
'ORDER BY d.created_at DESC LIMIT ? OFFSET ?',
|
||||
[pageSize, offset]
|
||||
)
|
||||
|
||||
return RESPONSE.paginate(devices, total[0].cnt, page, pageSize)
|
||||
}
|
||||
|
||||
async function getDeviceDetail(deviceId) {
|
||||
var devices = await db.query(
|
||||
'SELECT * FROM devices WHERE device_id = ? AND deleted_at IS NULL LIMIT 1',
|
||||
[deviceId]
|
||||
)
|
||||
if (devices.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.DEVICE_NOT_FOUND.code, ERROR_CODES.DEVICE_NOT_FOUND.message)
|
||||
}
|
||||
|
||||
var bindings = await db.query(
|
||||
'SELECT b.*, u.nickname, u.avatar_url FROM bindings b JOIN users u ON b.user_id = u.id WHERE b.device_id = ? ORDER BY b.bound_at DESC',
|
||||
[devices[0].id]
|
||||
)
|
||||
|
||||
return RESPONSE.success(Object.assign({}, devices[0], { binding_history: bindings }))
|
||||
}
|
||||
|
||||
async function sendDeviceCommand(deviceId, body) {
|
||||
var devices = await db.query(
|
||||
'SELECT id, device_id, product_id, device_name FROM devices WHERE device_id = ? AND deleted_at IS NULL LIMIT 1',
|
||||
[deviceId]
|
||||
)
|
||||
if (devices.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.DEVICE_NOT_FOUND.code, ERROR_CODES.DEVICE_NOT_FOUND.message)
|
||||
}
|
||||
|
||||
return RESPONSE.success({ message: 'command queued', device_id: deviceId, command: body })
|
||||
}
|
||||
|
||||
async function listUsers(body) {
|
||||
var page = parseInt(body.page) || 1
|
||||
var pageSize = parseInt(body.page_size) || 20
|
||||
var offset = (page - 1) * pageSize
|
||||
|
||||
var total = await db.query('SELECT COUNT(*) AS cnt FROM users WHERE deleted_at IS NULL')
|
||||
var users = await db.query(
|
||||
'SELECT u.id AS user_id, u.nickname, u.avatar_url, u.phone, u.status, u.created_at, ' +
|
||||
'COUNT(DISTINCT b.id) AS device_count, ' +
|
||||
'MAX(s.expired_at) AS subscription_expire ' +
|
||||
'FROM users u ' +
|
||||
'LEFT JOIN bindings b ON u.id = b.user_id AND b.status = 1 ' +
|
||||
'LEFT JOIN subscriptions s ON u.id = s.user_id AND s.status = 1 AND s.expired_at > NOW() ' +
|
||||
'WHERE u.deleted_at IS NULL ' +
|
||||
'GROUP BY u.id ' +
|
||||
'ORDER BY u.created_at DESC LIMIT ? OFFSET ?',
|
||||
[pageSize, offset]
|
||||
)
|
||||
|
||||
return RESPONSE.paginate(users, total[0].cnt, page, pageSize)
|
||||
}
|
||||
|
||||
async function getUserDetail(userId) {
|
||||
var users = await db.query(
|
||||
'SELECT id AS user_id, openid, nickname, avatar_url, phone, status, created_at FROM users WHERE id = ? AND deleted_at IS NULL LIMIT 1',
|
||||
[userId]
|
||||
)
|
||||
if (users.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.USER_NOT_FOUND.code, ERROR_CODES.USER_NOT_FOUND.message)
|
||||
}
|
||||
|
||||
var bindings = await db.query(
|
||||
'SELECT b.bound_at, d.device_id, d.device_name, d.fw_version FROM bindings b JOIN devices d ON b.device_id = d.id WHERE b.user_id = ? AND b.status = 1',
|
||||
[userId]
|
||||
)
|
||||
|
||||
var subs = await db.query(
|
||||
'SELECT type, plan, status, started_at, expired_at, source FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 5',
|
||||
[userId]
|
||||
)
|
||||
|
||||
var treatments = await db.query(
|
||||
'SELECT session_id, regions, total_duration_ms, mode, started_at, ended_at FROM treatment_records WHERE user_id = ? ORDER BY started_at DESC LIMIT 5',
|
||||
[userId]
|
||||
)
|
||||
|
||||
return RESPONSE.success(Object.assign({}, users[0], {
|
||||
devices: bindings,
|
||||
subscriptions: subs,
|
||||
recent_treatments: treatments
|
||||
}))
|
||||
}
|
||||
|
||||
async function listSubscriptions(body) {
|
||||
var page = parseInt(body.page) || 1
|
||||
var pageSize = parseInt(body.page_size) || 20
|
||||
var offset = (page - 1) * pageSize
|
||||
|
||||
var total = await db.query('SELECT COUNT(*) AS cnt FROM subscriptions')
|
||||
var subs = await db.query(
|
||||
'SELECT s.id, s.user_id, u.nickname, s.type, s.plan, s.status, s.trial_used, s.started_at, s.expired_at, s.source ' +
|
||||
'FROM subscriptions s ' +
|
||||
'JOIN users u ON s.user_id = u.id ' +
|
||||
'ORDER BY s.created_at DESC LIMIT ? OFFSET ?',
|
||||
[pageSize, offset]
|
||||
)
|
||||
|
||||
return RESPONSE.paginate(subs, total[0].cnt, page, pageSize)
|
||||
}
|
||||
|
||||
async function createSubscription(event, body) {
|
||||
if (!body.user_id || !body.plan || !body.days) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing user_id, plan or days')
|
||||
}
|
||||
|
||||
await db.insert(
|
||||
'INSERT INTO subscriptions (user_id, type, plan, status, started_at, expired_at, source) VALUES (?, 2, ?, 1, NOW(), DATE_ADD(NOW(), INTERVAL ? DAY), ?)',
|
||||
[body.user_id, body.plan, body.days, body.source || 'manual_create']
|
||||
)
|
||||
|
||||
await log.writeLog({
|
||||
operator_type: 1,
|
||||
operator_id: event.admin_id,
|
||||
target_type: 'subscription',
|
||||
target_id: String(body.user_id),
|
||||
action: 'create',
|
||||
remark: 'plan=' + body.plan + ',days=' + body.days
|
||||
})
|
||||
|
||||
return RESPONSE.success({ message: 'success' })
|
||||
}
|
||||
|
||||
async function getLogs(body) {
|
||||
var page = parseInt(body.page) || 1
|
||||
var pageSize = parseInt(body.page_size) || 20
|
||||
var offset = (page - 1) * pageSize
|
||||
|
||||
var where = '1=1'
|
||||
var params = []
|
||||
|
||||
if (body.type) {
|
||||
where += ' AND target_type = ?'
|
||||
params.push(body.type)
|
||||
}
|
||||
if (body.device_id) {
|
||||
where += ' AND target_id = ?'
|
||||
params.push(body.device_id)
|
||||
}
|
||||
if (body.start_time) {
|
||||
where += ' AND created_at >= ?'
|
||||
params.push(body.start_time)
|
||||
}
|
||||
if (body.end_time) {
|
||||
where += ' AND created_at <= ?'
|
||||
params.push(body.end_time)
|
||||
}
|
||||
|
||||
var total = await db.query('SELECT COUNT(*) AS cnt FROM operation_logs WHERE ' + where, params)
|
||||
var logs = await db.query(
|
||||
'SELECT * FROM operation_logs WHERE ' + where + ' ORDER BY created_at DESC LIMIT ? OFFSET ?',
|
||||
params.concat([pageSize, offset])
|
||||
)
|
||||
|
||||
return RESPONSE.paginate(logs, total[0].cnt, page, pageSize)
|
||||
}
|
||||
|
||||
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'] || ''
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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
|
||||
|
||||
exports.main_handler = async function (event) {
|
||||
try {
|
||||
var method = event.httpMethod || 'GET'
|
||||
var path = event.path || ''
|
||||
var body = parseBody(event)
|
||||
|
||||
if (method === 'POST' && path === '/api/v1/auth/login') {
|
||||
return await wxLogin(body, event)
|
||||
}
|
||||
|
||||
if (method === 'POST' && path === '/api/v1/auth/refresh') {
|
||||
return await refreshToken(body)
|
||||
}
|
||||
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'unknown route')
|
||||
} catch (err) {
|
||||
console.error('auth error:', err)
|
||||
return RESPONSE.error(ERROR_CODES.SERVER_ERROR.code, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function wxLogin(body, event) {
|
||||
if (!body.code) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing code')
|
||||
}
|
||||
|
||||
var wxResult = await requestWxSession(body.code)
|
||||
if (!wxResult || !wxResult.openid) {
|
||||
return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, 'wechat login failed')
|
||||
}
|
||||
|
||||
var users = await db.query(
|
||||
'SELECT id, openid, nickname, avatar_url, phone, status FROM users WHERE openid = ? AND deleted_at IS NULL LIMIT 1',
|
||||
[wxResult.openid]
|
||||
)
|
||||
|
||||
var userId
|
||||
var isNew = false
|
||||
if (users.length === 0) {
|
||||
var result = await db.insert(
|
||||
'INSERT INTO users (openid, nickname, avatar_url, status) VALUES (?, ?, ?, 1)',
|
||||
[wxResult.openid, body.nickname || null, body.avatar_url || null]
|
||||
)
|
||||
userId = result.insertId
|
||||
isNew = true
|
||||
} else {
|
||||
userId = users[0].id
|
||||
if (body.nickname || body.avatar_url) {
|
||||
await db.update(
|
||||
'UPDATE users SET nickname = COALESCE(?, nickname), avatar_url = COALESCE(?, avatar_url) WHERE id = ?',
|
||||
[body.nickname || null, body.avatar_url || null, userId]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var token = auth.generateToken(userId, wxResult.openid)
|
||||
|
||||
await log.writeLog({
|
||||
operator_type: 2,
|
||||
operator_id: userId,
|
||||
target_type: 'user',
|
||||
target_id: String(userId),
|
||||
action: isNew ? 'create' : 'login',
|
||||
ip_address: getClientIp(event)
|
||||
})
|
||||
|
||||
return RESPONSE.success({
|
||||
token: token,
|
||||
user_id: String(userId),
|
||||
user_info: {
|
||||
id: userId,
|
||||
nickname: body.nickname || null,
|
||||
avatar_url: body.avatar_url || null
|
||||
},
|
||||
expires_in: 604800
|
||||
})
|
||||
}
|
||||
|
||||
async function refreshToken(body) {
|
||||
if (!body.refresh_token) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing refresh_token')
|
||||
}
|
||||
|
||||
var decoded = auth.verifyToken(body.refresh_token)
|
||||
if (!decoded || decoded.type === 'admin') {
|
||||
return RESPONSE.error(ERROR_CODES.TOKEN_EXPIRED.code, 'invalid refresh_token')
|
||||
}
|
||||
|
||||
var token = auth.generateToken(decoded.user_id, decoded.openid)
|
||||
return RESPONSE.success({
|
||||
token: token,
|
||||
expires_in: 604800
|
||||
})
|
||||
}
|
||||
|
||||
async function requestWxSession(code) {
|
||||
var appId = process.env.WX_APPID || ''
|
||||
var appSecret = process.env.WX_APP_SECRET || ''
|
||||
var url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' + appId +
|
||||
'&secret=' + appSecret +
|
||||
'&js_code=' + code +
|
||||
'&grant_type=authorization_code'
|
||||
|
||||
var https = require('https')
|
||||
return new Promise(function (resolve) {
|
||||
https.get(url, function (res) {
|
||||
var data = ''
|
||||
res.on('data', function (chunk) { data += chunk })
|
||||
res.on('end', function () {
|
||||
try {
|
||||
resolve(JSON.parse(data))
|
||||
} catch (e) {
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}).on('error', function () { resolve(null) })
|
||||
})
|
||||
}
|
||||
|
||||
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'] || headers['x-real-ip'] || ''
|
||||
}
|
||||
@@ -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'] || ''
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
var db = require('../common/db')
|
||||
var RESPONSE = require('../common/response').RESPONSE
|
||||
var ERROR_CODES = require('../common/response').ERROR_CODES
|
||||
|
||||
exports.main_handler = async function (event, context) {
|
||||
try {
|
||||
var token = extractToken(event)
|
||||
if (!token) {
|
||||
return RESPONSE.error(ERROR_CODES.UNAUTHORIZED.code, ERROR_CODES.UNAUTHORIZED.message)
|
||||
}
|
||||
|
||||
var user = await verifyToken(token)
|
||||
if (!user) {
|
||||
return RESPONSE.error(ERROR_CODES.TOKEN_EXPIRED.code, ERROR_CODES.TOKEN_EXPIRED.message)
|
||||
}
|
||||
|
||||
var body = parseBody(event)
|
||||
|
||||
switch (event.path || event.action) {
|
||||
case '/record/sync':
|
||||
return await syncRecord(user, body)
|
||||
case '/record/list':
|
||||
return await listRecords(user, body)
|
||||
case '/record/detail':
|
||||
return await getRecordDetail(user, body)
|
||||
default:
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, '未知操作')
|
||||
}
|
||||
} catch (err) {
|
||||
return RESPONSE.error(ERROR_CODES.INTERNAL_ERROR.code, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function syncRecord(user, body) {
|
||||
if (!body.device_id || !body.started_at || !body.ended_at) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, '参数不完整')
|
||||
}
|
||||
|
||||
var dbConfig = getDbConfig()
|
||||
|
||||
var sessionResult = await db.query(
|
||||
'INSERT INTO sessions (user_id, device_id, status, started_at, ended_at, created_at, updated_at) ' +
|
||||
'VALUES (?, ?, 2, ?, ?, NOW(), NOW())',
|
||||
[user.id, body.device_id, body.started_at, body.ended_at],
|
||||
dbConfig
|
||||
)
|
||||
|
||||
var sessionId = sessionResult.insertId
|
||||
|
||||
await db.query(
|
||||
'INSERT INTO treatment_records ' +
|
||||
'(session_id, user_id, device_id, duration_seconds, mode, result_summary, sync_status, synced_at, started_at, ended_at, created_at, updated_at) ' +
|
||||
'VALUES (?, ?, ?, ?, ?, ?, 2, NOW(), ?, ?, NOW(), NOW())',
|
||||
[
|
||||
sessionId,
|
||||
user.id,
|
||||
body.device_id,
|
||||
body.duration_seconds || null,
|
||||
body.mode || null,
|
||||
body.result_summary || null,
|
||||
body.started_at,
|
||||
body.ended_at
|
||||
],
|
||||
dbConfig
|
||||
)
|
||||
|
||||
return RESPONSE.success({ session_id: sessionId })
|
||||
}
|
||||
|
||||
async function listRecords(user, body) {
|
||||
var dbConfig = getDbConfig()
|
||||
var limit = parseInt(body.limit) || 20
|
||||
var offset = parseInt(body.offset) || 0
|
||||
|
||||
var records = await db.query(
|
||||
'SELECT * FROM treatment_records WHERE user_id = ? ORDER BY started_at DESC LIMIT ? OFFSET ?',
|
||||
[user.id, limit, offset],
|
||||
dbConfig
|
||||
)
|
||||
|
||||
return RESPONSE.success({ list: records })
|
||||
}
|
||||
|
||||
async function getRecordDetail(user, body) {
|
||||
if (!body.record_id) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, '缺少记录ID')
|
||||
}
|
||||
|
||||
var dbConfig = getDbConfig()
|
||||
|
||||
var records = await db.query(
|
||||
'SELECT * FROM treatment_records WHERE id = ? AND user_id = ? LIMIT 1',
|
||||
[body.record_id, user.id],
|
||||
dbConfig
|
||||
)
|
||||
|
||||
if (records.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.NOT_FOUND.code, ERROR_CODES.NOT_FOUND.message)
|
||||
}
|
||||
|
||||
return RESPONSE.success(records[0])
|
||||
}
|
||||
|
||||
function extractToken(event) {
|
||||
var header = event.headers || {}
|
||||
var auth = header['Authorization'] || header['authorization'] || ''
|
||||
if (auth.startsWith('Bearer ')) {
|
||||
return auth.substring(7)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function verifyToken(token) {
|
||||
return { id: 1, openid: 'placeholder' }
|
||||
}
|
||||
|
||||
function parseBody(event) {
|
||||
if (event.body) {
|
||||
try {
|
||||
return JSON.parse(event.body)
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
return event.queryString || {}
|
||||
}
|
||||
|
||||
function getDbConfig() {
|
||||
return {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '3306'),
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
database: process.env.DB_NAME || 'hox'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
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
|
||||
|
||||
exports.main_handler = async function (event) {
|
||||
try {
|
||||
var method = event.httpMethod || 'GET'
|
||||
var path = event.path || ''
|
||||
var body = parseBody(event)
|
||||
|
||||
if (method === 'GET' && path === '/api/v1/subscription') {
|
||||
return await getSubscription(event)
|
||||
}
|
||||
if (method === 'POST' && path === '/api/v1/subscription/purchase') {
|
||||
return await purchaseSubscription(event, body)
|
||||
}
|
||||
if (method === 'POST' && path === '/api/v1/subscription/verify') {
|
||||
return await verifyPayment(event, body)
|
||||
}
|
||||
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'unknown route')
|
||||
} catch (err) {
|
||||
console.error('subscription error:', err)
|
||||
return RESPONSE.error(ERROR_CODES.SERVER_ERROR.code, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function getSubscription(event) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
var subs = await db.query(
|
||||
'SELECT id, type, plan, status, trial_used, started_at AS start_time, expired_at AS expire_time ' +
|
||||
'FROM subscriptions ' +
|
||||
'WHERE user_id = ? AND status = 1 AND expired_at > NOW() ' +
|
||||
'ORDER BY expired_at DESC LIMIT 1',
|
||||
[user.user_id]
|
||||
)
|
||||
|
||||
if (subs.length === 0) {
|
||||
return RESPONSE.success({
|
||||
status: 0,
|
||||
plan: null,
|
||||
start_time: null,
|
||||
expire_time: null,
|
||||
remaining_days: 0
|
||||
})
|
||||
}
|
||||
|
||||
var sub = subs[0]
|
||||
var remainingMs = new Date(sub.expire_time) - new Date()
|
||||
var remainingDays = Math.ceil(remainingMs / 86400000)
|
||||
|
||||
return RESPONSE.success({
|
||||
status: sub.type === 1 ? 1 : 2,
|
||||
plan: sub.plan,
|
||||
start_time: sub.start_time,
|
||||
expire_time: sub.expire_time,
|
||||
remaining_days: remainingDays
|
||||
})
|
||||
}
|
||||
|
||||
async function purchaseSubscription(event, body) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
if (!body.plan) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing plan')
|
||||
}
|
||||
|
||||
var planDays = { monthly: 30, quarterly: 90, yearly: 365 }
|
||||
var days = planDays[body.plan] || 30
|
||||
var orderId = 'ORD' + Date.now() + Math.random().toString(36).substring(2, 8)
|
||||
|
||||
return RESPONSE.success({
|
||||
order_id: orderId,
|
||||
payment_params: {
|
||||
plan: body.plan,
|
||||
days: days,
|
||||
amount: 0,
|
||||
channel: body.payment_method || 'wechat'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function verifyPayment(event, body) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
if (!body.order_id) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing order_id')
|
||||
}
|
||||
|
||||
var planDays = { monthly: 30, quarterly: 90, yearly: 365 }
|
||||
|
||||
var currentSub = await db.query(
|
||||
'SELECT id, expired_at FROM subscriptions WHERE user_id = ? AND status = 1 AND expired_at > NOW() ORDER BY expired_at DESC LIMIT 1',
|
||||
[user.user_id]
|
||||
)
|
||||
|
||||
var startDate = new Date()
|
||||
if (currentSub.length > 0 && new Date(currentSub[0].expired_at) > startDate) {
|
||||
startDate = new Date(currentSub[0].expired_at)
|
||||
}
|
||||
|
||||
var plan = body.plan || 'monthly'
|
||||
var days = planDays[plan] || 30
|
||||
var expiredAt = new Date(startDate.getTime() + days * 86400000)
|
||||
|
||||
await db.insert(
|
||||
'INSERT INTO subscriptions (user_id, type, plan, status, started_at, expired_at, source, source_id) VALUES (?, 2, ?, 1, ?, ?, ?, ?)',
|
||||
[user.user_id, plan, startDate.toISOString().slice(0, 19).replace('T', ' '), expiredAt.toISOString().slice(0, 19).replace('T', ' '), 'purchase', body.order_id]
|
||||
)
|
||||
|
||||
await log.writeLog({
|
||||
operator_type: 2,
|
||||
operator_id: user.user_id,
|
||||
target_type: 'subscription',
|
||||
target_id: body.order_id,
|
||||
action: 'purchase',
|
||||
remark: 'plan=' + plan + ',days=' + days
|
||||
})
|
||||
|
||||
return RESPONSE.success({
|
||||
status: 2,
|
||||
plan: plan,
|
||||
expire_time: expiredAt.toISOString().slice(0, 19).replace('T', ' ')
|
||||
})
|
||||
}
|
||||
|
||||
function parseBody(event) {
|
||||
if (event.body) {
|
||||
try { return JSON.parse(event.body) } catch (e) { return {} }
|
||||
}
|
||||
return event.queryStringParameters || {}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
var db = require('../../common/db')
|
||||
var auth = require('../../common/auth')
|
||||
var RESPONSE = require('../../common/response').RESPONSE
|
||||
var ERROR_CODES = require('../../common/response').ERROR_CODES
|
||||
|
||||
exports.main_handler = async function (event) {
|
||||
try {
|
||||
var method = event.httpMethod || 'GET'
|
||||
var path = event.path || ''
|
||||
var body = parseBody(event)
|
||||
|
||||
if (method === 'POST' && path === '/api/v1/treatment/sync') {
|
||||
return await syncRecord(event, body)
|
||||
}
|
||||
if (method === 'GET' && path === '/api/v1/treatment/history') {
|
||||
return await getHistory(event, body)
|
||||
}
|
||||
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'unknown route')
|
||||
} catch (err) {
|
||||
console.error('treatment error:', err)
|
||||
return RESPONSE.error(ERROR_CODES.SERVER_ERROR.code, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function syncRecord(event, body) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
if (!body.session_id || !body.device_id) {
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'missing session_id or device_id')
|
||||
}
|
||||
|
||||
var devices = await db.query(
|
||||
'SELECT 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 devicePkId = devices[0].id
|
||||
|
||||
var existingSession = await db.query(
|
||||
'SELECT id FROM sessions WHERE session_id = ? LIMIT 1',
|
||||
[body.session_id]
|
||||
)
|
||||
|
||||
if (existingSession.length === 0) {
|
||||
await db.insert(
|
||||
'INSERT INTO sessions (session_id, user_id, device_id, status, started_at, ended_at) VALUES (?, ?, ?, 2, ?, ?)',
|
||||
[
|
||||
body.session_id,
|
||||
user.user_id,
|
||||
devicePkId,
|
||||
body.start_time ? new Date(body.start_time).toISOString().slice(0, 19).replace('T', ' ') : null,
|
||||
body.end_time ? new Date(body.end_time).toISOString().slice(0, 19).replace('T', ' ') : null
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
var existingRecord = await db.query(
|
||||
'SELECT id FROM treatment_records WHERE session_id = ? LIMIT 1',
|
||||
[body.session_id]
|
||||
)
|
||||
|
||||
if (existingRecord.length > 0) {
|
||||
return RESPONSE.success({ record_id: existingRecord[0].id })
|
||||
}
|
||||
|
||||
var result = await db.insert(
|
||||
'INSERT INTO treatment_records (session_id, user_id, device_id, regions, total_duration_ms, mode, avg_pd, sync_status, synced_at, started_at, ended_at) ' +
|
||||
'VALUES (?, ?, ?, ?, ?, ?, ?, 2, NOW(), ?, ?)',
|
||||
[
|
||||
body.session_id,
|
||||
user.user_id,
|
||||
devicePkId,
|
||||
body.regions || null,
|
||||
body.total_duration_ms || null,
|
||||
body.mode !== undefined ? body.mode : null,
|
||||
body.avg_pd || null,
|
||||
body.start_time ? new Date(body.start_time).toISOString().slice(0, 19).replace('T', ' ') : null,
|
||||
body.end_time ? new Date(body.end_time).toISOString().slice(0, 19).replace('T', ' ') : null
|
||||
]
|
||||
)
|
||||
|
||||
if (body.pd_data && Array.isArray(body.pd_data)) {
|
||||
for (var i = 0; i < body.pd_data.length; i++) {
|
||||
var pd = body.pd_data[i]
|
||||
await db.insert(
|
||||
'INSERT INTO pd_data (session_id, device_id, region_name, pd_value, recorded_at) VALUES (?, ?, ?, ?, ?)',
|
||||
[body.session_id, devicePkId, pd.name, pd.pd || null, new Date().toISOString().slice(0, 19).replace('T', ' ')]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return RESPONSE.success({ record_id: result.insertId })
|
||||
}
|
||||
|
||||
async function getHistory(event, body) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
var page = parseInt(body.page) || 1
|
||||
var pageSize = parseInt(body.page_size) || 20
|
||||
var offset = (page - 1) * pageSize
|
||||
|
||||
var totalResult = await db.query(
|
||||
'SELECT COUNT(*) AS cnt FROM treatment_records WHERE user_id = ?',
|
||||
[user.user_id]
|
||||
)
|
||||
var total = totalResult[0].cnt
|
||||
|
||||
var records = await db.query(
|
||||
'SELECT tr.id AS record_id, tr.session_id, tr.regions, tr.total_duration_ms, tr.mode, tr.avg_pd, tr.started_at AS start_time, tr.ended_at AS end_time, d.device_id, d.device_name ' +
|
||||
'FROM treatment_records tr ' +
|
||||
'JOIN devices d ON tr.device_id = d.id ' +
|
||||
'WHERE tr.user_id = ? ' +
|
||||
'ORDER BY tr.started_at DESC LIMIT ? OFFSET ?',
|
||||
[user.user_id, pageSize, offset]
|
||||
)
|
||||
|
||||
return RESPONSE.paginate(records, total, page, pageSize)
|
||||
}
|
||||
|
||||
function parseBody(event) {
|
||||
if (event.body) {
|
||||
try { return JSON.parse(event.body) } catch (e) { return {} }
|
||||
}
|
||||
return event.queryStringParameters || {}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
var db = require('../../common/db')
|
||||
var auth = require('../../common/auth')
|
||||
var RESPONSE = require('../../common/response').RESPONSE
|
||||
var ERROR_CODES = require('../../common/response').ERROR_CODES
|
||||
|
||||
exports.main_handler = async function (event) {
|
||||
try {
|
||||
var method = event.httpMethod || 'GET'
|
||||
var path = event.path || ''
|
||||
var body = parseBody(event)
|
||||
|
||||
if (method === 'GET' && path === '/api/v1/user/profile') {
|
||||
return await getProfile(event)
|
||||
}
|
||||
if (method === 'PUT' && path === '/api/v1/user/profile') {
|
||||
return await updateProfile(event, body)
|
||||
}
|
||||
|
||||
return RESPONSE.error(ERROR_CODES.PARAM_ERROR.code, 'unknown route')
|
||||
} catch (err) {
|
||||
console.error('user error:', err)
|
||||
return RESPONSE.error(ERROR_CODES.SERVER_ERROR.code, err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function getProfile(event) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
var users = await db.query(
|
||||
'SELECT id AS user_id, nickname, avatar_url AS avatar, phone, status FROM users WHERE id = ? AND deleted_at IS NULL LIMIT 1',
|
||||
[user.user_id]
|
||||
)
|
||||
if (users.length === 0) {
|
||||
return RESPONSE.error(ERROR_CODES.USER_NOT_FOUND.code, ERROR_CODES.USER_NOT_FOUND.message)
|
||||
}
|
||||
|
||||
var profile = users[0]
|
||||
|
||||
var bindings = await db.query(
|
||||
'SELECT COUNT(*) AS cnt FROM bindings WHERE user_id = ? AND status = 1',
|
||||
[user.user_id]
|
||||
)
|
||||
var bindTime = await db.query(
|
||||
'SELECT bound_at AS bind_time FROM bindings WHERE user_id = ? AND status = 1 ORDER BY bound_at ASC LIMIT 1',
|
||||
[user.user_id]
|
||||
)
|
||||
|
||||
profile.device_count = bindings[0].cnt
|
||||
profile.bind_time = bindTime.length > 0 ? bindTime[0].bind_time : null
|
||||
|
||||
return RESPONSE.success(profile)
|
||||
}
|
||||
|
||||
async function updateProfile(event, body) {
|
||||
var user = auth.extractUser(event)
|
||||
if (!user) return RESPONSE.error(ERROR_CODES.INVALID_TOKEN.code, ERROR_CODES.INVALID_TOKEN.message)
|
||||
|
||||
if (body.nickname) {
|
||||
await db.update(
|
||||
'UPDATE users SET nickname = ? WHERE id = ?',
|
||||
[body.nickname, user.user_id]
|
||||
)
|
||||
}
|
||||
|
||||
return RESPONSE.success({ message: 'success' })
|
||||
}
|
||||
|
||||
function parseBody(event) {
|
||||
if (event.body) {
|
||||
try { return JSON.parse(event.body) } catch (e) { return {} }
|
||||
}
|
||||
return event.queryStringParameters || {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户