add guestbook
This commit is contained in:
126
js/guestbook-worker.js
Normal file
126
js/guestbook-worker.js
Normal file
@@ -0,0 +1,126 @@
|
||||
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
};
|
||||
|
||||
const KV_KEY = 'messages';
|
||||
const MAX_MESSAGES = 500;
|
||||
const MAX_NAME_LENGTH = 30;
|
||||
const MAX_MESSAGE_LENGTH = 500;
|
||||
|
||||
|
||||
function jsonResponse(data, status = 200) {
|
||||
return new Response(JSON.stringify(data), {
|
||||
status,
|
||||
headers: { 'Content-Type': 'application/json', ...CORS_HEADERS },
|
||||
});
|
||||
}
|
||||
|
||||
function generateId() {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
function formatDate() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function sanitize(str) {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function hashIP(ip) {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(ip + '_guestbook_salt');
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 16);
|
||||
}
|
||||
|
||||
async function getMessages(env) {
|
||||
const raw = await env.GUESTBOOK.get(KV_KEY);
|
||||
return raw ? JSON.parse(raw) : [];
|
||||
}
|
||||
|
||||
async function saveMessages(env, messages) {
|
||||
await env.GUESTBOOK.put(KV_KEY, JSON.stringify(messages));
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request, env) {
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: CORS_HEADERS });
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
|
||||
if (request.method === 'GET' && path === '/messages') {
|
||||
const messages = await getMessages(env);
|
||||
const sanitized = messages.map(m => ({
|
||||
...m,
|
||||
likes: Array.isArray(m.likes) ? m.likes.length : (typeof m.likes === 'number' ? m.likes : 0),
|
||||
}));
|
||||
return jsonResponse(sanitized);
|
||||
}
|
||||
|
||||
if (request.method === 'POST' && path === '/messages') {
|
||||
const body = await request.json().catch(() => null);
|
||||
if (!body || !body.message || !body.message.trim()) {
|
||||
return jsonResponse({ error: 'Message is required' }, 400);
|
||||
}
|
||||
|
||||
const name = sanitize((body.name || 'Anonymous').trim().slice(0, MAX_NAME_LENGTH));
|
||||
const message = sanitize(body.message.trim().slice(0, MAX_MESSAGE_LENGTH));
|
||||
|
||||
const messages = await getMessages(env);
|
||||
const entry = {
|
||||
id: generateId(),
|
||||
name,
|
||||
message,
|
||||
date: formatDate(),
|
||||
likes: [],
|
||||
replies: [],
|
||||
};
|
||||
|
||||
messages.unshift(entry);
|
||||
if (messages.length > MAX_MESSAGES) messages.length = MAX_MESSAGES;
|
||||
await saveMessages(env, messages);
|
||||
|
||||
return jsonResponse(entry, 201);
|
||||
}
|
||||
|
||||
const likeMatch = path.match(/^\/messages\/([^/]+)\/like$/);
|
||||
if (request.method === 'POST' && likeMatch) {
|
||||
const messageId = likeMatch[1];
|
||||
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
||||
const ipHash = await hashIP(ip);
|
||||
|
||||
const messages = await getMessages(env);
|
||||
const msg = messages.find(m => m.id === messageId);
|
||||
if (!msg) return jsonResponse({ error: 'Message not found' }, 404);
|
||||
|
||||
if (!Array.isArray(msg.likes)) {
|
||||
const oldCount = typeof msg.likes === 'number' ? msg.likes : 0;
|
||||
msg.likes = [];
|
||||
|
||||
for (let i = 0; i < oldCount; i++) msg.likes.push('legacy_' + i);
|
||||
}
|
||||
const alreadyLiked = msg.likes.includes(ipHash);
|
||||
if (!alreadyLiked) {
|
||||
msg.likes.push(ipHash);
|
||||
}
|
||||
|
||||
await saveMessages(env, messages);
|
||||
|
||||
return jsonResponse({ likes: msg.likes.length, liked: true });
|
||||
}
|
||||
|
||||
return jsonResponse({ error: 'Not found' }, 404);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user