diff options
Diffstat (limited to 'docs/pages/api')
| -rw-r--r-- | docs/pages/api/binaries/version.ts | 113 | ||||
| -rw-r--r-- | docs/pages/api/og.tsx | 167 | ||||
| -rw-r--r-- | docs/pages/api/signup.tsx | 33 | ||||
| -rw-r--r-- | docs/pages/api/user/[id].tsx | 36 |
4 files changed, 349 insertions, 0 deletions
diff --git a/docs/pages/api/binaries/version.ts b/docs/pages/api/binaries/version.ts new file mode 100644 index 0000000..339a34d --- /dev/null +++ b/docs/pages/api/binaries/version.ts @@ -0,0 +1,113 @@ +import type { NextRequest } from "next/server"; + +const REGISTRY = "https://registry.npmjs.org"; +const DEFAULT_TAG = "latest"; +const SUPPORTED_PACKAGES = ["turbo"]; +const SUPPORTED_METHODS = ["GET"]; +const [DEFAULT_NAME] = SUPPORTED_PACKAGES; + +async function fetchDistTags({ name }: { name: string }) { + const result = await fetch(`${REGISTRY}/${name}`); + const json = await result.json(); + return json["dist-tags"]; +} + +function errorResponse({ + status, + message, +}: { + status: 400 | 404 | 500; + message: string; +}) { + return new Response( + JSON.stringify({ + error: message, + }), + { + status, + } + ); +} + +/* +This API is called via the turbo rust binary to check for version updates. + +Response Schema (200): +{ + "type": "object", + "properties": { + "name": { + "type": "string", + }, + "version": { + "type": "string", + }, + "tag": { + "type": "string", + } + } +} + +Errors (400 | 404 | 500): +{ + "type": "object", + "properties": { + "error": { + "type": "string", + } + } +} + +*/ +export default async function handler(req: NextRequest) { + if (!SUPPORTED_METHODS.includes(req.method)) { + return errorResponse({ + status: 404, + message: `unsupported method - ${req.method}`, + }); + } + + try { + const { searchParams } = new URL(req.url); + const name = searchParams.get("name") || DEFAULT_NAME; + const tag = searchParams.get("tag") || DEFAULT_TAG; + + if (!SUPPORTED_PACKAGES.includes(name)) { + return errorResponse({ + status: 400, + message: `unsupported package - ${name}`, + }); + } + + const versions = await fetchDistTags({ name }); + if (!versions || !versions[tag]) { + return errorResponse({ + status: 404, + message: `unsupported tag - ${tag}`, + }); + } + + return new Response( + JSON.stringify({ + name, + version: versions[tag], + tag, + }), + { + status: 200, + headers: { + "content-type": "application/json", + // cache for 15 minutes, and allow stale responses for 5 minutes + "cache-control": "public, s-maxage=900, stale-while-revalidate=300", + }, + } + ); + } catch (e) { + console.error(e); + return errorResponse({ status: 500, message: e.message }); + } +} + +export const config = { + runtime: "experimental-edge", +}; diff --git a/docs/pages/api/og.tsx b/docs/pages/api/og.tsx new file mode 100644 index 0000000..196ca72 --- /dev/null +++ b/docs/pages/api/og.tsx @@ -0,0 +1,167 @@ +import React, { createElement } from "react"; +import { ImageResponse } from "@vercel/og"; + +import PackLogo from "../../components/logos/og/PackLogo"; +import RepoLogo from "../../components/logos/og/RepoLogo"; +import TurboLogo from "../../components/logos/og/TurboLogo"; +import VercelLogo from "../../components/logos/og/VercelLogo"; + +import type { NextApiRequest } from "next/index"; + +function _arrayBufferToBase64(buffer) { + var binary = ""; + var bytes = new Uint8Array(buffer); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); +} + +async function loadAssets(): Promise< + [ + { name: string; data: ArrayBuffer; weight: 400 | 700; style: "normal" }[], + string + ] +> { + const [inter, spaceMono, bg] = await Promise.all([ + fetch( + String(new URL("../../assets/inter-v12-latin-700.ttf", import.meta.url)) + ).then((res) => res.arrayBuffer()), + fetch( + String( + new URL( + "../../assets/space-mono-v12-latin-regular.ttf", + import.meta.url + ) + ) + ).then((res) => res.arrayBuffer()), + fetch(String(new URL("../../assets/bg.jpeg", import.meta.url))).then( + (res) => res.arrayBuffer() + ), + ]); + return [ + [ + { + name: "Inter", + data: inter, + weight: 700 as const, + style: "normal" as const, + }, + { + name: "Space Mono", + data: spaceMono, + weight: 400 as const, + style: "normal" as const, + }, + ], + _arrayBufferToBase64(bg), + ]; +} + +export const config = { + runtime: "experimental-edge", +}; + +export default async function openGraphImage( + req: NextApiRequest +): Promise<ImageResponse> { + try { + const [fonts, bg] = await loadAssets(); + const { searchParams } = new URL(req.url); + + const type = searchParams.get("type"); + + // ?title=<title> + const hasTitle = searchParams.has("title"); + const title = hasTitle + ? searchParams.get("title")?.slice(0, 100) + : type === "pack" + ? "The successor to Webpack" + : type === "repo" + ? "The build system that makes ship happen" + : ""; + + return new ImageResponse(createElement(OGImage, { title, type, bg }), { + width: 1200, + height: 630, + fonts, + }); + } catch (e: unknown) { + return new Response(undefined, { + status: 302, + headers: { + Location: "https://turbo.build/og-image.png", + }, + }); + } +} + +export function OGImage({ + title, + type, + bg, +}: { + title: string; + type: string; + bg: string; +}): JSX.Element { + return ( + <div + style={{ + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + width: "100%", + height: "100%", + fontFamily: "Inter", + fontWeight: 700, + fontSize: 60, + backgroundImage: `url(data:image/jpeg;base64,${bg})`, + backgroundSize: "1200px 630px", + color: "#fff", + }} + > + {/* eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text */} + <div style={{ display: "flex", height: 97 * 1.1, alignItems: "center" }}> + {type === "pack" ? ( + <PackLogo height={103 * 1.1} width={697 * 1.1} /> + ) : type === "repo" ? ( + <RepoLogo height={83 * 1.1} width={616 * 1.1} /> + ) : ( + <TurboLogo height={97 * 1.1} width={459 * 1.1} /> + )} + </div> + {title ? ( + <div + style={{ + fontFamily: "Space Mono", + fontSize: 36, + letterSpacing: -1.5, + padding: "15px 20px 30px", + textAlign: "center", + backgroundImage: "linear-gradient(to bottom, #fff, #aaa)", + backgroundClip: "text", + color: "transparent", + }} + > + {title} + </div> + ) : null} + <div + style={{ + fontFamily: "Space Mono", + fontSize: 18, + marginTop: 80, + display: "flex", + color: "#fff", + alignItems: "center", + }} + > + <div style={{ marginRight: 12 }}>by</div> + <VercelLogo fill="white" height={30} /> + </div> + </div> + ); +} diff --git a/docs/pages/api/signup.tsx b/docs/pages/api/signup.tsx new file mode 100644 index 0000000..0a082ee --- /dev/null +++ b/docs/pages/api/signup.tsx @@ -0,0 +1,33 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { withSentry } from "@sentry/nextjs"; + +const CAMPAIGN_ID = process.env.TURBOREPO_SFDC_CAMPAIGN_ID; +const TRAY_URL = process.env.TRAY_URL; + +async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method === "POST") { + const user = { + email: req.body.email, + campaign_id: CAMPAIGN_ID, + }; + + try { + const trayRes = await fetch(TRAY_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ user: user }), + }); + + return res.status(201).json(user); + } catch (error) { + return res.status(500).json(error); + } + } else { + return res.status(404).send(null); + } +} + +export default withSentry(handler); diff --git a/docs/pages/api/user/[id].tsx b/docs/pages/api/user/[id].tsx new file mode 100644 index 0000000..091d716 --- /dev/null +++ b/docs/pages/api/user/[id].tsx @@ -0,0 +1,36 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { withSentry } from "@sentry/nextjs"; +import { + getSubscriber, + Subscriber, + updateSubscriber, +} from "../../../lib/ConvertKitApi"; + +async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + if (req.method === "PUT") { + const subscriber = await updateSubscriber( + req.query.id as string, + { + first_name: req.body.first_name, + email_address: req.body.email_address, + fields: req.body.fields, + } as Subscriber + ); + res.setHeader("Content-Type", "application/json"); + res.statusCode = 204; + res.json(subscriber); + } else { + const subscriber = await getSubscriber(req.query.id as string); + res.setHeader("Content-Type", "application/json"); + res.statusCode = 200; + res.json(subscriber); + } + } catch (error) { + console.log(error); + res.statusCode = 500; + res.json({ okay: false }); + } +} + +export default withSentry(handler); |
