this repo has no description
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: track leaderboard

+78 -1
+78 -1
src/features/services/check_hn.ts
··· 4 4 import { 5 5 users as usersTable, 6 6 stories as storiesTable, 7 + leaderboardSnapshots, 7 8 } from "../../libs/schema"; 8 9 import { eq, and, isNull, lt, gte, notInArray, not, count } from "drizzle-orm"; 9 10 import { ··· 252 253 expiresAt: expiresAt, 253 254 }); 254 255 256 + // Also record a leaderboard snapshot for the story's first appearance 257 + await recordLeaderboardSnapshot({ 258 + storyId: story.id, 259 + position, 260 + score: story.score, 261 + timestamp: currentTime, 262 + isFromVerifiedUser, 263 + expiresAt, 264 + }); 265 + 255 266 // Send a notification only if it's from a verified user or if it's #1 256 267 if (isFromVerifiedUser || isNumberOne) { 257 268 await sendNotification( ··· 269 280 isFromMonitoredUser: 270 281 isFromVerifiedUser || existingStory.isFromMonitoredUser, 271 282 }; 283 + 284 + // Record this snapshot in the leaderboard history - used for graphs 285 + await recordLeaderboardSnapshot({ 286 + storyId: story.id, 287 + position, 288 + score: story.score, 289 + timestamp: currentTime, 290 + isFromVerifiedUser, 291 + expiresAt: calculateExpirationTime(isFromVerifiedUser), 292 + }); 272 293 273 294 let shouldSendNotification = false; 274 295 let notificationType: "front_page_story" | "number_one_story" = ··· 369 390 /** 370 391 * Clean up expired stories 371 392 */ 393 + /** 394 + * Record a snapshot of a story's position on the leaderboard 395 + * This data can be used to generate graphs showing position over time 396 + */ 397 + async function recordLeaderboardSnapshot({ 398 + storyId, 399 + position, 400 + score, 401 + timestamp, 402 + isFromVerifiedUser, 403 + expiresAt, 404 + }: { 405 + storyId: number; 406 + position: number; 407 + score: number; 408 + timestamp: number; 409 + isFromVerifiedUser: boolean; 410 + expiresAt: number | null; 411 + }) { 412 + try { 413 + // Calculate TTL for the snapshot - either match story TTL or use default 414 + const snapshotExpiresAt = 415 + expiresAt || 416 + Math.floor(addDays(new Date(), RETENTION_DAYS).getTime() / 1000); 417 + 418 + // Add the snapshot to the leaderboard_snapshots table 419 + await db.insert(leaderboardSnapshots).values({ 420 + storyId, 421 + timestamp, 422 + position, 423 + score, 424 + expiresAt: snapshotExpiresAt, 425 + }); 426 + } catch (error) { 427 + console.error("Error recording leaderboard snapshot:", error); 428 + Sentry.captureException(error); 429 + } 430 + } 431 + 372 432 async function cleanupExpiredStories() { 373 433 try { 374 434 const currentTime = Math.floor(Date.now() / 1000); ··· 400 460 ), 401 461 ); 402 462 } 463 + 464 + // Also clean up expired leaderboard snapshots 465 + const expiredSnapshotsCount = await db 466 + .select({ count: count() }) 467 + .from(leaderboardSnapshots) 468 + .where(lt(leaderboardSnapshots.expiresAt, currentTime)) 469 + .then((a) => a[0]); 470 + 471 + if (expiredSnapshotsCount && Number(expiredSnapshotsCount.count) > 0) { 472 + console.log( 473 + `Cleaning up ${expiredSnapshotsCount.count} expired leaderboard snapshots`, 474 + ); 475 + 476 + await db 477 + .delete(leaderboardSnapshots) 478 + .where(lt(leaderboardSnapshots.expiresAt, currentTime)); 479 + } 403 480 } catch (error) { 404 - console.error("Error cleaning up expired stories:", error); 481 + console.error("Error cleaning up expired data:", error); 405 482 Sentry.captureException(error); 406 483 } 407 484 }