Added page views functionality

This commit is contained in:
r-freeman 2023-01-17 21:24:16 +00:00
parent 8feb76efc4
commit 7281397fd5
13 changed files with 494 additions and 35 deletions

4
.gitignore vendored
View File

@ -1,5 +1,9 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
prisma/**/*
!prisma/schema.prisma
.env
.idea/ .idea/
public/rss public/rss

View File

@ -4,8 +4,8 @@ import rehypePrism from '@mapbox/rehype-prism'
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
pageExtensions: ['jsx', 'js', 'tsx', 'mdx'], pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'mdx'],
reactStrictMode: true, reactStrictMode: false,
swcMinify: true, swcMinify: true,
experimental: { experimental: {
newNextLinkBehavior: true, newNextLinkBehavior: true,
@ -15,12 +15,16 @@ const nextConfig = {
domains: ['i.scdn.co'] domains: ['i.scdn.co']
}, },
async rewrites() { async rewrites() {
return [ if (process.env.NODE_ENV === 'production') {
{ return [
source: '/api/:path', {
destination: 'https://ryanfreeman.dev/:path/', source: '/api/:path',
} destination: 'https://ryanfreeman.dev/:path/',
] }
]
} else {
return []
}
} }
} }

346
package-lock.json generated
View File

@ -11,10 +11,10 @@
"@headlessui/react": "^1.7.7", "@headlessui/react": "^1.7.7",
"@mapbox/rehype-prism": "^0.8.0", "@mapbox/rehype-prism": "^0.8.0",
"@next/mdx": "^13.1.1", "@next/mdx": "^13.1.1",
"@prisma/client": "^4.8.1",
"@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.8", "@tailwindcss/typography": "^0.5.8",
"@types/mdx": "^2.0.3", "@types/mdx": "^2.0.3",
"@types/node": "18.11.18",
"@types/react": "18.0.26", "@types/react": "18.0.26",
"@types/react-dom": "18.0.10", "@types/react-dom": "18.0.10",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
@ -34,7 +34,12 @@
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"sharp": "^0.31.3", "sharp": "^0.31.3",
"swr": "^2.0.0", "swr": "^2.0.0",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4"
},
"devDependencies": {
"@types/node": "^18.11.18",
"prisma": "^4.8.1",
"ts-node": "^10.9.1",
"typescript": "4.9.4" "typescript": "4.9.4"
} }
}, },
@ -49,6 +54,28 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"devOptional": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"devOptional": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
@ -134,7 +161,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"peer": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -161,8 +187,7 @@
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14", "version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
"peer": true
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.17", "version": "0.3.17",
@ -613,6 +638,38 @@
"url": "https://opencollective.com/unts" "url": "https://opencollective.com/unts"
} }
}, },
"node_modules/@prisma/client": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.8.1.tgz",
"integrity": "sha512-d4xhZhETmeXK/yZ7K0KcVOzEfI5YKGGEr4F5SBV04/MU4ncN/HcE28sy3e4Yt8UFW0ZuImKFQJE+9rWt9WbGSQ==",
"hasInstallScript": true,
"dependencies": {
"@prisma/engines-version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe"
},
"engines": {
"node": ">=14.17"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
}
},
"node_modules/@prisma/engines": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.8.1.tgz",
"integrity": "sha512-93tctjNXcIS+i/e552IO6tqw17sX8liivv8WX9lDMCpEEe3ci+nT9F+1oHtAafqruXLepKF80i/D20Mm+ESlOw==",
"devOptional": true,
"hasInstallScript": true
},
"node_modules/@prisma/engines-version": {
"version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe.tgz",
"integrity": "sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw=="
},
"node_modules/@rushstack/eslint-patch": { "node_modules/@rushstack/eslint-patch": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@ -667,6 +724,30 @@
"tailwindcss": ">=3.0.0 || insiders" "tailwindcss": ">=3.0.0 || insiders"
} }
}, },
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"devOptional": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"devOptional": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"devOptional": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"devOptional": true
},
"node_modules/@types/acorn": { "node_modules/@types/acorn": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
@ -2035,6 +2116,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -5284,6 +5371,12 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"devOptional": true
},
"node_modules/markdown-extensions": { "node_modules/markdown-extensions": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
@ -7469,6 +7562,23 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/prisma": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.8.1.tgz",
"integrity": "sha512-ZMLnSjwulIeYfaU1O6/LF6PEJzxN5par5weykxMykS9Z6ara/j76JH3Yo2AH3bgJbPN4Z6NeCK9s5fDkzf33cg==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/engines": "4.8.1"
},
"bin": {
"prisma": "build/index.js",
"prisma2": "build/index.js"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/prismjs": { "node_modules/prismjs": {
"version": "1.27.0", "version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
@ -8740,6 +8850,73 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"devOptional": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/ts-node/node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"devOptional": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ts-node/node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"devOptional": true
},
"node_modules/ts-node/node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"devOptional": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/tsconfig-paths": { "node_modules/tsconfig-paths": {
"version": "3.14.1", "version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@ -9217,6 +9394,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"devOptional": true
},
"node_modules/validate-npm-package-name": { "node_modules/validate-npm-package-name": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
@ -9737,6 +9920,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"devOptional": true,
"engines": {
"node": ">=6"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@ -9767,6 +9959,27 @@
"regenerator-runtime": "^0.13.11" "regenerator-runtime": "^0.13.11"
} }
}, },
"@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"devOptional": true,
"requires": {
"@jridgewell/trace-mapping": "0.3.9"
},
"dependencies": {
"@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"devOptional": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
}
}
},
"@eslint/eslintrc": { "@eslint/eslintrc": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
@ -9825,8 +10038,7 @@
"@jridgewell/resolve-uri": { "@jridgewell/resolve-uri": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
"peer": true
}, },
"@jridgewell/set-array": { "@jridgewell/set-array": {
"version": "1.1.2", "version": "1.1.2",
@ -9847,8 +10059,7 @@
"@jridgewell/sourcemap-codec": { "@jridgewell/sourcemap-codec": {
"version": "1.4.14", "version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
"peer": true
}, },
"@jridgewell/trace-mapping": { "@jridgewell/trace-mapping": {
"version": "0.3.17", "version": "0.3.17",
@ -10140,6 +10351,25 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"@prisma/client": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.8.1.tgz",
"integrity": "sha512-d4xhZhETmeXK/yZ7K0KcVOzEfI5YKGGEr4F5SBV04/MU4ncN/HcE28sy3e4Yt8UFW0ZuImKFQJE+9rWt9WbGSQ==",
"requires": {
"@prisma/engines-version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe"
}
},
"@prisma/engines": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.8.1.tgz",
"integrity": "sha512-93tctjNXcIS+i/e552IO6tqw17sX8liivv8WX9lDMCpEEe3ci+nT9F+1oHtAafqruXLepKF80i/D20Mm+ESlOw==",
"devOptional": true
},
"@prisma/engines-version": {
"version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe.tgz",
"integrity": "sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw=="
},
"@rushstack/eslint-patch": { "@rushstack/eslint-patch": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@ -10183,6 +10413,30 @@
"postcss-selector-parser": "6.0.10" "postcss-selector-parser": "6.0.10"
} }
}, },
"@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"devOptional": true
},
"@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"devOptional": true
},
"@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"devOptional": true
},
"@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"devOptional": true
},
"@types/acorn": { "@types/acorn": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
@ -11200,6 +11454,12 @@
"capture-stack-trace": "^1.0.0" "capture-stack-trace": "^1.0.0"
} }
}, },
"create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -13548,6 +13808,12 @@
} }
} }
}, },
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"devOptional": true
},
"markdown-extensions": { "markdown-extensions": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz",
@ -14992,6 +15258,15 @@
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
"integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==" "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="
}, },
"prisma": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.8.1.tgz",
"integrity": "sha512-ZMLnSjwulIeYfaU1O6/LF6PEJzxN5par5weykxMykS9Z6ara/j76JH3Yo2AH3bgJbPN4Z6NeCK9s5fDkzf33cg==",
"devOptional": true,
"requires": {
"@prisma/engines": "4.8.1"
}
},
"prismjs": { "prismjs": {
"version": "1.27.0", "version": "1.27.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
@ -15874,6 +16149,47 @@
"resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
"integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g=="
}, },
"ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"devOptional": true,
"requires": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"dependencies": {
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"devOptional": true
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"devOptional": true
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"devOptional": true
}
}
},
"tsconfig-paths": { "tsconfig-paths": {
"version": "3.14.1", "version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@ -16217,6 +16533,12 @@
} }
} }
}, },
"v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"devOptional": true
},
"validate-npm-package-name": { "validate-npm-package-name": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
@ -16617,6 +16939,12 @@
"decamelize": "^1.2.0" "decamelize": "^1.2.0"
} }
}, },
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"devOptional": true
},
"yocto-queue": { "yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -13,10 +13,10 @@
"@headlessui/react": "^1.7.7", "@headlessui/react": "^1.7.7",
"@mapbox/rehype-prism": "^0.8.0", "@mapbox/rehype-prism": "^0.8.0",
"@next/mdx": "^13.1.1", "@next/mdx": "^13.1.1",
"@prisma/client": "^4.8.1",
"@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.8", "@tailwindcss/typography": "^0.5.8",
"@types/mdx": "^2.0.3", "@types/mdx": "^2.0.3",
"@types/node": "18.11.18",
"@types/react": "18.0.26", "@types/react": "18.0.26",
"@types/react-dom": "18.0.10", "@types/react-dom": "18.0.10",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
@ -35,8 +35,13 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"sharp": "^0.31.3", "sharp": "^0.31.3",
"swr": "^2.0.0", "swr": "^2.0.0"
"tailwindcss": "^3.2.4", },
"typescript": "4.9.4" "devDependencies": {
"@types/node": "^18.11.18",
"prisma": "^4.8.1",
"ts-node": "^10.9.1",
"typescript": "4.9.4",
"tailwindcss": "^3.2.4"
} }
} }

