// @deno-types="https://unpkg.com/pocketbase@0.8.3/dist/pocketbase.es.d.ts" import PocketBase from "https://unpkg.com/pocketbase@0.8.3/dist/pocketbase.es.mjs"; import { serve } from "https://deno.land/std/http/server.ts"; import { Md5 } from "https://deno.land/std@0.160.0/hash/md5.ts"; import "https://deno.land/std/dotenv/load.ts"; const allowOrigin = Deno.env.get("ALLOW_ORIGIN")?.split(","); const webhookUrl = Deno.env.get("WEBHOOK_URL"); let allowedOrigin = "*"; const pb = new PocketBase(Deno.env.get("PB_URL")); const _authData = await pb.collection("users").authWithPassword( Deno.env.get("PB_USER"), Deno.env.get("PB_PASSWORD"), ); // Validate Url const isValidUrl = (url: string) => { if (url === "") { return true; // "website" filed is optional } else { try { new URL(url); } catch (e) { console.error(e); return false; } return true; } }; async function handler(req: Request): Promise { const url = new URL(req.url); // console.log(req.method, url.pathname, "uri:", url.searchParams.get("uri")); const reqestOrigin = req.headers.get("origin"); if (reqestOrigin === null || !allowOrigin.includes(reqestOrigin)) { return new Response("Request is rejected due to CORS policy."); } else { allowedOrigin = reqestOrigin; } // List comments for a given page uri if (req.method === "GET" && url.searchParams.get("uri") !== null) { const resultList = await pb.collection("comments").getFullList(0, { filter: `uri='${url.searchParams.get("uri")}'`, sort: "created,-parent", }); const commentlist: { id: string; author: string; avatar: string; website: string; content: string; created: string; reply: unknown[]; }[] = []; resultList.forEach((item) => { if (item.parent === "") { commentlist.push({ id: item.id, author: item.author, avatar: new Md5().update(item.email).toString(), website: item.website, content: item.content, created: item.created, reply: [], }); } else { const index = commentlist.findIndex((e) => e.id === item.parent); commentlist[index].reply.push({ id: item.id, author: item.author, avatar: new Md5().update(item.email).toString(), website: item.website, content: item.content, created: item.created, }); } }); const body = JSON.stringify({ count: resultList.length, list: commentlist.reverse(), }); return new Response(body, { status: 200, headers: { "Content-Type": "application/json; charset=UTF-8", "Access-Control-Allow-Origin": allowedOrigin, }, }); } // handle new comments if (req.method === "POST") { const newComment = await req.json(); if (!newComment.author || !newComment.email || !newComment.content) { return new Response("名字、邮箱、评论内容不能为空"); } else if (!isValidUrl(newComment.website)) { return new Response("网址格式错误"); } else { const record = await pb.collection("comments").create({ "uri": newComment.uri, "author": newComment.author, "email": newComment.email, "website": newComment.website, "content": newComment.content, "parent": newComment.parent, }); const body = JSON.stringify({ id: record.id, author: record.author, avatar: new Md5().update(record.email).toString(), website: record.website, content: record.content, created: record.created, }); if (webhookUrl) { fetch(webhookUrl, { method: "POST", body: body, }); } return new Response(body, { status: 200, headers: { "Content-Type": "application/json; charset=UTF-8", "Access-Control-Allow-Origin": allowedOrigin, }, }); } } if (req.method === "OPTIONS") { return new Response(null, { status: 204, headers: { "Access-Control-Allow-Methods": "GET, POST", "Access-Control-Allow-Origin": allowedOrigin, "Access-Control-Allow-Headers": "Origin, Referer, Content-Type", }, }); } return new Response("Bad request!"); } serve(handler);