Add engagement metrics and health check logging to API
This commit is contained in:
@@ -6,6 +6,7 @@ RUN apt-get update && apt-get install -y \
|
||||
gnupg \
|
||||
ca-certificates \
|
||||
fonts-liberation \
|
||||
fonts-noto-color-emoji \
|
||||
libasound2 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
|
||||
86
api.js
86
api.js
@@ -18,6 +18,11 @@ app.use(express.urlencoded({ limit: '1gb', extended: true }));
|
||||
|
||||
// Request logging middleware
|
||||
app.use((req, res, next) => {
|
||||
// skip logging health checks
|
||||
if (req.url === '/health') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`\n[${timestamp}] ${req.method} ${req.url}`);
|
||||
|
||||
@@ -134,6 +139,23 @@ function cleanupOldCache() {
|
||||
}
|
||||
}
|
||||
|
||||
function formatCount(num) {
|
||||
if (num === null || num === undefined) return '0';
|
||||
|
||||
num = parseInt(num);
|
||||
|
||||
if (num >= 1000000000) {
|
||||
return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B';
|
||||
}
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
|
||||
}
|
||||
if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
function formatTimestamp(epoch) {
|
||||
const date = new Date(epoch * 1000);
|
||||
|
||||
@@ -160,6 +182,28 @@ async function generateQuoteBuffer(config) {
|
||||
? `<div class="tweet-image-container" style="margin-bottom:12px;border-radius:16px;overflow:hidden;border:1px solid rgb(47,51,54);"><img src="${config.imageUrl}" style="width:100%;display:block;" /></div>`
|
||||
: '';
|
||||
|
||||
// Only show engagement bar if all fields provided
|
||||
const engagementHtml = config.engagement ? `
|
||||
<div class="engagement-bar">
|
||||
<div class="engagement-item">
|
||||
<span class="engagement-count">${config.engagement.replies}</span>
|
||||
<span class="engagement-label">Replies</span>
|
||||
</div>
|
||||
<div class="engagement-item">
|
||||
<span class="engagement-count">${config.engagement.retweets}</span>
|
||||
<span class="engagement-label">Reposts</span>
|
||||
</div>
|
||||
<div class="engagement-item">
|
||||
<span class="engagement-count">${config.engagement.likes}</span>
|
||||
<span class="engagement-label">Likes</span>
|
||||
</div>
|
||||
<div class="engagement-item">
|
||||
<span class="engagement-count">${config.engagement.views}</span>
|
||||
<span class="engagement-label">Views</span>
|
||||
</div>
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -231,6 +275,28 @@ async function generateQuoteBuffer(config) {
|
||||
font-size: 15px;
|
||||
color: rgb(113, 118, 123);
|
||||
}
|
||||
.engagement-bar {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
padding: 12px 0;
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid rgb(47, 51, 54);
|
||||
}
|
||||
.engagement-item {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.engagement-count {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: rgb(231, 233, 234);
|
||||
}
|
||||
.engagement-label {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
color: rgb(113, 118, 123);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -246,6 +312,7 @@ async function generateQuoteBuffer(config) {
|
||||
<div class="tweet-text">${config.text}</div>
|
||||
${imageHtml}
|
||||
<div class="tweet-time">${config.timestamp}</div>
|
||||
${engagementHtml}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
@@ -278,7 +345,7 @@ app.get('/generate', async (req, res) => {
|
||||
text: req.query.text || "No text provided",
|
||||
imageUrl: fixDataUri(req.query.imageUrl) || null,
|
||||
timestamp: timestamp,
|
||||
viewsCount: req.query.viewsCount || "0"
|
||||
engagement: null
|
||||
};
|
||||
|
||||
// Check cache first
|
||||
@@ -344,6 +411,21 @@ app.post('/generate', async (req, res) => {
|
||||
|
||||
const username = req.body.username?.trim();
|
||||
|
||||
// Only include engagement if all fields are provded
|
||||
let engagement = null;
|
||||
if (req.body.engagement &&
|
||||
req.body.engagement.likes !== undefined &&
|
||||
req.body.engagement.retweets !== undefined &&
|
||||
req.body.engagement.replies !== undefined &&
|
||||
req.body.engagement.views !== undefined) {
|
||||
engagement = {
|
||||
likes: formatCount(req.body.engagement.likes),
|
||||
retweets: formatCount(req.body.engagement.retweets),
|
||||
replies: formatCount(req.body.engagement.replies),
|
||||
views: formatCount(req.body.engagement.views)
|
||||
};
|
||||
}
|
||||
|
||||
const config = {
|
||||
displayName: req.body.displayName || "Anonymous",
|
||||
username: (username && username !== "@") ? username : "@anonymous",
|
||||
@@ -351,7 +433,7 @@ app.post('/generate', async (req, res) => {
|
||||
text: req.body.text || "No text provided",
|
||||
imageUrl: fixDataUri(req.body.imageUrl) || null,
|
||||
timestamp: timestamp,
|
||||
viewsCount: req.body.viewsCount || "0"
|
||||
engagement: engagement
|
||||
};
|
||||
|
||||
// Check cache first
|
||||
|
||||
Reference in New Issue
Block a user