diff --git a/lib/api/auto-pin.ts b/lib/api/auto-pin.ts new file mode 100644 index 0000000..3e80a86 --- /dev/null +++ b/lib/api/auto-pin.ts @@ -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 { + 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 +} \ No newline at end of file diff --git a/lib/api/performance.ts b/lib/api/performance.ts index d64fb77..9f8c11d 100644 --- a/lib/api/performance.ts +++ b/lib/api/performance.ts @@ -4,6 +4,7 @@ import { performanceEntries, games, gameVersions, hardware, user, gamePlatformSu import { db } from "@/lib/db/index" import { eq, and, desc, sql } from "drizzle-orm" import { requireRole } from "@/lib/auth/guard" +import { checkAndAutoPin } from "./auto-pin" // ── Performance Entries CRUD ────────────────────────────────────── export const performanceRoutes = createCrudRoutes(performanceEntries, { @@ -93,6 +94,11 @@ export const performanceVerifyRoutes = new Elysia({ 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 }, { @@ -132,6 +138,11 @@ export const performanceVerifyRoutes = new Elysia({ 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 }, {