|
|
@@ -0,0 +1,433 @@
|
|
|
+import { Hono } from 'hono'
|
|
|
+import { authMiddleware, requireRole } from '../middleware/auth'
|
|
|
+import { hashPassword } from '../utils/password'
|
|
|
+import { generateId } from '../utils/jwt'
|
|
|
+import type { Env, ApiResponse, PageResponse, User, UserPermission } from '../types'
|
|
|
+
|
|
|
+const user = new Hono<{ Bindings: Env }>()
|
|
|
+
|
|
|
+/**
|
|
|
+ * 创建成功响应
|
|
|
+ */
|
|
|
+function success<T>(data: T, msg = 'success'): ApiResponse<T> {
|
|
|
+ return { code: 200, msg, data }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 创建错误响应
|
|
|
+ */
|
|
|
+function error(msg: string, code = 500): ApiResponse<null> {
|
|
|
+ return { code, msg, data: null }
|
|
|
+}
|
|
|
+
|
|
|
+// 所有用户管理接口都需要认证和 admin 角色
|
|
|
+user.use('*', authMiddleware())
|
|
|
+user.use('*', requireRole('admin'))
|
|
|
+
|
|
|
+// ==================== 用户管理 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取用户列表
|
|
|
+ * GET /api/users
|
|
|
+ */
|
|
|
+user.get('/', async (c) => {
|
|
|
+ try {
|
|
|
+ const { role, status, search, pageSize = '20', page = '1' } = c.req.query()
|
|
|
+ const limit = Math.min(parseInt(pageSize), 100)
|
|
|
+ const offset = (parseInt(page) - 1) * limit
|
|
|
+
|
|
|
+ let sql = 'SELECT id, username, email, role, status, last_login, created_at, updated_at FROM users WHERE 1=1'
|
|
|
+ const params: (string | number)[] = []
|
|
|
+
|
|
|
+ if (role) {
|
|
|
+ sql += ' AND role = ?'
|
|
|
+ params.push(role)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (status) {
|
|
|
+ sql += ' AND status = ?'
|
|
|
+ params.push(status)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (search) {
|
|
|
+ sql += ' AND (username LIKE ? OR email LIKE ?)'
|
|
|
+ params.push(`%${search}%`, `%${search}%`)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取总数
|
|
|
+ const countResult = await c.env.DB
|
|
|
+ .prepare(sql.replace('SELECT id, username, email, role, status, last_login, created_at, updated_at', 'SELECT COUNT(*) as count'))
|
|
|
+ .bind(...params)
|
|
|
+ .first<{ count: number }>()
|
|
|
+
|
|
|
+ // 获取分页数据
|
|
|
+ sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'
|
|
|
+ params.push(limit, offset)
|
|
|
+
|
|
|
+ const users = await c.env.DB
|
|
|
+ .prepare(sql)
|
|
|
+ .bind(...params)
|
|
|
+ .all<User>()
|
|
|
+
|
|
|
+ const data: PageResponse<User> = {
|
|
|
+ rows: users.results || [],
|
|
|
+ total: countResult?.count || 0,
|
|
|
+ }
|
|
|
+
|
|
|
+ return c.json(success(data))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('List users error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '获取用户列表失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取用户详情
|
|
|
+ * GET /api/users/:id
|
|
|
+ */
|
|
|
+user.get('/:id', async (c) => {
|
|
|
+ try {
|
|
|
+ const userId = c.req.param('id')
|
|
|
+
|
|
|
+ const userData = await c.env.DB
|
|
|
+ .prepare('SELECT id, username, email, role, status, last_login, created_at, updated_at FROM users WHERE id = ?')
|
|
|
+ .bind(userId)
|
|
|
+ .first<User>()
|
|
|
+
|
|
|
+ if (!userData) {
|
|
|
+ return c.json(error('用户不存在', 404), 404)
|
|
|
+ }
|
|
|
+
|
|
|
+ return c.json(success(userData))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Get user error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '获取用户详情失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 创建用户
|
|
|
+ * POST /api/users
|
|
|
+ */
|
|
|
+user.post('/', async (c) => {
|
|
|
+ try {
|
|
|
+ const body = await c.req.json<{
|
|
|
+ username: string
|
|
|
+ password: string
|
|
|
+ email?: string
|
|
|
+ role?: 'admin' | 'operator' | 'viewer'
|
|
|
+ }>()
|
|
|
+
|
|
|
+ if (!body.username || !body.password) {
|
|
|
+ return c.json(error('用户名和密码不能为空', 400), 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查用户名是否已存在
|
|
|
+ const existing = await c.env.DB
|
|
|
+ .prepare('SELECT id FROM users WHERE username = ?')
|
|
|
+ .bind(body.username)
|
|
|
+ .first()
|
|
|
+
|
|
|
+ if (existing) {
|
|
|
+ return c.json(error('用户名已存在', 400), 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ const userId = generateId()
|
|
|
+ const passwordHash = await hashPassword(body.password)
|
|
|
+ const now = Math.floor(Date.now() / 1000)
|
|
|
+
|
|
|
+ await c.env.DB
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO users (id, username, email, password_hash, role, status, created_at, updated_at)
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
+ `)
|
|
|
+ .bind(
|
|
|
+ userId,
|
|
|
+ body.username,
|
|
|
+ body.email || null,
|
|
|
+ passwordHash,
|
|
|
+ body.role || 'viewer',
|
|
|
+ 'active',
|
|
|
+ now,
|
|
|
+ now
|
|
|
+ )
|
|
|
+ .run()
|
|
|
+
|
|
|
+ // 记录操作日志
|
|
|
+ const adminUser = c.get('user')
|
|
|
+ await c.env.DB
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO audit_logs (id, user_id, action, resource, resource_id, details, created_at)
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
|
+ `)
|
|
|
+ .bind(generateId(), adminUser.sub, 'create', 'user', userId, JSON.stringify({ username: body.username }), now)
|
|
|
+ .run()
|
|
|
+
|
|
|
+ const newUser = await c.env.DB
|
|
|
+ .prepare('SELECT id, username, email, role, status, created_at, updated_at FROM users WHERE id = ?')
|
|
|
+ .bind(userId)
|
|
|
+ .first<User>()
|
|
|
+
|
|
|
+ return c.json(success(newUser, '用户创建成功'))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Create user error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '创建用户失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 更新用户
|
|
|
+ * PUT /api/users/:id
|
|
|
+ */
|
|
|
+user.put('/:id', async (c) => {
|
|
|
+ try {
|
|
|
+ const userId = c.req.param('id')
|
|
|
+ const body = await c.req.json<{
|
|
|
+ email?: string
|
|
|
+ role?: 'admin' | 'operator' | 'viewer'
|
|
|
+ status?: 'active' | 'disabled'
|
|
|
+ password?: string
|
|
|
+ }>()
|
|
|
+
|
|
|
+ const existing = await c.env.DB
|
|
|
+ .prepare('SELECT id FROM users WHERE id = ?')
|
|
|
+ .bind(userId)
|
|
|
+ .first()
|
|
|
+
|
|
|
+ if (!existing) {
|
|
|
+ return c.json(error('用户不存在', 404), 404)
|
|
|
+ }
|
|
|
+
|
|
|
+ const updates: string[] = []
|
|
|
+ const params: (string | number)[] = []
|
|
|
+
|
|
|
+ if (body.email !== undefined) {
|
|
|
+ updates.push('email = ?')
|
|
|
+ params.push(body.email)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (body.role) {
|
|
|
+ updates.push('role = ?')
|
|
|
+ params.push(body.role)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (body.status) {
|
|
|
+ updates.push('status = ?')
|
|
|
+ params.push(body.status)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (body.password) {
|
|
|
+ const passwordHash = await hashPassword(body.password)
|
|
|
+ updates.push('password_hash = ?')
|
|
|
+ params.push(passwordHash)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (updates.length === 0) {
|
|
|
+ return c.json(error('没有要更新的字段', 400), 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ const now = Math.floor(Date.now() / 1000)
|
|
|
+ updates.push('updated_at = ?')
|
|
|
+ params.push(now)
|
|
|
+ params.push(userId)
|
|
|
+
|
|
|
+ await c.env.DB
|
|
|
+ .prepare(`UPDATE users SET ${updates.join(', ')} WHERE id = ?`)
|
|
|
+ .bind(...params)
|
|
|
+ .run()
|
|
|
+
|
|
|
+ // 记录操作日志
|
|
|
+ const adminUser = c.get('user')
|
|
|
+ await c.env.DB
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO audit_logs (id, user_id, action, resource, resource_id, details, created_at)
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
|
+ `)
|
|
|
+ .bind(generateId(), adminUser.sub, 'update', 'user', userId, JSON.stringify(body), now)
|
|
|
+ .run()
|
|
|
+
|
|
|
+ const updatedUser = await c.env.DB
|
|
|
+ .prepare('SELECT id, username, email, role, status, created_at, updated_at FROM users WHERE id = ?')
|
|
|
+ .bind(userId)
|
|
|
+ .first<User>()
|
|
|
+
|
|
|
+ return c.json(success(updatedUser, '用户更新成功'))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Update user error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '更新用户失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 删除用户
|
|
|
+ * DELETE /api/users/:id
|
|
|
+ */
|
|
|
+user.delete('/:id', async (c) => {
|
|
|
+ try {
|
|
|
+ const userId = c.req.param('id')
|
|
|
+ const adminUser = c.get('user')
|
|
|
+
|
|
|
+ // 不能删除自己
|
|
|
+ if (userId === adminUser.sub) {
|
|
|
+ return c.json(error('不能删除自己', 400), 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ const existing = await c.env.DB
|
|
|
+ .prepare('SELECT id, username FROM users WHERE id = ?')
|
|
|
+ .bind(userId)
|
|
|
+ .first<User>()
|
|
|
+
|
|
|
+ if (!existing) {
|
|
|
+ return c.json(error('用户不存在', 404), 404)
|
|
|
+ }
|
|
|
+
|
|
|
+ await c.env.DB
|
|
|
+ .prepare('DELETE FROM users WHERE id = ?')
|
|
|
+ .bind(userId)
|
|
|
+ .run()
|
|
|
+
|
|
|
+ // 记录操作日志
|
|
|
+ const now = Math.floor(Date.now() / 1000)
|
|
|
+ await c.env.DB
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO audit_logs (id, user_id, action, resource, resource_id, details, created_at)
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
|
+ `)
|
|
|
+ .bind(generateId(), adminUser.sub, 'delete', 'user', userId, JSON.stringify({ username: existing.username }), now)
|
|
|
+ .run()
|
|
|
+
|
|
|
+ return c.json(success(null, '用户删除成功'))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Delete user error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '删除用户失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// ==================== 权限管理 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取用户权限列表
|
|
|
+ * GET /api/users/:id/permissions
|
|
|
+ */
|
|
|
+user.get('/:id/permissions', async (c) => {
|
|
|
+ try {
|
|
|
+ const userId = c.req.param('id')
|
|
|
+
|
|
|
+ const permissions = await c.env.DB
|
|
|
+ .prepare(`
|
|
|
+ SELECT up.*, c.name as camera_name
|
|
|
+ FROM user_permissions up
|
|
|
+ LEFT JOIN cameras c ON up.camera_id = c.id
|
|
|
+ WHERE up.user_id = ?
|
|
|
+ ORDER BY up.granted_at DESC
|
|
|
+ `)
|
|
|
+ .bind(userId)
|
|
|
+ .all<UserPermission & { camera_name: string }>()
|
|
|
+
|
|
|
+ return c.json(success(permissions.results || []))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('List permissions error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '获取权限列表失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 添加用户权限
|
|
|
+ * POST /api/users/:id/permissions
|
|
|
+ */
|
|
|
+user.post('/:id/permissions', async (c) => {
|
|
|
+ try {
|
|
|
+ const userId = c.req.param('id')
|
|
|
+ const adminUser = c.get('user')
|
|
|
+ const body = await c.req.json<{
|
|
|
+ camera_id: string
|
|
|
+ permission: 'view' | 'control' | 'manage'
|
|
|
+ }>()
|
|
|
+
|
|
|
+ if (!body.camera_id || !body.permission) {
|
|
|
+ return c.json(error('camera_id 和 permission 不能为空', 400), 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查用户是否存在
|
|
|
+ const userExists = await c.env.DB
|
|
|
+ .prepare('SELECT id FROM users WHERE id = ?')
|
|
|
+ .bind(userId)
|
|
|
+ .first()
|
|
|
+
|
|
|
+ if (!userExists) {
|
|
|
+ return c.json(error('用户不存在', 404), 404)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查摄像头是否存在
|
|
|
+ const cameraExists = await c.env.DB
|
|
|
+ .prepare('SELECT id FROM cameras WHERE id = ?')
|
|
|
+ .bind(body.camera_id)
|
|
|
+ .first()
|
|
|
+
|
|
|
+ if (!cameraExists) {
|
|
|
+ return c.json(error('摄像头不存在', 404), 404)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否已存在权限
|
|
|
+ const existing = await c.env.DB
|
|
|
+ .prepare('SELECT id FROM user_permissions WHERE user_id = ? AND camera_id = ?')
|
|
|
+ .bind(userId, body.camera_id)
|
|
|
+ .first()
|
|
|
+
|
|
|
+ const now = Math.floor(Date.now() / 1000)
|
|
|
+
|
|
|
+ if (existing) {
|
|
|
+ // 更新现有权限
|
|
|
+ await c.env.DB
|
|
|
+ .prepare('UPDATE user_permissions SET permission = ?, granted_at = ?, granted_by = ? WHERE user_id = ? AND camera_id = ?')
|
|
|
+ .bind(body.permission, now, adminUser.sub, userId, body.camera_id)
|
|
|
+ .run()
|
|
|
+ } else {
|
|
|
+ // 创建新权限
|
|
|
+ await c.env.DB
|
|
|
+ .prepare(`
|
|
|
+ INSERT INTO user_permissions (id, user_id, camera_id, permission, granted_at, granted_by)
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?)
|
|
|
+ `)
|
|
|
+ .bind(generateId(), userId, body.camera_id, body.permission, now, adminUser.sub)
|
|
|
+ .run()
|
|
|
+ }
|
|
|
+
|
|
|
+ return c.json(success(null, '权限设置成功'))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Add permission error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '设置权限失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 删除用户权限
|
|
|
+ * DELETE /api/users/:id/permissions/:permissionId
|
|
|
+ */
|
|
|
+user.delete('/:id/permissions/:permissionId', async (c) => {
|
|
|
+ try {
|
|
|
+ const userId = c.req.param('id')
|
|
|
+ const permissionId = c.req.param('permissionId')
|
|
|
+
|
|
|
+ const existing = await c.env.DB
|
|
|
+ .prepare('SELECT id FROM user_permissions WHERE id = ? AND user_id = ?')
|
|
|
+ .bind(permissionId, userId)
|
|
|
+ .first()
|
|
|
+
|
|
|
+ if (!existing) {
|
|
|
+ return c.json(error('权限不存在', 404), 404)
|
|
|
+ }
|
|
|
+
|
|
|
+ await c.env.DB
|
|
|
+ .prepare('DELETE FROM user_permissions WHERE id = ?')
|
|
|
+ .bind(permissionId)
|
|
|
+ .run()
|
|
|
+
|
|
|
+ return c.json(success(null, '权限删除成功'))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Delete permission error:', err)
|
|
|
+ return c.json(error(err instanceof Error ? err.message : '删除权限失败'))
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+export default user
|