Skip to content

Instantly share code, notes, and snippets.

@johnmaguire
Last active January 7, 2024 05:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnmaguire/5a551fcdd42fb60e69907aa11033d57b to your computer and use it in GitHub Desktop.
Save johnmaguire/5a551fcdd42fb60e69907aa11033d57b to your computer and use it in GitHub Desktop.
00:18:51.513 Cloning repository...
00:18:52.660 From https://github.com/johnmaguire/helical-piers
00:18:52.660 * branch 09e1545825ca73d66f47bf32855c606e091a0c65 -> FETCH_HEAD
00:18:52.661
00:18:52.738 HEAD is now at 09e1545 Try logging values for contact form
00:18:52.739
00:18:52.849
00:18:52.883 Success: Finished cloning repository files
00:18:53.723 Detected the following tools from environment: npm@9.6.7, nodejs@18.17.1
00:18:53.724 Installing project dependencies: npm install --progress=false
00:19:23.714
00:19:23.714 added 883 packages, and audited 884 packages in 29s
00:19:23.715
00:19:23.715 347 packages are looking for funding
00:19:23.715 run `npm fund` for details
00:19:23.716
00:19:23.717 found 0 vulnerabilities
00:19:23.749 Executing user command: npm run build
00:19:24.671
00:19:24.672 > @onwidget/astrowind@1.0.0-beta.6 build
00:19:24.672 > astro build
00:19:24.672
00:19:27.843 05:19:27 [WARN] [@astrojs/cloudflare] The current configuration does not support image optimization. To allow your project to build with the original, unoptimized images, the image service has been automatically switched to the 'noop' option. See https://docs.astro.build/en/reference/configuration-reference/#imageservice
00:19:28.265 05:19:28 [WARN] [content] The post collection is defined but no content/post folder exists in the content directory. Create a new folder for the collection, or check your content configuration file for typos.
00:19:28.266 05:19:28 Types generated 370ms
00:19:28.267 05:19:28 [build] output: "hybrid"
00:19:28.267 05:19:28 [build] directory: /opt/buildhome/repo/dist/
00:19:28.268 05:19:28 [build] adapter: @astrojs/cloudflare
00:19:28.268 05:19:28 [build] Collecting build info...
00:19:28.269 05:19:28 [build] ✓ Completed in 489ms.
00:19:28.272 05:19:28 [build] Building hybrid entrypoints...
00:19:30.612 05:19:30 [astro-icon] Loaded icons from flat-color-icons, tabler
00:19:31.394 05:19:31 [build] ✓ Completed in 3.12s.
00:19:31.415
00:19:31.415 prerendering static routes
00:19:31.555 05:19:31 ▶ src/pages/index.astro
00:19:31.600 05:19:31 └─ /index.html (+44ms)
00:19:31.601 05:19:31 ▶ src/pages/contact.astro
00:19:31.612 05:19:31 └─ /contact/index.html (+10ms)
00:19:31.614 05:19:31 ▶ src/pages/about.astro
00:19:31.636 05:19:31 └─ /about/index.html (+22ms)
00:19:31.641 05:19:31 ▶ src/pages/404.astro
00:19:31.642 05:19:31 └─ /404.html (+4ms)
00:19:31.643 05:19:31 ✓ Completed in 238ms.
00:19:31.643
00:19:31.643 generating optimized images
00:19:31.650 05:19:31 ▶ /_astro/default.Th1dKr9c_ZJGkcR.webp (before: 285kB, after: 285kB) (+4ms) (1/1)
00:19:31.650 05:19:31 ✓ Completed in 5ms.
00:19:31.651
00:19:31.676 05:19:31
00:19:31.677 finalizing server assets
00:19:31.677
00:19:31.677 05:19:31 [build] Rearranging server assets...
00:19:31.689 05:19:31 [@astrojs/sitemap] `sitemap-index.xml` created at `dist`
00:19:31.691
00:19:31.691 AstroCompress processing
00:19:31.915 ✓ Successfully compressed a total of 1 CSS file for 530 Bytes.
00:19:32.466 ✓ Successfully compressed a total of 4 HTML files for 10.56 KB.
00:19:33.127 ✓ Successfully compressed a total of 17 JavaScript files for 69.5 KB.
00:19:33.306 ✓ Successfully compressed a total of 1 SVG file for 105 Bytes.
00:19:33.355 05:19:33 [build] Server built in 5.58s
00:19:33.356 05:19:33 [build] Complete!
00:19:33.405 Finished
00:19:33.405 Found Functions directory at /functions. Uploading.
00:19:34.801 ✨ Compiled Worker successfully
00:19:34.868 Found _routes.json in output directory. Uploading.
00:19:34.887 Validating asset output directory
00:19:35.644 Deploying your site to Cloudflare's global network...
00:19:38.100 Parsed 1 valid header rule.
00:19:39.722 Uploading... (23/23)
00:19:39.722 ✨ Success! Uploaded 0 files (23 already uploaded) (0.54 sec)
00:19:39.723
00:19:39.988 ✨ Upload complete!
00:19:42.285 Success: Assets published!
00:19:44.563 Success: Your site was deployed!
Routing configuration
Configuration of routing for this deployment. This file is generated based on the files present in the /functions directory.
Using routing
{
"routes": [
{
"routePath": "/do/contact",
"mountPath": "/do",
"method": "POST",
"module": [
"do/contact.ts:onRequestPost"
]
},
{
"routePath": "/:path*",
"mountPath": "/",
"method": "",
"module": [
"[[path]].js:onRequest"
]
}
],
"baseURL": "/"
}
Invocation routes
Invocation routes determine when your Functions script is executed. This file is generated based on the files present in the /functions directory.
Using billable routes
{
"version": 1,
"include": [
"/_image"
],
"exclude": []
}
❯ tree functions
functions
├── [[path]].js
└── do
└── contact.ts
2 directories, 2 files
❯ cat functions/do/contact.ts
interface Env {}
// ========= API ==========
interface EmailAddress {
email: string;
name?: string;
}
export interface Personalization {
to: [EmailAddress, ...EmailAddress[]];
from?: EmailAddress;
dkim_domain?: string;
dkim_private_key?: string;
dkim_selector?: string;
reply_to?: EmailAddress;
cc?: EmailAddress[];
bcc?: EmailAddress[];
subject?: string;
headers?: Record<string, string>;
}
export interface ContentItem {
type: string;
value: string;
}
export interface MailSendBody {
personalizations: [Personalization, ...Personalization[]];
from: EmailAddress;
reply_to?: EmailAddress;
subject: string;
content: [ContentItem, ...ContentItem[]];
headers?: Record<string, string>;
}
interface Success {
success: true;
}
interface Failure {
success: false;
errors: string[];
}
export const sendEmail = async (
payload: MailSendBody
): Promise<Success | Failure> => {
const response = await fetch("https://api.mailchannels.net/tx/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (response.status === 202) return { success: true };
try {
const { errors } = await response.clone().json();
return { success: false, errors };
} catch {
return { success: false, errors: [response.statusText] };
}
};
// ========== Types ==========
interface Submission {
request: Request;
formData: FormData;
}
// ========== Email Customization ==========
const textPlainContent = ({ request, formData }: Submission) => {
return `At ${new Date().toISOString()}, you received a new form submission from ${request.headers.get(
"CF-Connecting-IP"
)}:
${[...formData.entries()]
.map(
([field, value]) => `${field}
${value}
`
)
.join("\n")}`;
};
const textHTMLContent = ({ request, formData }: Submission) => {
return `<!DOCTYPE html>
<html>
<body>
<h1>New contact form submission</h1>
<div>At ${new Date().toISOString()}, you received a new form submission from ${request.headers.get(
"CF-Connecting-IP"
)}:</div>
<table>
<tbody>
${[...formData.entries()]
.map(
([field, value]) =>
`<tr><td><strong>${field}</strong></td><td>${value}</td></tr>`
)
.join("\n")}
</tbody>
</table>
</body>
</html>`;
};
// ========== Handler ==========
export const onRequestPost: PagesFunction<Env> = async(context) => {
console.log("Received contact form request");
const formData = await context.request.formData();
const request = context.request;
const submission: Submission = { formData,request };
const personalizations: MailSendBody["personalizations"] = [
{
to: [{ email: "contact@johnmaguire.me", name: "John Maguire" }],
},
];
// This should be set via the form body
const from: MailSendBody["from"] = { email: "no-reply@mipiersupport.com", name: "MI Pier Support" };
const subject: MailSendBody["subject"] = "New contact form submission"
const content: MailSendBody["content"] =
[
{
type: "text/plain",
value: textPlainContent(submission),
},
{
type: "text/html",
value: textHTMLContent(submission),
},
];
const { success } = await sendEmail({
personalizations,
from,
subject,
content,
});
console.log("Finishing request");
if (success) {
return Response.redirect('https://www.mipiersupport.com/contact?success=true');
}
return new Response(`Could not send your email. Please try again.`, {
status: 512,
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment