aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/docs/pages/api
diff options
context:
space:
mode:
authorHsiangNianian <admin@jyunko.cn>2023-04-22 19:52:26 +0800
committerHsiangNianian <admin@jyunko.cn>2023-04-22 19:52:26 +0800
commit4838df315931bb883f704ec3e1abe2685f296cdf (patch)
tree57a8550c4cd5338f1126364bb518c6cde8d96e7d /docs/pages/api
parentdb74ade0234a40c2120ad5f2a41bee50ce13de02 (diff)
downloadHydroRoll-4838df315931bb883f704ec3e1abe2685f296cdf.tar.gz
HydroRoll-4838df315931bb883f704ec3e1abe2685f296cdf.zip
😀
Diffstat (limited to 'docs/pages/api')
-rw-r--r--docs/pages/api/binaries/version.ts113
-rw-r--r--docs/pages/api/og.tsx167
-rw-r--r--docs/pages/api/signup.tsx33
-rw-r--r--docs/pages/api/user/[id].tsx36
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);