1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-07-21 19:21:18 -04:00

finally got myself a real contact form 📬

This commit is contained in:
2021-10-14 08:12:40 -04:00
parent d4ad28b494
commit 2106b3703d
17 changed files with 403 additions and 54 deletions

98
api/contact.js Normal file
View File

@@ -0,0 +1,98 @@
import fetch from "node-fetch";
import queryString from "query-string";
// fallback to dummy secret for testing: https://docs.hcaptcha.com/#integration-testing-test-keys
const HCAPTCHA_SITE_KEY = process.env.HCAPTCHA_SITE_KEY || "10000000-ffff-ffff-ffff-000000000001";
const HCAPTCHA_SECRET_KEY =
process.env.HCAPTCHA_SECRET_KEY || "0x0000000000000000000000000000000000000000";
const HCAPTCHA_API_ENDPOINT = "https://hcaptcha.com/siteverify";
const { AIRTABLE_API_KEY, AIRTABLE_BASE } = process.env;
const AIRTABLE_API_ENDPOINT = `https://api.airtable.com/v0/`;
export default async (req, res) => {
// disable caching on both ends
res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate");
res.setHeader("Expires", 0);
res.setHeader("Pragma", "no-cache");
// permissive access control headers
res.setHeader("Access-Control-Allow-Methods", "POST");
res.setHeader("Access-Control-Allow-Origin", "*");
try {
// some rudimentary error handling
if (req.method !== "POST") {
throw new Error(`Method ${req.method} not allowed.`);
}
if (!AIRTABLE_API_KEY || !AIRTABLE_BASE) {
throw new Error("Airtable API credentials aren't set.");
}
const { body } = req;
// all fields are required
if (!body.name || !body.email || !body.message) {
throw new Error("missingData");
}
// either the captcha is wrong or completely missing
if (!body["h-captcha-response"] || !(await validateCaptcha(body["h-captcha-response"]))) {
throw new Error("invalidCaptcha");
}
// sent directly to airtable
const sendResult = await sendMessage({
Name: body.name,
Email: body.email,
Message: body.message,
});
// throw an internal error, not user's fault
if (sendResult !== true) {
throw new Error("airtableApiError");
}
// return in JSON format
res.status(200).json({ success: true });
} catch (error) {
console.error(error);
const message = error instanceof Error ? error.message : "Unknown error.";
res.status(400).json({ success: false, message: message });
}
};
const validateCaptcha = async (formResponse) => {
const response = await fetch(HCAPTCHA_API_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: queryString.stringify({
response: formResponse,
sitekey: HCAPTCHA_SITE_KEY,
secret: HCAPTCHA_SECRET_KEY,
}),
});
const result = await response.json();
return result.success;
};
const sendMessage = async (data) => {
const response = await fetch(`${AIRTABLE_API_ENDPOINT}${AIRTABLE_BASE}/Messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${AIRTABLE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
fields: data,
}),
});
return response.ok;
};

View File

@@ -3,7 +3,7 @@
import * as Sentry from "@sentry/node";
import fetch from "node-fetch";
import * as queryString from "query-string";
import queryString from "query-string";
const { SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REFRESH_TOKEN } = process.env;