Openstatus www.openstatus.dev
at 4c0f4c00a38753a5d0dfd7e7b7b7706dec6f1503 60 lines 2.1 kB view raw
1import { TRPCError } from "@trpc/server"; 2import { z } from "zod"; 3import { env } from "../env"; 4import { createTRPCRouter, protectedProcedure } from "../trpc"; 5 6export const feedbackRouter = createTRPCRouter({ 7 submit: protectedProcedure 8 .input( 9 z.object({ 10 // NOTE: coming from NavFeedback 11 message: z.string().min(1, "Message required"), 12 path: z.string().optional(), 13 isMobile: z.boolean().optional(), 14 // NOTE: coming from ContactForm 15 name: z.string().optional(), 16 email: z.email().optional(), 17 blocker: z.boolean().optional(), 18 type: z.string().optional(), 19 }), 20 ) 21 .mutation(async (opts) => { 22 if (!env.SLACK_FEEDBACK_WEBHOOK_URL) { 23 throw new TRPCError({ 24 code: "INTERNAL_SERVER_ERROR", 25 message: "Slack feedback webhook not configured.", 26 }); 27 } 28 29 const textLines: string[] = []; 30 if (opts.input.name) textLines.push(`*Name:* ${opts.input.name}`); 31 if (opts.input.email) textLines.push(`*Email:* ${opts.input.email}`); 32 if (opts.input.blocker) 33 textLines.push(`*Blocker:* ${opts.input.blocker}`); 34 if (opts.input.type) textLines.push(`*Type:* ${opts.input.type}`); 35 if (opts.ctx.user) textLines.push(`*User:* ${opts.ctx.user.email}`); 36 if (opts.input.path) textLines.push(`*Path:* ${opts.input.path}`); 37 if (opts.input.isMobile) 38 textLines.push(`*Mobile:* ${opts.input.isMobile}`); 39 if (opts.ctx.metadata?.userAgent) 40 textLines.push(`*User Agent:* ${opts.ctx.metadata.userAgent}`); 41 if (opts.ctx.metadata?.location) 42 textLines.push(`*Location:* ${opts.ctx.metadata.location}`); 43 44 textLines.push("--------------------------------"); 45 46 textLines.push(`*Message:* ${opts.input.message}`); 47 48 await fetch(env.SLACK_FEEDBACK_WEBHOOK_URL, { 49 method: "POST", 50 headers: { 51 "Content-Type": "application/json", 52 }, 53 body: JSON.stringify({ 54 text: textLines.join("\n"), 55 }), 56 }); 57 58 return { success: true } as const; 59 }), 60});