1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 09:25:22 -04:00

add prefix to redis keys

This commit is contained in:
Jake Jarvis 2025-04-04 08:49:42 -04:00
parent 774c3a5d96
commit 2b9c421d42
Signed by: jake
SSH Key Fingerprint: SHA256:nCkvAjYA6XaSPUqc4TfbBQTpzr8Xj7ritg/sGInCdkc
9 changed files with 65 additions and 87 deletions

View File

@ -17,6 +17,7 @@ export const GET = async (): Promise<
> => {
// get all keys (aka slugs)
const slugs = await redis.scan(0, {
match: "hits:*",
type: "string",
// set an arbitrary yet generous upper limit, just in case...
count: 99,
@ -27,7 +28,7 @@ export const GET = async (): Promise<
// pair the slugs with their hit values
const pages = slugs[1].map((slug, index) => ({
slug,
slug: slug.split(":").pop() as string, // remove the "hits:" prefix
hits: parseInt(values[index], 10),
}));

View File

@ -96,7 +96,7 @@ const ContactForm = () => {
Markdown syntax
</Link>{" "}
is allowed here, e.g.: <strong>**bold**</strong>, <em>_italics_</em>, [
<Link href="https://jarv.is" plain openInNewTab>
<Link href="https://jarv.is" plain>
links
</Link>
](https://jarv.is), and <code style={{ fontFamily: "var(--fonts-mono)" }}>`code`</code>.

View File

@ -54,7 +54,7 @@ const Page = () => {
G4techTV Canada
</Link>{" "}
&amp;{" "}
<Link href="https://leolaporte.com/" style={{ fontWeight: 700 }}>
<Link href="https://leo.fm/" style={{ fontWeight: 700 }}>
Leo Laporte
</Link>
. &copy; 2007 G4 Media, Inc.

View File

@ -12,7 +12,7 @@ const HitCounter = async ({ slug }: { slug: string }) => {
// TODO: maybe don't allow this? or maybe it's fine? kinda unclear how secure this is:
// https://nextjs.org/blog/security-nextjs-server-components-actions
// https://nextjs.org/docs/app/building-your-application/rendering/server-components
const hits = await redis.incr(slug);
const hits = await redis.incr(`hits:${slug}`);
// we have data!
return (

View File

@ -1,43 +1,26 @@
import NextLink from "next/link";
import clsx from "clsx";
import objStr from "obj-str";
import { BASE_URL } from "../../lib/config/constants";
import type { ComponentPropsWithoutRef } from "react";
import styles from "./Link.module.css";
export type LinkProps = ComponentPropsWithoutRef<typeof NextLink> & {
plain?: boolean; // disable fancy text-decoration effect
openInNewTab?: boolean;
};
const Link = ({ href, rel, target, prefetch = false, plain, openInNewTab, className, ...rest }: LinkProps) => {
const Link = ({ href, rel, target, prefetch = false, plain, className, ...rest }: LinkProps) => {
// This component auto-detects whether or not this link should open in the same window (the default for internal
// links) or a new tab (the default for external links). Defaults can be overridden with `openInNewTab={true}`.
const isExternal = typeof href === "string" && !(["/", "#"].includes(href[0]) || href.startsWith(BASE_URL));
// links) or a new tab (the default for external links). Defaults can be overridden with `target="_blank"`.
const isExternal = typeof href === "string" && !["/", "#"].includes(href[0]);
if (openInNewTab || isExternal) {
return (
<NextLink
href={href}
target={target || "_blank"}
rel={objStr({
[`${rel}`]: rel, // prepend whatever string is passed via optional `rel` prop
noopener: true,
noreferrer: isExternal, // don't add "noreferrer" if link isn't external, and only opening in a new tab
})}
prefetch={false}
className={clsx(styles.link, plain && styles.plain, className)}
{...rest}
/>
);
}
// If link is to an internal page, simply pass *everything* along as-is to next/link.
return (
<NextLink
href={href}
prefetch={prefetch}
target={target || (isExternal ? "_blank" : undefined)}
rel={`${rel ? `${rel} ` : ""}${target === "_blank" || isExternal ? "noopener noreferrer" : ""}` || undefined}
className={clsx(styles.link, plain && styles.plain, className)}
{...{ href, rel, target, prefetch, ...rest }}
{...rest}
/>
);
};

View File

@ -64,18 +64,22 @@ const nextConfig: NextConfig = {
},
],
},
{
// https://community.torproject.org/onion-services/advanced/onion-location/
// only needed on actual pages, not static assets, so make a best effort by matching any path **without** a file
// extension (aka a period) and/or an underscore (e.g. /_next/image).
source: "/:path([^._]*)",
headers: [
{
key: "onion-location",
value: `http://${process.env.NEXT_PUBLIC_ONION_DOMAIN}/:path`,
},
],
},
...(process.env.NEXT_PUBLIC_ONION_DOMAIN
? [
{
// https://community.torproject.org/onion-services/advanced/onion-location/
// only needed on actual pages, not static assets, so make a best effort by matching any path **without** a file
// extension (aka a period) and/or an underscore (e.g. /_next/image).
source: "/:path([^._]*)",
headers: [
{
key: "onion-location",
value: `http://${process.env.NEXT_PUBLIC_ONION_DOMAIN}/:path`,
},
],
},
]
: []),
{
source: "/tweets(|/.*)",
headers: [
@ -87,11 +91,15 @@ const nextConfig: NextConfig = {
},
],
rewrites: async () => [
{
// https://umami.is/docs/guides/running-on-vercel#proxy-umami-analytics-via-vercel
source: "/_stream/u/:path(script.js|api/send)",
destination: `${process.env.NEXT_PUBLIC_UMAMI_URL || "https://cloud.umami.is"}/:path`,
},
...(process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID
? [
{
// https://umami.is/docs/guides/running-on-vercel#proxy-umami-analytics-via-vercel
source: "/_stream/u/:path(script.js|api/send)",
destination: `${process.env.NEXT_PUBLIC_UMAMI_URL || "https://cloud.umami.is"}/:path`,
},
]
: []),
{
// https://github.com/jakejarvis/tweets
source: "/tweets/:path*",

View File

@ -39,7 +39,6 @@
"lucide-react": "0.487.0",
"modern-normalize": "^3.0.1",
"next": "15.3.0-canary.29",
"obj-str": "^1.1.0",
"polished": "^4.3.1",
"prop-types": "^15.8.1",
"react": "19.1.0",
@ -126,17 +125,5 @@
"stylelint",
"prettier --check"
]
},
"pnpm": {
"onlyBuiltDependencies": [
"sharp",
"simple-git-hooks"
],
"peerDependencyRules": {
"allowedVersions": {
"react": "^19",
"react-dom": "^19"
}
}
}
}

47
pnpm-lock.yaml generated
View File

@ -74,9 +74,6 @@ importers:
next:
specifier: 15.3.0-canary.29
version: 15.3.0-canary.29(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250328)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
obj-str:
specifier: ^1.1.0
version: 1.1.0
polished:
specifier: ^4.3.1
version: 4.3.1
@ -182,7 +179,7 @@ importers:
version: 2.0.13
'@types/node':
specifier: ^22.13.17
version: 22.13.17
version: 22.14.0
'@types/prop-types':
specifier: ^15.7.14
version: 15.7.14
@ -884,8 +881,8 @@ packages:
'@types/nlcst@2.0.3':
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
'@types/node@22.13.17':
resolution: {integrity: sha512-nAJuQXoyPj04uLgu+obZcSmsfOenUg6DxPKogeUy6yNCFwWaj5sBF8/G/pNo8EtBJjAfSVgfIlugR/BCOleO+g==}
'@types/node@22.14.0':
resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==}
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
@ -1217,8 +1214,8 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001707:
resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==}
caniuse-lite@1.0.30001709:
resolution: {integrity: sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@ -1454,8 +1451,8 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
electron-to-chromium@1.5.129:
resolution: {integrity: sha512-JlXUemX4s0+9f8mLqib/bHH8gOHf5elKS6KeWG3sk3xozb/JTq/RLXIv8OKUWiK4Ah00Wm88EFj5PYkFr4RUPA==}
electron-to-chromium@1.5.130:
resolution: {integrity: sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==}
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
@ -2687,10 +2684,6 @@ packages:
resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
obj-str@1.1.0:
resolution: {integrity: sha512-iQNCv4NPzzVSnG4nVmkBsBwSq3+Z5/X/Yi4omFpzALC4ZbLXd4QByOFqWd+Khh2nQnbzhsklRxbDwhYKHotrYA==}
engines: {node: '>=4'}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@ -3498,8 +3491,8 @@ packages:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
undici-types@6.20.0:
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
unified-engine@11.2.2:
resolution: {integrity: sha512-15g/gWE7qQl9tQ3nAEbMd5h9HV1EACtFs6N9xaRBZICoCwnNGbal1kOs++ICf4aiTdItZxU2s/kYWhW7htlqJg==}
@ -4379,7 +4372,7 @@ snapshots:
'@types/concat-stream@2.0.3':
dependencies:
'@types/node': 22.13.17
'@types/node': 22.14.0
'@types/debug@4.1.12':
dependencies:
@ -4413,9 +4406,9 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
'@types/node@22.13.17':
'@types/node@22.14.0':
dependencies:
undici-types: 6.20.0
undici-types: 6.21.0
'@types/prop-types@15.7.14': {}
@ -4725,8 +4718,8 @@ snapshots:
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001707
electron-to-chromium: 1.5.129
caniuse-lite: 1.0.30001709
electron-to-chromium: 1.5.130
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4)
@ -4765,7 +4758,7 @@ snapshots:
callsites@3.1.0: {}
caniuse-lite@1.0.30001707: {}
caniuse-lite@1.0.30001709: {}
ccount@2.0.1: {}
@ -4979,7 +4972,7 @@ snapshots:
eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.129: {}
electron-to-chromium@1.5.130: {}
emoji-regex-xs@1.0.0: {}
@ -6677,7 +6670,7 @@ snapshots:
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.15
busboy: 1.6.0
caniuse-lite: 1.0.30001707
caniuse-lite: 1.0.30001709
postcss: 8.4.31
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
@ -6739,8 +6732,6 @@ snapshots:
dependencies:
path-key: 4.0.0
obj-str@1.1.0: {}
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
@ -7794,14 +7785,14 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
undici-types@6.20.0: {}
undici-types@6.21.0: {}
unified-engine@11.2.2:
dependencies:
'@types/concat-stream': 2.0.3
'@types/debug': 4.1.12
'@types/is-empty': 1.2.3
'@types/node': 22.13.17
'@types/node': 22.14.0
'@types/unist': 3.0.3
concat-stream: 2.0.0
debug: 4.4.0

8
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,8 @@
onlyBuiltDependencies:
- sharp
- simple-git-hooks
peerDependencyRules:
allowedVersions:
react: "^19"
react-dom: "^19"