From 7281397fd597b96b6eb25b0e437b35f344261f75 Mon Sep 17 00:00:00 2001 From: r-freeman Date: Tue, 17 Jan 2023 21:24:16 +0000 Subject: [PATCH] Added page views functionality --- .gitignore | 4 + next.config.mjs | 20 +- package-lock.json | 346 +++++++++++++++++- package.json | 13 +- prisma/schema.prisma | 17 + src/components/ArticleLayout.tsx | 19 +- src/components/Views.tsx | 33 ++ src/lib/createSlug.ts | 3 + src/lib/prisma.ts | 11 + src/pages/api/views/[slug].ts | 47 +++ src/pages/index.tsx | 1 - ...rsonal-journey-in-software-engineering.mdx | 8 +- ...pescript-to-an-existing-nextjs-project.mdx | 7 +- 13 files changed, 494 insertions(+), 35 deletions(-) create mode 100644 prisma/schema.prisma create mode 100644 src/components/Views.tsx create mode 100644 src/lib/createSlug.ts create mode 100644 src/lib/prisma.ts create mode 100644 src/pages/api/views/[slug].ts diff --git a/.gitignore b/.gitignore index 3d4cb39..e0c0937 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +prisma/**/* +!prisma/schema.prisma +.env + .idea/ public/rss diff --git a/next.config.mjs b/next.config.mjs index ecf6659..9aa7ff4 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -4,8 +4,8 @@ import rehypePrism from '@mapbox/rehype-prism' /** @type {import('next').NextConfig} */ const nextConfig = { - pageExtensions: ['jsx', 'js', 'tsx', 'mdx'], - reactStrictMode: true, + pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'mdx'], + reactStrictMode: false, swcMinify: true, experimental: { newNextLinkBehavior: true, @@ -15,12 +15,16 @@ const nextConfig = { domains: ['i.scdn.co'] }, async rewrites() { - return [ - { - source: '/api/:path', - destination: 'https://ryanfreeman.dev/:path/', - } - ] + if (process.env.NODE_ENV === 'production') { + return [ + { + source: '/api/:path', + destination: 'https://ryanfreeman.dev/:path/', + } + ] + } else { + return [] + } } } diff --git a/package-lock.json b/package-lock.json index 8402a2d..e624ce8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,10 @@ "@headlessui/react": "^1.7.7", "@mapbox/rehype-prism": "^0.8.0", "@next/mdx": "^13.1.1", + "@prisma/client": "^4.8.1", "@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/typography": "^0.5.8", "@types/mdx": "^2.0.3", - "@types/node": "18.11.18", "@types/react": "18.0.26", "@types/react-dom": "18.0.10", "autoprefixer": "^10.4.13", @@ -34,7 +34,12 @@ "remark-gfm": "^3.0.1", "sharp": "^0.31.3", "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" } }, @@ -49,6 +54,28 @@ "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": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -134,7 +161,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -161,8 +187,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "peer": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.17", @@ -613,6 +638,38 @@ "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -667,6 +724,30 @@ "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": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", @@ -2035,6 +2116,12 @@ "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": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5284,6 +5371,12 @@ "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": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", @@ -7469,6 +7562,23 @@ "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": { "version": "1.27.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", @@ -8740,6 +8850,73 @@ "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": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -9217,6 +9394,12 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", @@ -9737,6 +9920,15 @@ "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": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -9767,6 +9959,27 @@ "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": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -9825,8 +10038,7 @@ "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "peer": true + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { "version": "1.1.2", @@ -9847,8 +10059,7 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "peer": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "@jridgewell/trace-mapping": { "version": "0.3.17", @@ -10140,6 +10351,25 @@ "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", @@ -10183,6 +10413,30 @@ "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": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", @@ -11200,6 +11454,12 @@ "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": { "version": "7.0.3", "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": { "version": "1.1.1", "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", "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": { "version": "1.27.0", "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", "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": { "version": "3.14.1", "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": { "version": "3.0.0", "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" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 14f5ddd..b3b25f4 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,10 @@ "@headlessui/react": "^1.7.7", "@mapbox/rehype-prism": "^0.8.0", "@next/mdx": "^13.1.1", + "@prisma/client": "^4.8.1", "@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/typography": "^0.5.8", "@types/mdx": "^2.0.3", - "@types/node": "18.11.18", "@types/react": "18.0.26", "@types/react-dom": "18.0.10", "autoprefixer": "^10.4.13", @@ -35,8 +35,13 @@ "react-dom": "18.2.0", "remark-gfm": "^3.0.1", "sharp": "^0.31.3", - "swr": "^2.0.0", - "tailwindcss": "^3.2.4", - "typescript": "4.9.4" + "swr": "^2.0.0" + }, + "devDependencies": { + "@types/node": "^18.11.18", + "prisma": "^4.8.1", + "ts-node": "^10.9.1", + "typescript": "4.9.4", + "tailwindcss": "^3.2.4" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..5c1abb8 --- /dev/null +++ b/prisma/schema.prisma @@ -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) +} \ No newline at end of file diff --git a/src/components/ArticleLayout.tsx b/src/components/ArticleLayout.tsx index 45274c1..3df6a5f 100644 --- a/src/components/ArticleLayout.tsx +++ b/src/components/ArticleLayout.tsx @@ -1,9 +1,10 @@ +import {ReactNode} from 'react' import Head from 'next/head' import {usePathname} from 'next/navigation' import {Container} from './Container' import {formatDate} from '@/lib/formatDate' import {Prose} from './Prose' -import {ReactNode} from 'react' +import {Views} from '@/components/Views' type ArticleLayout = { children?: ReactNode @@ -12,6 +13,7 @@ type ArticleLayout = { description: string ogImage: string date: string + slug: string } export function ArticleLayout({ @@ -20,7 +22,8 @@ export function ArticleLayout({ title, description, ogImage, - date + date, + slug }: ArticleLayout) { const pathname = usePathname() @@ -90,12 +93,12 @@ export function ArticleLayout({

{title}

- +

+ + {' '}·{' '} +

{children} diff --git a/src/components/Views.tsx b/src/components/Views.tsx new file mode 100644 index 0000000..4f75ddb --- /dev/null +++ b/src/components/Views.tsx @@ -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(`/api/views/${slug}`, fetcher) + const views = Number(data?.views) + + useEffect(() => { + const registerView = () => + fetch(`/api/views/${slug}`, { + method: 'POST' + }) + + registerView().then(r => r) + }, [slug]) + + return ( + + {views > 0 ? `${numberFormat(views)} views` : ''} + + ) +} \ No newline at end of file diff --git a/src/lib/createSlug.ts b/src/lib/createSlug.ts new file mode 100644 index 0000000..2ec15fe --- /dev/null +++ b/src/lib/createSlug.ts @@ -0,0 +1,3 @@ +export function createSlug(title: string) { + return title.toLowerCase().replace(/\s+/g, '-') +} \ No newline at end of file diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..9cdb7b6 --- /dev/null +++ b/src/lib/prisma.ts @@ -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 \ No newline at end of file diff --git a/src/pages/api/views/[slug].ts b/src/pages/api/views/[slug].ts new file mode 100644 index 0000000..a1ef382 --- /dev/null +++ b/src/pages/api/views/[slug].ts @@ -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}) + } +} \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index dfbbf39..f4a7f15 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -11,7 +11,6 @@ import { LinkedInIcon, TwitterIcon } from '@/components/SocialIcons' -import {InlineLink} from '@/components/InlineLink'; import {formatDate} from '@/lib/formatDate' import {generateRssFeed} from '@/lib/generateRssFeed' import {generateSitemap} from '@/lib/generateSitemap' diff --git a/src/pages/writing/a-personal-journey-in-software-engineering.mdx b/src/pages/writing/a-personal-journey-in-software-engineering.mdx index 0c4b4b9..eb10afb 100644 --- a/src/pages/writing/a-personal-journey-in-software-engineering.mdx +++ b/src/pages/writing/a-personal-journey-in-software-engineering.mdx @@ -1,18 +1,20 @@ import {ArticleLayout} from '@/components/ArticleLayout' +import {createSlug} from '@/lib/createSlug' export const meta = { author: 'Ryan Freeman', date: '2022-12-04', title: 'A personal journey in software engineering', - 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.', + 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.', } export default (props) => + description={meta.description} + slug={createSlug(meta.title)} + {...props} /> Hello there! diff --git a/src/pages/writing/how-to-add-typescript-to-an-existing-nextjs-project.mdx b/src/pages/writing/how-to-add-typescript-to-an-existing-nextjs-project.mdx index 8faf9eb..b47fc6e 100644 --- a/src/pages/writing/how-to-add-typescript-to-an-existing-nextjs-project.mdx +++ b/src/pages/writing/how-to-add-typescript-to-an-existing-nextjs-project.mdx @@ -1,17 +1,20 @@ import {ArticleLayout} from '@/components/ArticleLayout' +import {createSlug} from '@/lib/createSlug' export const meta = { author: 'Ryan Freeman', date: '2023-01-02', 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) => + 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, 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.