17
prisma/schema.prisma Normal file
View File

@ -0,0 +1,17 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
model views {
slug String @id
count BigInt @default(1)
}

View File

@ -1,9 +1,10 @@
import {ReactNode} from 'react'
import Head from 'next/head' import Head from 'next/head'
import {usePathname} from 'next/navigation' import {usePathname} from 'next/navigation'
import {Container} from './Container' import {Container} from './Container'
import {formatDate} from '@/lib/formatDate' import {formatDate} from '@/lib/formatDate'
import {Prose} from './Prose' import {Prose} from './Prose'
import {ReactNode} from 'react' import {Views} from '@/components/Views'
type ArticleLayout = { type ArticleLayout = {
children?: ReactNode children?: ReactNode
@ -12,6 +13,7 @@ type ArticleLayout = {
description: string description: string
ogImage: string ogImage: string
date: string date: string
slug: string
} }
export function ArticleLayout({ export function ArticleLayout({
@ -20,7 +22,8 @@ export function ArticleLayout({
title, title,
description, description,
ogImage, ogImage,
date date,
slug
}: ArticleLayout) { }: ArticleLayout) {
const pathname = usePathname() const pathname = usePathname()
@ -90,12 +93,12 @@ export function ArticleLayout({
<h1 className="mt-6 text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl"> <h1 className="mt-6 text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
{title} {title}
</h1> </h1>
<time <p className="order-first text-base text-zinc-500 dark:text-zinc-400">
dateTime={date} <time dateTime={date}>
className="order-first flex items-center text-base text-zinc-500 dark:text-zinc-400" <span>{formatDate(date)}</span>
> </time>
<span>{formatDate(date)}</span> {' '}&middot;{' '}<Views slug={slug}/>
</time> </p>
</header> </header>
<Prose className="mt-8">{children}</Prose> <Prose className="mt-8">{children}</Prose>
</article> </article>

33
src/components/Views.tsx Normal file
View File

@ -0,0 +1,33 @@
import {ElementType, useEffect} from 'react'
import useSWR from 'swr'
import fetcher from '@/lib/fetcher'
function numberFormat(value: number) {
return new Intl.NumberFormat('en', {
notation: 'compact'
}).format(value)
}
type ViewsType = {
views: string
}
export function Views({as: Component = 'span', slug}: { as?: ElementType, slug: string }) {
const {data} = useSWR<ViewsType>(`/api/views/${slug}`, fetcher)
const views = Number(data?.views)
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, {
method: 'POST'
})
registerView().then(r => r)
}, [slug])
return (
<Component>
{views > 0 ? `${numberFormat(views)} views` : ''}
</Component>
)
}

3
src/lib/createSlug.ts Normal file
View File

@ -0,0 +1,3 @@
export function createSlug(title: string) {
return title.toLowerCase().replace(/\s+/g, '-')
}

11
src/lib/prisma.ts Normal file
View File

@ -0,0 +1,11 @@
import {PrismaClient} from '@prisma/client'
const globalForPrisma = global as unknown as { prisma: PrismaClient }
export const prisma =
globalForPrisma.prisma ||
new PrismaClient({
log: ['query']
})
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

View File

@ -0,0 +1,47 @@
import type {NextApiRequest, NextApiResponse} from 'next'
import {prisma} from '@/lib/prisma'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.query.slug !== undefined) {
const slug: string = req.query.slug.toString()
if (req.method === 'POST') {
const newOrUpdatedViews = await prisma.views.upsert({
where: {slug},
create: {
slug
},
update: {
count: {
increment: 1
}
}
})
return res.status(200).json({
views: newOrUpdatedViews.count.toString()
})
}
if (req.method === 'GET') {
const views = await prisma.views.findUnique({
where: {
slug
}
})
let count = null
if (views !== null) {
count = views.count.toString()
}
return res.status(200).json({
views: count
});
}
}
} catch (e: any) {
return res.status(500).json({message: e.message})
}
}

View File

@ -11,7 +11,6 @@ import {
LinkedInIcon, LinkedInIcon,
TwitterIcon TwitterIcon
} from '@/components/SocialIcons' } from '@/components/SocialIcons'
import {InlineLink} from '@/components/InlineLink';
import {formatDate} from '@/lib/formatDate' import {formatDate} from '@/lib/formatDate'
import {generateRssFeed} from '@/lib/generateRssFeed' import {generateRssFeed} from '@/lib/generateRssFeed'
import {generateSitemap} from '@/lib/generateSitemap' import {generateSitemap} from '@/lib/generateSitemap'

