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:
@ -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 });
|
||||
};
|
||||
|
@ -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>;
|
||||
|
@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user