this repo has no description
3
fork

Configure Feed

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

feat: better header stats

+158 -12
+80 -9
public/app.js
··· 30 30 const mostActiveTimeEl = document.getElementById("most-active-time"); 31 31 const avgFrontpageTimeEl = document.getElementById("avg-frontpage-time"); 32 32 let totalStoriesCount = 0; // Track total stories count 33 + let headerStatsLoaded = false; // Track if header stats have been loaded 33 34 34 35 // Initialize stats 35 36 updatePerformanceMetrics([]); ··· 221 222 }) 222 223 .catch((error) => { 223 224 console.error("Error fetching verified user stats:", error); 225 + }); 226 + 227 + // Fetch header stats for performance metrics 228 + const statsHeaderOptions = { 229 + headers: {}, 230 + }; 231 + 232 + // Add If-None-Match header if we have an ETag 233 + if (etagCache.statsHeader) { 234 + statsHeaderOptions.headers["If-None-Match"] = etagCache.statsHeader; 235 + } 236 + 237 + // Add Accept-Encoding header if browser supports it 238 + if ('Accept-Encoding' in navigator) { 239 + statsHeaderOptions.headers["Accept-Encoding"] = "gzip, deflate, br"; 240 + } 241 + 242 + fetch("/api/stats/header", statsHeaderOptions) 243 + .then((response) => { 244 + // Store the new ETag if available 245 + const etag = response.headers.get("ETag"); 246 + if (etag) { 247 + etagCache.statsHeader = etag; 248 + persistCaches(); 249 + } 250 + 251 + // If 304 Not Modified, use cached data 252 + if (response.status === 304) { 253 + console.log("Stats header not modified, using cached data"); 254 + return Promise.resolve(responseCache.statsHeader); // Use cached data 255 + } 256 + 257 + return response.json(); 258 + }) 259 + .then((data) => { 260 + if (data) { 261 + // Store in cache for future 304 responses 262 + responseCache.statsHeader = data; 263 + persistCaches(); 264 + 265 + // Update UI with the stats header data 266 + updateHeaderStats(data); 267 + } 268 + }) 269 + .catch((error) => { 270 + console.error("Error fetching stats header:", error); 224 271 }); 225 272 226 273 // Fetch stories ··· 818 865 } 819 866 820 867 // Live counter function to update time-based elements 821 - // Update top row stats based on verified user data 822 - function updateTopStats(data) { 823 - // Update performance metrics if they exist 868 + // Update header stats based on API data 869 + function updateHeaderStats(data) { 870 + // Update the stats from the header API endpoint 871 + const currentFrontpageCountEl = document.getElementById("current-frontpage-count"); 824 872 const topTenCountEl = document.getElementById("top-ten-count"); 873 + const avgFrontpageTimeEl = document.getElementById("avg-frontpage-time"); 825 874 const mostActiveTimeEl = document.getElementById("most-active-time"); 826 - 875 + 876 + if (currentFrontpageCountEl) { 877 + currentFrontpageCountEl.textContent = data.totalStories || "0"; 878 + } 879 + 827 880 if (topTenCountEl) { 828 - topTenCountEl.textContent = data.frontPageCount || "0"; 881 + topTenCountEl.textContent = data.topPoints || "0"; 829 882 } 830 - if (mostActiveTimeEl) { 831 - mostActiveTimeEl.textContent = data.avgPeakPoints || "0"; 883 + 884 + if (avgFrontpageTimeEl && data.avgTimeOnFrontPageMinutes) { 885 + const minutes = data.avgTimeOnFrontPageMinutes; 886 + const hours = Math.floor(minutes / 60); 887 + const remainingMinutes = minutes % 60; 888 + avgFrontpageTimeEl.textContent = `${hours}:${remainingMinutes.toString().padStart(2, '0')}`; 832 889 } 890 + 891 + // Mark stats as loaded 892 + headerStatsLoaded = true; 893 + } 833 894 895 + function updateTopStats(data) { 896 + // This function is now primarily used for verified user data updates 897 + // Main stats are updated directly from the /api/stats/header endpoint 898 + 834 899 // Update verified user analytics metrics if they exist 835 900 const verifiedUserCountEl = document.getElementById("verified-user-count"); 836 901 const verifiedAvgPointsEl = document.getElementById("verified-avg-points"); 902 + const mostActiveTimeEl = document.getElementById("most-active-time"); 837 903 838 904 if (verifiedUserCountEl) { 839 905 verifiedUserCountEl.textContent = data.totalCount || "0"; 840 906 } 841 907 if (verifiedAvgPointsEl) { 842 908 verifiedAvgPointsEl.textContent = data.avgPeakPoints || "0"; 909 + } 910 + 911 + // Update the most active time element if it exists 912 + if (mostActiveTimeEl && !mostActiveTimeEl.textContent.trim() && !headerStatsLoaded) { 913 + mostActiveTimeEl.textContent = data.avgPeakPoints || "0"; 843 914 } 844 915 } 845 916 ··· 887 958 } 888 959 } 889 960 890 - // Update average duration in performance metrics if we have the displayed stories 891 - if (document.getElementById("avg-frontpage-time")) { 961 + // Only update if we don't have data from the API 962 + if (document.getElementById("avg-frontpage-time") && !headerStatsLoaded) { 892 963 let totalMs = 0; 893 964 let storiesWithDuration = 0; 894 965
+9 -3
public/index.html
··· 823 823 <div class="performance-metric" style="--i: 2"> 824 824 <div class="metric-label">Highest Points</div> 825 825 <div class="metric-value" id="top-ten-count">-</div> 826 - <div class="metric-description">Most upvoted story</div> 826 + <div class="metric-description"> 827 + Most upvoted story in the last 5 days 828 + </div> 827 829 </div> 828 830 <div class="performance-metric" style="--i: 3"> 829 831 <div class="metric-label">Average Points</div> 830 832 <div class="metric-value" id="most-active-time">-</div> 831 - <div class="metric-description">Per story</div> 833 + <div class="metric-description"> 834 + Per story in the last 5 days 835 + </div> 832 836 </div> 833 837 <div class="performance-metric" style="--i: 4"> 834 838 <div class="metric-label">Average Time on FP</div> 835 839 <div class="metric-value" id="avg-frontpage-time">-</div> 836 - <div class="metric-description">Hours:minutes</div> 840 + <div class="metric-description"> 841 + Hours:minutes in the last 5 days 842 + </div> 837 843 </div> 838 844 </div> 839 845 </div>
+69
src/index.ts
··· 321 321 ), 322 322 ), 323 323 324 + "/api/stats/header": handleCORS( 325 + createCachedEndpoint( 326 + "stats_header", 327 + async () => { 328 + // Get total stories count 329 + const storiesCountResult = await db 330 + .select({ count: count() }) 331 + .from(stories); 332 + const totalStories = Number(storiesCountResult[0]?.count || 0); 333 + 334 + // Calculate time threshold for last 5 days 335 + const fiveDaysAgo = Math.floor(Date.now() / 1000) - 5 * 24 * 60 * 60; 336 + 337 + // Get top points in last 5 days 338 + const topPointsStory = await db.query.stories.findFirst({ 339 + columns: { 340 + score: true, 341 + peakScore: true, 342 + }, 343 + where: (stories, { gt }) => gt(stories.firstSeenAt, fiveDaysAgo), 344 + orderBy: (stories, { desc }) => [desc(stories.peakScore)], 345 + }); 346 + 347 + // Calculate average time on front page for recent stories 348 + const recentCompletedStories = await db.query.stories.findMany({ 349 + columns: { 350 + enteredLeaderboardAt: true, 351 + firstSeenAt: true, 352 + isOnLeaderboard: true, 353 + }, 354 + where: (stories, { and, gt, eq }) => 355 + and( 356 + gt(stories.firstSeenAt, fiveDaysAgo), 357 + eq(stories.isOnLeaderboard, false), 358 + stories.enteredLeaderboardAt, 359 + ), 360 + }); 361 + 362 + // Calculate average time on front page 363 + let totalTimeOnFrontPage = 0; 364 + let storiesWithFrontPageTime = 0; 365 + 366 + for (const story of recentCompletedStories) { 367 + if (story.enteredLeaderboardAt) { 368 + const timeOnFrontPage = 369 + story.firstSeenAt - story.enteredLeaderboardAt; 370 + if (timeOnFrontPage > 0) { 371 + totalTimeOnFrontPage += timeOnFrontPage; 372 + storiesWithFrontPageTime++; 373 + } 374 + } 375 + } 376 + 377 + const avgTimeOnFrontPage = 378 + storiesWithFrontPageTime > 0 379 + ? Math.round(totalTimeOnFrontPage / storiesWithFrontPageTime / 60) // Convert to minutes 380 + : 0; 381 + 382 + return { 383 + totalStories: totalStories, 384 + topPoints: topPointsStory?.peakScore || 0, 385 + avgTimeOnFrontPageMinutes: avgTimeOnFrontPage, 386 + timestamp: Math.floor(Date.now() / 1000), 387 + }; 388 + }, 389 + 1800, 390 + ), 391 + ), 392 + 324 393 "/api/stats/verified-users": handleCORS( 325 394 createCachedEndpoint( 326 395 "verified_users_stats",