View File

@ -1,18 +1,20 @@
import {ArticleLayout} from '@/components/ArticleLayout' import {ArticleLayout} from '@/components/ArticleLayout'
import {createSlug} from '@/lib/createSlug'
export const meta = { export const meta = {
author: 'Ryan Freeman', author: 'Ryan Freeman',
date: '2022-12-04', date: '2022-12-04',
title: 'A personal journey in software engineering', title: 'A personal journey in software engineering',
description: description: 'Hello there! If you\'re reading this, you\'ve likely stumbled upon my website — welcome! My name is Ryan Freeman, and I\'m a full-stack developer with a passion for creating intuitive and dynamic web applications.',
'Hello there! If you\'re reading this, you\'ve likely stumbled upon my website — welcome! My name is Ryan Freeman, and I\'m a full-stack developer with a passion for creating intuitive and dynamic web applications.',
} }
export default (props) => <ArticleLayout export default (props) => <ArticleLayout
author={meta.author} author={meta.author}
date={meta.date} date={meta.date}
title={meta.title} title={meta.title}
description={meta.description} {...props} /> description={meta.description}
slug={createSlug(meta.title)}
{...props} />
Hello there! Hello there!

View File

@ -1,17 +1,20 @@
import {ArticleLayout} from '@/components/ArticleLayout' import {ArticleLayout} from '@/components/ArticleLayout'
import {createSlug} from '@/lib/createSlug'
export const meta = { export const meta = {
author: 'Ryan Freeman', author: 'Ryan Freeman',
date: '2023-01-02', date: '2023-01-02',
title: 'How to add TypeScript to an existing Next.js project', title: 'How to add TypeScript to an existing Next.js project',
description: 'Next.js includes support for TypeScript by default. To add TypeScript to an existing Next.js project create a tsconfig.json file in the project root with touch tsconfig.json.' description: 'Next.js includes support for TypeScript by default. To add TypeScript to an existing Next.js project create a tsconfig.json file in the project root with touch tsconfig.json.',
} }
export default (props) => <ArticleLayout export default (props) => <ArticleLayout
author={meta.author} author={meta.author}
date={meta.date} date={meta.date}
title={meta.title} title={meta.title}
description={meta.description} {...props} /> description={meta.description}
slug={createSlug(meta.title)}
{...props} />
# Next.js includes support for TypeScript by default. To add TypeScript to an existing Next.js project create a _tsconfig.json_ file in the project root with `touch tsconfig.json`. # Next.js includes support for TypeScript by default. To add TypeScript to an existing Next.js project create a _tsconfig.json_ file in the project root with `touch tsconfig.json`.
Next, run `next dev` and the _tsconfig.json_ file will be populated with the default values, you may customise this configuration to your liking. A file called _next-env.d.ts_ will be created at the project root, this file **should not** be removed or edited. Next, run `next dev` and the _tsconfig.json_ file will be populated with the default values, you may customise this configuration to your liking. A file called _next-env.d.ts_ will be created at the project root, this file **should not** be removed or edited.