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

only reveal a db record via /api/hits if it matches a real page

This commit is contained in:
Jake Jarvis 2022-07-06 11:49:41 -04:00
parent 8d47958473
commit 155c6cacd9
Signed by: jake
GPG Key ID: 2B0C9CF251E69A39
9 changed files with 82 additions and 76 deletions

View File

@ -1,9 +1,7 @@
// Next.js constants (not needed in frontend)
import path from "path";
// directory containing .mdx files relative to project root
export const NOTES_DIR = path.join(process.cwd(), "notes");
export const NOTES_DIR = "notes";
// normalize the timestamp saved when building/deploying (see next.config.js) and fall back to right now:
export const RELEASE_DATE = new Date(process.env.RELEASE_DATE || Date.now()).toISOString();

View File

@ -3,8 +3,7 @@ import { getAllNotes } from "./parse-notes";
import * as config from "../config";
import { RELEASE_DATE } from "../config/constants";
import { favicons } from "../config/seo";
import type { GetServerSidePropsContext, GetServerSidePropsResult, PreviewData } from "next";
import type { ParsedUrlQuery } from "querystring";
import type { GetServerSideProps } from "next";
export type BuildFeedOptions = {
edgeCacheAge?: number; // in seconds, defaults to 43200 (12 hours)
@ -13,10 +12,10 @@ export type BuildFeedOptions = {
// handles literally *everything* about building the server-side rss/atom feeds and writing the response.
// all the page needs to do is `return buildFeed(context, "rss")` from getServerSideProps.
export const buildFeed = async (
context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>,
context: Parameters<GetServerSideProps>[0],
type: "rss" | "atom" | "json",
options?: BuildFeedOptions
): Promise<GetServerSidePropsResult<Record<string, never>>> => {
): Promise<ReturnType<GetServerSideProps<Record<string, never>>>> => {
const { res } = context;
// https://github.com/jpmonette/feed#example

View File

@ -21,13 +21,13 @@ const IsomorphicDayJs = (date?: dayjs.ConfigType): dayjs.Dayjs => {
// simple wrapper around dayjs.format() to normalize timezone across the site, both server and client side, to prevent
// hydration errors by returning an instance of dayjs with these defaults set.
// date defaults to now, format defaults to ISO 8601 (e.g. 2022-04-07T21:53:33-04:00)
export const formatDate = (date?: dayjs.ConfigType, formatStr?: string) => {
export const formatDate = (date?: dayjs.ConfigType, formatStr?: string): string => {
return IsomorphicDayJs(date).tz(timeZone).format(formatStr);
};
// returns the human-friendly difference between now and given date (e.g. "5 minutes", "9 months", etc.)
// set `{ suffix: true }` to include the "... ago" or "in ..." for past/future
export const formatTimeAgo = (date: dayjs.ConfigType, options?: { suffix?: boolean }) => {
export const formatTimeAgo = (date: dayjs.ConfigType, options?: { suffix?: boolean }): string => {
return IsomorphicDayJs().isBefore(date)
? IsomorphicDayJs(date).toNow(!options?.suffix)
: IsomorphicDayJs(date).fromNow(!options?.suffix);

View File

@ -14,7 +14,7 @@ import type { NoteFrontMatter } from "../../types";
export const getNoteSlugs = async (): Promise<string[]> => {
// list all .mdx files in NOTES_DIR
const mdxFiles = await glob("*.mdx", {
cwd: NOTES_DIR,
cwd: path.join(process.cwd(), NOTES_DIR),
dot: false,
});
@ -31,7 +31,7 @@ export const getNoteData = async (
frontMatter: NoteFrontMatter;
content: string;
}> => {
const fullPath = path.join(NOTES_DIR, `${slug}.mdx`);
const fullPath = path.join(process.cwd(), NOTES_DIR, `${slug}.mdx`);
const rawContent = await fs.readFile(fullPath, "utf8");
const { data, content } = matter(rawContent);
@ -47,7 +47,7 @@ export const getNoteData = async (
smartypants: true,
}),
slug,
permalink: `${baseUrl}/notes/${slug}/`,
permalink: `${baseUrl}/${NOTES_DIR}/${slug}/`,
date: formatDate(data.date), // validate/normalize the date string provided from front matter
},
content,

View File

@ -29,8 +29,8 @@
"@primer/octicons": "^17.3.0",
"@prisma/client": "^4.0.0",
"@react-spring/web": "^9.4.5",
"@sentry/node": "^7.5.0",
"@sentry/tracing": "^7.5.0",
"@sentry/node": "^7.5.1",
"@sentry/tracing": "^7.5.1",
"@stitches/react": "^1.2.8",
"comma-number": "^2.1.0",
"copy-to-clipboard": "^3.3.1",

View File

@ -1,6 +1,7 @@
import { prisma } from "../../lib/helpers/prisma";
import { getAllNotes } from "../../lib/helpers/parse-notes";
import { logServerError } from "../../lib/helpers/sentry";
import { NOTES_DIR } from "../../lib/config/constants";
import type { NextApiRequest, NextApiResponse } from "next";
import type { PageStats, DetailedPageStats, SiteStats } from "../../types";
@ -58,7 +59,8 @@ const incrementPageHits = async (slug: string): Promise<PageStats> => {
};
const getSiteStats = async (): Promise<SiteStats> => {
const [pages, notes] = await Promise.all([
// simultaneously fetch the entire hits db and notes from the filesystem
const [hits, notes] = await Promise.all([
prisma.hits.findMany({
orderBy: [
{
@ -69,22 +71,29 @@ const getSiteStats = async (): Promise<SiteStats> => {
getAllNotes(),
]);
const pages: DetailedPageStats[] = [];
const total = { hits: 0 };
pages.forEach((page: DetailedPageStats) => {
// match URLs from RSS feed with db to populate some metadata
const match = notes.find((note) => `notes/${note.slug}` === page.slug);
hits.forEach((record) => {
// match slugs from getAllNotes() with db results to populate some metadata
// TODO: add support for pages other than notes.
const match = notes.find((note) => `${NOTES_DIR}/${note.slug}` === record.slug);
if (match) {
page.title = match.title;
page.url = match.permalink;
page.date = match.date;
// don't reveal via API if the db entry doesn't belong to a valid page
if (!match) {
return;
}
// add these hits to running tally
total.hits += page.hits;
// merge record with its matching front matter data
pages.push({
...record,
title: match.title,
url: match.permalink,
date: match.date,
});
return page;
// add these hits to running tally
total.hits += record.hits;
});
return { total, pages };

View File

@ -1,7 +1,7 @@
import { SitemapStream, SitemapItemLoose, EnumChangefreq } from "sitemap";
import { getAllNotes } from "../lib/helpers/parse-notes";
import { baseUrl } from "../lib/config";
import { RELEASE_DATE } from "../lib/config/constants";
import { RELEASE_DATE, NOTES_DIR } from "../lib/config/constants";
import type { GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps<Record<string, never>> = async (context) => {
@ -42,16 +42,16 @@ export const getServerSideProps: GetServerSideProps<Record<string, never>> = asy
const notes = await getAllNotes();
notes.forEach((note) => {
pages.push({
url: `/notes/${note.slug}/`,
url: `/${NOTES_DIR}/${note.slug}/`,
// pull lastMod from front matter date
lastmod: new Date(note.date).toISOString(),
lastmod: note.date,
});
});
// set lastmod of /notes/ page to most recent post's date
pages.push({
url: "/notes/",
lastmod: new Date(notes[0].date).toISOString(),
url: `/${NOTES_DIR}/`,
lastmod: notes[0].date,
});
// sort alphabetically by URL

12
types/stats.d.ts vendored
View File

@ -1,13 +1,13 @@
import type { NoteFrontMatter } from "./note";
export type PageStats = {
hits: number;
};
export type DetailedPageStats = PageStats & {
slug: string;
title?: string;
url?: string;
date?: string;
};
export type DetailedPageStats = PageStats &
Pick<NoteFrontMatter, "slug" | "title" | "date"> & {
url: string;
};
export type SiteStats = {
total: PageStats;

View File

@ -1378,60 +1378,60 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz#0c8b74c50f29ee44f423f7416829c0bf8bb5eb27"
integrity sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==
"@sentry/core@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.5.0.tgz#4ccc2312017fc6158cc379f5828dc6bbe2cdf1f7"
integrity sha512-2KO2hVUki3WgvPlB0qj9+yea56CmsK2b1XtBSyAnqbs+JiXWgerF4qshVsH52kS/1h2B0CisyeIv64/WfuGvQQ==
"@sentry/core@7.5.1":
version "7.5.1"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.5.1.tgz#6ad186e671d0398dfa4552a2e5686bff6c1938d3"
integrity sha512-1ac5eaJi9LBIpCaert+IrttyaL8rnrK5fcdB6tyqDf8jNV5s9O32PyqjvjpWCrGOvZ4kmp+6UXB9bw/NNtvpkQ==
dependencies:
"@sentry/hub" "7.5.0"
"@sentry/types" "7.5.0"
"@sentry/utils" "7.5.0"
"@sentry/hub" "7.5.1"
"@sentry/types" "7.5.1"
"@sentry/utils" "7.5.1"
tslib "^1.9.3"
"@sentry/hub@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.5.0.tgz#30801accb9475cc3f155802a3fefd218d66fbfda"
integrity sha512-R3jGEOtRtZaYCswSNs/7SmjOj/Pp8BhRyXk4q0a5GXghbuVAdzZvlJH0XnD/6jOJAF0iSXFuyGSLqVUmjkY9Ow==
"@sentry/hub@7.5.1":
version "7.5.1"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.5.1.tgz#15817d30aec9e4c1b1bbf7ded6735813f8613e66"
integrity sha512-q14zzf5GlE4xvwFP7lZaAI4UnuqWMc3nD62Md5XBptY35bm42CGzawx9aDQ8cegZoQ5bHyX1GPzFju4lDO3O6g==
dependencies:
"@sentry/types" "7.5.0"
"@sentry/utils" "7.5.0"
"@sentry/types" "7.5.1"
"@sentry/utils" "7.5.1"
tslib "^1.9.3"
"@sentry/node@^7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.5.0.tgz#e22b27cfb19157aea5019daf6241615887e64321"
integrity sha512-lcHIgzcOjKnBeXf9CPbHOwFTuRf+huYMwhF3IVz5Ewbpm4eZn3LPk838ypqtspD7UtFbAdaTGXs/w3Y9P3Zi4g==
"@sentry/node@^7.5.1":
version "7.5.1"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.5.1.tgz#b0c94ed4bc24891c51809e75199e6d1f8de284f2"
integrity sha512-XSpNbxBVIpcklLpk9NtQSkTZM0/mEj0TYMnzQmE2UR7UChpGhZyc19nbQWceSsaLMrrAgOX4Zzo28ENk/QY5FA==
dependencies:
"@sentry/core" "7.5.0"
"@sentry/hub" "7.5.0"
"@sentry/types" "7.5.0"
"@sentry/utils" "7.5.0"
"@sentry/core" "7.5.1"
"@sentry/hub" "7.5.1"
"@sentry/types" "7.5.1"
"@sentry/utils" "7.5.1"
cookie "^0.4.1"
https-proxy-agent "^5.0.0"
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/tracing@^7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.5.0.tgz#ad6da27563246e9d754c36a2a3d398cfb979117e"
integrity sha512-tSVnCJNImsWms4tBhJ2Xr+HI1i9zKg4eZ0dImi93/H3sf5hmK9r2E11Xs/8rTxqpGWzB8axVi2tcmqmfqXKGTg==
"@sentry/tracing@^7.5.1":
version "7.5.1"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.5.1.tgz#86aca47db8de2c940ca587fb771acd2a88bff815"
integrity sha512-fOmzTk3/mTKF5d1P43yZ29lc5z/1wyL2+qX+N5rLluIeR6dEYISyjFistK8z1esMCCvJu8/x3u0imbrrFDNx0Q==
dependencies:
"@sentry/hub" "7.5.0"
"@sentry/types" "7.5.0"
"@sentry/utils" "7.5.0"
"@sentry/hub" "7.5.1"
"@sentry/types" "7.5.1"
"@sentry/utils" "7.5.1"
tslib "^1.9.3"
"@sentry/types@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.5.0.tgz#610f14c1219ba461ca84a3c89e06de8c0cf357bc"
integrity sha512-VPQ/53mLo5N8NQUB4k6R2GQBWoW8otFyhhPnC75gYXeBTItVCzJAylVyWy8b+gGqGst+pQN3wb2dl9xhrd69YQ==
"@sentry/types@7.5.1":
version "7.5.1"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.5.1.tgz#3dd647973fee256588f92d3d74d8ce560b73758b"
integrity sha512-+OHxQL4lXCEsUA31qlhcPABOjxtbuL+VTpgamXJjxEpQQDPUPyPK0pu7c+uTc7x4Re96Ss3pwUYE9tl3WW3xIg==
"@sentry/utils@7.5.0":
version "7.5.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.5.0.tgz#64435ea094aa7d79d1dfe7586d2d5a2bff9e3839"
integrity sha512-DgHrkGgHplVMgMbU9hGBfGBV6LcOwNBrhHiVaFwo2NHiXnGwMkaILi5XTRjKm9Iu/m2choAFABA80HEtPKmjtA==
"@sentry/utils@7.5.1":
version "7.5.1"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.5.1.tgz#10877a19372040ebf5bc0342fed69b8147a8d269"
integrity sha512-5w5dEDilAkH/4x5h8VMlfFcGKdDQ8tbSEfxnMOheD3/bwk18lTVTgp6kk+VxmugGdvxsTLiPEoORsuofufWvGQ==
dependencies:
"@sentry/types" "7.5.0"
"@sentry/types" "7.5.1"
tslib "^1.9.3"
"@stitches/react@^1.2.8":
@ -1653,9 +1653,9 @@
"@types/unist" "*"
"@types/node@*":
version "18.0.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.2.tgz#a594e580c396c22dd6b1470be81737c79ec0b1b1"
integrity sha512-b947SdS4GH+g2W33wf5FzUu1KLj5FcSIiNWbU1ZyMvt/X7w48ZsVcsQoirIgE/Oq03WT5Qbn/dkY0hePi4ZXcQ==
version "18.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199"
integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==
"@types/node@^17.0.5":
version "17.0.45"