update
Deploy / deploy (push) Canceled after 0s

This commit is contained in:
xuyue committed 2026-09-27 19:36:28 +08:00
1 parent 23844ae301
commit d25b5dbe6d
268 files changed
+3684 -8861

No files matched your search

+24 -81
View File
@@ -40,8 +40,7 @@ async function unlockAchievements(
unlocked.map((row) => row.id),
),
)
if (onlyMeta)
filters.push(eq(schema.achievement.metric, "achievement_unlocked_count"))
if (onlyMeta) filters.push(eq(schema.achievement.metric, "achievement_unlocked_count"))
else filters.push(ne(schema.achievement.metric, "achievement_unlocked_count"))
const candidates = await db
.select()
@@ -69,10 +68,7 @@ async function unlockAchievements(
})),
)
.onConflictDoNothing({
target: [
schema.userAchievement.achievementId,
schema.userAchievement.userId,
],
target: [schema.userAchievement.achievementId, schema.userAchievement.userId],
})
.returning({ achievementId: schema.userAchievement.achievementId })
if (inserted.length === 0) return []
@@ -88,10 +84,7 @@ export async function updateAchievementsForSubmission(submissionId: string) {
const [row] = await db
.select({ submission: schema.submission, problem: schema.problem })
.from(schema.submission)
.innerJoin(
schema.problem,
eq(schema.submission.problemId, schema.problem.id),
)
.innerJoin(schema.problem, eq(schema.submission.problemId, schema.problem.id))
.where(eq(schema.submission.id, submissionId))
.limit(1)
if (!row || row.submission.contestId !== null) return []
@@ -137,32 +130,23 @@ export async function updateAchievementsForSubmission(submissionId: string) {
value.mid_ac_count = numberMetric(value, "mid_ac_count") + 1
if (row.problem.difficulty === "High")
value.hard_ac_count = numberMetric(value, "hard_ac_count") + 1
if (firstTry)
value.first_try_ac_count = numberMetric(value, "first_try_ac_count") + 1
value.max_wa_before_ac = Math.max(
numberMetric(value, "max_wa_before_ac"),
priorRows.length,
)
if (firstTry) value.first_try_ac_count = numberMetric(value, "first_try_ac_count") + 1
value.max_wa_before_ac = Math.max(numberMetric(value, "max_wa_before_ac"), priorRows.length)
const perDay = asRecord(value._ac_per_day)
perDay[date] = (typeof perDay[date] === "number" ? perDay[date] : 0) + 1
value._ac_per_day = perDay
value.max_ac_in_one_day = Math.max(
...Object.values(perDay).filter(
(item): item is number => typeof item === "number",
),
...Object.values(perDay).filter((item): item is number => typeof item === "number"),
)
}
const activeDates = Array.isArray(value._active_dates)
? value._active_dates.filter(
(item): item is string => typeof item === "string",
)
? value._active_dates.filter((item): item is string => typeof item === "string")
: []
if (!activeDates.includes(date)) activeDates.push(date)
value._active_dates = activeDates
value.active_days = activeDates.length
if (accepted) {
const last =
typeof value._last_ac_date === "string" ? value._last_ac_date : null
const last = typeof value._last_ac_date === "string" ? value._last_ac_date : null
if (last !== date) {
// 差一天要按日历日算,不能用 Date 相减:夏令时地区相邻两天差 23/25 小时,
// 除 86400000 得到的不是 1,`=== 1` 会静默把连续打卡判成断掉。
@@ -172,27 +156,18 @@ export async function updateAchievementsForSubmission(submissionId: string) {
: 1
value._last_ac_date = date
value._current_ac_streak = current
value.max_ac_streak_days = Math.max(
numberMetric(value, "max_ac_streak_days"),
current,
)
value.max_ac_streak_days = Math.max(numberMetric(value, "max_ac_streak_days"), current)
}
}
const languages = Array.isArray(value._languages)
? value._languages.filter(
(item): item is string => typeof item === "string",
)
? value._languages.filter((item): item is string => typeof item === "string")
: []
if (!languages.includes(row.submission.language))
languages.push(row.submission.language)
if (!languages.includes(row.submission.language)) languages.push(row.submission.language)
value._languages = languages
value.languages_used = languages.length
if (hour < 5)
value.midnight_submissions =
numberMetric(value, "midnight_submissions") + 1
if (hour < 5) value.midnight_submissions = numberMetric(value, "midnight_submissions") + 1
else if (hour < 7)
value.early_bird_submissions =
numberMetric(value, "early_bird_submissions") + 1
value.early_bird_submissions = numberMetric(value, "early_bird_submissions") + 1
if (row.submission.result === JudgeStatus.COMPILE_ERROR)
value.compile_error_count = numberMetric(value, "compile_error_count") + 1
value.max_code_lines = Math.max(
@@ -211,10 +186,7 @@ export async function updateAchievementsForSubmission(submissionId: string) {
const [meta] = await db
.select({ value: count() })
.from(schema.userAchievement)
.innerJoin(
schema.achievement,
eq(schema.userAchievement.achievementId, schema.achievement.id),
)
.innerJoin(schema.achievement, eq(schema.userAchievement.achievementId, schema.achievement.id))
.where(
and(
eq(schema.userAchievement.userId, row.submission.userId),
@@ -226,18 +198,12 @@ export async function updateAchievementsForSubmission(submissionId: string) {
.update(schema.userStat)
.set({ metrics, updateTime: new Date().toISOString() })
.where(eq(schema.userStat.userId, row.submission.userId))
return [
...first,
...(await unlockAchievements(row.submission.userId, metrics, true)),
]
return [...first, ...(await unlockAchievements(row.submission.userId, metrics, true))]
}
export async function updateAchievementsForProblemSet(userId: number) {
const [[badgeRow], [completedRow]] = await Promise.all([
db
.select({ value: count() })
.from(schema.userBadge)
.where(eq(schema.userBadge.userId, userId)),
db.select({ value: count() }).from(schema.userBadge).where(eq(schema.userBadge.userId, userId)),
db
.select({ value: count() })
.from(schema.problemsetProgress)
@@ -279,15 +245,9 @@ export async function updateAchievementsForProblemSet(userId: number) {
const [meta] = await db
.select({ value: count() })
.from(schema.userAchievement)
.innerJoin(
schema.achievement,
eq(schema.userAchievement.achievementId, schema.achievement.id),
)
.innerJoin(schema.achievement, eq(schema.userAchievement.achievementId, schema.achievement.id))
.where(
and(
eq(schema.userAchievement.userId, userId),
ne(schema.achievement.rarity, "platinum"),
),
and(eq(schema.userAchievement.userId, userId), ne(schema.achievement.rarity, "platinum")),
)
metrics.achievement_unlocked_count = meta?.value ?? 0
await db
@@ -312,12 +272,7 @@ export async function rescanAchievement(achievementId: number) {
const [achievement] = await db
.select()
.from(schema.achievement)
.where(
and(
eq(schema.achievement.id, achievementId),
eq(schema.achievement.visible, true),
),
)
.where(and(eq(schema.achievement.id, achievementId), eq(schema.achievement.visible, true)))
.limit(1)
if (!achievement) return { scanned: 0, unlocked: 0 }
@@ -325,8 +280,7 @@ export async function rescanAchievement(achievementId: number) {
if (!metric) return { scanned: 0, unlocked: 0 }
// contest_joined 不由判题结算维护,扫之前先把它刷新一遍,否则永远读到旧值(或没有值)
if (achievement.metric === "contest_joined")
await refreshContestJoinedForAll()
if (achievement.metric === "contest_joined") await refreshContestJoinedForAll()
const already = new Set(
(
@@ -357,11 +311,7 @@ export async function rescanAchievement(achievementId: number) {
// 计数改成一次 +N,通知照旧逐人推(那是 Redis,不是数据库)。
const unlockTime = new Date().toISOString()
const unlockedUserIds: number[] = []
for (
let start = 0;
start < eligible.length;
start += USER_ACHIEVEMENT_INSERT_CHUNK
) {
for (let start = 0; start < eligible.length; start += USER_ACHIEVEMENT_INSERT_CHUNK) {
const chunk = eligible.slice(start, start + USER_ACHIEVEMENT_INSERT_CHUNK)
const inserted = await db
.insert(schema.userAchievement)
@@ -375,10 +325,7 @@ export async function rescanAchievement(achievementId: number) {
})),
)
.onConflictDoNothing({
target: [
schema.userAchievement.achievementId,
schema.userAchievement.userId,
],
target: [schema.userAchievement.achievementId, schema.userAchievement.userId],
})
.returning({ userId: schema.userAchievement.userId })
unlockedUserIds.push(...inserted.map((row) => row.userId))
@@ -406,13 +353,9 @@ export async function rescanAchievement(achievementId: number) {
// 旧 `rescan_achievement` 就漏了这步,OJ2 原样搬过来:2026-09-07 一次补发之后
// 269 人的计数停在旧值,其中 10 人实际够了「奖杯收藏家」却一直没发 ——
// 判题结算只在「这次有新解锁」时才重算,被补发的人不再解锁新成就就永远不会自愈。
if (
achievement.rarity !== "platinum" &&
achievement.metric !== "achievement_unlocked_count"
) {
if (achievement.rarity !== "platinum" && achievement.metric !== "achievement_unlocked_count") {
await refreshUnlockedCount(unlockedUserIds)
for (const meta of await metaAchievements())
await rescanAchievement(meta.id)
for (const meta of await metaAchievements()) await rescanAchievement(meta.id)
}
}
return { scanned: stats.length, unlocked: unlockedUserIds.length }