import type { IncomingHttpHeaders } from "http"; import type { NextApiRequest, NextApiResponse } from "next"; import type { WebhookRequiredHeaders } from "svix"; import { Webhook } from "svix"; import { User, prisma } from "@acme/db"; const webhookSecret: string = process.env.NEXT_WEBHOOK_SECRET || ""; type UnwantedKeys = | "emailAddresses" | "firstName" | "lastName" | "primaryEmailAddressId" | "primaryPhoneNumberId" | "phoneNumbers"; interface UserInterface extends Omit { primary_email_address_id: string | null; first_name: string | null; last_name: string | null; username: string | null; primary_phone_number_id: string | null; unsafe_metadata: { gstNumber: string | null; latitude: string | null; longitude: string | null; }; phone_numbers: { phone_number: string | null; id: string | null; }[]; } const getAddress = async ({ latitude, longitude, }: { latitude: string; longitude: string; }) => { const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&zoom=18&addressdetails=1`; try { const response = await fetch(url); const data = await response.json(); const address = data.address; // The district may be located in either the "suburb" or "town" field const district = address.state_district; return district; } catch (error) { console.error(error); return "Error Can't find District"; } }; export default async function handler( req: NextApiRequestWithSvixRequiredHeaders, res: NextApiResponse, ) { try { const payload = JSON.stringify(req.body); const headers = req.headers; const wh = new Webhook(webhookSecret); let evt: Event | null = null; try { evt = wh.verify(payload, headers) as Event; } catch (error) { console.log(error); return res.status(400).json(error); } console.log("===> ~ file: create-user.ts:44 ~ evt:", evt); const { id, username } = evt.data; // Handle the webhook const eventType: EventType = evt.type; if (eventType === "user.created" || eventType === "user.updated") { const { username, id, first_name, last_name } = evt.data; const { gstNumber, latitude, longitude } = evt.data.unsafe_metadata; const name = `${first_name || ""} ${last_name || ""}`; if (!username) { return res .status(400) .json({ error: "Please provide phone number as username" }); } console.log("===> ~ file: create-user.ts:61 ~ Creating User with", { id: id, phoneNumber: username, gstNumber: "", role: "BASIC", name: name, subscribedPlans: { create: { enabledPlan: "BASIC" } }, }); await prisma.user .upsert({ where: { id: id }, update: { name: name, phoneNumber: username, gstNumber: gstNumber ?? "", }, create: { id: id, phoneNumber: username, gstNumber: gstNumber ?? "", role: "BASIC", name: name, subscribedPlans: { create: { enabledPlan: "BASIC" } }, }, }) .catch((e) => { console.log(`User with id: ${id}\n, can't add/update new account`); res.status(500).json({ response: `User with id: ${id}\n, can't add/update new account\n error: ${e}`, }); }); if (latitude && longitude) { const addressResponse = await getAddress({ latitude, longitude }); await prisma.address .upsert({ where: { userId: id }, update: { stateDistrict: addressResponse.toString() ?? "error", latitude: Number(latitude), longitude: Number(longitude), }, create: { userId: id, latitude: Number(latitude), longitude: Number(longitude), stateDistrict: addressResponse.toString() ?? "error", }, }) .then((value) => console.log("===> ", value)) .catch((e) => { console.log(`User with id: ${id}\n, can't add/update address`); res.status(500).json({ response: `User with id: ${id}\n, can't add/update address\n error: ${e}`, }); }); } console.log(`User ${id} was ${eventType}`); res.status(201).json({ response: `Created user with userId ${id} and phone number ${username}`, }); } else { console.log(`User ${id} cannot be ${eventType}`); res.status(500).json({ response: `User with userId ${id} and phone number ${username} cannot be created/updated`, }); } } catch (error) { console.log(`Error: ${error}`); res.status(500).json({ response: `Encountered an error`, }); } } type NextApiRequestWithSvixRequiredHeaders = NextApiRequest & { headers: IncomingHttpHeaders & WebhookRequiredHeaders; }; type Event = { data: UserInterface; object: "event"; type: EventType; }; type EventType = "user.created" | "user.updated" | "*";