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 \
|
gnupg \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
fonts-liberation \
|
fonts-liberation \
|
||||||
|
fonts-noto-color-emoji \
|
||||||
libasound2 \
|
libasound2 \
|
||||||
libatk-bridge2.0-0 \
|
libatk-bridge2.0-0 \
|
||||||
libatk1.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
|
// Request logging middleware
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
// skip logging health checks
|
||||||
|
if (req.url === '/health') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
console.log(`\n[${timestamp}] ${req.method} ${req.url}`);
|
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) {
|
function formatTimestamp(epoch) {
|
||||||
const date = new Date(epoch * 1000);
|
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>`
|
? `<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>
|
const html = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -231,6 +275,28 @@ async function generateQuoteBuffer(config) {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: rgb(113, 118, 123);
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -246,6 +312,7 @@ async function generateQuoteBuffer(config) {
|
|||||||
<div class="tweet-text">${config.text}</div>
|
<div class="tweet-text">${config.text}</div>
|
||||||
${imageHtml}
|
${imageHtml}
|
||||||
<div class="tweet-time">${config.timestamp}</div>
|
<div class="tweet-time">${config.timestamp}</div>
|
||||||
|
${engagementHtml}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
@@ -278,7 +345,7 @@ app.get('/generate', async (req, res) => {
|
|||||||
text: req.query.text || "No text provided",
|
text: req.query.text || "No text provided",
|
||||||
imageUrl: fixDataUri(req.query.imageUrl) || null,
|
imageUrl: fixDataUri(req.query.imageUrl) || null,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
viewsCount: req.query.viewsCount || "0"
|
engagement: null
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
@@ -344,6 +411,21 @@ app.post('/generate', async (req, res) => {
|
|||||||
|
|
||||||
const username = req.body.username?.trim();
|
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 = {
|
const config = {
|
||||||
displayName: req.body.displayName || "Anonymous",
|
displayName: req.body.displayName || "Anonymous",
|
||||||
username: (username && username !== "@") ? username : "@anonymous",
|
username: (username && username !== "@") ? username : "@anonymous",
|
||||||
@@ -351,7 +433,7 @@ app.post('/generate', async (req, res) => {
|
|||||||
text: req.body.text || "No text provided",
|
text: req.body.text || "No text provided",
|
||||||
imageUrl: fixDataUri(req.body.imageUrl) || null,
|
imageUrl: fixDataUri(req.body.imageUrl) || null,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
viewsCount: req.body.viewsCount || "0"
|
engagement: engagement
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check cache first
|
// Check cache first
|
||||||
|
|||||||
Reference in New Issue
Block a user