217 行
6.5 KiB
JavaScript
217 行
6.5 KiB
JavaScript
const { query, one, transaction, limitClause } = require('../lib/db')
|
|
|
|
/**
|
|
* Find user by WeChat openid
|
|
* @param {string} openid
|
|
* @returns {Promise<Object|null>}
|
|
*/
|
|
async function findByOpenid(openid) {
|
|
return one(
|
|
'SELECT * FROM users WHERE openid = :openid',
|
|
{ openid }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Create a new user from WeChat login
|
|
* @param {string} openid
|
|
* @returns {Promise<Object>} query result with insertId
|
|
*/
|
|
async function create(openid) {
|
|
return query(
|
|
'INSERT INTO users (openid, nickname, avatar, status) VALUES (:openid, :nickname, :avatar, 1)',
|
|
{ openid, nickname: '', avatar: '' }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Find user by user_id
|
|
* @param {number} userId
|
|
* @returns {Promise<Object|null>}
|
|
*/
|
|
async function findById(userId) {
|
|
return one(
|
|
'SELECT * FROM users WHERE user_id = :user_id',
|
|
{ user_id: userId }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Find active user by user_id (status=1)
|
|
* @param {number} userId
|
|
* @returns {Promise<Object|null>}
|
|
*/
|
|
async function findActiveById(userId) {
|
|
return one(
|
|
'SELECT * FROM users WHERE user_id = :user_id AND status = 1',
|
|
{ user_id: userId }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Update user profile fields (nickname, avatar, gender)
|
|
* @param {number} userId
|
|
* @param {Object} fields
|
|
* @param {string|null} [fields.nickname]
|
|
* @param {string|null} [fields.avatar]
|
|
* @param {number|null} [fields.gender]
|
|
* @returns {Promise<Array>} query result
|
|
*/
|
|
async function updateProfile(userId, fields) {
|
|
return query(
|
|
'UPDATE users SET nickname = COALESCE(:nickname, nickname), avatar = COALESCE(:avatar, avatar), gender = COALESCE(:gender, gender) WHERE user_id = :user_id',
|
|
{
|
|
user_id: userId,
|
|
nickname: fields.nickname || null,
|
|
avatar: fields.avatar || null,
|
|
gender: fields.gender === undefined ? null : fields.gender
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Update user phone number
|
|
* @param {number} userId
|
|
* @param {string} phone
|
|
* @returns {Promise<Array>} query result
|
|
*/
|
|
async function updatePhone(userId, phone) {
|
|
return query(
|
|
'UPDATE users SET phone = :phone WHERE user_id = :user_id',
|
|
{ user_id: userId, phone }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Admin paginated user list with device/treatment/subscription subquery stats
|
|
* @param {Object} opts
|
|
* @param {string} [opts.keyword] - search nickname, phone, or exact user_id
|
|
* @param {number} opts.pageSize
|
|
* @param {number} opts.offset
|
|
* @returns {Promise<{records: Array, total: number}>}
|
|
*/
|
|
async function listAdmin({ keyword, pageSize, offset }) {
|
|
let where = ''
|
|
const params = {}
|
|
if (keyword) {
|
|
where = ' WHERE u.nickname LIKE :kw OR u.phone LIKE :kw OR u.user_id = :keyword'
|
|
params.kw = '%' + keyword + '%'
|
|
params.keyword = keyword
|
|
}
|
|
const total = await query('SELECT COUNT(*) AS total FROM users u' + where, params)
|
|
const records = await query(
|
|
'SELECT u.*,' +
|
|
' (SELECT COUNT(*) FROM bindings WHERE user_id = u.user_id AND bind_status = 1) AS device_count,' +
|
|
' (SELECT COUNT(*) FROM treatment_records WHERE user_id = u.user_id) AS treatment_count,' +
|
|
' COALESCE((SELECT status FROM subscriptions WHERE user_id = u.user_id AND status = 1 AND expire_time > NOW() ORDER BY expire_time DESC LIMIT 1), 0) AS subscription_status' +
|
|
' FROM users u' + where + ' ORDER BY u.created_at DESC' + limitClause(pageSize, offset),
|
|
params
|
|
)
|
|
return { records, total: total[0].total }
|
|
}
|
|
|
|
/**
|
|
* Count admin users with optional keyword
|
|
* @param {Object} opts
|
|
* @param {string} [opts.keyword]
|
|
* @returns {Promise<number>}
|
|
*/
|
|
async function countAdmin({ keyword }) {
|
|
let where = ''
|
|
const params = {}
|
|
if (keyword) {
|
|
where = ' WHERE u.nickname LIKE :kw OR u.phone LIKE :kw OR u.user_id = :keyword'
|
|
params.kw = '%' + keyword + '%'
|
|
params.keyword = keyword
|
|
}
|
|
const rows = await query('SELECT COUNT(*) AS total FROM users u' + where, params)
|
|
return rows[0].total
|
|
}
|
|
|
|
/**
|
|
* Admin detail view: user + bound devices, recent treatments, subscription, stats
|
|
* @param {number} userId
|
|
* @returns {Promise<Object|null>} enriched user object or null
|
|
*/
|
|
async function findByIdAdmin(userId) {
|
|
const user = await one('SELECT * FROM users WHERE user_id = :user_id', { user_id: userId })
|
|
if (!user) return null
|
|
|
|
const devices = await query(
|
|
'SELECT d.device_id, d.device_name FROM bindings b JOIN devices d ON d.device_id = b.device_id WHERE b.user_id = :user_id AND b.bind_status = 1',
|
|
{ user_id: userId }
|
|
)
|
|
const treatments = await query(
|
|
'SELECT * FROM treatment_records WHERE user_id = :user_id ORDER BY created_at DESC LIMIT 5',
|
|
{ user_id: userId }
|
|
)
|
|
const subscription = await one(
|
|
'SELECT plan, status, start_time, expire_time FROM subscriptions WHERE user_id = :user_id AND status = 1 AND expire_time > NOW() ORDER BY expire_time DESC LIMIT 1',
|
|
{ user_id: userId }
|
|
)
|
|
const stats = await one(
|
|
'SELECT COUNT(*) AS treatment_count, COALESCE(SUM(total_duration_ms), 0) AS total_duration FROM treatment_records WHERE user_id = :user_id',
|
|
{ user_id: userId }
|
|
)
|
|
|
|
return Object.assign({}, user, {
|
|
devices,
|
|
recent_treatments: treatments,
|
|
subscription_status: subscription ? subscription.status : 0,
|
|
subscription_type: subscription ? subscription.plan : null,
|
|
subscription_expire: subscription ? subscription.expire_time : null,
|
|
treatment_count: stats ? stats.treatment_count : 0,
|
|
total_duration: stats ? stats.total_duration : 0
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Update user status.
|
|
* @param {number} userId
|
|
* @param {number} status - 1=active, 2=disabled, 3=cancelled
|
|
* @returns {Promise<Array>} query result
|
|
*/
|
|
async function updateStatus(userId, status) {
|
|
return query(
|
|
'UPDATE users SET status = :status WHERE user_id = :user_id',
|
|
{ user_id: userId, status }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Soft-cancel a user account and unbind all active devices.
|
|
* Historical treatment/subscription/log rows are kept.
|
|
* @param {number} userId
|
|
* @returns {Promise<{userAffectedRows: number, unboundRows: number}>}
|
|
*/
|
|
async function deactivate(userId) {
|
|
return transaction(async conn => {
|
|
const [userResult] = await conn.execute(
|
|
'UPDATE users SET status = 3 WHERE user_id = ? AND status <> 3',
|
|
[userId]
|
|
)
|
|
const [unbindResult] = await conn.execute(
|
|
'UPDATE bindings SET bind_status = 2, unbind_time = NOW() WHERE user_id = ? AND bind_status = 1',
|
|
[userId]
|
|
)
|
|
return {
|
|
userAffectedRows: userResult.affectedRows || 0,
|
|
unboundRows: unbindResult.affectedRows || 0
|
|
}
|
|
})
|
|
}
|
|
|
|
module.exports = {
|
|
findByOpenid,
|
|
create,
|
|
findById,
|
|
findActiveById,
|
|
updateProfile,
|
|
updatePhone,
|
|
listAdmin,
|
|
countAdmin,
|
|
findByIdAdmin,
|
|
updateStatus,
|
|
deactivate
|
|
}
|