111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
import { db, songs } from '~/drizzle/db'
|
|
import { and, count, eq, gte, isNull, lte } from 'drizzle-orm'
|
|
import {
|
|
getBeijingEndOfDay,
|
|
getBeijingEndOfWeek,
|
|
getBeijingEndOfMonth,
|
|
getBeijingStartOfDay,
|
|
getBeijingStartOfWeek,
|
|
getBeijingStartOfMonth
|
|
} from '~/utils/timeUtils'
|
|
|
|
export type LimitType = 'daily' | 'weekly' | 'monthly'
|
|
|
|
type SubmissionCountOptions = {
|
|
excludeCardCodeRequests?: boolean
|
|
}
|
|
|
|
type CardCodeLimitSettings = {
|
|
enableSubmissionLimit?: boolean | null
|
|
enableCardCodeRequests?: boolean | null
|
|
requireCardCodeForRequests?: boolean | null
|
|
enableCardCodeLimitBypass?: boolean | null
|
|
}
|
|
|
|
export function isCardCodeLimitBypassActive(
|
|
settings: CardCodeLimitSettings | null | undefined
|
|
): boolean {
|
|
return !!(
|
|
settings?.enableSubmissionLimit &&
|
|
settings.enableCardCodeLimitBypass &&
|
|
settings.enableCardCodeRequests === true
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 获取指定限额类型的时间窗口(北京时间)
|
|
* @param type 限额类型
|
|
* @returns { start: Date, end: Date }
|
|
*/
|
|
export function getTimeRange(type: LimitType) {
|
|
switch (type) {
|
|
case 'weekly':
|
|
return {
|
|
start: getBeijingStartOfWeek(),
|
|
end: getBeijingEndOfWeek()
|
|
}
|
|
case 'monthly':
|
|
return {
|
|
start: getBeijingStartOfMonth(),
|
|
end: getBeijingEndOfMonth()
|
|
}
|
|
case 'daily':
|
|
default:
|
|
// 'daily' 是默认情况,为保证健壮性处理任何意外值。
|
|
return {
|
|
start: getBeijingStartOfDay(),
|
|
end: getBeijingEndOfDay()
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取用户在指定周期内的投稿数量
|
|
* @param dbOrTx 数据库实例或事务
|
|
* @param userId 用户ID
|
|
* @param type 限额类型
|
|
* @param options 投稿统计选项
|
|
* @returns 投稿数量
|
|
*/
|
|
export async function getSubmissionCount(
|
|
dbOrTx: typeof db,
|
|
userId: number,
|
|
type: LimitType,
|
|
options: SubmissionCountOptions = {}
|
|
): Promise<number> {
|
|
const { start, end } = getTimeRange(type)
|
|
|
|
const [result] = await dbOrTx
|
|
.select({ count: count() })
|
|
.from(songs)
|
|
.where(
|
|
and(
|
|
eq(songs.requesterId, userId),
|
|
gte(songs.createdAt, start),
|
|
lte(songs.createdAt, end),
|
|
options.excludeCardCodeRequests ? isNull(songs.cardCodeId) : undefined
|
|
)
|
|
)
|
|
|
|
return result?.count || 0
|
|
}
|
|
|
|
/**
|
|
* 检查用户是否达到投稿限额
|
|
* @param dbOrTx 数据库实例或事务
|
|
* @param userId 用户ID
|
|
* @param type 限额类型
|
|
* @param limit 限额数量
|
|
* @param options 投稿统计选项
|
|
* @returns boolean true 表示已达到限额(不允许继续投稿),false 表示未达到
|
|
*/
|
|
export async function isLimitReached(
|
|
dbOrTx: typeof db,
|
|
userId: number,
|
|
type: LimitType,
|
|
limit: number,
|
|
options: SubmissionCountOptions = {}
|
|
): Promise<boolean> {
|
|
return (await getSubmissionCount(dbOrTx, userId, type, options)) >= limit
|
|
}
|