fix: og text

This commit is contained in:
chronark 2023-11-06 09:39:11 +01:00
parent 8f14ec4fbe
commit 68228c44fc
No known key found for this signature in database
19 changed files with 264 additions and 265 deletions

View File

@ -1,2 +1,2 @@
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
UPSTASH_REDIS_REST_TOKEN=

View File

@ -1,6 +1,6 @@
<div align="center">
<a href="https://chronark.com"><h1 align="center">chronark.com</h1></a>
My personal website, built with [Next.js](https://nextjs.org/), [Tailwind CSS](https://tailwindcss.com/), [Upstash](https://upstash.com?ref=chronark.com), [Contentlayer](https://www.contentlayer.dev/) and deployed to [Vercel](https://vercel.com/).
</div>
@ -30,4 +30,4 @@ pnpm dev
## Cloning / Forking
Please remove all of my personal information (projects, images, etc.) before deploying your own version of this site.
Please remove all of my personal information (projects, images, etc.) before deploying your own version of this site.

View File

@ -5,73 +5,72 @@ import { Metadata } from "next";
import { Analytics } from "./components/analytics";
export const metadata: Metadata = {
title: {
default: "chronark.com",
template: "%s | chronark.com",
},
description: "Software engineer at upstash.com and founder of planetfall.io",
openGraph: {
title: "chronark.com",
description:
"Software engineer at upstash.com and founder of planetfall.io",
url: "https://chronark.com",
siteName: "chronark.com",
images: [
{
url: "https://chronark.com/og.png",
width: 1920,
height: 1080,
},
],
locale: "en-US",
type: "website",
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
twitter: {
title: "Chronark",
card: "summary_large_image",
},
icons: {
shortcut: "/favicon.png",
},
title: {
default: "chronark.com",
template: "%s | chronark.com",
},
description: "Co-founder of unkey.dev and founder of planetfall.io",
openGraph: {
title: "chronark.com",
description:
"Co-founder of unkey.dev and founder of planetfall.io",
url: "https://chronark.com",
siteName: "chronark.com",
images: [
{
url: "https://chronark.com/og.png",
width: 1920,
height: 1080,
},
],
locale: "en-US",
type: "website",
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
twitter: {
title: "Chronark",
card: "summary_large_image",
},
icons: {
shortcut: "/favicon.png",
},
};
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
subsets: ["latin"],
variable: "--font-inter",
});
const calSans = LocalFont({
src: "../public/fonts/CalSans-SemiBold.ttf",
variable: "--font-calsans",
src: "../public/fonts/CalSans-SemiBold.ttf",
variable: "--font-calsans",
});
export default function RootLayout({
children,
children,
}: {
children: React.ReactNode;
children: React.ReactNode;
}) {
return (
<html lang="en" className={[inter.variable, calSans.variable].join(" ")}>
<head>
<Analytics />
</head>
<body
className={`bg-black ${
process.env.NODE_ENV === "development" ? "debug-screens" : undefined
}`}
>
{children}
</body>
</html>
);
return (
<html lang="en" className={[inter.variable, calSans.variable].join(" ")}>
<head>
<Analytics />
</head>
<body
className={`bg-black ${process.env.NODE_ENV === "development" ? "debug-screens" : undefined
}`}
>
{children}
</body>
</html>
);
}

View File

@ -9,40 +9,40 @@ import { Redis } from "@upstash/redis";
export const revalidate = 60;
type Props = {
params: {
slug: string;
};
params: {
slug: string;
};
};
const redis = Redis.fromEnv();
export async function generateStaticParams(): Promise<Props["params"][]> {
return allProjects
.filter((p) => p.published)
.map((p) => ({
slug: p.slug,
}));
return allProjects
.filter((p) => p.published)
.map((p) => ({
slug: p.slug,
}));
}
export default async function PostPage({ params }: Props) {
const slug = params?.slug;
const project = allProjects.find((project) => project.slug === slug);
const slug = params?.slug;
const project = allProjects.find((project) => project.slug === slug);
if (!project) {
notFound();
}
if (!project) {
notFound();
}
const views =
(await redis.get<number>(["pageviews", "projects", slug].join(":"))) ?? 0;
const views =
(await redis.get<number>(["pageviews", "projects", slug].join(":"))) ?? 0;
return (
<div className="bg-zinc-50 min-h-screen">
<Header project={project} views={views} />
<ReportView slug={project.slug} />
return (
<div className="bg-zinc-50 min-h-screen">
<Header project={project} views={views} />
<ReportView slug={project.slug} />
<article className="px-4 py-12 mx-auto prose prose-zinc prose-quoteless">
<Mdx code={project.body.code} />
</article>
</div>
);
<article className="px-4 py-12 mx-auto prose prose-zinc prose-quoteless">
<Mdx code={project.body.code} />
</article>
</div>
);
}

View File

@ -11,128 +11,128 @@ const redis = Redis.fromEnv();
export const revalidate = 60;
export default async function ProjectsPage() {
const views = (
await redis.mget<number[]>(
...allProjects.map((p) => ["pageviews", "projects", p.slug].join(":")),
)
).reduce((acc, v, i) => {
acc[allProjects[i].slug] = v ?? 0;
return acc;
}, {} as Record<string, number>);
const views = (
await redis.mget<number[]>(
...allProjects.map((p) => ["pageviews", "projects", p.slug].join(":")),
)
).reduce((acc, v, i) => {
acc[allProjects[i].slug] = v ?? 0;
return acc;
}, {} as Record<string, number>);
const featured = allProjects.find((project) => project.slug === "unkey")!;
const top2 = allProjects.find((project) => project.slug === "planetfall")!;
const top3 = allProjects.find((project) => project.slug === "highstorm")!;
const sorted = allProjects
.filter((p) => p.published)
.filter(
(project) =>
project.slug !== featured.slug &&
project.slug !== top2.slug &&
project.slug !== top3.slug,
)
.sort(
(a, b) =>
new Date(b.date ?? Number.POSITIVE_INFINITY).getTime() -
new Date(a.date ?? Number.POSITIVE_INFINITY).getTime(),
);
const featured = allProjects.find((project) => project.slug === "unkey")!;
const top2 = allProjects.find((project) => project.slug === "planetfall")!;
const top3 = allProjects.find((project) => project.slug === "highstorm")!;
const sorted = allProjects
.filter((p) => p.published)
.filter(
(project) =>
project.slug !== featured.slug &&
project.slug !== top2.slug &&
project.slug !== top3.slug,
)
.sort(
(a, b) =>
new Date(b.date ?? Number.POSITIVE_INFINITY).getTime() -
new Date(a.date ?? Number.POSITIVE_INFINITY).getTime(),
);
return (
<div className="relative pb-16">
<Navigation />
<div className="px-6 pt-20 mx-auto space-y-8 max-w-7xl lg:px-8 md:space-y-16 md:pt-24 lg:pt-32">
<div className="max-w-2xl mx-auto lg:mx-0">
<h2 className="text-3xl font-bold tracking-tight text-zinc-100 sm:text-4xl">
Projects
</h2>
<p className="mt-4 text-zinc-400">
Some of the projects are from work and some are on my own time.
</p>
</div>
<div className="w-full h-px bg-zinc-800" />
return (
<div className="relative pb-16">
<Navigation />
<div className="px-6 pt-20 mx-auto space-y-8 max-w-7xl lg:px-8 md:space-y-16 md:pt-24 lg:pt-32">
<div className="max-w-2xl mx-auto lg:mx-0">
<h2 className="text-3xl font-bold tracking-tight text-zinc-100 sm:text-4xl">
Projects
</h2>
<p className="mt-4 text-zinc-400">
Some of the projects are from work and some are on my own time.
</p>
</div>
<div className="w-full h-px bg-zinc-800" />
<div className="grid grid-cols-1 gap-8 mx-auto lg:grid-cols-2 ">
<Card>
<Link href={`/projects/${featured.slug}`}>
<article className="relative w-full h-full p-4 md:p-8">
<div className="flex items-center justify-between gap-2">
<div className="text-xs text-zinc-100">
{featured.date ? (
<time dateTime={new Date(featured.date).toISOString()}>
{Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
}).format(new Date(featured.date))}
</time>
) : (
<span>SOON</span>
)}
</div>
<span className="flex items-center gap-1 text-xs text-zinc-500">
<Eye className="w-4 h-4" />{" "}
{Intl.NumberFormat("en-US", { notation: "compact" }).format(
views[featured.slug] ?? 0,
)}
</span>
</div>
<div className="grid grid-cols-1 gap-8 mx-auto lg:grid-cols-2 ">
<Card>
<Link href={`/projects/${featured.slug}`}>
<article className="relative w-full h-full p-4 md:p-8">
<div className="flex items-center justify-between gap-2">
<div className="text-xs text-zinc-100">
{featured.date ? (
<time dateTime={new Date(featured.date).toISOString()}>
{Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
}).format(new Date(featured.date))}
</time>
) : (
<span>SOON</span>
)}
</div>
<span className="flex items-center gap-1 text-xs text-zinc-500">
<Eye className="w-4 h-4" />{" "}
{Intl.NumberFormat("en-US", { notation: "compact" }).format(
views[featured.slug] ?? 0,
)}
</span>
</div>
<h2
id="featured-post"
className="mt-4 text-3xl font-bold text-zinc-100 group-hover:text-white sm:text-4xl font-display"
>
{featured.title}
</h2>
<p className="mt-4 leading-8 duration-150 text-zinc-400 group-hover:text-zinc-300">
{featured.description}
</p>
<div className="absolute bottom-4 md:bottom-8">
<p className="hidden text-zinc-200 hover:text-zinc-50 lg:block">
Read more <span aria-hidden="true">&rarr;</span>
</p>
</div>
</article>
</Link>
</Card>
<h2
id="featured-post"
className="mt-4 text-3xl font-bold text-zinc-100 group-hover:text-white sm:text-4xl font-display"
>
{featured.title}
</h2>
<p className="mt-4 leading-8 duration-150 text-zinc-400 group-hover:text-zinc-300">
{featured.description}
</p>
<div className="absolute bottom-4 md:bottom-8">
<p className="hidden text-zinc-200 hover:text-zinc-50 lg:block">
Read more <span aria-hidden="true">&rarr;</span>
</p>
</div>
</article>
</Link>
</Card>
<div className="flex flex-col w-full gap-8 mx-auto border-t border-gray-900/10 lg:mx-0 lg:border-t-0 ">
{[top2, top3].map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
</div>
<div className="hidden w-full h-px md:block bg-zinc-800" />
<div className="flex flex-col w-full gap-8 mx-auto border-t border-gray-900/10 lg:mx-0 lg:border-t-0 ">
{[top2, top3].map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
</div>
<div className="hidden w-full h-px md:block bg-zinc-800" />
<div className="grid grid-cols-1 gap-4 mx-auto lg:mx-0 md:grid-cols-3">
<div className="grid grid-cols-1 gap-4">
{sorted
.filter((_, i) => i % 3 === 0)
.map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
<div className="grid grid-cols-1 gap-4">
{sorted
.filter((_, i) => i % 3 === 1)
.map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
<div className="grid grid-cols-1 gap-4">
{sorted
.filter((_, i) => i % 3 === 2)
.map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
</div>
</div>
</div>
);
<div className="grid grid-cols-1 gap-4 mx-auto lg:mx-0 md:grid-cols-3">
<div className="grid grid-cols-1 gap-4">
{sorted
.filter((_, i) => i % 3 === 0)
.map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
<div className="grid grid-cols-1 gap-4">
{sorted
.filter((_, i) => i % 3 === 1)
.map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
<div className="grid grid-cols-1 gap-4">
{sorted
.filter((_, i) => i % 3 === 2)
.map((project) => (
<Card key={project.slug}>
<Article project={project} views={views[project.slug] ?? 0} />
</Card>
))}
</div>
</div>
</div>
</div>
);
}

View File

@ -113,4 +113,4 @@ $ curl -s https://envshare.dev/api/v1/secret/HdPbXgpvUvNk43oxSdK97u
"remainingReads": 1
}
}
```
```

View File

@ -4,7 +4,7 @@ description: QStash is a fully managed serverless queue and messaging service de
date: "2022-07-18"
url: https://upstash.com/qstash
published: true
---
QStash is an HTTP based messaging and scheduling solution for the serverless and edge runtimes.
@ -33,4 +33,4 @@ If you use QStash in the above example, you simply send a request to QStash from
With QStash, you can add delays to the requests. Send an email 3 days after the shipment to remind the customer to add a review. You can also schedule tasks. You can send the requests with a CRON expression, so it will be run repetitively.
To learn more about QStash, visit [upstash.com/qstash](upstash.com/qstash).
To learn more about QStash, visit [upstash.com/qstash](upstash.com/qstash).

View File

@ -5,4 +5,4 @@ url: https://console.upstash.com/ratelimit
repository: upstash/auth-analytics
---
TODO:
TODO:

View File

@ -4,7 +4,7 @@ description: A CLI to provision and manage Upstash resources, including Redis an
date: "2022-05-16"
repository: upstash/cli
published: true
---
@ -38,7 +38,7 @@ for windows, linux and mac (both intel and m1).
```bash
> upstash
Usage: upstash
Usage: upstash
Version: development
Description:
@ -47,15 +47,15 @@ for windows, linux and mac (both intel and m1).
Options:
-h, --help - Show this help.
-V, --version - Show the version number for this program.
-h, --help - Show this help.
-V, --version - Show the version number for this program.
-c, --config <string> - Path to .upstash.json file
Commands:
auth - Login and logout
redis - Manage redis database instances
kafka - Manage kafka clusters and topics
auth - Login and logout
redis - Manage redis database instances
kafka - Manage kafka clusters and topics
team - Manage your teams and their members
Environment variables:

View File

@ -5,7 +5,7 @@ date: "2023-02-13"
url: https://console.upstash.com/ratelimit
repository: upstash/core-analytics
published: true
---

View File

@ -5,7 +5,7 @@ date: "2022-12-12"
url: https://upstash.com/blog/edge-flags-beta
repository: upstash/edge-flags
published: true
---
@ -13,7 +13,7 @@ Whether you want to ship without breaking things, run A/B tests or just want to
feature flags are a great way to dynamically change the behaviour of your app without redeploying. We're excited to announce the public
beta release of our new feature flagging library: [@upstash/edge-flags](https://github.com/upstash/edge-flags).
*Edge Flags*, as the name implies, is a feature flag solution built to run at the edge. It is using [Upstash Redis](https://upstash.com/), a globally replicated serverless Redis service, to store configuration and is
*Edge Flags*, as the name implies, is a feature flag solution built to run at the edge. It is using [Upstash Redis](https://upstash.com/), a globally replicated serverless Redis service, to store configuration and is
designed to work with [Next.js](https://nextjs.org) and [Vercel](https://vercel.com). We want to support other frameworks in the future, so if you have a suggestion, please let us know!
With the ability to toggle features on and off at the edge, you can quickly respond

View File

@ -5,7 +5,7 @@ date: "2022-01-08"
url: https://upstash.com/kafka
repository: upstash/upstash-kafka
published: true
---

View File

@ -4,7 +4,7 @@ description: Near realtime analytics for your ratelimits. Integrated into the @u
repository: upstash/ratelimit"
url: https://console.upstash.com/ratelimit
published: true
---
TODO:
TODO:

View File

@ -5,7 +5,7 @@ date: "2022-06-06"
url: https://upstash.com/blog/upstash-ratelimit
repository: upstash/ratelimit
published: true
---
In today's digital age, serverless computing has become increasingly popular due to its scalability and cost-efficiency. One of the challenges of serverless computing is to manage resources efficiently, and one critical aspect of this is rate limiting. Rate limiting is a technique that limits the number of requests a client can make to a server over a given period. This technique can prevent abuse, improve performance, and reduce costs. One npm package that helps implement rate limiting for serverless applications is @upstash/ratelimit, built on top of Upstash Redis.
@ -46,4 +46,4 @@ const { success } = await ratelimit.limit("identifier")
In the code above, we initialize Upstash with our Upstash Redis credentials and define our rate limiting rules. We then call the `limit` function, passing the identifier. The function returns a Promise that resolves with `success` and some other useful data.
`@upstash/ratelimit` is a useful npm package for serverless rate limiting that simplifies the process of implementing rate limiting for serverless applications. The package is built on top of Upstash Redis, which provides a complete solution for serverless applications. With `@upstash/ratelimit`, serverless developers can easily implement rate limiting, which can help prevent abuse, improve performance, and reduce costs.
`@upstash/ratelimit` is a useful npm package for serverless rate limiting that simplifies the process of implementing rate limiting for serverless applications. The package is built on top of Upstash Redis, which provides a complete solution for serverless applications. With `@upstash/ratelimit`, serverless developers can easily implement rate limiting, which can help prevent abuse, improve performance, and reduce costs.

View File

@ -5,8 +5,8 @@ date: "2023-02-05"
url: https://upstash.com
repository: upstash/react-ui
published: true
---
`@upstash/react-ui` is powering the CLI in your browser on [console.upstash.com](https://console.upstash.com). It allows you to interact with your Upstash Redis database in a simple and intuitive way.
`@upstash/react-ui` is powering the CLI in your browser on [console.upstash.com](https://console.upstash.com). It allows you to interact with your Upstash Redis database in a simple and intuitive way.

View File

@ -5,7 +5,7 @@ date: "2022-03-14"
url: https://upstash.com/redis
repository: upstash/upstash-redis
published: true
---
Upstash is a cloud-based service provider that offers a Redis-compatible service. In addition to that, we have also created an npm package called `@upstash/redis`. This package provides a strongly typed Redis client that uses HTTP instead of TCP to communicate with the database, making it perfect for serverless and edge runtimes.
@ -42,4 +42,4 @@ console.log(data)
await redis.lpush('elements', 'magnesium')
data = await redis.lrange('elements', 0, 100 )
console.log(data)
```
```

View File

@ -5,4 +5,4 @@ description: A library to record and analyse web page traffic and user behaviour
published: true
---
Coming soon
Coming soon

View File

@ -52,4 +52,4 @@
"@opentelemetry/semantic-conventions": "1.13.0"
}
}
}
}

View File

@ -3,45 +3,45 @@ import { NextRequest, NextResponse } from "next/server";
const redis = Redis.fromEnv();
export const config = {
runtime: "edge",
runtime: "edge",
};
export default async function incr(req: NextRequest): Promise<NextResponse> {
if (req.method !== "POST") {
return new NextResponse("use POST", { status: 405 });
}
if (req.headers.get("Content-Type") !== "application/json") {
return new NextResponse("must be json", { status: 400 });
}
if (req.method !== "POST") {
return new NextResponse("use POST", { status: 405 });
}
if (req.headers.get("Content-Type") !== "application/json") {
return new NextResponse("must be json", { status: 400 });
}
const body = await req.json();
let slug: string | undefined = undefined;
if ("slug" in body) {
slug = body.slug;
}
if (!slug) {
return new NextResponse("Slug not found", { status: 400 });
}
const ip = req.ip;
if (ip) {
// Hash the IP in order to not store it directly in your db.
const buf = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(ip),
);
const hash = Array.from(new Uint8Array(buf))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
const body = await req.json();
let slug: string | undefined = undefined;
if ("slug" in body) {
slug = body.slug;
}
if (!slug) {
return new NextResponse("Slug not found", { status: 400 });
}
const ip = req.ip;
if (ip) {
// Hash the IP in order to not store it directly in your db.
const buf = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(ip),
);
const hash = Array.from(new Uint8Array(buf))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
// deduplicate the ip for each slug
const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, {
nx: true,
ex: 24 * 60 * 60,
});
if (!isNew) {
new NextResponse(null, { status: 202 });
}
}
await redis.incr(["pageviews", "projects", slug].join(":"));
return new NextResponse(null, { status: 202 });
// deduplicate the ip for each slug
const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, {
nx: true,
ex: 24 * 60 * 60,
});
if (!isNew) {
new NextResponse(null, { status: 202 });
}
}
await redis.incr(["pageviews", "projects", slug].join(":"));
return new NextResponse(null, { status: 202 });
}