119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
import { db } from '~/drizzle/db'
|
|
import { createSongPlayedNotification } from '~~/server/services/notificationService'
|
|
import { songs, songReplayRequests } from '~/drizzle/schema'
|
|
import { eq, and, inArray } from 'drizzle-orm'
|
|
import { getBeijingTime } from '~/utils/timeUtils'
|
|
import { getClientIP } from '~~/server/utils/ip-utils'
|
|
import { restoreReplayRequestsToPending } from '~~/server/utils/scheduleReplayBinding'
|
|
import { z } from 'zod'
|
|
|
|
const markPlayedSchema = z.object({
|
|
songId: z.number().optional(),
|
|
songIds: z.array(z.number()).max(100, '批量操作单次最多支持 100 首歌曲').optional(),
|
|
unmark: z.boolean().optional()
|
|
})
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const body = await readBody(event)
|
|
const validatedDataResult = markPlayedSchema.safeParse(body)
|
|
|
|
if (!validatedDataResult.success) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: validatedDataResult.error.issues[0]?.message || '请求参数无效'
|
|
})
|
|
}
|
|
|
|
const validatedData = validatedDataResult.data
|
|
|
|
const songIds = Array.from(
|
|
new Set([
|
|
...(validatedData.songId !== undefined ? [validatedData.songId] : []),
|
|
...(validatedData.songIds || [])
|
|
])
|
|
)
|
|
|
|
if (songIds.length === 0) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: '歌曲ID列表不能为空'
|
|
})
|
|
}
|
|
|
|
// 撤回已播放
|
|
const isUnmark = validatedData.unmark === true
|
|
|
|
// 过滤需要更新的歌曲
|
|
const condition = !isUnmark
|
|
? and(inArray(songs.id, songIds), eq(songs.played, false))
|
|
: and(inArray(songs.id, songIds), eq(songs.played, true))
|
|
|
|
const { updatedSongsResult, updatedSongIds } = await db.transaction(async (tx) => {
|
|
const updatedSongsResult = await tx
|
|
.update(songs)
|
|
.set({
|
|
played: !isUnmark,
|
|
playedAt: isUnmark ? null : getBeijingTime()
|
|
})
|
|
.where(condition)
|
|
.returning({ id: songs.id })
|
|
|
|
const updatedSongIds = updatedSongsResult.map((s) => s.id)
|
|
|
|
if (updatedSongIds.length > 0) {
|
|
if (!isUnmark) {
|
|
// 标记已播放:履行全部待处理的重播申请
|
|
const updatedRequests = await tx
|
|
.update(songReplayRequests)
|
|
.set({
|
|
status: 'FULFILLED',
|
|
updatedAt: getBeijingTime()
|
|
})
|
|
.where(
|
|
and(
|
|
inArray(songReplayRequests.songId, updatedSongIds),
|
|
eq(songReplayRequests.status, 'PENDING')
|
|
)
|
|
)
|
|
.returning({ id: songReplayRequests.id })
|
|
|
|
if (updatedRequests.length > 0) {
|
|
console.log(`将 ${updatedRequests.length} 个重播申请状态更新为 FULFILLED(歌曲已播放)`)
|
|
}
|
|
} else {
|
|
// 撤回已播放:每人仅恢复最新一条,避免违反部分唯一索引
|
|
const restoredCount = await restoreReplayRequestsToPending({
|
|
tx,
|
|
songIds: updatedSongIds,
|
|
at: getBeijingTime()
|
|
})
|
|
|
|
if (restoredCount > 0) {
|
|
console.log(`将 ${restoredCount} 个重播申请状态恢复为 PENDING(撤回已播放)`)
|
|
}
|
|
}
|
|
}
|
|
|
|
return { updatedSongsResult, updatedSongIds }
|
|
})
|
|
|
|
// 异步发送通知
|
|
if (!isUnmark && updatedSongIds.length > 0) {
|
|
event.waitUntil(
|
|
Promise.allSettled(
|
|
updatedSongIds.map((songId) =>
|
|
createSongPlayedNotification(songId).catch((err) => {
|
|
console.error(`发送歌曲(${songId})已播放通知失败:`, err)
|
|
})
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
return {
|
|
message: isUnmark ? '歌曲已成功撤回已播放状态' : '歌曲已成功标记为已播放',
|
|
count: updatedSongsResult.length,
|
|
updatedSongIds: updatedSongIds
|
|
}
|
|
})
|