1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-07-03 13:06:37 -04:00

move database from postgres/prisma to redis

This commit is contained in:
2025-03-27 09:21:22 -04:00
parent e865d9d8e5
commit bbf6e9dc66
16 changed files with 283 additions and 651 deletions

View File

@ -1,31 +1,43 @@
import { NextResponse } from "next/server";
import { prisma } from "../../../lib/helpers/prisma";
import type { hits as Hits } from "@prisma/client";
import redis from "../../../lib/helpers/redis";
export const revalidate = 900; // 15 mins
export const GET = async (): Promise<
NextResponse<{
total: Pick<Hits, "hits">;
pages: Hits[];
total: {
hits: number;
};
pages: Array<{
slug: string;
hits: number;
}>;
}>
> => {
// fetch all rows from db sorted by most hits
const pages = await prisma.hits.findMany({
orderBy: [
{
hits: "desc",
},
],
const slugs = await redis.scan(0, {
count: 50,
});
const total = { hits: 0 };
// fetch all rows from db sorted by most hits
const data = await Promise.all(
slugs[1].map(async (slug) => {
const hits = (await redis.get(slug)) as number;
return {
slug,
hits,
};
})
);
// sort by hits
data.sort((a, b) => b.hits - a.hits);
// calculate total hits
pages.forEach((page) => {
const total = { hits: 0 };
data.forEach((page) => {
// add these hits to running tally
total.hits += page.hits;
});
return NextResponse.json({ total, pages });
return NextResponse.json({ total, pages: data });
};

View File

@ -1,20 +1,12 @@
import { connection } from "next/server";
import commaNumber from "comma-number";
import { prisma } from "../../../lib/helpers/prisma";
import redis from "../../../lib/helpers/redis";
const HitCounter = async ({ slug }: { slug: string }) => {
await connection();
try {
const { hits } = await prisma.hits.upsert({
where: { slug },
create: { slug },
update: {
hits: {
increment: 1,
},
},
});
const hits = await redis.incr(slug);
// we have data!
return <span title={`${commaNumber(hits)} ${hits === 1 ? "view" : "views"}`}>{commaNumber(hits)}</span>;

View File

@ -22,9 +22,9 @@ For a likely excessive level of privacy and security, this website is also mirro
## Analytics
A very simple hit counter on each blog post tallies an aggregate number of pageviews (i.e. `hits = hits + 1`) in a [Neon Postgres](https://neon.tech/) database. Individual views and identifying (or non-identifying) details are **never stored or logged**.
A very simple hit counter on each blog post tallies an aggregate number of pageviews (i.e. `hits = hits + 1`) in a [Upstash Redis](https://upstash.com/) database. Individual views and identifying (or non-identifying) details are **never stored or logged**.
The [database schema](https://github.com/jakejarvis/jarv.is/blob/main/prisma/schema.prisma), [serverless function](https://github.com/jakejarvis/jarv.is/blob/main/app/api/hits/route.ts) and [client script](https://github.com/jakejarvis/jarv.is/blob/main/app/notes/%5Bslug%5D/counter.tsx) are open source, and [snapshots of the database](https://github.com/jakejarvis/website-stats) are public.
The [server component](https://github.com/jakejarvis/jarv.is/blob/main/app/notes/%5Bslug%5D/counter.tsx) is open source, and [snapshots of the database](https://github.com/jakejarvis/website-stats) are public.
A self-hosted [**Umami**](https://umami.is/) instance is also used to gain insights into referrers, search terms, etc. [without collecting anything identifiable](https://umami.is/blog/why-privacy-matters) about you. [The dashboard is even public!](/stats)