Skip to content

Commit 87653b3

Browse files
author
Your Name
committed
Fix CORS for stats endpoint
Ensure CORS headers are set for all responses and return 204 on preflight.
1 parent 759076f commit 87653b3

1 file changed

Lines changed: 104 additions & 0 deletions

File tree

api/serverless/write-stats.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Serverless function to write stats to GitHub Gist
2+
// Works with Vercel, Netlify, or Cloudflare Workers
3+
// Set GITHUB_TOKEN and GIST_ID as environment variables
4+
5+
// Vercel/Netlify format
6+
export default async function handler(req, res) {
7+
const allowedOrigin = 'https://persistence-ai.github.io';
8+
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
9+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
10+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
11+
res.setHeader('Access-Control-Max-Age', '600');
12+
res.setHeader('Vary', 'Origin');
13+
14+
// Handle CORS preflight
15+
if (req.method === 'OPTIONS') {
16+
return res.status(204).end();
17+
}
18+
19+
// Only allow POST requests
20+
if (req.method !== 'POST') {
21+
return res.status(405).json({ error: 'Method not allowed' });
22+
}
23+
24+
const { GITHUB_TOKEN, GIST_ID } = process.env;
25+
26+
if (!GITHUB_TOKEN || !GIST_ID) {
27+
return res.status(500).json({ error: 'Server configuration missing' });
28+
}
29+
30+
try {
31+
const { type, platform, fingerprint, date } = req.body;
32+
33+
// Fetch current Gist
34+
const gistResponse = await fetch(`https://api.github.com/gists/${GIST_ID}`, {
35+
headers: {
36+
'Accept': 'application/vnd.github.v3+json',
37+
'Authorization': `token ${GITHUB_TOKEN}`,
38+
},
39+
});
40+
41+
if (!gistResponse.ok) {
42+
throw new Error(`Failed to fetch gist: ${gistResponse.status}`);
43+
}
44+
45+
const gist = await gistResponse.json();
46+
const filename = Object.keys(gist.files)[0];
47+
const currentStats = JSON.parse(gist.files[filename].content);
48+
49+
// Update stats based on type
50+
const updatedStats = { ...currentStats };
51+
52+
if (type === 'copy' && platform) {
53+
// Increment copy count for platform
54+
updatedStats.copyCounts[platform] = (updatedStats.copyCounts[platform] || 0) + 1;
55+
updatedStats.copyCounts.total =
56+
(updatedStats.copyCounts.windows || 0) +
57+
(updatedStats.copyCounts.linux || 0) +
58+
(updatedStats.copyCounts.mac || 0);
59+
}
60+
61+
if (type === 'pageView' && fingerprint && date) {
62+
// Check if new visitor
63+
const isNewVisitor = !updatedStats.visitors.fingerprints[fingerprint];
64+
65+
if (isNewVisitor) {
66+
updatedStats.visitors.total = (updatedStats.visitors.total || 0) + 1;
67+
updatedStats.visitors.fingerprints[fingerprint] = date;
68+
updatedStats.visitors.daily[date] = (updatedStats.visitors.daily[date] || 0) + 1;
69+
}
70+
71+
// Always increment page views
72+
updatedStats.pageViews.total = (updatedStats.pageViews.total || 0) + 1;
73+
updatedStats.pageViews.daily[date] = (updatedStats.pageViews.daily[date] || 0) + 1;
74+
}
75+
76+
updatedStats.lastUpdated = new Date().toISOString();
77+
78+
// Update Gist
79+
const updateResponse = await fetch(`https://api.github.com/gists/${GIST_ID}`, {
80+
method: 'PATCH',
81+
headers: {
82+
'Accept': 'application/vnd.github.v3+json',
83+
'Authorization': `token ${GITHUB_TOKEN}`,
84+
'Content-Type': 'application/json',
85+
},
86+
body: JSON.stringify({
87+
files: {
88+
[filename]: {
89+
content: JSON.stringify(updatedStats, null, 2),
90+
},
91+
},
92+
}),
93+
});
94+
95+
if (!updateResponse.ok) {
96+
throw new Error(`Failed to update gist: ${updateResponse.status}`);
97+
}
98+
99+
return res.status(200).json({ success: true, stats: updatedStats });
100+
} catch (error) {
101+
console.error('Error updating stats:', error);
102+
return res.status(500).json({ error: error.message });
103+
}
104+
}

0 commit comments

Comments
 (0)