Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | 28x 1x 1x 1x 15x 15x 15x 15x 15x 15x 15x 3x 12x 15x 11x 11x 4x 4x 4x 4x 1x 11x 1x 1x | import { Hono } from "hono";
import { feedback } from "../../db/schema";
import { createEmailService } from "../../lib/email";
import { success, handleError } from "../../lib/response";
import { ValidationError } from "../../lib/errors";
import { logger } from "../../lib/logger";
function escHtml(str: string): string {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
}
const feedbackRoutes = new Hono<{
Bindings: CloudflareBindings;
Variables: {
user: { id: string; name: string; email: string } | null;
session: unknown;
proId: string;
proRole: string;
db: ReturnType<typeof import("../../db").getDb>;
};
}>();
const VALID_CATEGORIES = ["general", "bug", "feature_request"] as const;
feedbackRoutes.post("/feedback", async (c) => {
try {
const user = c.get("user");
const proId = c.get("proId");
const db = c.get("db");
const body = await c.req.json();
const { category, message, pageUrl, browser, screenResolution, location } =
body;
// Validate
if (!message || typeof message !== "string" || message.trim().length === 0) {
return handleError(c, new ValidationError("Message is required"));
}
const validCategory =
category && VALID_CATEGORIES.includes(category)
? category
: "general";
// Insert feedback
const [inserted] = await db
.insert(feedback)
.values({
userId: user?.id ?? null,
proId: proId ?? null,
category: validCategory,
message: message.trim(),
pageUrl: pageUrl ?? null,
userEmail: user?.email ?? null,
browser: browser ?? null,
screenResolution: screenResolution ?? null,
location: location ?? null,
})
.returning({ id: feedback.id });
// Send email notification (non-blocking — don't fail the request if email fails)
const notifyEmail = c.env.FEEDBACK_NOTIFY_EMAIL;
if (notifyEmail) {
try {
const emailService = createEmailService(c.env);
const truncatedMessage =
message.length > 50 ? `${message.substring(0, 50)}...` : message;
await emailService.sendRawEmail(
notifyEmail,
`[Interioring Feedback] ${validCategory}: ${truncatedMessage}`,
`
<h2>New Feedback Received</h2>
<p><strong>Category:</strong> ${escHtml(validCategory)}</p>
<p><strong>From:</strong> ${escHtml(user?.email ?? "Unknown")}</p>
<p><strong>Page:</strong> ${escHtml(pageUrl ?? "N/A")}</p>
<p><strong>Message:</strong></p>
<blockquote>${escHtml(message)}</blockquote>
<hr>
<p><strong>Browser:</strong> ${escHtml(browser ?? "N/A")}</p>
<p><strong>Resolution:</strong> ${escHtml(screenResolution ?? "N/A")}</p>
<p><strong>Timezone:</strong> ${escHtml(location ?? "N/A")}</p>
<p><strong>Submitted:</strong> ${new Date().toISOString()}</p>
`,
);
} catch (emailErr) {
logger.error("[FEEDBACK] Email notification failed:", emailErr);
}
}
return success(c, { id: inserted.id, message: "Feedback submitted successfully" }, 201);
} catch (err) {
logger.error("[FEEDBACK] Error submitting feedback:", err);
return handleError(c, err);
}
});
export default feedbackRoutes;
|