(req: VercelRequest, res: VercelResponse)
| 115 | // ---------- Handler ---------- |
| 116 | |
| 117 | export default async function handler(req: VercelRequest, res: VercelResponse) { |
| 118 | if (req.method !== "POST") { |
| 119 | return res.status(405).json({ error: "Method not allowed" }); |
| 120 | } |
| 121 | |
| 122 | // Rate limiting is handled by Vercel Firewall (project dashboard) |
| 123 | |
| 124 | // Validate |
| 125 | const { |
| 126 | page_url: pageUrl, |
| 127 | rating, |
| 128 | reason, |
| 129 | timestamp, |
| 130 | } = req.body as FeedbackBody; |
| 131 | |
| 132 | if (!pageUrl || !rating || !["yes", "no"].includes(rating)) { |
| 133 | return res |
| 134 | .status(400) |
| 135 | .json({ error: "page_url and rating (yes/no) are required" }); |
| 136 | } |
| 137 | |
| 138 | if (!isValidPageUrl(pageUrl)) { |
| 139 | return res.status(400).json({ error: "invalid page_url" }); |
| 140 | } |
| 141 | |
| 142 | const cleanReason = reason ? sanitizeFeedbackText(reason) : ""; |
| 143 | |
| 144 | if (rating === "no" && !cleanReason) { |
| 145 | return res |
| 146 | .status(400) |
| 147 | .json({ error: "reason is required for negative feedback" }); |
| 148 | } |
| 149 | const ts = timestamp ?? new Date().toISOString(); |
| 150 | |
| 151 | // Google Sheet — every submission |
| 152 | try { |
| 153 | await appendToSheet([ |
| 154 | ts, |
| 155 | pageUrl, |
| 156 | rating, |
| 157 | cleanReason, |
| 158 | getDeviceType((req.headers["user-agent"] as string) ?? ""), |
| 159 | ]); |
| 160 | } catch (err) { |
| 161 | console.error("Google Sheets append failed:", err); |
| 162 | // Don't block the response |
| 163 | } |
| 164 | |
| 165 | // Negative + reason → GitHub issue, then Slack with issue link |
| 166 | if (rating === "no" && cleanReason) { |
| 167 | let issueUrl: string | null = null; |
| 168 | try { |
| 169 | issueUrl = await createGitHubIssue(pageUrl, cleanReason); |
| 170 | } catch (err) { |
| 171 | console.error("GitHub issue failed:", err); |
| 172 | } |
| 173 | |
| 174 | try { |
nothing calls this directly
no test coverage detected