Compare commits
22 Commits
35efde0db9
...
stats
Author | SHA1 | Date | |
---|---|---|---|
56a60a9039 | |||
0bc6afbb4b | |||
9c6c0b315c | |||
f28c2defc4 | |||
42c8a9e28a | |||
5a3ce73ce8 | |||
82ce4b345a | |||
f640afbcb1 | |||
952d9541b9 | |||
a223fc709e | |||
64e5fd3354 | |||
434536d1f2 | |||
4656df7fca | |||
c96525e1fc | |||
4406de6986 | |||
5c139e83f5 | |||
3fc41e6fc4 | |||
b1422269bb | |||
dcde0cb893 | |||
4d5b1692c4 | |||
20da33d412 | |||
f2df5c4f75 |
8
compose.yml
Normal file
8
compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
668
package-lock.json
generated
668
package-lock.json
generated
@ -13,16 +13,16 @@
|
|||||||
"argon2": "^0.41.1",
|
"argon2": "^0.41.1",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"jose": "^6.0.10",
|
|
||||||
"lucide-react": "^0.485.0",
|
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"next": "15.2.4",
|
"next": "15.2.4",
|
||||||
|
"next-auth": "^5.0.0-beta.25",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-social-icons": "^6.22.0",
|
"react-social-icons": "^6.22.0",
|
||||||
"slugify": "^1.6.6"
|
"slugify": "^1.6.6",
|
||||||
|
"strava-v3": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
@ -38,6 +38,46 @@
|
|||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@auth/core": {
|
||||||
|
"version": "0.37.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.2.tgz",
|
||||||
|
"integrity": "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@panva/hkdf": "^1.2.1",
|
||||||
|
"@types/cookie": "0.6.0",
|
||||||
|
"cookie": "0.7.1",
|
||||||
|
"jose": "^5.9.3",
|
||||||
|
"oauth4webapi": "^3.0.0",
|
||||||
|
"preact": "10.11.3",
|
||||||
|
"preact-render-to-string": "5.2.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"nodemailer": "^6.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@auth/core/node_modules/jose": {
|
||||||
|
"version": "5.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||||
|
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.27.0",
|
"version": "7.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||||
@ -862,6 +902,15 @@
|
|||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@panva/hkdf": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@phc/format": {
|
"node_modules/@phc/format": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
||||||
@ -933,6 +982,12 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||||
@ -1512,7 +1567,6 @@
|
|||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
@ -1730,6 +1784,24 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asn1": {
|
||||||
|
"version": "0.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||||
|
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": "~2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/assert-plus": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ast-types-flow": {
|
"node_modules/ast-types-flow": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
|
||||||
@ -1747,6 +1819,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@ -1763,6 +1841,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/aws-sign2": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||||
|
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/aws4": {
|
||||||
|
"version": "1.13.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
|
||||||
|
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/axe-core": {
|
"node_modules/axe-core": {
|
||||||
"version": "4.10.3",
|
"version": "4.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
||||||
@ -1790,6 +1883,30 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/bcrypt-pbkdf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tweetnacl": "^0.14.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bignumber.js": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-JocpCSOixzy5XFJi2ub6IMmV/G9i8Lrm2lZvwBv9xPdglmZM0ufDVBbjbrfU/zuLvBfD7Bv2eYxz9i+OHTgkew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bluebird": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
@ -1911,6 +2028,12 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/caseless": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@ -2021,6 +2144,18 @@
|
|||||||
"simple-swizzle": "^0.2.2"
|
"simple-swizzle": "^0.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -2028,6 +2163,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||||
|
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/core-util-is": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -2085,6 +2235,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/dashdash": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/data-view-buffer": {
|
"node_modules/data-view-buffer": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
|
||||||
@ -2200,6 +2362,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||||
@ -2293,6 +2464,16 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ecc-jsbn": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jsbn": "~0.1.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
@ -2938,11 +3119,25 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/extend": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/extsprintf": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
|
||||||
|
"engines": [
|
||||||
|
"node >=0.6.0"
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
@ -2979,7 +3174,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-levenshtein": {
|
"node_modules/fast-levenshtein": {
|
||||||
@ -3079,6 +3273,29 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/forever-agent": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.6",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@ -3190,6 +3407,15 @@
|
|||||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/getpass": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||||
|
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@ -3253,6 +3479,29 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/har-schema": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/har-validator": {
|
||||||
|
"version": "5.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
|
||||||
|
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
|
||||||
|
"deprecated": "this library is no longer supported",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^6.12.3",
|
||||||
|
"har-schema": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/has-bigints": {
|
"node_modules/has-bigints": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||||
@ -3384,6 +3633,21 @@
|
|||||||
"entities": "^4.5.0"
|
"entities": "^4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/http-signature": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"jsprim": "^1.2.2",
|
||||||
|
"sshpk": "^1.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8",
|
||||||
|
"npm": ">=1.3.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
@ -3797,6 +4061,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-typedarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-weakmap": {
|
"node_modules/is-weakmap": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
|
||||||
@ -3857,6 +4127,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/isstream": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/iterator.prototype": {
|
"node_modules/iterator.prototype": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
|
||||||
@ -3875,15 +4151,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jose": {
|
|
||||||
"version": "6.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz",
|
|
||||||
"integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/panva"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -3903,6 +4170,21 @@
|
|||||||
"js-yaml": "bin/js-yaml.js"
|
"js-yaml": "bin/js-yaml.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsbn": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/json-bigint": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bignumber.js": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/json-buffer": {
|
"node_modules/json-buffer": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||||
@ -3910,11 +4192,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-schema": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||||
|
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
||||||
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/json-stable-stringify-without-jsonify": {
|
"node_modules/json-stable-stringify-without-jsonify": {
|
||||||
@ -3924,6 +4211,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-stringify-safe": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
@ -3937,6 +4230,21 @@
|
|||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsprim": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "1.0.0",
|
||||||
|
"extsprintf": "1.3.0",
|
||||||
|
"json-schema": "0.4.0",
|
||||||
|
"verror": "1.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsx-ast-utils": {
|
"node_modules/jsx-ast-utils": {
|
||||||
"version": "3.3.5",
|
"version": "3.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||||
@ -4022,6 +4330,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -4041,15 +4355,6 @@
|
|||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
|
||||||
"version": "0.485.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.485.0.tgz",
|
|
||||||
"integrity": "sha512-NvyQJ0LKyyCxL23nPKESlr/jmz8r7fJO1bkuptSNYSy0s8VVj4ojhX0YAgmE1e0ewfxUZjIlZpvH+otfTnla8Q==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/markdown-it": {
|
"node_modules/markdown-it": {
|
||||||
"version": "14.1.0",
|
"version": "14.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||||
@ -4107,6 +4412,27 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@ -4216,6 +4542,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-auth": {
|
||||||
|
"version": "5.0.0-beta.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.25.tgz",
|
||||||
|
"integrity": "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/core": "0.37.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"next": "^14.0.0-0 || ^15.0.0-0",
|
||||||
|
"nodemailer": "^6.6.5",
|
||||||
|
"react": "^18.2.0 || ^19.0.0-0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-addon-api": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "8.3.1",
|
"version": "8.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz",
|
||||||
@ -4257,6 +4610,24 @@
|
|||||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oauth-sign": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/oauth4webapi": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-5lcbectYuzQHvh0Ni7Epvc13sMVq7BxWUlHEYHaNko64OA1hcats0Huq30vZjqCZULcVE/PZxAGGPansfRAWKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@ -4524,6 +4895,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/performance-now": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@ -4581,6 +4958,28 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
|
||||||
|
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/preact-render-to-string": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pretty-format": "^3.8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@ -4591,6 +4990,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pretty-format": {
|
||||||
|
"version": "3.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||||
|
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@ -4602,11 +5007,22 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/psl": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/lupomontero"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@ -4621,6 +5037,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
||||||
|
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -4794,6 +5219,72 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/request": {
|
||||||
|
"version": "2.88.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||||
|
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||||
|
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"aws-sign2": "~0.7.0",
|
||||||
|
"aws4": "^1.8.0",
|
||||||
|
"caseless": "~0.12.0",
|
||||||
|
"combined-stream": "~1.0.6",
|
||||||
|
"extend": "~3.0.2",
|
||||||
|
"forever-agent": "~0.6.1",
|
||||||
|
"form-data": "~2.3.2",
|
||||||
|
"har-validator": "~5.1.3",
|
||||||
|
"http-signature": "~1.2.0",
|
||||||
|
"is-typedarray": "~1.0.0",
|
||||||
|
"isstream": "~0.1.2",
|
||||||
|
"json-stringify-safe": "~5.0.1",
|
||||||
|
"mime-types": "~2.1.19",
|
||||||
|
"oauth-sign": "~0.9.0",
|
||||||
|
"performance-now": "^2.1.0",
|
||||||
|
"qs": "~6.5.2",
|
||||||
|
"safe-buffer": "^5.1.2",
|
||||||
|
"tough-cookie": "~2.5.0",
|
||||||
|
"tunnel-agent": "^0.6.0",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/request-promise": {
|
||||||
|
"version": "4.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
|
||||||
|
"integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==",
|
||||||
|
"deprecated": "request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^3.5.0",
|
||||||
|
"request-promise-core": "1.1.4",
|
||||||
|
"stealthy-require": "^1.1.1",
|
||||||
|
"tough-cookie": "^2.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"request": "^2.34"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/request-promise-core": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.19"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"request": "^2.34"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
@ -4890,6 +5381,26 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/safe-push-apply": {
|
"node_modules/safe-push-apply": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
|
||||||
@ -5166,6 +5677,31 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sshpk": {
|
||||||
|
"version": "1.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
|
||||||
|
"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asn1": "~0.2.3",
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"bcrypt-pbkdf": "^1.0.0",
|
||||||
|
"dashdash": "^1.12.0",
|
||||||
|
"ecc-jsbn": "~0.1.1",
|
||||||
|
"getpass": "^0.1.1",
|
||||||
|
"jsbn": "~0.1.0",
|
||||||
|
"safer-buffer": "^2.0.2",
|
||||||
|
"tweetnacl": "~0.14.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sshpk-conv": "bin/sshpk-conv",
|
||||||
|
"sshpk-sign": "bin/sshpk-sign",
|
||||||
|
"sshpk-verify": "bin/sshpk-verify"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/stable-hash": {
|
"node_modules/stable-hash": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
|
||||||
@ -5173,6 +5709,30 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/stealthy-require": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strava-v3": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strava-v3/-/strava-v3-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-gIz0JIk8cAjhOvCqE6bOy+YL+jjPKtHI1iXa69sLD+JhKyVm7Nnb49MBvVC1G6gIfjM7sGZIVNOtTaCblekT7Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^3.5.0",
|
||||||
|
"json-bigint": "^1.0.0",
|
||||||
|
"request": "^2.81.0",
|
||||||
|
"request-promise": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/streamsearch": {
|
"node_modules/streamsearch": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||||
@ -5424,6 +5984,19 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tough-cookie": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"psl": "^1.1.28",
|
||||||
|
"punycode": "^2.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||||
@ -5456,6 +6029,24 @@
|
|||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tweetnacl": {
|
||||||
|
"version": "0.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
|
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||||
|
"license": "Unlicense"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@ -5633,12 +6224,35 @@
|
|||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||||
|
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/verror": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
||||||
|
"engines": [
|
||||||
|
"node >=0.6.0"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"core-util-is": "1.0.2",
|
||||||
|
"extsprintf": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/whatwg-encoding": {
|
"node_modules/whatwg-encoding": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
@ -14,16 +14,16 @@
|
|||||||
"argon2": "^0.41.1",
|
"argon2": "^0.41.1",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"jose": "^6.0.10",
|
|
||||||
"lucide-react": "^0.485.0",
|
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"next": "15.2.4",
|
"next": "15.2.4",
|
||||||
|
"next-auth": "^5.0.0-beta.25",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-social-icons": "^6.22.0",
|
"react-social-icons": "^6.22.0",
|
||||||
"slugify": "^1.6.6"
|
"slugify": "^1.6.6",
|
||||||
|
"strava-v3": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Tokens" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"application" TEXT NOT NULL,
|
||||||
|
"access_token" TEXT NOT NULL,
|
||||||
|
"refresh_token" TEXT NOT NULL,
|
||||||
|
"expires_at" TIMESTAMP(6) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Tokens_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Tokens_application_key" ON "Tokens"("application");
|
@ -30,3 +30,11 @@ model User {
|
|||||||
username String @unique
|
username String @unique
|
||||||
password String
|
password String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Tokens {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
application String @unique
|
||||||
|
access_token String
|
||||||
|
refresh_token String
|
||||||
|
expires_at DateTime @db.Timestamp(6)
|
||||||
|
}
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
/* eslint-disable react/no-unescaped-entities */
|
|
||||||
|
|
||||||
export default function About() {
|
export default function About() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
so hey, i'm naresh!
|
so hey, i'm naresh!
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
i'm an engineer and i love building things and tinkering on things.
|
i'm an engineer and i love building things and tinkering on things.
|
||||||
<br />
|
<br />
|
||||||
<br /> i like going on runs and going for rides on my bike.
|
<br /> i like going on runs and going for rides on my bike.
|
||||||
<br />
|
<br />
|
||||||
<br /> umm, maybe i'll write more here later.
|
<br /> umm, maybe i'll write more here later.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
2
src/app/api/auth/[...nextauth]/route.ts
Normal file
2
src/app/api/auth/[...nextauth]/route.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { handlers } from "@/auth";
|
||||||
|
export const { GET, POST } = handlers;
|
38
src/app/blog/[[...tag]]/ActionButtons.tsx
Normal file
38
src/app/blog/[[...tag]]/ActionButtons.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
export default function ActionButtons({ loggedIn }: { loggedIn: boolean }) {
|
||||||
|
const router = useRouter();
|
||||||
|
return loggedIn ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "2rem",
|
||||||
|
padding: "0.5rem 1rem",
|
||||||
|
backgroundColor: "#333",
|
||||||
|
width: "fit-content",
|
||||||
|
userSelect: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => router.push("/blog/write")}
|
||||||
|
>
|
||||||
|
write a post
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "2rem",
|
||||||
|
padding: "0.3rem 0.5rem",
|
||||||
|
backgroundColor: "#333",
|
||||||
|
width: "fit-content",
|
||||||
|
userSelect: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
}}
|
||||||
|
onClick={() => signIn()}
|
||||||
|
>
|
||||||
|
login
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -26,169 +26,171 @@ export default function PostSummary({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
key={metadata.slug}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
borderColor: elementColor,
|
|
||||||
transition: "border-color 0.3s linear",
|
|
||||||
borderStyle: "solid",
|
|
||||||
marginTop: "1rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
backgroundColor: elementColor,
|
|
||||||
color: "#111",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "0.5rem",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
fontSize: "1rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
transition: "background-color 0.3s linear",
|
|
||||||
}}
|
|
||||||
onMouseEnter={hoverStart}
|
|
||||||
onMouseLeave={hoverEnd}
|
|
||||||
onClick={navigateToPost}
|
|
||||||
>
|
|
||||||
<span>{metadata.title}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
maxWidth: "100%",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
padding: "0.5rem",
|
|
||||||
gap: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
tags: {metadata.tags.join(", ")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
padding: "0.5rem",
|
|
||||||
gap: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{metadata.is_draft && (
|
|
||||||
<>
|
|
||||||
<span>draft - </span>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#999",
|
|
||||||
transition: "color 0.3s linear",
|
|
||||||
}}
|
|
||||||
onMouseOver={(e) => {
|
|
||||||
e.currentTarget.style.color = "#eee";
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = "#999";
|
|
||||||
}}
|
|
||||||
onClick={async () => {
|
|
||||||
await publishArticle(metadata.slug);
|
|
||||||
router.refresh();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
move to published
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!metadata.is_draft && (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<span>published: </span>
|
|
||||||
<span>{metadata.publishedDate.toLocaleDateString()}</span>
|
|
||||||
</div>
|
|
||||||
{loggedIn && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#999",
|
|
||||||
transition: "color 0.3s linear",
|
|
||||||
}}
|
|
||||||
onMouseOver={(e) => {
|
|
||||||
e.currentTarget.style.color = "#eee";
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.color = "#999";
|
|
||||||
}}
|
|
||||||
onClick={async () => {
|
|
||||||
await unpublishArticle(metadata.slug);
|
|
||||||
router.refresh();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
move to drafts
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "2px",
|
|
||||||
backgroundColor: elementColor,
|
|
||||||
transition: "background-color 0.3s linear",
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<div
|
<div
|
||||||
|
key={metadata.slug}
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: "1rem 0.5rem 0 0.5rem",
|
borderColor: elementColor,
|
||||||
|
transition: "border-color 0.3s linear",
|
||||||
|
borderStyle: "solid",
|
||||||
|
marginTop: "1rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ fontSize: "0.9rem" }}>
|
<div
|
||||||
{metadata.blurb}
|
|
||||||
<span style={{ color: "#999" }}>…</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
marginTop: "0.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
style={{
|
||||||
textDecoration: "none",
|
width: "100%",
|
||||||
color: "#222",
|
|
||||||
transition: "background-color 0.3s linear",
|
|
||||||
padding: "0.5rem 1rem",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
fontWeight: "500",
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "0.25rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
backgroundColor: elementColor,
|
backgroundColor: elementColor,
|
||||||
|
color: "#111",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "0.5rem",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
fontSize: "1rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "background-color 0.3s linear",
|
||||||
}}
|
}}
|
||||||
onMouseEnter={hoverStart}
|
onMouseEnter={hoverStart}
|
||||||
onMouseLeave={hoverEnd}
|
onMouseLeave={hoverEnd}
|
||||||
onClick={navigateToPost}
|
onClick={navigateToPost}
|
||||||
>
|
>
|
||||||
Read more <span style={{ fontSize: "1rem" }}>→</span>
|
<span>{metadata.title}</span>
|
||||||
</span>
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
maxWidth: "100%",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
padding: "0.5rem",
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
tags: {metadata.tags.join(", ")}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
padding: "0.5rem",
|
||||||
|
gap: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{metadata.is_draft && (
|
||||||
|
<>
|
||||||
|
<span>draft - </span>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
color: "#999",
|
||||||
|
transition: "color 0.3s linear",
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => {
|
||||||
|
e.currentTarget.style.color = "#eee";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = "#999";
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
await publishArticle(metadata.slug);
|
||||||
|
router.refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
move to published
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!metadata.is_draft && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span>published: </span>
|
||||||
|
<span>{metadata.publishedDate.toLocaleDateString()}</span>
|
||||||
|
</div>
|
||||||
|
{loggedIn && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
color: "#999",
|
||||||
|
transition: "color 0.3s linear",
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => {
|
||||||
|
e.currentTarget.style.color = "#eee";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = "#999";
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
await unpublishArticle(metadata.slug);
|
||||||
|
router.refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
move to drafts
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "2px",
|
||||||
|
backgroundColor: elementColor,
|
||||||
|
transition: "background-color 0.3s linear",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: "1rem 0.5rem 0 0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: "0.9rem" }}>
|
||||||
|
{metadata.blurb}
|
||||||
|
<span style={{ color: "#999" }}>…</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
marginTop: "0.5rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "#222",
|
||||||
|
transition: "background-color 0.3s linear",
|
||||||
|
padding: "0.5rem 1rem",
|
||||||
|
fontSize: "0.9rem",
|
||||||
|
fontWeight: "500",
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "0.25rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
backgroundColor: elementColor,
|
||||||
|
}}
|
||||||
|
onMouseEnter={hoverStart}
|
||||||
|
onMouseLeave={hoverEnd}
|
||||||
|
onClick={navigateToPost}
|
||||||
|
>
|
||||||
|
Read more <span style={{ fontSize: "1rem" }}>→</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ import React from "react";
|
|||||||
import PostSummary from "./PostSummary";
|
import PostSummary from "./PostSummary";
|
||||||
import Pagination from "./Pagination";
|
import Pagination from "./Pagination";
|
||||||
import TagOverview from "./TagOverview";
|
import TagOverview from "./TagOverview";
|
||||||
import { isLoggedIn } from "@/components/auth";
|
import ActionButtons from "./ActionButtons";
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
|
||||||
export default async function Blog({
|
export default async function Blog({
|
||||||
params,
|
params,
|
||||||
@ -19,6 +20,7 @@ export default async function Blog({
|
|||||||
const pageNumber = await getPageNumber(searchParams);
|
const pageNumber = await getPageNumber(searchParams);
|
||||||
const { tag } = await params;
|
const { tag } = await params;
|
||||||
const currentTag = tag != null && tag.length >= 1 ? tag[0] : null;
|
const currentTag = tag != null && tag.length >= 1 ? tag[0] : null;
|
||||||
|
|
||||||
if ((tag?.length ?? 0) > 1) {
|
if ((tag?.length ?? 0) > 1) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
@ -27,10 +29,8 @@ export default async function Blog({
|
|||||||
pageNumber,
|
pageNumber,
|
||||||
currentTag
|
currentTag
|
||||||
);
|
);
|
||||||
if (pageNumber > numberOfPages) {
|
|
||||||
notFound();
|
const loggedIn = (await auth())?.user != null;
|
||||||
}
|
|
||||||
const loggedIn = await isLoggedIn();
|
|
||||||
const tags = await getTags(loggedIn);
|
const tags = await getTags(loggedIn);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -45,13 +45,24 @@ export default async function Blog({
|
|||||||
<span style={{ fontSize: 18 }}>naresh writes</span>
|
<span style={{ fontSize: 18 }}>naresh writes</span>
|
||||||
<span style={{ fontSize: 12 }}>...occasionally</span>
|
<span style={{ fontSize: 12 }}>...occasionally</span>
|
||||||
</div>
|
</div>
|
||||||
<TagOverview tags={tags} currentTag={currentTag} />
|
{pageNumber > numberOfPages ? (
|
||||||
{metadata
|
<div style={{ marginTop: "1rem" }}>
|
||||||
.filter((m) => loggedIn || !m.is_draft)
|
ah fuck...
|
||||||
.map((m) => (
|
<br /> it seems like i haven't written anything yet
|
||||||
<PostSummary metadata={m} key={m.slug} loggedIn={loggedIn} />
|
<br /> so much for a blog, eh?
|
||||||
))}
|
</div>
|
||||||
<Pagination numberOfPages={numberOfPages} pageNumber={pageNumber} />
|
) : (
|
||||||
|
<>
|
||||||
|
<TagOverview tags={tags} currentTag={currentTag} />
|
||||||
|
{metadata
|
||||||
|
.filter((m) => loggedIn || !m.is_draft)
|
||||||
|
.map((m) => (
|
||||||
|
<PostSummary metadata={m} key={m.slug} loggedIn={loggedIn} />
|
||||||
|
))}
|
||||||
|
<Pagination numberOfPages={numberOfPages} pageNumber={pageNumber} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<ActionButtons loggedIn={loggedIn} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export async function getSummaries(
|
|||||||
...filter,
|
...filter,
|
||||||
omit: { contentMarkdown: true, contentRendered: true },
|
omit: { contentMarkdown: true, contentRendered: true },
|
||||||
include: { tags: { select: { name: true } } },
|
include: { tags: { select: { name: true } } },
|
||||||
orderBy: { publishedDate: "asc" },
|
orderBy: { publishedDate: "desc" },
|
||||||
skip: PAGE_SIZE * (pageNumber - 1),
|
skip: PAGE_SIZE * (pageNumber - 1),
|
||||||
take: PAGE_SIZE,
|
take: PAGE_SIZE,
|
||||||
})
|
})
|
||||||
@ -79,3 +79,19 @@ export async function unpublishArticle(slug: string) {
|
|||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
await prisma.post.update({ where: { slug }, data: { is_draft: true } });
|
await prisma.post.update({ where: { slug }, data: { is_draft: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteArticle(slug: string) {
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
const post = await prisma.post.delete({
|
||||||
|
where: { slug },
|
||||||
|
include: { tags: true },
|
||||||
|
});
|
||||||
|
for (const tag of post.tags) {
|
||||||
|
const postsWithTag = await prisma.post.count({
|
||||||
|
where: { tags: { some: { id: tag.id } } },
|
||||||
|
});
|
||||||
|
if (postsWithTag == 0) {
|
||||||
|
await prisma.tag.delete({ where: { id: tag.id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Form from "next/form";
|
|
||||||
import { handleLogin } from "./action";
|
|
||||||
|
|
||||||
export default function FormComponent() {
|
|
||||||
return (
|
|
||||||
<Form action={handleLogin}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "1rem",
|
|
||||||
fontSize: 14,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
Username:
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
style={{
|
|
||||||
height: "2rem",
|
|
||||||
backgroundColor: "#333",
|
|
||||||
borderStyle: "none",
|
|
||||||
color: "#eee",
|
|
||||||
fontSize: 16,
|
|
||||||
padding: "0.5rem 1rem",
|
|
||||||
}}
|
|
||||||
name="username"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
Password:
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
style={{
|
|
||||||
height: "2rem",
|
|
||||||
backgroundColor: "#333",
|
|
||||||
borderStyle: "none",
|
|
||||||
color: "#eee",
|
|
||||||
fontSize: 16,
|
|
||||||
padding: "0.5rem 1rem",
|
|
||||||
}}
|
|
||||||
name="password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#333",
|
|
||||||
borderStyle: "none",
|
|
||||||
color: "#eee",
|
|
||||||
fontSize: "1rem",
|
|
||||||
padding: "0.5rem 1rem",
|
|
||||||
transition: "background-color 0.3s linear",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
onMouseOver={(e) => {
|
|
||||||
e.currentTarget.style.backgroundColor = "#444";
|
|
||||||
}}
|
|
||||||
onMouseOut={(e) => {
|
|
||||||
e.currentTarget.style.backgroundColor = "#333";
|
|
||||||
}}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
|
||||||
import argon2 from "argon2";
|
|
||||||
import { setSession } from "@/components/auth";
|
|
||||||
import { redirect, RedirectType } from "next/navigation";
|
|
||||||
|
|
||||||
export async function handleLogin(data: FormData) {
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
const username = data.get("username")?.toString();
|
|
||||||
const password = data.get("password")?.toString();
|
|
||||||
if (!username || !password) {
|
|
||||||
throw new Error("Missing username or password");
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({ where: { username } });
|
|
||||||
if (!user) {
|
|
||||||
redirect("/blog/login?error=Invalid%20credentials", RedirectType.replace);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await argon2.verify(user.password, password)) {
|
|
||||||
setSession();
|
|
||||||
redirect("/blog/write", RedirectType.replace);
|
|
||||||
} else {
|
|
||||||
redirect("/blog/login?error=Invalid%20credentials", RedirectType.replace);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
import { isLoggedIn } from "@/components/auth";
|
|
||||||
import FormComponent from "./Form";
|
|
||||||
|
|
||||||
export default async function Login() {
|
|
||||||
if (await isLoggedIn()) {
|
|
||||||
redirect("/blog/write");
|
|
||||||
}
|
|
||||||
return <FormComponent />;
|
|
||||||
}
|
|
@ -3,7 +3,8 @@
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import * as Types from "../../types";
|
import * as Types from "../../types";
|
||||||
import "highlight.js/styles/github-dark.css";
|
import "highlight.js/styles/github-dark.css";
|
||||||
import { publishArticle, unpublishArticle } from "../../action";
|
import { deleteArticle, publishArticle, unpublishArticle } from "../../action";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function PostDisplay({
|
export default function PostDisplay({
|
||||||
post,
|
post,
|
||||||
@ -13,6 +14,9 @@ export default function PostDisplay({
|
|||||||
loggedIn: boolean;
|
loggedIn: boolean;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@ -151,25 +155,55 @@ export default function PostDisplay({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loggedIn ? (
|
{loggedIn ? (
|
||||||
<div
|
<div style={{ display: "flex", gap: "1rem" }}>
|
||||||
style={{
|
<div
|
||||||
backgroundColor: "#a66",
|
style={{
|
||||||
color: "#111",
|
backgroundColor: "#6a6",
|
||||||
fontSize: "0.8rem",
|
color: "#111",
|
||||||
padding: "0.5rem",
|
fontSize: "0.8rem",
|
||||||
transition: "background-color 0.3s linear",
|
padding: "0.5rem",
|
||||||
userSelect: "none",
|
transition: "background-color 0.3s linear",
|
||||||
cursor: "pointer",
|
userSelect: "none",
|
||||||
}}
|
cursor: "pointer",
|
||||||
onMouseOver={(e) => {
|
}}
|
||||||
e.currentTarget.style.backgroundColor = "#c88";
|
onMouseOver={(e) => {
|
||||||
}}
|
e.currentTarget.style.backgroundColor = "#8c8";
|
||||||
onMouseOut={(e) => {
|
}}
|
||||||
e.currentTarget.style.backgroundColor = "#a66";
|
onMouseOut={(e) => {
|
||||||
}}
|
e.currentTarget.style.backgroundColor = "#6a6";
|
||||||
onClick={() => router.push(`/blog/write/${post.slug}`)}
|
}}
|
||||||
>
|
onClick={() => router.push(`/blog/write/${post.slug}`)}
|
||||||
edit
|
>
|
||||||
|
edit
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#a66",
|
||||||
|
color: "#111",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
padding: "0.5rem",
|
||||||
|
transition: "background-color 0.3s linear",
|
||||||
|
userSelect: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = "#c88";
|
||||||
|
}}
|
||||||
|
onMouseOut={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = "#a66";
|
||||||
|
}}
|
||||||
|
onClick={() => setConfirmDelete(true)}
|
||||||
|
>
|
||||||
|
delete
|
||||||
|
</div>
|
||||||
|
<DeleteModal
|
||||||
|
open={confirmDelete}
|
||||||
|
onCancel={() => setConfirmDelete(false)}
|
||||||
|
onConfirm={async () => {
|
||||||
|
await deleteArticle(post.slug);
|
||||||
|
router.push("/blog");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div></div>
|
<div></div>
|
||||||
@ -198,3 +232,96 @@ export default function PostDisplay({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DeleteModal({
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="deleteModal"
|
||||||
|
style={{
|
||||||
|
display: open
|
||||||
|
? "flex"
|
||||||
|
: "none" /* Will be changed to "flex" when opened */,
|
||||||
|
position: "fixed",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||||
|
zIndex: 1000,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target == e.currentTarget) {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#222",
|
||||||
|
padding: "1.5rem",
|
||||||
|
borderRadius: "4px",
|
||||||
|
maxWidth: "400px",
|
||||||
|
width: "90%",
|
||||||
|
boxShadow: "0 0 10px rgba(0, 0, 0, 0.5)",
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h3 style={{ margin: "0 0 1rem 0", color: "#eee" }}>
|
||||||
|
Confirm Deletion
|
||||||
|
</h3>
|
||||||
|
<p style={{ marginBottom: "1.5rem", color: "#ccc" }}>
|
||||||
|
Are you sure you want to delete this post? This action cannot be
|
||||||
|
undone.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
gap: "0.75rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#a66",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
padding: "0.5rem 1rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "2px",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
onConfirm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Yes, delete it
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#555",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
padding: "0.5rem 1rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "2px",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
onCancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { getPost } from "../../action";
|
import { getPost } from "../../action";
|
||||||
import PostDisplay from "./PostDisplay";
|
import PostDisplay from "./PostDisplay";
|
||||||
import { isLoggedIn } from "@/components/auth";
|
import { auth } from "@/auth";
|
||||||
|
|
||||||
export default async function Post({
|
export default async function Post({
|
||||||
params,
|
params,
|
||||||
@ -13,7 +13,7 @@ export default async function Post({
|
|||||||
if (!post) {
|
if (!post) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
const loggedIn = await isLoggedIn();
|
const loggedIn = (await auth())?.user != null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PostDisplay post={post} loggedIn={loggedIn} />
|
<PostDisplay post={post} loggedIn={loggedIn} />
|
||||||
|
@ -6,6 +6,7 @@ import { KeyboardEvent } from "react";
|
|||||||
const md = new MarkdownIt({
|
const md = new MarkdownIt({
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
|
breaks: true,
|
||||||
highlight: (str, lang) => {
|
highlight: (str, lang) => {
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { Post } from "../../types";
|
import { Post } from "../../types";
|
||||||
import Write from "../Write";
|
import Write from "../Write";
|
||||||
import { isLoggedIn } from "@/components/auth";
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import { auth, signIn } from "@/auth";
|
||||||
|
|
||||||
export default async function WritePage({
|
export default async function WritePage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ slug: string[] | undefined }>;
|
params: Promise<{ slug: string[] | undefined }>;
|
||||||
}) {
|
}) {
|
||||||
if (!(await isLoggedIn())) {
|
if ((await auth())?.user == null) {
|
||||||
redirect("/blog/login");
|
await signIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
const slug = (await params).slug?.[0];
|
const slug = (await params).slug?.[0];
|
||||||
|
@ -5,6 +5,7 @@ import MarkdownIt from "markdown-it";
|
|||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
import * as cheerio from "cheerio";
|
import * as cheerio from "cheerio";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
|
||||||
export async function savePostServer(
|
export async function savePostServer(
|
||||||
title: string,
|
title: string,
|
||||||
@ -13,11 +14,16 @@ export async function savePostServer(
|
|||||||
is_draft: boolean,
|
is_draft: boolean,
|
||||||
existingSlug?: string
|
existingSlug?: string
|
||||||
) {
|
) {
|
||||||
|
if ((await auth())?.user == null) {
|
||||||
|
throw new Error("Not authenticated");
|
||||||
|
}
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
const md = new MarkdownIt({
|
const md = new MarkdownIt({
|
||||||
linkify: true,
|
linkify: true,
|
||||||
typographer: true,
|
typographer: true,
|
||||||
|
breaks: true,
|
||||||
highlight: (str, lang) => {
|
highlight: (str, lang) => {
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
|
@ -1,4 +1,80 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
|
import { createTransport } from "nodemailer";
|
||||||
|
|
||||||
export async function getReCaptchaSiteKey() {
|
export async function getReCaptchaSiteKey() {
|
||||||
return process.env.RECAPTCHA_SITE_KEY;
|
return process.env.RECAPTCHA_SITE_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RECAPTCHA_SERVER_KEY = process.env.RECAPTCHA_SERVER_KEY;
|
||||||
|
|
||||||
|
export type SubmitContactReturn = { error: string } | { success: true };
|
||||||
|
|
||||||
|
export default async function SubmitContact(
|
||||||
|
prevState: unknown,
|
||||||
|
data: FormData
|
||||||
|
): Promise<SubmitContactReturn> {
|
||||||
|
if (!RECAPTCHA_SERVER_KEY) {
|
||||||
|
console.error(
|
||||||
|
"RECAPTCHA_SERVER_KEY is not set. Please check your environment variables."
|
||||||
|
);
|
||||||
|
throw new Error("Server error: RECAPTCHA not configure correctly.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = data.get("name");
|
||||||
|
const email = data.get("email");
|
||||||
|
const message = data.get("message");
|
||||||
|
const recaptcha = data.get("g-recaptcha-response");
|
||||||
|
|
||||||
|
if (!name || !email || !message) {
|
||||||
|
return { error: "All fields are required." };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recaptcha) {
|
||||||
|
return { error: "Please complete the reCAPTCHA." };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const recaptchaResponse = await fetch(
|
||||||
|
`https://www.google.com/recaptcha/api/siteverify`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
body: `secret=${RECAPTCHA_SERVER_KEY}&response=${recaptcha}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const recaptchaData = await recaptchaResponse.json();
|
||||||
|
if (!recaptchaData.success) {
|
||||||
|
return { error: "reCAPTCHA verification failed." };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return { error: "Could not reach reCAPTCHA for verification." };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const transporter = createTransport({
|
||||||
|
host: process.env.MAIL_HOST ?? "localhost",
|
||||||
|
port: process.env.MAIL_PORT ? parseInt(process.env.MAIL_PORT) : undefined,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: process.env.MAIL_USER,
|
||||||
|
pass: process.env.MAIL_PASS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: process.env.MAIL_FROM,
|
||||||
|
to: process.env.MAIL_TO,
|
||||||
|
subject: `From ${name}`,
|
||||||
|
replyTo: email.toString(),
|
||||||
|
text: `Name:${name}\nEmail: ${email}\nMessage:\n ${message}`,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return { error: "Failed to send email." };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import SubmitContact from "@/components/contact";
|
|
||||||
import { useActionState, useEffect, useState } from "react";
|
import { useActionState, useEffect, useState } from "react";
|
||||||
import ReCAPTCHA from "react-google-recaptcha";
|
import ReCAPTCHA from "react-google-recaptcha";
|
||||||
import { getReCaptchaSiteKey } from "./action";
|
import SubmitContact, { getReCaptchaSiteKey } from "./action";
|
||||||
|
|
||||||
export default function ContactPage() {
|
export default function ContactPage() {
|
||||||
const [recaptchaSiteKey, setRecaptchaSiteKey] = useState<string | undefined>(
|
const [recaptchaSiteKey, setRecaptchaSiteKey] = useState<string | undefined>(
|
||||||
|
@ -3,6 +3,7 @@ import "./globals.css";
|
|||||||
import Title from "@/components/Title";
|
import Title from "@/components/Title";
|
||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import { bodyFont } from "@/components/fonts";
|
import { bodyFont } from "@/components/fonts";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "nrx.sh",
|
title: "nrx.sh",
|
||||||
@ -37,11 +38,29 @@ export default function RootLayout({
|
|||||||
maxWidth: "100vw",
|
maxWidth: "100vw",
|
||||||
padding: "2rem",
|
padding: "2rem",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
fontSize: "1.1rem",
|
fontSize: "0.9rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.7rem",
|
||||||
|
marginTop: "2rem",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#888",
|
||||||
|
maxWidth: "80vw",
|
||||||
|
}}
|
||||||
|
className={bodyFont.className}
|
||||||
|
>
|
||||||
|
this site is built from scratch using <b>next.js</b> - it is
|
||||||
|
<Link
|
||||||
|
style={{ color: "#88f" }}
|
||||||
|
href={"https://git.nrx.sh/naresh/nrx.sh"}
|
||||||
|
>
|
||||||
|
completely open source
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { SiGitea } from "@icons-pack/react-simple-icons";
|
import { SiGitea } from "@icons-pack/react-simple-icons";
|
||||||
import { NotebookText } from "lucide-react";
|
|
||||||
import { SocialIcon } from "react-social-icons";
|
import { SocialIcon } from "react-social-icons";
|
||||||
|
|
||||||
export default function Links() {
|
export default function Links() {
|
||||||
@ -17,9 +16,6 @@ export default function Links() {
|
|||||||
gap: "1rem",
|
gap: "1rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link href="https://blog.nrx.sh">
|
|
||||||
<NotebookText size={26} style={{ padding: 10 }} /> My Blog {"<3"}
|
|
||||||
</Link>
|
|
||||||
<Link href="https://github.com/naresh97">
|
<Link href="https://github.com/naresh97">
|
||||||
<SocialIcon network="github" bgColor="#111" /> GitHub
|
<SocialIcon network="github" bgColor="#111" /> GitHub
|
||||||
</Link>
|
</Link>
|
||||||
@ -27,7 +23,7 @@ export default function Links() {
|
|||||||
<SocialIcon network="linkedin" bgColor="#111" /> LinkedIn
|
<SocialIcon network="linkedin" bgColor="#111" /> LinkedIn
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="https://git.nrx.sh">
|
<Link href="https://git.nrx.sh">
|
||||||
<SiGitea size={40} /> Private Git Repo
|
<SiGitea size={40} /> Self-Hosted Git Repos
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable react/no-unescaped-entities */
|
|
||||||
|
|
||||||
import { titleFont } from "@/components/fonts";
|
import { titleFont } from "@/components/fonts";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
@ -11,11 +9,11 @@ export default function NotFound() {
|
|||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
shit... something is fucked and now you're here
|
shit... something is fucked and now you're here
|
||||||
<br />
|
<br />
|
||||||
i'll look into it, at some point
|
i'll look into it, at some point
|
||||||
<br />
|
<br />
|
||||||
but for now, maybe you'd like to
|
but for now, maybe you'd like to
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<Link
|
<Link
|
||||||
|
@ -1,20 +1,42 @@
|
|||||||
/* eslint-disable react/no-unescaped-entities */
|
"use server";
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
|
import { getGiteaActivity } from "@/components/gitea";
|
||||||
|
import { getStravaActivityStats } from "@/components/strava";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default function App() {
|
export default async function App() {
|
||||||
|
const strava = await getStravaActivityStats();
|
||||||
|
const gitea = await getGiteaActivity();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
hi,
|
hi,
|
||||||
<br />
|
<br />
|
||||||
<br /> thanks for stopping by.
|
<br /> how nice of you to stop by.
|
||||||
<br />
|
<br />
|
||||||
<br /> this is still a work in progress, so it's a little sparse.
|
<br /> this cute little site is my own little playground. my own corner of
|
||||||
|
the web for me to share and show off.
|
||||||
<br />
|
<br />
|
||||||
|
<br /> feel free to poke around and see what you find.
|
||||||
<br />
|
<br />
|
||||||
<br /> naresh.
|
<br /> — naresh
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: "2rem",
|
||||||
|
borderTopStyle: "dashed",
|
||||||
|
borderTopWidth: "1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: "0.8rem" }}>
|
||||||
|
recent rides: {strava.rides.toFixed(1)} km
|
||||||
|
{strava.runs >= 5 && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
recent runs: {strava.runs.toFixed(1)} km
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
6
src/auth.ts
Normal file
6
src/auth.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import NextAuth from "next-auth";
|
||||||
|
import Authentik from "next-auth/providers/authentik";
|
||||||
|
|
||||||
|
export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||||
|
providers: [Authentik],
|
||||||
|
});
|
@ -1,49 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { jwtVerify, SignJWT } from "jose";
|
|
||||||
import { cookies } from "next/headers";
|
|
||||||
const SECRET_KEY = process.env.SESSION_SECRET;
|
|
||||||
const encodedKey = new TextEncoder().encode(SECRET_KEY);
|
|
||||||
|
|
||||||
export type SessionPayload = { admin: true };
|
|
||||||
|
|
||||||
export async function encrypt(payload: SessionPayload) {
|
|
||||||
return new SignJWT(payload)
|
|
||||||
.setProtectedHeader({ alg: "HS256" })
|
|
||||||
.setIssuedAt()
|
|
||||||
.setExpirationTime("7d")
|
|
||||||
.sign(encodedKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function decrypt(
|
|
||||||
token: string | undefined = ""
|
|
||||||
): Promise<SessionPayload | null> {
|
|
||||||
try {
|
|
||||||
const { payload } = await jwtVerify(token, encodedKey, {
|
|
||||||
algorithms: ["HS256"],
|
|
||||||
});
|
|
||||||
return payload as SessionPayload;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isLoggedIn(): Promise<boolean> {
|
|
||||||
const cookieStore = (await cookies()).get("session")?.value;
|
|
||||||
const session = await decrypt(cookieStore);
|
|
||||||
if (session != null && session.admin) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setSession(): Promise<void> {
|
|
||||||
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
||||||
(await cookies()).set("session", await encrypt({ admin: true }), {
|
|
||||||
httpOnly: true,
|
|
||||||
secure: true,
|
|
||||||
expires: expiresAt,
|
|
||||||
sameSite: "lax",
|
|
||||||
path: "/",
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { createTransport } from "nodemailer";
|
|
||||||
|
|
||||||
const RECAPTCHA_SECRET_KEY = process.env.RECAPTCHA_SECRET_KEY;
|
|
||||||
|
|
||||||
export type SubmitContactReturn = { error: string } | { success: true };
|
|
||||||
|
|
||||||
export default async function SubmitContact(
|
|
||||||
prevState: unknown,
|
|
||||||
data: FormData
|
|
||||||
): Promise<SubmitContactReturn> {
|
|
||||||
if (!RECAPTCHA_SECRET_KEY) {
|
|
||||||
console.error(
|
|
||||||
"RECAPTCHA_SECRET_KEY is not set. Please check your environment variables."
|
|
||||||
);
|
|
||||||
throw new Error("Server error: RECAPTCHA not configure correctly.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = data.get("name");
|
|
||||||
const email = data.get("email");
|
|
||||||
const message = data.get("message");
|
|
||||||
const recaptcha = data.get("g-recaptcha-response");
|
|
||||||
|
|
||||||
if (!name || !email || !message) {
|
|
||||||
return { error: "All fields are required." };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!recaptcha) {
|
|
||||||
return { error: "Please complete the reCAPTCHA." };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const recaptchaResponse = await fetch(
|
|
||||||
`https://www.google.com/recaptcha/api/siteverify`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: `secret=${RECAPTCHA_SECRET_KEY}&response=${recaptcha}`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const recaptchaData = await recaptchaResponse.json();
|
|
||||||
if (!recaptchaData.success) {
|
|
||||||
return { error: "reCAPTCHA verification failed." };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return { error: "Could not reach reCAPTCHA for verification." };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const transporter = createTransport({
|
|
||||||
host: process.env.MAIL_HOST ?? "localhost",
|
|
||||||
port: process.env.MAIL_PORT ? parseInt(process.env.MAIL_PORT) : undefined,
|
|
||||||
secure: true,
|
|
||||||
auth: {
|
|
||||||
user: process.env.MAIL_USER,
|
|
||||||
pass: process.env.MAIL_PASS,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await transporter.sendMail({
|
|
||||||
from: process.env.MAIL_FROM,
|
|
||||||
to: process.env.MAIL_TO,
|
|
||||||
subject: `From ${name}`,
|
|
||||||
replyTo: email.toString(),
|
|
||||||
text: `Name:${name}\nEmail: ${email}\nMessage:\n ${message}`,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return { error: "Failed to send email." };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
51
src/components/gitea.ts
Normal file
51
src/components/gitea.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
const GITEA_API_KEY = process.env.GITEA_API_KEY;
|
||||||
|
|
||||||
|
export interface GiteaActivity {
|
||||||
|
repo: { name: string; url: string };
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGiteaActivity() {
|
||||||
|
if (!GITEA_API_KEY) {
|
||||||
|
throw new Error("Missing Gitea API key in environment variables");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
"https://git.nrx.sh/api/v1/users/naresh/activities/feeds",
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
accept: "application/json",
|
||||||
|
Authorization: GITEA_API_KEY,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const j = await response.json();
|
||||||
|
const data: (GiteaActivity | null)[] = j.map(
|
||||||
|
(m: {
|
||||||
|
content: string;
|
||||||
|
repo: { name: string; html_url: string };
|
||||||
|
}): GiteaActivity | null => {
|
||||||
|
try {
|
||||||
|
const content = JSON.parse(m.content);
|
||||||
|
const repo = { name: m.repo.name, url: m.repo.html_url };
|
||||||
|
const message = content.HeadCommit.Message as string;
|
||||||
|
return { repo, message };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Gitea activity:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
66
src/components/strava.ts
Normal file
66
src/components/strava.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
"use server";
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import strava from "strava-v3";
|
||||||
|
|
||||||
|
const STRAVA_CLIENT_ID = process.env.STRAVA_CLIENT_ID;
|
||||||
|
const STRAVA_CLIENT_SECRET = process.env.STRAVA_CLIENT_SECRET;
|
||||||
|
const STRAVA_USER_ID = 108482361;
|
||||||
|
|
||||||
|
export interface StravaActivityStats {
|
||||||
|
rides: number;
|
||||||
|
runs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStravaActivityStats(): Promise<StravaActivityStats> {
|
||||||
|
if (!STRAVA_CLIENT_ID || !STRAVA_CLIENT_SECRET) {
|
||||||
|
throw new Error(
|
||||||
|
"Missing Strava client ID or secret in environment variables"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
const token = await prisma.tokens.findUnique({
|
||||||
|
where: { application: "strava" },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("No token found in the database");
|
||||||
|
}
|
||||||
|
|
||||||
|
strava.config({
|
||||||
|
access_token: token.access_token,
|
||||||
|
client_id: STRAVA_CLIENT_ID,
|
||||||
|
client_secret: STRAVA_CLIENT_SECRET,
|
||||||
|
redirect_uri: "https://nrx.sh/",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (token.expires_at < new Date()) {
|
||||||
|
const result = await strava.oauth.refreshToken(token.refresh_token);
|
||||||
|
const expires_at = new Date(result.expires_at * 1000);
|
||||||
|
await prisma.tokens.update({
|
||||||
|
where: { application: "strava" },
|
||||||
|
data: {
|
||||||
|
expires_at,
|
||||||
|
access_token: result.access_token,
|
||||||
|
refresh_token: result.refresh_token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
token.access_token = result.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let result = await strava.athletes.stats({
|
||||||
|
access_token: token.access_token,
|
||||||
|
id: STRAVA_USER_ID,
|
||||||
|
});
|
||||||
|
result = JSON.parse(JSON.stringify(result));
|
||||||
|
const recent_distances = {
|
||||||
|
rides: (result.recent_ride_totals.distance / 1000) as number,
|
||||||
|
runs: (result.recent_run_totals.distance / 1000) as number,
|
||||||
|
};
|
||||||
|
return recent_distances;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error("Error fetching athlete data: " + JSON.stringify(e));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user