API 报错
This commit is contained in:
@@ -51,6 +51,7 @@ const errorHandler = async (ctx, next) => {
|
|||||||
code: 404,
|
code: 404,
|
||||||
message: '接口不存在'
|
message: '接口不存在'
|
||||||
}
|
}
|
||||||
|
ctx.status = 404
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Server error:', error)
|
console.error('Server error:', error)
|
||||||
|
|||||||
+77
-1
@@ -361,8 +361,84 @@ async function refundPayment(ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询支付结果
|
||||||
|
* 调用微信支付订单查询API,确认支付状态
|
||||||
|
*/
|
||||||
|
async function queryPayment(ctx) {
|
||||||
|
const { orderId } = ctx.params
|
||||||
|
|
||||||
|
if (!orderId) {
|
||||||
|
ctx.body = { code: 400, message: '订单ID不能为空' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询本地订单
|
||||||
|
const orders = await query('SELECT * FROM orders WHERE id = ?', [orderId])
|
||||||
|
if (orders.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '订单不存在' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = orders[0]
|
||||||
|
|
||||||
|
// 如果本地已标记为已支付,直接返回
|
||||||
|
if (order.status === 'paid') {
|
||||||
|
ctx.body = { code: 200, data: { orderId: order.id, status: 'paid', transactionId: order.transaction_id } }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查支付配置
|
||||||
|
if (!APPID || !MCH_ID || !API_KEY) {
|
||||||
|
ctx.body = { code: 200, data: { orderId: order.id, status: order.status } }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用微信订单查询API
|
||||||
|
const params = {
|
||||||
|
appid: APPID,
|
||||||
|
mch_id: MCH_ID,
|
||||||
|
out_trade_no: orderId,
|
||||||
|
nonce_str: generateNonceStr()
|
||||||
|
}
|
||||||
|
params.sign = generateSign(params)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const xmlData = buildXML(params)
|
||||||
|
const response = await fetch(ORDER_QUERY_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: xmlData,
|
||||||
|
headers: { 'Content-Type': 'application/xml' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const resultXml = await response.text()
|
||||||
|
const result = await parseXML(resultXml)
|
||||||
|
|
||||||
|
if (result.return_code !== 'SUCCESS') {
|
||||||
|
ctx.body = { code: 500, message: '查询支付状态通信失败' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.trade_state === 'SUCCESS') {
|
||||||
|
// 微信确认已支付,更新本地订单状态
|
||||||
|
const transactionId = result.transaction_id || null
|
||||||
|
await query(
|
||||||
|
'UPDATE orders SET status = ?, transaction_id = ? WHERE id = ? AND status != ?',
|
||||||
|
['paid', transactionId, orderId, 'paid']
|
||||||
|
)
|
||||||
|
ctx.body = { code: 200, data: { orderId: order.id, status: 'paid', transactionId } }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 200, data: { orderId: order.id, status: order.status, tradeState: result.trade_state } }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询支付状态失败:', error)
|
||||||
|
ctx.body = { code: 200, data: { orderId: order.id, status: order.status } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createPayment,
|
createPayment,
|
||||||
paymentNotify,
|
paymentNotify,
|
||||||
refundPayment
|
refundPayment,
|
||||||
|
queryPayment
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-1
@@ -139,8 +139,49 @@ async function getStockByGoodsId(ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取库存变动日志
|
||||||
|
async function getStockLogs(ctx) {
|
||||||
|
const goodsId = ctx.query.goods_id
|
||||||
|
const page = parseInt(ctx.query.page) || 1
|
||||||
|
const pageSize = parseInt(ctx.query.pageSize) || 20
|
||||||
|
|
||||||
|
await ensureStockLogTable()
|
||||||
|
|
||||||
|
let sql = `
|
||||||
|
SELECT
|
||||||
|
sl.id,
|
||||||
|
sl.goods_id,
|
||||||
|
g.name as goods_name,
|
||||||
|
sl.change_type,
|
||||||
|
sl.delta,
|
||||||
|
sl.quantity_after,
|
||||||
|
sl.operator_id,
|
||||||
|
sl.remark,
|
||||||
|
sl.created_at
|
||||||
|
FROM stock_logs sl
|
||||||
|
LEFT JOIN goods g ON sl.goods_id = g.id
|
||||||
|
WHERE 1=1
|
||||||
|
`
|
||||||
|
const params = []
|
||||||
|
|
||||||
|
if (goodsId) {
|
||||||
|
sql += ' AND sl.goods_id = ?'
|
||||||
|
params.push(goodsId)
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += ' ORDER BY sl.created_at DESC'
|
||||||
|
|
||||||
|
const result = await paginate(query, sql, params, page, pageSize)
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
code: 200,
|
||||||
|
...result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getStockList,
|
getStockList,
|
||||||
adjustStock,
|
adjustStock,
|
||||||
getStockByGoodsId
|
getStockByGoodsId,
|
||||||
|
getStockLogs
|
||||||
}
|
}
|
||||||
|
|||||||
+205
-7
@@ -20,13 +20,15 @@ async function issueTokenPair(user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function publicUser(user, tokenObj) {
|
function publicUser(user, tokenObj) {
|
||||||
|
const roleMap = { 0: 'customer', 1: 'staff', 2: 'admin' }
|
||||||
const base = {
|
const base = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
phone: user.phone,
|
phone: user.phone,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
points: user.points,
|
points: user.points,
|
||||||
role: user.role
|
role: roleMap[user.role] || 'customer',
|
||||||
|
roleCode: user.role
|
||||||
}
|
}
|
||||||
if (tokenObj) {
|
if (tokenObj) {
|
||||||
return { ...base, token: tokenObj.access, accessToken: tokenObj.access, refreshToken: tokenObj.refresh, legacyToken: tokenObj.legacy, accessTtl: tokenObj.accessTtl, refreshTtl: tokenObj.refreshTtl }
|
return { ...base, token: tokenObj.access, accessToken: tokenObj.access, refreshToken: tokenObj.refresh, legacyToken: tokenObj.legacy, accessTtl: tokenObj.accessTtl, refreshTtl: tokenObj.refreshTtl }
|
||||||
@@ -62,8 +64,8 @@ async function login(ctx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginType === 'admin' && user.role !== 2) {
|
if (loginType === 'admin' && user.role !== 1 && user.role !== 2) {
|
||||||
ctx.body = { code: 401, message: '该账号不是管理员账号' }
|
ctx.body = { code: 401, message: '该账号不是管理员或店员账号' }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +90,39 @@ async function login(ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 用户注册(普通用户)
|
// 用户注册(普通用户)
|
||||||
|
// 注册频率限制(IP+手机号维度)
|
||||||
|
const registerLimiter = new Map()
|
||||||
|
const REGISTER_LIMIT_WINDOW = 60 * 1000 // 1分钟
|
||||||
|
const REGISTER_LIMIT_MAX = 3 // 每分钟最多3次
|
||||||
|
|
||||||
|
function checkRegisterLimit(ip, phone) {
|
||||||
|
const key = `${ip}:${phone}`
|
||||||
|
const now = Date.now()
|
||||||
|
const record = registerLimiter.get(key)
|
||||||
|
|
||||||
|
if (!record || now - record.time > REGISTER_LIMIT_WINDOW) {
|
||||||
|
registerLimiter.set(key, { time: now, count: 1 })
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.count >= REGISTER_LIMIT_MAX) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
record.count++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定期清理过期记录
|
||||||
|
setInterval(() => {
|
||||||
|
const now = Date.now()
|
||||||
|
for (const [key, record] of registerLimiter) {
|
||||||
|
if (now - record.time > REGISTER_LIMIT_WINDOW) {
|
||||||
|
registerLimiter.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 60000)
|
||||||
|
|
||||||
async function register(ctx) {
|
async function register(ctx) {
|
||||||
const { phone, password, name } = ctx.request.body
|
const { phone, password, name } = ctx.request.body
|
||||||
if (!phone || !password || !name) {
|
if (!phone || !password || !name) {
|
||||||
@@ -95,6 +130,13 @@ async function register(ctx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注册频率限制
|
||||||
|
const clientIp = ctx.ip || 'unknown'
|
||||||
|
if (!checkRegisterLimit(clientIp, phone)) {
|
||||||
|
ctx.body = { code: 429, message: '注册过于频繁,请稍后再试' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (password.length < 8) {
|
if (password.length < 8) {
|
||||||
ctx.body = { code: 400, message: '密码至少8位' }
|
ctx.body = { code: 400, message: '密码至少8位' }
|
||||||
return
|
return
|
||||||
@@ -293,7 +335,7 @@ async function getUsers(ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const USER_UPDATEABLE_FIELDS = ['name', 'avatar', 'points', 'status']
|
const USER_UPDATEABLE_FIELDS = ['name', 'avatar', 'points', 'status', 'role']
|
||||||
|
|
||||||
// 更新用户信息(管理员可改任意人;本人仅可改 name/avatar)
|
// 更新用户信息(管理员可改任意人;本人仅可改 name/avatar)
|
||||||
async function updateUser(ctx) {
|
async function updateUser(ctx) {
|
||||||
@@ -309,6 +351,10 @@ async function updateUser(ctx) {
|
|||||||
ctx.body = { code: 403, message: '无权修改他人信息' }
|
ctx.body = { code: 403, message: '无权修改他人信息' }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if ('role' in ctx.request.body) {
|
||||||
|
ctx.body = { code: 403, message: '无权修改角色' }
|
||||||
|
return
|
||||||
|
}
|
||||||
allowedFields = ['name', 'avatar']
|
allowedFields = ['name', 'avatar']
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +365,7 @@ async function updateUser(ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, avatar, points, status } = filtered
|
const { name, avatar, points, status, role } = filtered
|
||||||
|
|
||||||
const updateFields = []
|
const updateFields = []
|
||||||
const updateParams = []
|
const updateParams = []
|
||||||
@@ -340,6 +386,15 @@ async function updateUser(ctx) {
|
|||||||
updateFields.push('status = ?')
|
updateFields.push('status = ?')
|
||||||
updateParams.push(parseInt(status))
|
updateParams.push(parseInt(status))
|
||||||
}
|
}
|
||||||
|
if (role !== undefined) {
|
||||||
|
const roleVal = parseInt(role)
|
||||||
|
if (![0, 1, 2].includes(roleVal)) {
|
||||||
|
ctx.body = { code: 400, message: '角色值无效,必须为 0(用户)/1(店员)/2(管理员)' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updateFields.push('role = ?')
|
||||||
|
updateParams.push(roleVal)
|
||||||
|
}
|
||||||
|
|
||||||
if (updateFields.length === 0) {
|
if (updateFields.length === 0) {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
@@ -619,7 +674,7 @@ async function wechatLogin(ctx) {
|
|||||||
// 已有用户,直接登录
|
// 已有用户,直接登录
|
||||||
const user = users[0]
|
const user = users[0]
|
||||||
const tokenObj = await issueTokenPair(user)
|
const tokenObj = await issueTokenPair(user)
|
||||||
ctx.body = { code: 200, data: publicUser(user, tokenObj) }
|
ctx.body = { code: 200, data: { ...publicUser(user, tokenObj), isNewUser: false } }
|
||||||
} else {
|
} else {
|
||||||
// 新用户,自动注册
|
// 新用户,自动注册
|
||||||
const phone = `wx_${openid.slice(0, 10)}`
|
const phone = `wx_${openid.slice(0, 10)}`
|
||||||
@@ -630,7 +685,7 @@ async function wechatLogin(ctx) {
|
|||||||
)
|
)
|
||||||
const newUser = { id: result.insertId, phone, name, avatar: '', points: 0, role: 0, openid }
|
const newUser = { id: result.insertId, phone, name, avatar: '', points: 0, role: 0, openid }
|
||||||
const tokenObj = await issueTokenPair(newUser)
|
const tokenObj = await issueTokenPair(newUser)
|
||||||
ctx.body = { code: 200, data: publicUser(newUser, tokenObj), isNewUser: true }
|
ctx.body = { code: 200, data: { ...publicUser(newUser, tokenObj), isNewUser: true } }
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('微信登录异常:', err)
|
console.error('微信登录异常:', err)
|
||||||
@@ -638,6 +693,146 @@ async function wechatLogin(ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ 忘记密码 - 验证码相关 ============
|
||||||
|
|
||||||
|
// 内存中存储验证码(生产环境应使用 Redis)
|
||||||
|
const resetCodeStore = new Map()
|
||||||
|
|
||||||
|
// 生成6位数字验证码
|
||||||
|
function generateVerifyCode() {
|
||||||
|
return String(Math.floor(100000 + Math.random() * 900000))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送重置密码验证码
|
||||||
|
async function sendResetCode(ctx) {
|
||||||
|
const { phone } = ctx.request.body
|
||||||
|
|
||||||
|
if (!phone || phone.length !== 11) {
|
||||||
|
ctx.body = { code: 400, message: '请输入正确的手机号' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户是否存在
|
||||||
|
const users = await query('SELECT id FROM users WHERE phone = ?', [phone])
|
||||||
|
if (users.length === 0) {
|
||||||
|
ctx.body = { code: 404, message: '该手机号未注册' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查发送频率(60秒内不可重复发送)
|
||||||
|
const existing = resetCodeStore.get(phone)
|
||||||
|
if (existing && Date.now() - existing.createdAt < 60000) {
|
||||||
|
ctx.body = { code: 429, message: '验证码发送过于频繁,请稍后再试' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = generateVerifyCode()
|
||||||
|
resetCodeStore.set(phone, {
|
||||||
|
code,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
verified: false,
|
||||||
|
attempts: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: 接入短信服务发送验证码,目前仅输出到日志
|
||||||
|
console.log(`[忘记密码] 手机号 ${phone} 的验证码: ${code}`)
|
||||||
|
|
||||||
|
ctx.body = { code: 200, message: '验证码已发送' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证重置密码验证码
|
||||||
|
async function verifyResetCode(ctx) {
|
||||||
|
const { phone, code } = ctx.request.body
|
||||||
|
|
||||||
|
if (!phone || !code) {
|
||||||
|
ctx.body = { code: 400, message: '请输入手机号和验证码' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stored = resetCodeStore.get(phone)
|
||||||
|
|
||||||
|
if (!stored) {
|
||||||
|
ctx.body = { code: 400, message: '请先获取验证码' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码5分钟过期
|
||||||
|
if (Date.now() - stored.createdAt > 5 * 60 * 1000) {
|
||||||
|
resetCodeStore.delete(phone)
|
||||||
|
ctx.body = { code: 400, message: '验证码已过期,请重新获取' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最多尝试5次
|
||||||
|
if (stored.attempts >= 5) {
|
||||||
|
resetCodeStore.delete(phone)
|
||||||
|
ctx.body = { code: 400, message: '验证码错误次数过多,请重新获取' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stored.code !== code) {
|
||||||
|
stored.attempts += 1
|
||||||
|
ctx.body = { code: 400, message: '验证码错误' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记为已验证
|
||||||
|
stored.verified = true
|
||||||
|
ctx.body = { code: 200, message: '验证成功' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过验证码重置密码
|
||||||
|
async function resetPasswordWithCode(ctx) {
|
||||||
|
const { phone, code, newPassword } = ctx.request.body
|
||||||
|
|
||||||
|
if (!phone || !code || !newPassword) {
|
||||||
|
ctx.body = { code: 400, message: '参数不完整' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword.length < 8) {
|
||||||
|
ctx.body = { code: 400, message: '密码至少8位' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[a-zA-Z]/.test(newPassword) || !/[0-9]/.test(newPassword)) {
|
||||||
|
ctx.body = { code: 400, message: '密码需包含字母和数字' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stored = resetCodeStore.get(phone)
|
||||||
|
|
||||||
|
if (!stored || !stored.verified) {
|
||||||
|
ctx.body = { code: 400, message: '请先验证手机号' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码已验证但需确认code一致
|
||||||
|
if (stored.code !== code) {
|
||||||
|
ctx.body = { code: 400, message: '验证码不正确' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证通过后10分钟内有效
|
||||||
|
if (Date.now() - stored.createdAt > 10 * 60 * 1000) {
|
||||||
|
resetCodeStore.delete(phone)
|
||||||
|
ctx.body = { code: 400, message: '验证已过期,请重新操作' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新密码
|
||||||
|
const result = await query('UPDATE users SET password = ? WHERE phone = ?', [hashPassword(newPassword), phone])
|
||||||
|
|
||||||
|
// 清除验证码记录
|
||||||
|
resetCodeStore.delete(phone)
|
||||||
|
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
ctx.body = { code: 200, message: '密码重置成功' }
|
||||||
|
} else {
|
||||||
|
ctx.body = { code: 404, message: '用户不存在' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireStaffAuth,
|
requireStaffAuth,
|
||||||
@@ -653,6 +848,9 @@ module.exports = {
|
|||||||
deleteUser,
|
deleteUser,
|
||||||
changePassword,
|
changePassword,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
|
sendResetCode,
|
||||||
|
verifyResetCode,
|
||||||
|
resetPasswordWithCode,
|
||||||
addPoints,
|
addPoints,
|
||||||
getPointsLogs,
|
getPointsLogs,
|
||||||
logout,
|
logout,
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ const router = new Router()
|
|||||||
// 创建支付(需要用户登录)
|
// 创建支付(需要用户登录)
|
||||||
router.post('/create', requireAuth(), paymentController.createPayment)
|
router.post('/create', requireAuth(), paymentController.createPayment)
|
||||||
|
|
||||||
|
// 查询支付结果(需要用户登录)
|
||||||
|
router.get('/query/:orderId', requireAuth(), paymentController.queryPayment)
|
||||||
|
|
||||||
// 微信支付回调(无需登录)
|
// 微信支付回调(无需登录)
|
||||||
router.post('/notify', paymentController.paymentNotify)
|
router.post('/notify', paymentController.paymentNotify)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const { requireStaffAuth } = require('../middleware/auth')
|
|||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
router.get('/', requireStaffAuth(), stockController.getStockList)
|
router.get('/', requireStaffAuth(), stockController.getStockList)
|
||||||
|
router.get('/logs', requireStaffAuth(), stockController.getStockLogs)
|
||||||
router.get('/:id', stockController.getStockByGoodsId)
|
router.get('/:id', stockController.getStockByGoodsId)
|
||||||
router.post('/:id/adjust', requireStaffAuth(), stockController.adjustStock)
|
router.post('/:id/adjust', requireStaffAuth(), stockController.adjustStock)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ router.post('/login', userController.login)
|
|||||||
router.post('/wechat-login', userController.wechatLogin)
|
router.post('/wechat-login', userController.wechatLogin)
|
||||||
router.post('/register', userController.register)
|
router.post('/register', userController.register)
|
||||||
router.post('/change-password', userController.changePassword)
|
router.post('/change-password', userController.changePassword)
|
||||||
|
router.post('/send-reset-code', userController.sendResetCode)
|
||||||
|
router.post('/verify-reset-code', userController.verifyResetCode)
|
||||||
|
router.post('/reset-password-with-code', userController.resetPasswordWithCode)
|
||||||
router.post('/refresh-token', userController.refreshToken)
|
router.post('/refresh-token', userController.refreshToken)
|
||||||
router.get('/info', userController.getUserInfo)
|
router.get('/info', userController.getUserInfo)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user