feat: align with upstream 1.5.9.9
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notifications } from '~/drizzle/schema'
|
||||
import { SERVER_ERROR_CODES } from '~~/server/config/constants'
|
||||
import { createApiError } from '~~/server/utils/apiError'
|
||||
import {
|
||||
createNotificationUserDeleteUpdate,
|
||||
shouldRetainNotificationHistory
|
||||
} from '~~/server/utils/important-notification-policy'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) {
|
||||
throw createApiError(401, SERVER_ERROR_CODES.NOTIFICATION_AUTH_REQUIRED, '需要登录才能删除通知')
|
||||
}
|
||||
|
||||
const id = Number.parseInt(event.context.params?.id || '0', 10)
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw createApiError(400, SERVER_ERROR_CODES.NOTIFICATION_ID_INVALID, '无效的通知 ID')
|
||||
}
|
||||
|
||||
try {
|
||||
const notification = await db
|
||||
.select({
|
||||
id: notifications.id,
|
||||
type: notifications.type,
|
||||
source: notifications.source
|
||||
})
|
||||
.from(notifications)
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.id, id),
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false)
|
||||
)
|
||||
)
|
||||
.then((result) => result[0])
|
||||
|
||||
if (!notification) {
|
||||
throw createApiError(404, SERVER_ERROR_CODES.NOTIFICATION_NOT_FOUND, '通知不存在')
|
||||
}
|
||||
|
||||
if (shouldRetainNotificationHistory(notification.type, notification.source)) {
|
||||
const result = await db
|
||||
.update(notifications)
|
||||
.set(createNotificationUserDeleteUpdate())
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.id, id),
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false)
|
||||
)
|
||||
)
|
||||
.returning({ id: notifications.id })
|
||||
if (result.length === 0) {
|
||||
throw createApiError(404, SERVER_ERROR_CODES.NOTIFICATION_NOT_FOUND, '通知不存在')
|
||||
}
|
||||
} else {
|
||||
const result = await db
|
||||
.delete(notifications)
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.id, id),
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false)
|
||||
)
|
||||
)
|
||||
.returning({ id: notifications.id })
|
||||
if (result.length === 0) {
|
||||
throw createApiError(404, SERVER_ERROR_CODES.NOTIFICATION_NOT_FOUND, '通知不存在')
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
if (typeof error === 'object' && error && 'statusCode' in error) throw error
|
||||
console.error('删除通知失败:', error)
|
||||
throw createApiError(500, SERVER_ERROR_CODES.NOTIFICATION_DELETE_FAILED, '删除通知失败')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notifications } from '~/drizzle/schema'
|
||||
import { SERVER_ERROR_CODES } from '~~/server/config/constants'
|
||||
import { createApiError } from '~~/server/utils/apiError'
|
||||
import { createNotificationReadUpdate, serializeNotificationSender } from '~~/server/utils/important-notification-policy'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
|
||||
if (!user) {
|
||||
throw createApiError(401, SERVER_ERROR_CODES.NOTIFICATION_AUTH_REQUIRED, '需要登录才能标记通知')
|
||||
}
|
||||
|
||||
const id = Number.parseInt(event.context.params?.id || '0', 10)
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
throw createApiError(400, SERVER_ERROR_CODES.NOTIFICATION_ID_INVALID, '无效的通知 ID')
|
||||
}
|
||||
|
||||
let notification
|
||||
try {
|
||||
notification = await db
|
||||
.select({
|
||||
id: notifications.id,
|
||||
userId: notifications.userId,
|
||||
userDeleted: notifications.userDeleted
|
||||
})
|
||||
.from(notifications)
|
||||
.where(eq(notifications.id, id))
|
||||
.limit(1)
|
||||
.then((result) => result[0])
|
||||
} catch (error) {
|
||||
console.error('查询待标记通知失败:', error)
|
||||
throw createApiError(500, SERVER_ERROR_CODES.NOTIFICATION_MARK_READ_FAILED, '标记通知失败')
|
||||
}
|
||||
|
||||
if (!notification || notification.userDeleted) {
|
||||
throw createApiError(404, SERVER_ERROR_CODES.NOTIFICATION_NOT_FOUND, '通知不存在')
|
||||
}
|
||||
|
||||
if (notification.userId !== user.id) {
|
||||
throw createApiError(403, SERVER_ERROR_CODES.NOTIFICATION_FORBIDDEN, '无权标记此通知')
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedNotification = await db
|
||||
.update(notifications)
|
||||
.set(createNotificationReadUpdate())
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.id, id),
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false)
|
||||
)
|
||||
)
|
||||
.returning()
|
||||
.then((result) => result[0])
|
||||
|
||||
// 与 /api/notifications 列表接口保持同一序列化契约,发送人原始列折叠为 sender 对象
|
||||
if (!updatedNotification) {
|
||||
throw createApiError(404, SERVER_ERROR_CODES.NOTIFICATION_NOT_FOUND, '通知不存在')
|
||||
}
|
||||
const { senderId, senderName, senderUsername, ...data } = updatedNotification
|
||||
return {
|
||||
...data,
|
||||
sender: serializeNotificationSender({ senderId, senderName, senderUsername })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('标记通知失败:', error)
|
||||
if ((error as { statusCode?: number })?.statusCode === 404) throw error
|
||||
throw createApiError(500, SERVER_ERROR_CODES.NOTIFICATION_MARK_READ_FAILED, '标记通知失败')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,58 @@
|
||||
import { and, eq, ne, or } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notifications } from '~/drizzle/schema'
|
||||
import { SERVER_ERROR_CODES } from '~~/server/config/constants'
|
||||
import { createApiError } from '~~/server/utils/apiError'
|
||||
import {
|
||||
createNotificationUserDeleteUpdate,
|
||||
NOTIFICATION_SOURCES
|
||||
} from '~~/server/utils/important-notification-policy'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) {
|
||||
throw createApiError(401, SERVER_ERROR_CODES.NOTIFICATION_AUTH_REQUIRED, '需要登录才能清空通知')
|
||||
}
|
||||
|
||||
try {
|
||||
const visibleUserCondition = and(
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false)
|
||||
)!
|
||||
// 同一事务内先软删后硬删,避免部分失败时状态不一致
|
||||
const { retainedNotifications, deletedNotifications } = await db.transaction(async (tx) => {
|
||||
const retained = await tx
|
||||
.update(notifications)
|
||||
.set(createNotificationUserDeleteUpdate())
|
||||
.where(
|
||||
and(
|
||||
visibleUserCondition,
|
||||
eq(notifications.type, 'SYSTEM_NOTICE'),
|
||||
eq(notifications.source, NOTIFICATION_SOURCES.ADMIN_MANUAL)
|
||||
)
|
||||
)
|
||||
.returning({ id: notifications.id })
|
||||
const deleted = await tx
|
||||
.delete(notifications)
|
||||
.where(
|
||||
and(
|
||||
visibleUserCondition,
|
||||
or(
|
||||
ne(notifications.type, 'SYSTEM_NOTICE'),
|
||||
ne(notifications.source, NOTIFICATION_SOURCES.ADMIN_MANUAL)
|
||||
)
|
||||
)
|
||||
)
|
||||
.returning({ id: notifications.id })
|
||||
return { retainedNotifications: retained, deletedNotifications: deleted }
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
count: retainedNotifications.length + deletedNotifications.length
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('清空通知失败:', error)
|
||||
throw createApiError(500, SERVER_ERROR_CODES.NOTIFICATION_CLEAR_FAILED, '清空通知失败')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { and, asc, eq } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notifications } from '~/drizzle/schema'
|
||||
import { SERVER_ERROR_CODES } from '~~/server/config/constants'
|
||||
import { createApiError } from '~~/server/utils/apiError'
|
||||
import {
|
||||
serializeNotificationSender,
|
||||
shouldCheckImportantNotification
|
||||
} from '~~/server/utils/important-notification-policy'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
|
||||
if (!shouldCheckImportantNotification(Boolean(user), user?.id, user?.requirePasswordChange === true)) {
|
||||
throw createApiError(
|
||||
401,
|
||||
SERVER_ERROR_CODES.NOTIFICATION_AUTH_REQUIRED,
|
||||
'需要登录才能获取重要通知'
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await db
|
||||
.select({
|
||||
id: notifications.id,
|
||||
senderId: notifications.senderId,
|
||||
senderName: notifications.senderName,
|
||||
senderUsername: notifications.senderUsername,
|
||||
title: notifications.title,
|
||||
message: notifications.message,
|
||||
important: notifications.important,
|
||||
read: notifications.read,
|
||||
createdAt: notifications.createdAt
|
||||
})
|
||||
.from(notifications)
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false),
|
||||
eq(notifications.important, true),
|
||||
eq(notifications.read, false)
|
||||
)
|
||||
)
|
||||
.orderBy(asc(notifications.createdAt), asc(notifications.id))
|
||||
.limit(1)
|
||||
|
||||
const notification = result[0]
|
||||
if (!notification) return { notification: null }
|
||||
|
||||
const { senderId, senderName, senderUsername, ...data } = notification
|
||||
return {
|
||||
notification: {
|
||||
...data,
|
||||
sender: serializeNotificationSender({ senderId, senderName, senderUsername })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取重要通知失败:', error)
|
||||
throw createApiError(500, SERVER_ERROR_CODES.NOTIFICATION_FETCH_FAILED, '获取重要通知失败')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,160 @@
|
||||
import { and, count, desc, eq, ilike, or } from 'drizzle-orm'
|
||||
import { defineEventHandler, getQuery } from 'h3'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notifications, songCollaborators, songs } from '~/drizzle/schema'
|
||||
import { SERVER_ERROR_CODES } from '~~/server/config/constants'
|
||||
import { createApiError } from '~~/server/utils/apiError'
|
||||
import { serializeNotificationSender } from '~~/server/utils/important-notification-policy'
|
||||
|
||||
const serializeNotification = <T extends {
|
||||
senderId: number | null
|
||||
senderName: string | null
|
||||
senderUsername: string | null
|
||||
}>(notification: T) => {
|
||||
const { senderId, senderName, senderUsername, ...data } = notification
|
||||
return {
|
||||
...data,
|
||||
sender: serializeNotificationSender({ senderId, senderName, senderUsername })
|
||||
}
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户认证
|
||||
const user = event.context.user
|
||||
|
||||
if (!user) {
|
||||
throw createApiError(401, SERVER_ERROR_CODES.NOTIFICATION_AUTH_REQUIRED, '需要登录才能获取通知')
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取查询参数
|
||||
const query = getQuery(event)
|
||||
const page = Math.max(1, parseInt(query.page as string) || 1)
|
||||
const limit = Math.min(50, Math.max(1, parseInt(query.limit as string) || 10))
|
||||
const offset = (page - 1) * limit
|
||||
const unreadOnly = query.filter === 'unread'
|
||||
const search = typeof query.search === 'string' ? query.search.trim().slice(0, 100) : ''
|
||||
// Drizzle ORM 的 ilike 已对参数做参数化绑定,无需手动转义 % 和 _
|
||||
const searchCondition = search
|
||||
? or(
|
||||
ilike(notifications.title, `%${search}%`),
|
||||
ilike(notifications.message, `%${search}%`)
|
||||
)
|
||||
: undefined
|
||||
const notificationCondition = and(
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false),
|
||||
unreadOnly ? eq(notifications.read, false) : undefined,
|
||||
searchCondition
|
||||
)
|
||||
|
||||
// 获取总通知数量
|
||||
const totalCountResult = await db
|
||||
.select({ count: count() })
|
||||
.from(notifications)
|
||||
.where(notificationCondition)
|
||||
const totalCount = totalCountResult[0]?.count || 0
|
||||
const totalPages = Math.ceil(totalCount / limit)
|
||||
|
||||
// 获取分页通知数据
|
||||
const userNotifications = await db
|
||||
.select()
|
||||
.from(notifications)
|
||||
.where(notificationCondition)
|
||||
.orderBy(desc(notifications.createdAt))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
|
||||
// 丰富通知数据,特别是对于联合投稿邀请
|
||||
const enrichedNotifications = await Promise.all(
|
||||
userNotifications.map(async (notification) => {
|
||||
const serializedNotification = serializeNotification(notification)
|
||||
if (notification.type === 'COLLABORATION_INVITE' && notification.songId) {
|
||||
// 1. 检查歌曲是否存在
|
||||
const songExists = await db
|
||||
.select({ id: songs.id })
|
||||
.from(songs)
|
||||
.where(eq(songs.id, notification.songId))
|
||||
.limit(1)
|
||||
.then((res) => res.length > 0)
|
||||
|
||||
if (!songExists) {
|
||||
return {
|
||||
...serializedNotification,
|
||||
handled: true,
|
||||
status: 'INVALID',
|
||||
isValid: false,
|
||||
message: notification.message // 保持原消息,前端会显示已失效
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查询对应的邀请状态
|
||||
const collab = await db
|
||||
.select()
|
||||
.from(songCollaborators)
|
||||
.where(
|
||||
and(
|
||||
eq(songCollaborators.songId, notification.songId),
|
||||
eq(songCollaborators.userId, user.id)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (collab.length > 0) {
|
||||
return {
|
||||
...serializedNotification,
|
||||
handled: collab[0].status !== 'PENDING',
|
||||
status: collab[0].status,
|
||||
isValid: true,
|
||||
repliedAt: collab[0].updatedAt || collab[0].createdAt // 假设更新时间为回复时间
|
||||
}
|
||||
} else {
|
||||
// 如果找不到邀请记录,说明可能已经被撤回或删除
|
||||
return {
|
||||
...serializedNotification,
|
||||
handled: true,
|
||||
status: 'INVALID',
|
||||
isValid: false,
|
||||
message: notification.message
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
...serializedNotification,
|
||||
handled: false, // 默认未处理,或者不适用
|
||||
isValid: true
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 计算未读通知数量
|
||||
const unreadCountResult = await db
|
||||
.select({ count: count() })
|
||||
.from(notifications)
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false),
|
||||
eq(notifications.read, false)
|
||||
)
|
||||
)
|
||||
|
||||
const unreadCount = unreadCountResult[0]?.count || 0
|
||||
|
||||
return {
|
||||
notifications: enrichedNotifications,
|
||||
unreadCount,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalCount,
|
||||
limit,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPrevPage: page > 1
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取通知失败:', err)
|
||||
throw createApiError(500, SERVER_ERROR_CODES.NOTIFICATION_FETCH_FAILED, '获取通知失败')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import { createError, defineEventHandler, readBody } from 'h3'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
const { nickname, verificationCode } = body
|
||||
|
||||
if (!nickname || !verificationCode) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '昵称和验证码不能为空'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证昵称格式
|
||||
if (nickname.includes('/')) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '昵称不能包含斜杠'
|
||||
})
|
||||
}
|
||||
|
||||
// 发送验证码到 MeoW
|
||||
const message = `VoiceHub 账号绑定验证码:${verificationCode}`
|
||||
const title = 'VoiceHub 账号绑定'
|
||||
|
||||
// 使用 GET 请求发送验证码
|
||||
const meowUrl = `https://api.chuckfang.com/${encodeURIComponent(nickname)}/${encodeURIComponent(title)}/${encodeURIComponent(message)}`
|
||||
|
||||
const response = await fetch(meowUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': 'VoiceHub/1.0'
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: '发送验证码失败,请检查昵称是否正确'
|
||||
})
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.status !== 200) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: result.message || '发送验证码失败'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '验证码发送成功'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送 MeoW 验证码失败:', error)
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: '发送验证码失败,请稍后重试'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,90 @@
|
||||
import { createError, defineEventHandler } from 'h3'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { users } from '~/drizzle/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// 使用认证中间件提供的用户信息
|
||||
const user = event.context.user
|
||||
if (!user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: '未授权访问'
|
||||
})
|
||||
}
|
||||
|
||||
const userId = user.id
|
||||
|
||||
// 获取用户的 MeoW 绑定信息
|
||||
const userDataResult = await db
|
||||
.select({
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
meowNickname: users.meowNickname
|
||||
})
|
||||
.from(users)
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1)
|
||||
const userData = userDataResult[0]
|
||||
|
||||
if (!userData) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
if (!userData.meowNickname) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '尚未绑定 MeoW 账号'
|
||||
})
|
||||
}
|
||||
|
||||
// 发送测试通知
|
||||
const message = `这是来自 VoiceHub 的测试通知!您的账号 ${userData.username} 已成功绑定 MeoW。`
|
||||
const title = 'VoiceHub 测试通知'
|
||||
|
||||
const meowUrl = `https://api.chuckfang.com/${encodeURIComponent(userData.meowNickname)}/${encodeURIComponent(title)}/${encodeURIComponent(message)}`
|
||||
|
||||
const response = await fetch(meowUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': 'VoiceHub/1.0'
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: '发送测试通知失败'
|
||||
})
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.status !== 200) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: result.message || '发送测试通知失败'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '测试通知发送成功'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发送 MeoW 测试通知失败:', error)
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: '发送测试通知失败,请稍后重试'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notifications } from '~/drizzle/schema'
|
||||
import { SERVER_ERROR_CODES } from '~~/server/config/constants'
|
||||
import { createApiError } from '~~/server/utils/apiError'
|
||||
import { createNotificationReadUpdate } from '~~/server/utils/important-notification-policy'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = event.context.user
|
||||
if (!user) {
|
||||
throw createApiError(401, SERVER_ERROR_CODES.NOTIFICATION_AUTH_REQUIRED, '需要登录才能标记通知')
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedNotifications = await db
|
||||
.update(notifications)
|
||||
.set(createNotificationReadUpdate())
|
||||
.where(
|
||||
and(
|
||||
eq(notifications.userId, user.id),
|
||||
eq(notifications.userDeleted, false),
|
||||
eq(notifications.read, false)
|
||||
)
|
||||
)
|
||||
.returning({ id: notifications.id })
|
||||
|
||||
return {
|
||||
success: true,
|
||||
count: updatedNotifications.length
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('标记所有通知失败:', error)
|
||||
throw createApiError(
|
||||
500,
|
||||
SERVER_ERROR_CODES.NOTIFICATION_MARK_READ_FAILED,
|
||||
'标记所有通知失败'
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,113 @@
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notificationSettings } from '~/drizzle/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import type { NotificationSettings } from '~/types'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户认证
|
||||
const user = event.context.user
|
||||
|
||||
if (!user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: '需要登录才能更新通知设置'
|
||||
})
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
|
||||
// 验证请求体
|
||||
if (typeof body !== 'object' || body === null) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: '无效的请求数据'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户当前的通知设置
|
||||
const existingSettingsResult = await db
|
||||
.select()
|
||||
.from(notificationSettings)
|
||||
.where(eq(notificationSettings.userId, user.id))
|
||||
.limit(1)
|
||||
let dbSettings: any = existingSettingsResult[0]
|
||||
|
||||
if (dbSettings) {
|
||||
// 更新现有设置
|
||||
const updatedSettingsResult = await db
|
||||
.update(notificationSettings)
|
||||
.set({
|
||||
enabled: body.systemNotify !== undefined ? body.systemNotify : dbSettings.enabled,
|
||||
songRequestEnabled:
|
||||
body.songSelectedNotify !== undefined
|
||||
? body.songSelectedNotify
|
||||
: dbSettings.songRequestEnabled,
|
||||
songPlayedEnabled:
|
||||
body.songPlayedNotify !== undefined
|
||||
? body.songPlayedNotify
|
||||
: dbSettings.songPlayedEnabled,
|
||||
songVotedEnabled:
|
||||
body.songVotedNotify !== undefined ? body.songVotedNotify : dbSettings.songVotedEnabled,
|
||||
songVotedThreshold:
|
||||
body.songVotedThreshold !== undefined
|
||||
? Math.max(1, Math.min(10, body.songVotedThreshold))
|
||||
: dbSettings.songVotedThreshold,
|
||||
refreshInterval:
|
||||
body.refreshInterval !== undefined
|
||||
? Math.max(10, Math.min(300, body.refreshInterval))
|
||||
: dbSettings.refreshInterval
|
||||
// 邮件通知总开关移除
|
||||
})
|
||||
.where(eq(notificationSettings.userId, user.id))
|
||||
.returning()
|
||||
dbSettings = updatedSettingsResult[0]
|
||||
} else {
|
||||
// 创建新设置
|
||||
const newSettingsResult = await db
|
||||
.insert(notificationSettings)
|
||||
.values({
|
||||
userId: user.id,
|
||||
enabled: body.systemNotify !== undefined ? body.systemNotify : true,
|
||||
songRequestEnabled:
|
||||
body.songSelectedNotify !== undefined ? body.songSelectedNotify : true,
|
||||
songPlayedEnabled: body.songPlayedNotify !== undefined ? body.songPlayedNotify : true,
|
||||
songVotedEnabled: body.songVotedNotify !== undefined ? body.songVotedNotify : true,
|
||||
songVotedThreshold:
|
||||
body.songVotedThreshold !== undefined
|
||||
? Math.max(1, Math.min(10, body.songVotedThreshold))
|
||||
: 1,
|
||||
refreshInterval:
|
||||
body.refreshInterval !== undefined
|
||||
? Math.max(10, Math.min(300, body.refreshInterval))
|
||||
: 60
|
||||
})
|
||||
.returning()
|
||||
dbSettings = newSettingsResult[0]
|
||||
}
|
||||
|
||||
// 转换为前端期望的格式
|
||||
const settings: NotificationSettings = {
|
||||
id: dbSettings.id,
|
||||
userId: dbSettings.userId,
|
||||
songSelectedNotify: dbSettings.songRequestEnabled,
|
||||
songPlayedNotify: dbSettings.songPlayedEnabled,
|
||||
songVotedNotify: dbSettings.songVotedEnabled,
|
||||
systemNotify: dbSettings.enabled,
|
||||
refreshInterval: dbSettings.refreshInterval,
|
||||
songVotedThreshold: dbSettings.songVotedThreshold
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: settings,
|
||||
message: '通知设置保存成功'
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('更新通知设置失败:', err)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: '更新通知设置失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { createError, defineEventHandler } from 'h3'
|
||||
import { db } from '~/drizzle/db'
|
||||
import { notificationSettings, users } from '~/drizzle/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// 检查用户认证
|
||||
const user = event.context.user
|
||||
|
||||
if (!user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: '需要登录才能获取通知设置'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户信息(包含meowNickname和邮箱信息)
|
||||
const userInfoResult = await db
|
||||
.select({
|
||||
meowNickname: users.meowNickname,
|
||||
email: users.email,
|
||||
emailVerified: users.emailVerified
|
||||
})
|
||||
.from(users)
|
||||
.where(eq(users.id, user.id))
|
||||
.limit(1)
|
||||
|
||||
const userInfo = userInfoResult[0]
|
||||
|
||||
// 获取用户的通知设置,如果不存在则创建默认设置
|
||||
const dbSettingsResult = await db
|
||||
.select()
|
||||
.from(notificationSettings)
|
||||
.where(eq(notificationSettings.userId, user.id))
|
||||
.limit(1)
|
||||
|
||||
let dbSettings: any = dbSettingsResult[0]
|
||||
|
||||
if (!dbSettings) {
|
||||
// 创建默认设置
|
||||
const insertResult = await db
|
||||
.insert(notificationSettings)
|
||||
.values({
|
||||
userId: user.id,
|
||||
enabled: true,
|
||||
songRequestEnabled: true,
|
||||
songVotedEnabled: true,
|
||||
songPlayedEnabled: true,
|
||||
refreshInterval: 60,
|
||||
songVotedThreshold: 1
|
||||
})
|
||||
.returning()
|
||||
|
||||
dbSettings = insertResult[0]
|
||||
}
|
||||
|
||||
// 转换为前端期望的格式(包含用户邮箱状态)
|
||||
const settings = {
|
||||
id: dbSettings.id,
|
||||
userId: dbSettings.userId,
|
||||
songSelectedNotify: dbSettings.songRequestEnabled,
|
||||
songPlayedNotify: dbSettings.songPlayedEnabled,
|
||||
songVotedNotify: dbSettings.songVotedEnabled,
|
||||
systemNotify: dbSettings.enabled,
|
||||
refreshInterval: dbSettings.refreshInterval,
|
||||
songVotedThreshold: dbSettings.songVotedThreshold,
|
||||
meowUserId: userInfo?.meowNickname || '',
|
||||
// 邮件通知总开关移除:以邮箱绑定状态为准
|
||||
// 用户邮箱信息
|
||||
userEmail: userInfo?.email || '',
|
||||
emailVerified: userInfo?.emailVerified || false
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: settings
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取通知设置失败:', err)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: '获取通知设置失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user