Files
BYW-VoiceHub/server/api/admin/api-keys/index.post.ts
T

162 lines
4.4 KiB
TypeScript

import { apiKeyPermissions, apiKeys, db } from '~/drizzle/db'
import { z } from 'zod'
import { getBeijingTime } from '~/utils/timeUtils'
import { apiPermissionSchema } from './permissions'
import { generateApiKey, hashApiKey } from '~~/server/utils/apiKeyUtils'
/**
* 创建API Key
* POST /api/admin/api-keys
*/
// 请求体验证schema
const createApiKeySchema = z.object({
name: z.string().min(1, 'API Key名称不能为空').max(100, 'API Key名称不能超过100个字符'),
description: z
.union([z.string().max(500, '描述不能超过500个字符'), z.null(), z.undefined()])
.optional(),
expiresAt: z.union([z.string(), z.null(), z.undefined()]).optional(),
permissions: z.array(apiPermissionSchema).min(1, '至少需要选择一个权限')
})
export default defineEventHandler(async (event) => {
// 检查用户权限 - 只有超级管理员可以管理 API Key
const user = event.context.user
if (!user || user.role !== 'SUPER_ADMIN') {
throw createError({
statusCode: 403,
message: '只有超级管理员可以管理 API Key'
})
}
try {
// 验证请求体
const body = await readBody(event)
const validatedData = createApiKeySchema.parse(body)
// 生成API Key
const apiKey = generateApiKey()
const keyPrefix = apiKey.substring(0, 10) // vhub_xxxxx
const keyHash = await hashApiKey(apiKey)
// 处理过期时间
let expiresAt: Date | null = null
if (
validatedData.expiresAt &&
typeof validatedData.expiresAt === 'string' &&
validatedData.expiresAt.trim() !== ''
) {
try {
// 处理预设选项格式 (3d, 7d, 30d, 60d, 90d)
if (validatedData.expiresAt.match(/^\d+d$/)) {
const days = parseInt(validatedData.expiresAt.replace('d', ''))
expiresAt = getBeijingTime()
expiresAt.setDate(expiresAt.getDate() + days)
} else {
// 处理传统的日期时间格式(向后兼容)
expiresAt = new Date(validatedData.expiresAt)
// 验证日期是否有效
if (isNaN(expiresAt.getTime())) {
throw createError({
statusCode: 400,
message: '无效的过期时间格式'
})
}
// 验证过期时间不能是过去的时间
if (expiresAt <= getBeijingTime()) {
throw createError({
statusCode: 400,
message: '过期时间不能是过去的时间'
})
}
}
} catch (error: any) {
if (error.statusCode) {
throw error
}
throw createError({
statusCode: 400,
message: '无效的过期时间格式'
})
}
}
// 开始事务
const result = await db.transaction(async (tx) => {
// 插入API Key记录
const apiKeyResult = await tx
.insert(apiKeys)
.values({
name: validatedData.name,
description: validatedData.description || null,
keyPrefix,
keyHash,
isActive: true,
expiresAt,
usageCount: 0,
createdByUserId: user.id
})
.returning({ id: apiKeys.id })
const createdApiKey = apiKeyResult[0]
if (!createdApiKey) {
throw createError({
statusCode: 500,
message: '创建 API Key 失败'
})
}
const apiKeyId = createdApiKey.id
// 插入权限记录
const permissionValues = validatedData.permissions.map((permission) => ({
apiKeyId,
permission
}))
await tx.insert(apiKeyPermissions).values(permissionValues)
return {
id: apiKeyId,
apiKey,
name: validatedData.name,
description: validatedData.description,
keyPrefix,
isActive: true,
expiresAt,
permissions: validatedData.permissions,
usageCount: 0,
createdBy: user.id,
creatorName: user.name
}
})
return {
success: true,
message: 'API Key创建成功',
data: result
}
} catch (error: any) {
if (error.statusCode) {
throw error
}
// 处理 Zod 验证错误
if (error.name === 'ZodError') {
throw createError({
statusCode: 400,
message: `请求参数验证失败:${error.errors.map((e: any) => e.message).join(', ')}`
})
}
throw createError({
statusCode: 500,
message: `创建 API Key 失败:${error.message}`
})
}
})