before art class
This commit is contained in:
parent
868c9df315
commit
ab620bdc91
34 changed files with 2557 additions and 13 deletions
288
package-lock.json
generated
288
package-lock.json
generated
|
@ -8,6 +8,10 @@
|
|||
"name": "abtmtr-v9",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@vercel/postgres-kysely": "^0.8.0",
|
||||
"formik": "^2.4.5",
|
||||
"kysely": "^0.27.3",
|
||||
"nanoid": "^5.0.7",
|
||||
"next": "14.1.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
|
@ -23,6 +27,14 @@
|
|||
"typescript": "^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@neondatabase/serverless": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.7.2.tgz",
|
||||
"integrity": "sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==",
|
||||
"dependencies": {
|
||||
"@types/pg": "8.6.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "14.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.4.tgz",
|
||||
|
@ -200,6 +212,15 @@
|
|||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz",
|
||||
|
@ -217,11 +238,20 @@
|
|||
"version": "20.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz",
|
||||
"integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pg": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz",
|
||||
"integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
"pg-types": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||
|
@ -255,6 +285,34 @@
|
|||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
|
||||
},
|
||||
"node_modules/@vercel/postgres": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/postgres/-/postgres-0.8.0.tgz",
|
||||
"integrity": "sha512-/QUV9ExwaNdKooRjOQqvrKNVnRvsaXeukPNI5DB1ovUTesglfR/fparw7ngo1KUWWKIVpEj2TRrA+ObRHRdaLg==",
|
||||
"dependencies": {
|
||||
"@neondatabase/serverless": "0.7.2",
|
||||
"bufferutil": "4.0.8",
|
||||
"utf-8-validate": "6.0.3",
|
||||
"ws": "8.14.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/postgres-kysely": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/postgres-kysely/-/postgres-kysely-0.8.0.tgz",
|
||||
"integrity": "sha512-3VCkqwtJ1p7p6P7tURJxDDVb6a7riXK8O2dsnXNESZ+C0txHLSNGlvL88w63isn10aBLJ2E1oGLqrgJRX72vJw==",
|
||||
"dependencies": {
|
||||
"@vercel/postgres": "0.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"kysely": "^0.24.2 || ^0.25.0 || ^0.26.0 || ^0.27.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bail": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||
|
@ -264,6 +322,18 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/bufferutil": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
|
||||
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
|
@ -386,6 +456,14 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
|
||||
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
|
@ -442,6 +520,30 @@
|
|||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"node_modules/formik": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz",
|
||||
"integrity": "sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://opencollective.com/formik"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"deepmerge": "^2.1.1",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react-fast-compare": "^2.0.1",
|
||||
"tiny-warning": "^1.0.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
|
@ -574,6 +676,14 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/howler": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",
|
||||
|
@ -658,6 +768,24 @@
|
|||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/kysely": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.3.tgz",
|
||||
"integrity": "sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/longest-streak": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
||||
|
@ -1483,9 +1611,9 @@
|
|||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
|
||||
"integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
@ -1493,10 +1621,10 @@
|
|||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
|
@ -1544,6 +1672,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
||||
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-entities": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
|
||||
|
@ -1579,6 +1717,34 @@
|
|||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-int8": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pg-protocol": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz",
|
||||
"integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg=="
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"postgres-array": "~2.0.0",
|
||||
"postgres-bytea": "~1.0.0",
|
||||
"postgres-date": "~1.0.4",
|
||||
"postgres-interval": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
|
@ -1611,6 +1777,58 @@
|
|||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss/node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-bytea": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-date": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-interval": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/property-information": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz",
|
||||
|
@ -1643,6 +1861,16 @@
|
|||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-fast-compare": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
|
||||
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
|
||||
|
@ -1820,6 +2048,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||
},
|
||||
"node_modules/trim-lines": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
|
||||
|
@ -1859,8 +2092,7 @@
|
|||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||
},
|
||||
"node_modules/unified": {
|
||||
"version": "11.0.4",
|
||||
|
@ -1967,6 +2199,18 @@
|
|||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz",
|
||||
"integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz",
|
||||
|
@ -2016,6 +2260,34 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.14.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
|
||||
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/postgres-kysely": "^0.8.0",
|
||||
"formik": "^2.4.5",
|
||||
"kysely": "^0.27.3",
|
||||
"nanoid": "^5.0.7",
|
||||
"next": "14.1.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
|
|
BIN
public/headers/jams.kra
Normal file
BIN
public/headers/jams.kra
Normal file
Binary file not shown.
BIN
public/headers/jams.png
Normal file
BIN
public/headers/jams.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
0
src/app/jams/(manip)/edit/content/[contentid]/page.tsx
Normal file
0
src/app/jams/(manip)/edit/content/[contentid]/page.tsx
Normal file
0
src/app/jams/(manip)/edit/jam/[jamid]/page.tsx
Normal file
0
src/app/jams/(manip)/edit/jam/[jamid]/page.tsx
Normal file
19
src/app/jams/(manip)/new/content/[jamid]/page.tsx
Normal file
19
src/app/jams/(manip)/new/content/[jamid]/page.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { MainLayout } from "@/layout/MainLayout/MainLayout";
|
||||
|
||||
export default async function Home({
|
||||
params,
|
||||
searchParams
|
||||
}: {
|
||||
params: {
|
||||
content: string
|
||||
},
|
||||
searchParams: {
|
||||
until: string
|
||||
}
|
||||
}) {
|
||||
|
||||
return (
|
||||
<MainLayout currentPage="/jams/" title="New Jams" subtitle="Jams" backButton>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
0
src/app/jams/(manip)/new/jam/page.tsx
Normal file
0
src/app/jams/(manip)/new/jam/page.tsx
Normal file
0
src/app/jams/(manip)/new/judgement/[contentid]/page.tsx
Normal file
0
src/app/jams/(manip)/new/judgement/[contentid]/page.tsx
Normal file
|
@ -0,0 +1,265 @@
|
|||
import { db, JudgementTable } from "@/lib/mastoauth/kysely";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {content:string, judgement:string}}) {
|
||||
const cid = params.content;
|
||||
if (cid == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', cid)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
const jid = params.judgement;
|
||||
if (jid == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let judgement = await db
|
||||
.selectFrom('judgements')
|
||||
.where('judgements.content_id', '=', cid)
|
||||
.where('judgements.id', '=', jid)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (judgement == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
return Response.json(judgement);
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, {params}: {params: {content:string, judgement:string}}) {
|
||||
const cid = params.content;
|
||||
if (cid == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', cid)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const id = params.content;
|
||||
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let updatingJudgement = await db
|
||||
.selectFrom('judgements')
|
||||
.where('judgements.content_id', '=', cid)
|
||||
.where('judgements.id', '=', id)
|
||||
.select(['author_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
console.log(updatingJudgement, existingUser)
|
||||
|
||||
if (updatingJudgement == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
if (!existingUser?.admin && updatingJudgement.author_id != existingUser.id) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
let newBody:Partial<JudgementTable> = {};
|
||||
|
||||
if (body.content != null && typeof body.content === 'string') newBody.content = body.content;
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await db
|
||||
.updateTable('judgements')
|
||||
.set(newBody)
|
||||
.where('content_id', '=', cid)
|
||||
.where('id', '=', id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest, {params}: {params: {content:string, judgement:string}}) {
|
||||
const cid = params.content;
|
||||
if (cid == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', cid)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const id = params.judgement;
|
||||
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let deletingJudgement = await db
|
||||
.selectFrom('judgements')
|
||||
.where('judgements.content_id', '=', cid)
|
||||
.where('judgements.id', '=', id)
|
||||
.select(['author_id', 'content_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
console.log(deletingJudgement, existingUser)
|
||||
|
||||
if (deletingJudgement == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let contentParent = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', deletingJudgement.content_id)
|
||||
.select(['author_id', 'jam_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (contentParent == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
let contentParentOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', contentParent.author_id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (contentParentOwner == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
let jamParent = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', contentParent.jam_id)
|
||||
.select('author_id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jamParent == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
let jamParentOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', jamParent.author_id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jamParentOwner == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
if (
|
||||
!existingUser?.admin
|
||||
&& contentParentOwner.id != existingUser.id
|
||||
&& jamParentOwner.id != existingUser.id
|
||||
&& deletingJudgement.author_id != existingUser.id
|
||||
) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
try {
|
||||
await db
|
||||
.deleteFrom('judgements')
|
||||
.where('judgements.id', '=', id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
}
|
124
src/app/jams/api/content/[content]/judgements/route.ts
Normal file
124
src/app/jams/api/content/[content]/judgements/route.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { db, JudgementTable } from "@/lib/mastoauth/kysely";
|
||||
import { nanoid } from "nanoid";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {content: string}}) {
|
||||
const id = params.content;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let judgement = await db
|
||||
.selectFrom('judgements')
|
||||
.where('judgements.content_id', '=', id)
|
||||
.where('judgements.published', '<', until)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (judgement == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(judgement);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest, {params}: {params: {content: string}}) {
|
||||
const id = params.content;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', id)
|
||||
.select(['id', 'jam_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
const te = [];
|
||||
if (body.id != null) te.push("id");
|
||||
if (body.author_id != null) te.push("author_id");
|
||||
if (body.content_id != null) te.push("content_id");
|
||||
if (typeof body.content !== 'string') te.push("content");
|
||||
if (body.published != null) te.push("published");
|
||||
|
||||
|
||||
if (te.length > 0) return new Response(JSON.stringify(te, null, 2), {
|
||||
status: 400
|
||||
})
|
||||
|
||||
let newBody:JudgementTable = body;
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await db
|
||||
.insertInto('judgements')
|
||||
.values({
|
||||
id: nanoid(21),
|
||||
author_id: existingUser.id,
|
||||
content_id: content.id,
|
||||
content: newBody.content,
|
||||
published: Date.now()
|
||||
})
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
}
|
200
src/app/jams/api/content/[content]/route.ts
Normal file
200
src/app/jams/api/content/[content]/route.ts
Normal file
|
@ -0,0 +1,200 @@
|
|||
import { ContentTable, db } from "@/lib/mastoauth/kysely";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {content:string}}) {
|
||||
const id = params.content;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
return Response.json(content);
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, {params}: {params: {content:string}}) {
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const id = params.content;
|
||||
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let updatingContent = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', id)
|
||||
.select(['author_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
console.log(updatingContent, existingUser)
|
||||
|
||||
if (updatingContent == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
if (!existingUser?.admin && updatingContent.author_id != existingUser.id) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
let newBody:Partial<ContentTable> = {};
|
||||
|
||||
if (body.name != null && typeof body.name === 'string') newBody.name = body.name;
|
||||
if (body.description != null && typeof body.description === 'string') newBody.description = body.description;
|
||||
if (body.url != null && typeof body.url === 'string') newBody.url = body.url;
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await db
|
||||
.updateTable('content')
|
||||
.set(newBody)
|
||||
.where('id', '=', id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest, {params}: {params: {content:string}}) {
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const id = params.content;
|
||||
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let deletingContent = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', id)
|
||||
.select(['author_id', 'jam_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
console.log(deletingContent, existingUser)
|
||||
|
||||
if (deletingContent == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let jamParent = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', deletingContent.jam_id)
|
||||
.select('author_id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jamParent == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
let jamParentOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', jamParent.author_id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jamParentOwner == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
if (
|
||||
!existingUser?.admin
|
||||
&& jamParentOwner.id != existingUser.id
|
||||
&& deletingContent.author_id != existingUser.id
|
||||
) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
try {
|
||||
await db
|
||||
.deleteFrom('content')
|
||||
.where('content.id', '=', id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
}
|
20
src/app/jams/api/content/route.ts
Normal file
20
src/app/jams/api/content/route.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { ContentTable, db } from "@/lib/mastoauth/kysely";
|
||||
import { nanoid } from "nanoid";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.submitted', '<', until)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(content);
|
||||
}
|
133
src/app/jams/api/jams/[jam]/content/route.ts
Normal file
133
src/app/jams/api/jams/[jam]/content/route.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
import { ContentTable, db, JamTable } from "@/lib/mastoauth/kysely";
|
||||
import { nanoid } from "nanoid";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {jam: string}}) {
|
||||
const id = params.jam;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let jam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jam == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.submitted', '<', until)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(content);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest, {params}: {params: {jam: string}}) {
|
||||
const id = params.jam;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let jam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', id)
|
||||
.select(['id', 'date_start', 'date_end'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jam == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
const currentDate = Date.now();
|
||||
|
||||
if (currentDate <= jam.date_start || currentDate >= jam.date_end) return new Response('', {
|
||||
status: 403
|
||||
});
|
||||
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
const te = [];
|
||||
if (body.id != null) te.push("id");
|
||||
if (body.author_id != null) te.push("author_id");
|
||||
if (body.jam_id != null) te.push("jam_id");
|
||||
if (typeof body.name !== 'string') te.push("name");
|
||||
if (typeof body.description !== 'string') te.push("description");
|
||||
if (typeof body.url !== 'string') te.push("url");
|
||||
if (body.submitted != null) te.push("submitted");
|
||||
|
||||
|
||||
if (te.length > 0) return new Response(JSON.stringify(te, null, 2), {
|
||||
status: 400
|
||||
})
|
||||
|
||||
let newBody:ContentTable = body;
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await db
|
||||
.insertInto('content')
|
||||
.values({
|
||||
id: nanoid(21),
|
||||
author_id: existingUser.id,
|
||||
jam_id: id,
|
||||
name: newBody.name,
|
||||
description: newBody.description,
|
||||
url: newBody.url,
|
||||
submitted: Date.now()
|
||||
})
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
})
|
||||
}
|
173
src/app/jams/api/jams/[jam]/route.ts
Normal file
173
src/app/jams/api/jams/[jam]/route.ts
Normal file
|
@ -0,0 +1,173 @@
|
|||
import { db, JamTable } from "@/lib/mastoauth/kysely";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {jam:string}}) {
|
||||
const id = params.jam;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let jam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jam == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
return Response.json(jam);
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, {params}: {params: {jam:string}}) {
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const id = params.jam;
|
||||
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let updatingJam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', id)
|
||||
.select(['author_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
console.log(updatingJam, existingUser)
|
||||
|
||||
if (updatingJam == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
if (!existingUser?.admin && updatingJam.author_id != existingUser.id) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
let newBody:Partial<JamTable> = {};
|
||||
|
||||
if (body.name != null && typeof body.name === 'string') newBody.name = body.name;
|
||||
if (body.description != null && typeof body.description === 'string') newBody.description = body.description;
|
||||
if (body.date_start != null && typeof body.date_start === 'number') newBody.date_start = body.date_start;
|
||||
if (body.date_end != null && typeof body.date_end === 'number') newBody.date_end = body.date_end;
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await db
|
||||
.updateTable('jams')
|
||||
.set(newBody)
|
||||
.where('id', '=', id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
})
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest, {params}: {params: {jam:string}}) {
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const id = params.jam;
|
||||
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let deletingJam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', id)
|
||||
.select(['author_id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
console.log(deletingJam, existingUser)
|
||||
|
||||
if (deletingJam == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
if (!existingUser?.admin && deletingJam.author_id != existingUser.id) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
try {
|
||||
await db
|
||||
.deleteFrom('jams')
|
||||
.where('jams.id', '=', id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
}
|
103
src/app/jams/api/jams/route.ts
Normal file
103
src/app/jams/api/jams/route.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
import { db, JamTable } from "@/lib/mastoauth/kysely";
|
||||
import { nanoid } from "nanoid";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let jams = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.created', '<', until)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (jams == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(jams);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
if (!existingUser.admin) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
const te = [];
|
||||
if (body.id != null) te.push("id");
|
||||
if (body.author_id != null) te.push("author_id");
|
||||
if (typeof body.name !== 'string') te.push("name");
|
||||
if (typeof body.description !== 'string') te.push("description");
|
||||
if (typeof body.date_start !== 'number') te.push("date_start");
|
||||
if (typeof body.date_end !== 'number') te.push("date_end");
|
||||
if (body.created != null) te.push("created");
|
||||
|
||||
|
||||
if (te.length > 0) return new Response(JSON.stringify(te, null, 2), {
|
||||
status: 400
|
||||
})
|
||||
|
||||
let newBody:JamTable = body;
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await db
|
||||
.insertInto('jams')
|
||||
.values({
|
||||
id: nanoid(21),
|
||||
author_id: existingUser.id,
|
||||
name: newBody.name,
|
||||
description: newBody.description,
|
||||
date_start: newBody.date_start,
|
||||
date_end: newBody.date_end,
|
||||
created: Date.now()
|
||||
})
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
})
|
||||
}
|
34
src/app/jams/api/users/[user]/content/route.ts
Normal file
34
src/app/jams/api/users/[user]/content/route.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { ContentTable, db, JamTable } from "@/lib/mastoauth/kysely";
|
||||
import { nanoid } from "nanoid";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {user: string}}) {
|
||||
const id = params.user;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let user = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (user == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.submitted', '<', until)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (content == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(content);
|
||||
}
|
33
src/app/jams/api/users/[user]/jams/route.ts
Normal file
33
src/app/jams/api/users/[user]/jams/route.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { db } from "@/lib/mastoauth/kysely";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: { params: {user: string} }) {
|
||||
const id = params.user;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let user = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (user == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let jams = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.created', '<', until)
|
||||
.where('jams.author_id', '=', id)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (jams == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(jams);
|
||||
}
|
35
src/app/jams/api/users/[user]/judgements/route.ts
Normal file
35
src/app/jams/api/users/[user]/judgements/route.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { db, JudgementTable } from "@/lib/mastoauth/kysely";
|
||||
import { nanoid } from "nanoid";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {user: string}}) {
|
||||
const id = params.user;
|
||||
if (id == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let user = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', id)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (user == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let judgement = await db
|
||||
.selectFrom('judgements')
|
||||
.where('judgements.author_id', '=', id)
|
||||
.where('judgements.published', '<', until)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (judgement == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(judgement);
|
||||
}
|
169
src/app/jams/api/users/[user]/route.ts
Normal file
169
src/app/jams/api/users/[user]/route.ts
Normal file
|
@ -0,0 +1,169 @@
|
|||
import { db, UserTable } from "@/lib/mastoauth/kysely";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest, {params}: {params: {user:string}}) {
|
||||
const name = params.user;
|
||||
if (name == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
let user = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', name)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (user == null) return new Response('', {
|
||||
status: 404
|
||||
});
|
||||
|
||||
return Response.json(user);
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest, {params}: {params: {user:string}}) {
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const name = params.user;
|
||||
|
||||
if (name == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let updatingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', name)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (updatingUser == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
if (!existingUser?.admin) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
let newBody:Partial<UserTable> = {};
|
||||
|
||||
if (body.admin != null && typeof body.admin === 'boolean') newBody.admin = body.admin;
|
||||
if (body.banned != null && typeof body.banned === 'boolean') newBody.banned = body.banned;
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await db
|
||||
.updateTable('jams')
|
||||
.set(newBody)
|
||||
.where('id', '=', updatingUser.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
})
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest, {params}: {params: {user:string}}) {
|
||||
// verify user
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
|
||||
if (token == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.select(['admin', 'id'])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) return new Response('', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
const name = params.user;
|
||||
|
||||
if (name == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let deletingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', name)
|
||||
.select('id')
|
||||
.executeTakeFirst();
|
||||
|
||||
console.log(deletingUser, existingUser)
|
||||
|
||||
if (deletingUser == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
if (!existingUser?.admin && deletingUser.id != existingUser.id) return new Response('you are NOT that guy.', {
|
||||
status: 401
|
||||
});
|
||||
|
||||
try {
|
||||
await db
|
||||
.deleteFrom('users')
|
||||
.where('users.id', '=', deletingUser.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: 204
|
||||
});
|
||||
}
|
18
src/app/jams/api/users/route.ts
Normal file
18
src/app/jams/api/users/route.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { db } from "@/lib/mastoauth/kysely";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const until = parseInt(request.nextUrl.searchParams.get("until") || Date.now().toString());
|
||||
let users = await db
|
||||
.selectFrom('users')
|
||||
.where('users.joined', '<', until)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute();
|
||||
|
||||
if (users == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
return Response.json(users);
|
||||
}
|
130
src/app/jams/content/[content]/page.tsx
Normal file
130
src/app/jams/content/[content]/page.tsx
Normal file
|
@ -0,0 +1,130 @@
|
|||
import { MainLayout } from "@/layout/MainLayout/MainLayout";
|
||||
import { cookies } from "next/headers";
|
||||
import { db, JamTable, UserTable } from "@/lib/mastoauth/kysely";
|
||||
import { ConditionalNull } from "@/components/utility/Conditional";
|
||||
import Link from "next/link";
|
||||
import Markdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import { JSONContentTable, JSONJamTable, JSONJudgementTable, JSONUserTable } from "@/lib/mastoauth/realtypes";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function Home({
|
||||
params,
|
||||
searchParams
|
||||
}: {
|
||||
params: {
|
||||
content: string
|
||||
},
|
||||
searchParams: {
|
||||
until: string
|
||||
}
|
||||
}) {
|
||||
const curDate = Date.now();
|
||||
const curPage = parseInt(searchParams?.until) || curDate;
|
||||
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
let existingUser:JSONUserTable;
|
||||
if (token != null) {
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken != null) {
|
||||
existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONUserTable;
|
||||
}
|
||||
}
|
||||
|
||||
const curContent = params.content;
|
||||
|
||||
if (curContent == null) return notFound();
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', curContent)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONContentTable;
|
||||
|
||||
console.log(curContent, content)
|
||||
|
||||
if (content == null) return notFound();
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let contentOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', content.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONUserTable;
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let submittedJam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', content.jam_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONJamTable;
|
||||
|
||||
let judgements = await db
|
||||
.selectFrom('judgements')
|
||||
.where('judgements.content_id', '=', content.id)
|
||||
.where('judgements.published', '<=', curPage)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute() as unknown[] as JSONJudgementTable[];
|
||||
|
||||
console.log(content, contentOwner)
|
||||
|
||||
return (
|
||||
<MainLayout currentPage="/jams/" title={content.name} subtitle="Jams" backButton>
|
||||
<h1>{content.name}</h1>
|
||||
<p><a href={content.url} target="_blank">{content.url}</a></p>
|
||||
<p><small>Submitted by <a href={`/jams/user/${contentOwner.id}`}>{`${contentOwner.username}@${contentOwner.instance}`}</a> to <a href={`/jams/jam/${submittedJam.id}?until=${content.submitted}`}>{submittedJam.name}</a> - {new Date(parseInt(content.submitted)).toDateString()}</small></p>
|
||||
<p>{content.description}</p>
|
||||
<ConditionalNull condition={existingUser != null}>
|
||||
<div>
|
||||
<ConditionalNull condition={existingUser?.admin || content.author_id == content.id}><p><b>You have the ability to modify this submission.</b></p></ConditionalNull>
|
||||
<ConditionalNull condition={existingUser?.admin || content.author_id == content.id}><p><a href={`/jams/edit/content/${content.id}`}>Edit submission</a></p></ConditionalNull>
|
||||
<ConditionalNull condition={existingUser?.admin || content.author_id == content.id}><p><a href={`/jams/delete/content/${content.id}`}>Delete submission</a></p></ConditionalNull>
|
||||
<p><a href={`/jams/new/judgement/${content.id}`}>Judge this submission</a></p>
|
||||
</div>
|
||||
</ConditionalNull>
|
||||
<h1>Judgements</h1>
|
||||
{judgements.map(async (judgement:JSONJudgementTable) => {
|
||||
let judgementOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', judgement.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (judgementOwner == null) return <p>An error occured.</p>;
|
||||
|
||||
return (<div style={{
|
||||
margin: "16px 0"
|
||||
}}>
|
||||
<h2><a href={`/jams/user/${judgementOwner.id}`}>{`${judgementOwner.username}@${judgementOwner.instance}`}</a></h2>
|
||||
<ConditionalNull condition={existingUser != null}>
|
||||
<div>
|
||||
<ConditionalNull condition={existingUser?.admin || content.author_id == content.id}><p><a href={`/jams/edit/judgement/${judgement.id}`}>Edit judgement</a> - <a href={`/jams/delete/judgement/${judgement.id}`}>Delete judgement</a></p></ConditionalNull>
|
||||
</div>
|
||||
</ConditionalNull>
|
||||
<p><small>Published {new Date(parseInt(judgement.published)).toDateString()}</small></p>
|
||||
<p>{judgement.content}</p>
|
||||
</div>);
|
||||
})}
|
||||
<div className="navigation">
|
||||
<ConditionalNull condition={curPage < curDate}>
|
||||
<Link href={`?until=${judgements.at(0)?.published}`}>Later</Link>
|
||||
</ConditionalNull>
|
||||
{/* <ConditionalNull condition={curPage * 10 < blogs.data.total_posts}> */}
|
||||
<Link href={`?until=${judgements.at(-1)?.published}`}>Earlier</Link>
|
||||
{/* </ConditionalNull> */}
|
||||
</div>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
119
src/app/jams/jam/[jam]/page.tsx
Normal file
119
src/app/jams/jam/[jam]/page.tsx
Normal file
|
@ -0,0 +1,119 @@
|
|||
import { MainLayout } from "@/layout/MainLayout/MainLayout";
|
||||
import { cookies } from "next/headers";
|
||||
import { db, JamTable, UserTable } from "@/lib/mastoauth/kysely";
|
||||
import { ConditionalNull } from "@/components/utility/Conditional";
|
||||
import Link from "next/link";
|
||||
import Markdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import { JSONContentTable, JSONJamTable, JSONUserTable } from "@/lib/mastoauth/realtypes";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function Home({
|
||||
params,
|
||||
searchParams
|
||||
}: {
|
||||
params: {
|
||||
jam: string
|
||||
},
|
||||
searchParams: {
|
||||
until: string
|
||||
}
|
||||
}) {
|
||||
const curDate = Date.now();
|
||||
const curPage = parseInt(searchParams?.until) || curDate;
|
||||
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
let existingUser;
|
||||
if (token != null) {
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken != null) {
|
||||
existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
const curJam = params.jam;
|
||||
|
||||
if (curJam == null) return notFound();
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let jam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', curJam)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONJamTable;
|
||||
|
||||
const started = Date.now() >= parseInt(jam.date_start);
|
||||
const ended = Date.now() >= parseInt(jam.date_end);
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let jamOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', jam.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONUserTable;
|
||||
|
||||
if (jam == null) return notFound();
|
||||
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.jam_id', '=', jam.id)
|
||||
.where('content.submitted', '<=', curPage)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute() as unknown[] as JSONContentTable[];
|
||||
|
||||
console.log(jam, jamOwner)
|
||||
|
||||
return (
|
||||
<MainLayout currentPage="/jams/" title={jam.name} subtitle="Jams" backButton>
|
||||
<h1>{jam.name}</h1>
|
||||
<p><small>{started ? "Started" : "Starts"} {new Date(parseInt(jam.date_start)).toDateString()} - {ended ? "Ended" : "Ends"} {new Date(parseInt(jam.date_end)).toDateString()}</small></p>
|
||||
<p>Hosted by <a href={`/jams/user/${jamOwner.id}`}>{`${jamOwner.username}@${jamOwner.instance}`}</a></p>
|
||||
<p>{jam.description}</p>
|
||||
<ConditionalNull condition={existingUser != null}>
|
||||
<div>
|
||||
<ConditionalNull condition={existingUser?.admin || jam.author_id == jam.id}><p><b>You have the ability to modify this Jam.</b></p></ConditionalNull>
|
||||
<ConditionalNull condition={existingUser?.admin || jam.author_id == jam.id}><p><a href={`/jams/edit/jam/${jam.id}`}>Edit jam</a></p></ConditionalNull>
|
||||
<ConditionalNull condition={existingUser?.admin || jam.author_id == jam.id}><p><a href={`/jams/delete/jam/${jam.id}`}>Delete jam</a></p></ConditionalNull>
|
||||
<ConditionalNull condition={started && !ended}><p><a href={`/jams/new/content/${jam.id}`}>Submit something</a></p></ConditionalNull>
|
||||
</div>
|
||||
</ConditionalNull>
|
||||
<h1>Submissions</h1>
|
||||
{content.map(async (content:JSONContentTable) => {
|
||||
let contentOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', content.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (contentOwner == null) return <p>An error occured.</p>;
|
||||
|
||||
return (<div style={{
|
||||
margin: "16px 0"
|
||||
}}>
|
||||
<h2><Link href={`/jams/content/${content.id}`}>{content.name}</Link></h2>
|
||||
<p><small>Submitted by <a href={`/jams/user/${contentOwner.id}`}>{`${contentOwner.username}@${contentOwner.instance}`}</a> - {new Date(parseInt(content.submitted)).toDateString()}</small></p>
|
||||
<p>{content.description}</p>
|
||||
</div>);
|
||||
})}
|
||||
<div className="navigation">
|
||||
<ConditionalNull condition={curPage < curDate}>
|
||||
<Link href={`?until=${content.at(0)?.submitted}`}>Later</Link>
|
||||
</ConditionalNull>
|
||||
{/* <ConditionalNull condition={curPage * 10 < blogs.data.total_posts}> */}
|
||||
<Link href={`?until=${content.at(-1)?.submitted}`}>Earlier</Link>
|
||||
{/* </ConditionalNull> */}
|
||||
</div>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
120
src/app/jams/oauth/code/route.ts
Normal file
120
src/app/jams/oauth/code/route.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { AppTable, db, UserTable } from "@/lib/mastoauth/kysely";
|
||||
import { MastoAuth } from "@/lib/mastoauth/mastoauth";
|
||||
import { nanoid } from "nanoid";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { stringify } from "node:querystring";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const cookieStore = cookies();
|
||||
const instance = cookieStore.get('instance')?.value;
|
||||
const code = request.nextUrl.searchParams.get("code");
|
||||
if (code == null || instance == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
const mauth = new MastoAuth(instance);
|
||||
|
||||
// Get instance app
|
||||
let existingInstanceApp = await db
|
||||
.selectFrom('apps')
|
||||
.where('apps.instance_domain', '=', instance)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingInstanceApp == null) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
// Get temporary user token
|
||||
let tUserToken = await mauth.getApplicationToken(existingInstanceApp, code);
|
||||
|
||||
console.log(tUserToken);
|
||||
|
||||
if (tUserToken == null) return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
// Test for user existence
|
||||
let tUserExists = await mauth.verifyUser(tUserToken.token_type + " " + tUserToken.access_token);
|
||||
|
||||
console.log(tUserExists,);
|
||||
|
||||
if (tUserExists == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
let currentUser = {
|
||||
id: nanoid(21),
|
||||
instance,
|
||||
username: tUserExists.acct,
|
||||
admin: false,
|
||||
url: tUserExists.url,
|
||||
banned: false,
|
||||
joined: Date.now()
|
||||
} as UserTable;
|
||||
|
||||
// Get if user already is registered
|
||||
let existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.url', '=', currentUser.url)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingUser == null) {
|
||||
try {
|
||||
await db.insertInto('users')
|
||||
.values(currentUser)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
} else currentUser = existingUser;
|
||||
|
||||
// "I've seen enough, give token"
|
||||
|
||||
// Clear tokens older than a week
|
||||
|
||||
try {
|
||||
await db.deleteFrom('tokens')
|
||||
.where('tokens.expires', '<=', Date.now())
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
// whatever, who cares?
|
||||
}
|
||||
|
||||
let currentToken = {
|
||||
id: nanoid(21),
|
||||
owner: currentUser.id,
|
||||
created: Date.now(),
|
||||
expires: Date.now() + 604800000 // 1 wk
|
||||
};
|
||||
|
||||
// If not expired, give existing token?
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.owner', '=', currentUser.id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken != null) currentToken = existingToken;
|
||||
else {
|
||||
try {
|
||||
await db.insertInto('tokens')
|
||||
.values(currentToken)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cookieStore.set("token", currentToken.id);
|
||||
return Response.redirect(new URL('/jams', request.url));
|
||||
}
|
52
src/app/jams/oauth/login/route.ts
Normal file
52
src/app/jams/oauth/login/route.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { AppTable, db } from "@/lib/mastoauth/kysely";
|
||||
import { MastoAuth } from "@/lib/mastoauth/mastoauth";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const cookieStore = cookies();
|
||||
const instance = request.nextUrl.searchParams.get("instance");
|
||||
if (instance == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
const mauth = new MastoAuth(instance);
|
||||
|
||||
// Check if instance app exists
|
||||
let existingInstanceApp = await db
|
||||
.selectFrom('apps')
|
||||
.where('apps.instance_domain', '=', instance)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
console.log(instance);
|
||||
|
||||
if (existingInstanceApp == null) {
|
||||
// Create new app
|
||||
const temp_iapp = await mauth.newApplication();
|
||||
|
||||
if (temp_iapp == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
existingInstanceApp = {
|
||||
instance_domain: instance,
|
||||
client_id: temp_iapp.client_id,
|
||||
client_secret: temp_iapp.client_secret,
|
||||
redirect_uri: temp_iapp.redirect_uri
|
||||
} as AppTable;
|
||||
|
||||
try {
|
||||
await db.insertInto('apps')
|
||||
.values(existingInstanceApp)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cookieStore.set("instance", instance);
|
||||
return Response.redirect(`https://${instance}/oauth/authorize?response_type=code&client_id=${existingInstanceApp.client_id}&redirect_uri=${existingInstanceApp.redirect_uri}&scope=read`);
|
||||
}
|
27
src/app/jams/oauth/logout/route.ts
Normal file
27
src/app/jams/oauth/logout/route.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { db } from "@/lib/mastoauth/kysely";
|
||||
import { cookies } from "next/headers";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const cookieStore = cookies();
|
||||
const instance = cookieStore.get('instance')?.value;
|
||||
if (instance == null) return new Response('', {
|
||||
status: 400
|
||||
});
|
||||
|
||||
// Delete token
|
||||
|
||||
try {
|
||||
await db.deleteFrom('tokens')
|
||||
.where('tokens.id', '=', instance)
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (err) {
|
||||
return new Response('Something failed!', {
|
||||
status: 500
|
||||
});
|
||||
}
|
||||
|
||||
cookieStore.delete("token");
|
||||
cookieStore.delete("instance");
|
||||
return Response.redirect(new URL('/jams', request.url));
|
||||
}
|
96
src/app/jams/page.tsx
Normal file
96
src/app/jams/page.tsx
Normal file
|
@ -0,0 +1,96 @@
|
|||
import { MainLayout } from "@/layout/MainLayout/MainLayout";
|
||||
import { cookies } from "next/headers";
|
||||
import { db, JamTable, UserTable } from "@/lib/mastoauth/kysely";
|
||||
import { ConditionalNull } from "@/components/utility/Conditional";
|
||||
import Link from "next/link";
|
||||
import Markdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import { JSONJamTable } from "@/lib/mastoauth/realtypes";
|
||||
|
||||
export default async function Home({
|
||||
searchParams
|
||||
}: {
|
||||
searchParams: {
|
||||
until: string
|
||||
}
|
||||
}) {
|
||||
const curDate = Date.now();
|
||||
const curPage = parseInt(searchParams?.until) || curDate;
|
||||
|
||||
const cookieStore = cookies();
|
||||
const token = cookieStore.get('token')?.value;
|
||||
let existingUser;
|
||||
if (token != null) {
|
||||
let existingToken = await db
|
||||
.selectFrom('tokens')
|
||||
.where('tokens.id', '=', token)
|
||||
.select('owner')
|
||||
.executeTakeFirst();
|
||||
|
||||
if (existingToken != null) {
|
||||
existingUser = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', existingToken.owner)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let jams = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.created', '<=', curPage)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute() as unknown[] as JSONJamTable[];
|
||||
|
||||
return (
|
||||
<MainLayout currentPage="/jams/" title="Jams" subtitle="">
|
||||
<img className="headerImage" src="/headers/jams.png" alt="JAMS"></img>
|
||||
<p>Some jams I host that you can participate in!</p>
|
||||
<p>These serve many purposes. To create a community bond, to help creatives find their way, and most importantly, <s>to make me popular</s>. X3</p>
|
||||
<p>Enjoy!</p>
|
||||
<ConditionalNull condition={existingUser == null}>
|
||||
<h2>Log in with the Fediverse</h2>
|
||||
<form action="/jams/oauth/login">
|
||||
<input name="instance" placeholder="Instance URL" type="text" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<p><small>Tested on Mastodon, GoToSocial, Pleroma, and Misskey</small></p>
|
||||
</ConditionalNull>
|
||||
<ConditionalNull condition={existingUser != null}>
|
||||
<p>Logged in as <a href={`/jams/user/${existingUser?.id}`}>{existingUser?.username}@{existingUser?.instance}</a> (<a href="/jams/oauth/logout">Logout</a>)</p>
|
||||
<ConditionalNull condition={existingUser?.admin == true}><p><a href="/jams/new/jam">Create Jam</a></p></ConditionalNull>
|
||||
</ConditionalNull>
|
||||
<h1>Jams</h1>
|
||||
{jams.map(async (jam:JSONJamTable) => {
|
||||
const started = Date.now() >= parseInt(jam.date_start);
|
||||
const ended = Date.now() >= parseInt(jam.date_end);
|
||||
|
||||
let jamOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', jam.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jamOwner == null) return <p>An error occured.</p>;
|
||||
|
||||
return (<div style={{
|
||||
margin: "16px 0"
|
||||
}}>
|
||||
<h2><Link href={`/jams/jam/${jam.id}`}>{jam.name}</Link></h2>
|
||||
<p><small>Hosted by <a href={`/jams/user/${jamOwner.id}`}>{`${jamOwner.username}@${jamOwner.instance}`}</a> - {started ? "Started" : "Starts"} {new Date(parseInt(jam.date_start)).toDateString()} - {ended ? "Ended" : "Ends"} {new Date(parseInt(jam.date_end)).toDateString()}</small></p>
|
||||
<Markdown rehypePlugins={[rehypeRaw]}>{jam.description}</Markdown>
|
||||
</div>);
|
||||
})}
|
||||
<div className="navigation">
|
||||
<ConditionalNull condition={curPage < curDate}>
|
||||
<Link href={`?until=${jams.at(0)?.created}`}>Later</Link>
|
||||
</ConditionalNull>
|
||||
{/* <ConditionalNull condition={curPage * 10 < blogs.data.total_posts}> */}
|
||||
<Link href={`?until=${jams.at(-1)?.created}`}>Earlier</Link>
|
||||
{/* </ConditionalNull> */}
|
||||
</div>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
147
src/app/jams/user/[user]/page.tsx
Normal file
147
src/app/jams/user/[user]/page.tsx
Normal file
|
@ -0,0 +1,147 @@
|
|||
import { MainLayout } from "@/layout/MainLayout/MainLayout";
|
||||
import { cookies } from "next/headers";
|
||||
import { db, JamTable, UserTable } from "@/lib/mastoauth/kysely";
|
||||
import { ConditionalNull } from "@/components/utility/Conditional";
|
||||
import Link from "next/link";
|
||||
import Markdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import { JSONContentTable, JSONJamTable, JSONJudgementTable, JSONUserTable } from "@/lib/mastoauth/realtypes";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export default async function Home({
|
||||
params,
|
||||
searchParams
|
||||
}: {
|
||||
params: {
|
||||
user: string
|
||||
},
|
||||
searchParams: {
|
||||
until: string
|
||||
}
|
||||
}) {
|
||||
const curDate = Date.now();
|
||||
const curPage = parseInt(searchParams?.until) || curDate;
|
||||
const curUser = params.user;
|
||||
|
||||
if (curUser == null) return notFound();
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let user = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', curUser)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONUserTable;
|
||||
|
||||
if (user == null) return notFound();
|
||||
|
||||
let content = await db
|
||||
.selectFrom('content')
|
||||
.where('content.author_id', '=', user.id)
|
||||
.where('content.submitted', '<=', curPage)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute() as unknown[] as JSONContentTable[];
|
||||
|
||||
let jams = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.author_id', '=', user.id)
|
||||
.where('jams.created', '<=', curPage)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute() as unknown[] as JSONJamTable[];
|
||||
|
||||
let judgements = await db
|
||||
.selectFrom('judgements')
|
||||
.where('judgements.author_id', '=', user.id)
|
||||
.where('judgements.published', '<=', curPage)
|
||||
.limit(20)
|
||||
.selectAll()
|
||||
.execute() as unknown[] as JSONJudgementTable[];
|
||||
|
||||
return (
|
||||
<MainLayout currentPage="/jams/" title={`${user.username}@${user.instance}`} subtitle="Jams" backButton>
|
||||
<h1>{`${user.username}@${user.instance}`}</h1>
|
||||
<p><small>{user.banned ? <><b>BANNED</b> - </> : <></>}{user.admin ? <><b>ADMIN</b> - </> : <></>}Joined {new Date(parseInt(user.joined)).toDateString()}</small></p>
|
||||
<p><a href={user.url} target="_blank">{user.url}</a></p>
|
||||
<h1>Jams</h1>
|
||||
{jams.map(async (jam:JSONJamTable) => {
|
||||
const started = Date.now() >= parseInt(jam.date_start);
|
||||
const ended = Date.now() >= parseInt(jam.date_end);
|
||||
|
||||
let jamOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', jam.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (jamOwner == null) return <p>An error occured.</p>;
|
||||
|
||||
return (<div style={{
|
||||
margin: "16px 0"
|
||||
}}>
|
||||
<h2><Link href={`/jams/jam/${jam.id}`}>{jam.name}</Link></h2>
|
||||
<p><small>{started ? "Started" : "Starts"} {new Date(parseInt(jam.date_start)).toDateString()} - {ended ? "Ended" : "Ends"} {new Date(parseInt(jam.date_end)).toDateString()}</small></p>
|
||||
<p>{jam.description}</p>
|
||||
</div>);
|
||||
})}
|
||||
<h1>Submissions</h1>
|
||||
{content.map(async (content:JSONContentTable) => {
|
||||
let contentOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', content.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
if (contentOwner == null) return <p>An error occured.</p>;
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let submittedJam = await db
|
||||
.selectFrom('jams')
|
||||
.where('jams.id', '=', content.jam_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONJamTable;
|
||||
|
||||
if (submittedJam == null) return <p>An error occured.</p>;
|
||||
|
||||
return (<div style={{
|
||||
margin: "16px 0"
|
||||
}}>
|
||||
<h2><Link href={`/jams/content/${content.id}`}>{content.name}</Link></h2>
|
||||
<p><small>Submitted {new Date(parseInt(content.submitted)).toDateString()} to <a href={`/jams/jam/${submittedJam.id}?until=${content.submitted}`}>{submittedJam.name}</a></small></p>
|
||||
<p>{content.description}</p>
|
||||
</div>);
|
||||
})}
|
||||
<h1>Judgements</h1>
|
||||
{judgements.map(async (judgement:JSONJudgementTable) => {
|
||||
let judgementOwner = await db
|
||||
.selectFrom('users')
|
||||
.where('users.id', '=', judgement.author_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
// It's a JSONJamTable. I don't know why TS hates `number` => `string` conversion.
|
||||
let submittedContent = await db
|
||||
.selectFrom('content')
|
||||
.where('content.id', '=', judgement.content_id)
|
||||
.selectAll()
|
||||
.executeTakeFirst() as unknown as JSONContentTable;
|
||||
|
||||
if (judgementOwner == null) return <p>An error occured.</p>;
|
||||
|
||||
return (<div style={{
|
||||
margin: "16px 0"
|
||||
}}>
|
||||
<h2><a href={`/jams/content/${judgement.content_id}?until=${judgement.published}`}>{`${judgementOwner.username}@${judgementOwner.instance}`}</a></h2>
|
||||
<p><small>Published {new Date(parseInt(judgement.published)).toDateString()} on <a href={`/jams/content/${submittedContent.id}?until=${judgement.published}`}>{submittedContent.name}</a></small></p>
|
||||
<p>{judgement.content}</p>
|
||||
</div>);
|
||||
})}
|
||||
{/* <div className="navigation">
|
||||
<ConditionalNull condition={curPage < curDate}>
|
||||
<Link href={`?until=${content.at(0)?.submitted}`}>Later</Link>
|
||||
</ConditionalNull>
|
||||
<Link href={`?until=${content.at(-1)?.submitted}`}>Earlier</Link>
|
||||
</div> */}
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
|
@ -21,15 +21,21 @@
|
|||
background-color: var(--bg-2);
|
||||
}
|
||||
|
||||
/* .Main_List_Button {
|
||||
transition: scale 0.0625s;
|
||||
.Main_List_Button_Badge {
|
||||
background-color: rgb(255, 64, 64);
|
||||
color: white;
|
||||
padding: 0 4px;
|
||||
margin: 4px;
|
||||
float: right;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.Main_List_Button:active {
|
||||
scale: 0.95;
|
||||
.Main_List_CurrentLink .Main_List_Button_Badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
*/ .Main_List_Button_Slim {
|
||||
.Main_List_Button_Slim {
|
||||
padding: 2px 8px !important;
|
||||
background-color: var(--bg-3);
|
||||
color: var(--fg-2);
|
||||
|
|
|
@ -25,6 +25,19 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
|
|||
<span>Root</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/jams/"
|
||||
className={currentPage.includes("/jams/") ? styles.Main_List_CurrentLink : ""}
|
||||
tabIndex={-1}
|
||||
// onClick={() => currentPage === "/" ? impossiblePlay() : openPlay()}
|
||||
// onMouseEnter={hoverPlay}
|
||||
>
|
||||
<div className={`fw ${styles.Main_List_Button}`}>
|
||||
<span className="icon">format_paint</span>
|
||||
<span>Jams</span>
|
||||
<span className={styles.Main_List_Button_Badge}>NEW</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/search/"
|
||||
className={currentPage.includes("/search/") ? styles.Main_List_CurrentLink : ""}
|
||||
|
|
68
src/lib/mastoauth/kysely.ts
Normal file
68
src/lib/mastoauth/kysely.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { createKysely } from "@vercel/postgres-kysely";
|
||||
import { Generated, KyselyConfig } from "kysely";
|
||||
|
||||
// fuck it new kysely
|
||||
export interface Database {
|
||||
apps: AppTable;
|
||||
users: UserTable;
|
||||
jams: JamTable;
|
||||
content: ContentTable;
|
||||
judgements: JudgementTable;
|
||||
tokens: TokenTable;
|
||||
}
|
||||
|
||||
export interface AppTable {
|
||||
instance_domain: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
redirect_uri: string;
|
||||
}
|
||||
|
||||
export interface UserTable {
|
||||
id: string;
|
||||
instance: string;
|
||||
username: string;
|
||||
admin: boolean;
|
||||
url: string;
|
||||
banned: boolean;
|
||||
joined: number;
|
||||
}
|
||||
|
||||
export interface TokenTable {
|
||||
id: string;
|
||||
owner: string;
|
||||
created: number;
|
||||
expires: number;
|
||||
}
|
||||
|
||||
export interface JamTable {
|
||||
id: string;
|
||||
author_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
date_start: number;
|
||||
date_end: number;
|
||||
created: number;
|
||||
}
|
||||
|
||||
export interface ContentTable {
|
||||
id: string;
|
||||
author_id: string;
|
||||
jam_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
submitted: number;
|
||||
}
|
||||
|
||||
export interface JudgementTable {
|
||||
id: string;
|
||||
author_id: string;
|
||||
content_id: string;
|
||||
content: string;
|
||||
published: number;
|
||||
}
|
||||
|
||||
export const db = createKysely<Database>({
|
||||
|
||||
});
|
118
src/lib/mastoauth/mastoauth.ts
Normal file
118
src/lib/mastoauth/mastoauth.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
type MastoClient = {
|
||||
id: string,
|
||||
name: string,
|
||||
website?: string,
|
||||
redirect_uri: string,
|
||||
client_id: string,
|
||||
client_secret: string,
|
||||
vapid_key: string,
|
||||
}
|
||||
|
||||
type MastoToken = {
|
||||
access_token: string,
|
||||
token_type: string,
|
||||
scope: string,
|
||||
created_at: number
|
||||
}
|
||||
|
||||
export class MastoAuth {
|
||||
instance: string;
|
||||
|
||||
constructor(instance:string) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
async newApplication() {
|
||||
let formData = new FormData();
|
||||
formData.append('client_name', 'abtmtr.link Jams');
|
||||
// formData.append('redirect_uris', 'https://abtmtr.link/jams/oauth/code');
|
||||
formData.append('redirect_uris', 'http://localhost:3000/jams/oauth/code');
|
||||
formData.append('scopes', 'read');
|
||||
formData.append('website', 'https://abtmtr.link/jams');
|
||||
|
||||
let appRequest;
|
||||
try {
|
||||
appRequest = await fetch(`https://${this.instance}/api/v1/apps`, {
|
||||
body: formData,
|
||||
method: "post"
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (appRequest.ok) {
|
||||
const reqEntities:MastoClient = await appRequest.json();
|
||||
return reqEntities;
|
||||
} else return null;
|
||||
}
|
||||
|
||||
// async getApplicationUserCode(client: MastoClient) {
|
||||
// let formData = new FormData();
|
||||
// formData.append('response_type', 'code');
|
||||
// formData.append('client_id', client.client_id);
|
||||
// formData.append('redirect_uri', client.redirect_uri);
|
||||
// formData.append('scope', 'read');
|
||||
// formData.append('force_login', 'true');
|
||||
|
||||
// const appRequest = await fetch(`https://${this.instance}/oauth/authorize`, {
|
||||
// body: formData
|
||||
// });
|
||||
|
||||
// if (appRequest.ok) {
|
||||
// const reqEntities:MastoToken = await appRequest.json();
|
||||
// return reqEntities;
|
||||
// } else return null;
|
||||
// }
|
||||
|
||||
async getApplicationToken(client: {
|
||||
client_id: string, client_secret: string,
|
||||
redirect_uri: string
|
||||
}, code:string) {
|
||||
let formData = new FormData();
|
||||
formData.append('client_id', client.client_id);
|
||||
formData.append('client_secret', client.client_secret);
|
||||
formData.append('redirect_uri', client.redirect_uri);
|
||||
formData.append('grant_type', 'authorization_code');
|
||||
formData.append('code', code);
|
||||
formData.append('scope', 'read');
|
||||
|
||||
let appRequest;
|
||||
try {
|
||||
appRequest = await fetch(`https://${this.instance}/oauth/token`, {
|
||||
body: formData,
|
||||
method: "post"
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (appRequest.ok) {
|
||||
const reqEntities:{
|
||||
access_token: string,
|
||||
token_type: string,
|
||||
scope: string,
|
||||
created_at: number
|
||||
} = await appRequest.json();
|
||||
return reqEntities;
|
||||
} else return null;
|
||||
}
|
||||
|
||||
async verifyUser(auth: string) {
|
||||
let appRequest;
|
||||
try {
|
||||
appRequest = await fetch(`https://${this.instance}/api/v1/accounts/verify_credentials`, {
|
||||
headers: {
|
||||
"Authorization": auth
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (appRequest.ok) {
|
||||
const reqEntities = await appRequest.json();
|
||||
return reqEntities;
|
||||
} else return null;
|
||||
}
|
||||
}
|
46
src/lib/mastoauth/realtypes.ts
Normal file
46
src/lib/mastoauth/realtypes.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
|
||||
export interface JSONUserTable {
|
||||
id: string;
|
||||
instance: string;
|
||||
username: string;
|
||||
admin: boolean;
|
||||
url: string;
|
||||
banned: boolean;
|
||||
joined: string;
|
||||
}
|
||||
|
||||
export interface JSONTokenTable {
|
||||
id: string;
|
||||
owner: string;
|
||||
created: string;
|
||||
expires: string;
|
||||
}
|
||||
|
||||
export interface JSONJamTable {
|
||||
id: string;
|
||||
author_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
date_start: string;
|
||||
date_end: string;
|
||||
created: string;
|
||||
}
|
||||
|
||||
export interface JSONContentTable {
|
||||
id: string;
|
||||
author_id: string;
|
||||
jam_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
submitted: string;
|
||||
}
|
||||
|
||||
export interface JSONJudgementTable {
|
||||
id: string;
|
||||
author_id: string;
|
||||
content_id: string;
|
||||
content: string;
|
||||
published: string;
|
||||
}
|
Loading…
Reference in a new issue