feat: auto-pin entries with ≥10 votes and ≥80% approval
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
import { db } from "@/lib/db/index"
|
||||||
|
import { performanceEntries } from "@/lib/db/schema"
|
||||||
|
import { eq, and } from "drizzle-orm"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-pin check: an entry is eligible for auto-pinning when:
|
||||||
|
* 1. NOT already pinned (isPinned = false)
|
||||||
|
* 2. NOT removed (isRemoved = false)
|
||||||
|
* 3. Total votes >= 10 (upvotes + downvotes)
|
||||||
|
* 4. Approval ratio >= 0.80 (upvotes / total_votes)
|
||||||
|
* 5. Absolute upvotes >= 8
|
||||||
|
*/
|
||||||
|
const AUTO_PIN_MIN_TOTAL_VOTES = 10
|
||||||
|
const AUTO_PIN_MIN_APPROVAL_RATIO = 0.80
|
||||||
|
const AUTO_PIN_MIN_UPVOTES = 8
|
||||||
|
|
||||||
|
export async function checkAndAutoPin(entryId: string): Promise<boolean> {
|
||||||
|
const [entry] = await db
|
||||||
|
.select({
|
||||||
|
id: performanceEntries.id,
|
||||||
|
isPinned: performanceEntries.isPinned,
|
||||||
|
isRemoved: performanceEntries.isRemoved,
|
||||||
|
upvotes: performanceEntries.upvotes,
|
||||||
|
downvotes: performanceEntries.downvotes,
|
||||||
|
})
|
||||||
|
.from(performanceEntries)
|
||||||
|
.where(eq(performanceEntries.id, entryId))
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
|
if (!entry) return false
|
||||||
|
if (entry.isPinned) return false
|
||||||
|
if (entry.isRemoved) return false
|
||||||
|
|
||||||
|
const totalVotes = entry.upvotes + entry.downvotes
|
||||||
|
if (totalVotes < AUTO_PIN_MIN_TOTAL_VOTES) return false
|
||||||
|
if (entry.upvotes < AUTO_PIN_MIN_UPVOTES) return false
|
||||||
|
|
||||||
|
const approvalRatio = entry.upvotes / totalVotes
|
||||||
|
if (approvalRatio < AUTO_PIN_MIN_APPROVAL_RATIO) return false
|
||||||
|
|
||||||
|
// Conditions met — auto-pin
|
||||||
|
await db
|
||||||
|
.update(performanceEntries)
|
||||||
|
.set({
|
||||||
|
isPinned: true,
|
||||||
|
pinnedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(performanceEntries.id, entryId))
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { performanceEntries, games, gameVersions, hardware, user, gamePlatformSu
|
|||||||
import { db } from "@/lib/db/index"
|
import { db } from "@/lib/db/index"
|
||||||
import { eq, and, desc, sql } from "drizzle-orm"
|
import { eq, and, desc, sql } from "drizzle-orm"
|
||||||
import { requireRole } from "@/lib/auth/guard"
|
import { requireRole } from "@/lib/auth/guard"
|
||||||
|
import { checkAndAutoPin } from "./auto-pin"
|
||||||
|
|
||||||
// ── Performance Entries CRUD ──────────────────────────────────────
|
// ── Performance Entries CRUD ──────────────────────────────────────
|
||||||
export const performanceRoutes = createCrudRoutes(performanceEntries, {
|
export const performanceRoutes = createCrudRoutes(performanceEntries, {
|
||||||
@@ -93,6 +94,11 @@ export const performanceVerifyRoutes = new Elysia({
|
|||||||
return { error: "Performance entry not found" }
|
return { error: "Performance entry not found" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-pin check (fire and forget, result doesn't affect response)
|
||||||
|
checkAndAutoPin(updated.id).catch((err) =>
|
||||||
|
console.error("Auto-pin check failed:", err),
|
||||||
|
)
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -132,6 +138,11 @@ export const performanceVerifyRoutes = new Elysia({
|
|||||||
return { error: "Performance entry not found" }
|
return { error: "Performance entry not found" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-pin check (fire and forget, result doesn't affect response)
|
||||||
|
checkAndAutoPin(updated.id).catch((err) =>
|
||||||
|
console.error("Auto-pin check failed:", err),
|
||||||
|
)
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user