diff --git a/package-lock.json b/package-lock.json index e0de0a1..1feef11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7f53eca..d662fb5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/headers/jams.kra b/public/headers/jams.kra new file mode 100644 index 0000000..6a752ed Binary files /dev/null and b/public/headers/jams.kra differ diff --git a/public/headers/jams.png b/public/headers/jams.png new file mode 100644 index 0000000..e9da589 Binary files /dev/null and b/public/headers/jams.png differ diff --git a/src/app/jams/(manip)/edit/content/[contentid]/page.tsx b/src/app/jams/(manip)/edit/content/[contentid]/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/jams/(manip)/edit/jam/[jamid]/page.tsx b/src/app/jams/(manip)/edit/jam/[jamid]/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/jams/(manip)/edit/judgement/[judgementid]/page.tsx b/src/app/jams/(manip)/edit/judgement/[judgementid]/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/jams/(manip)/new/content/[jamid]/page.tsx b/src/app/jams/(manip)/new/content/[jamid]/page.tsx new file mode 100644 index 0000000..193afd5 --- /dev/null +++ b/src/app/jams/(manip)/new/content/[jamid]/page.tsx @@ -0,0 +1,19 @@ +import { MainLayout } from "@/layout/MainLayout/MainLayout"; + +export default async function Home({ + params, + searchParams +}: { + params: { + content: string + }, + searchParams: { + until: string + } +}) { + + return ( + + + ) +} \ No newline at end of file diff --git a/src/app/jams/(manip)/new/jam/page.tsx b/src/app/jams/(manip)/new/jam/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/jams/(manip)/new/judgement/[contentid]/page.tsx b/src/app/jams/(manip)/new/judgement/[contentid]/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/app/jams/api/content/[content]/judgements/[judgement]/route.ts b/src/app/jams/api/content/[content]/judgements/[judgement]/route.ts new file mode 100644 index 0000000..6b9e383 --- /dev/null +++ b/src/app/jams/api/content/[content]/judgements/[judgement]/route.ts @@ -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 = {}; + + 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 + }); +} \ No newline at end of file diff --git a/src/app/jams/api/content/[content]/judgements/route.ts b/src/app/jams/api/content/[content]/judgements/route.ts new file mode 100644 index 0000000..5c08547 --- /dev/null +++ b/src/app/jams/api/content/[content]/judgements/route.ts @@ -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 + }); +} \ No newline at end of file diff --git a/src/app/jams/api/content/[content]/route.ts b/src/app/jams/api/content/[content]/route.ts new file mode 100644 index 0000000..103bf55 --- /dev/null +++ b/src/app/jams/api/content/[content]/route.ts @@ -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 = {}; + + 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 + }); +} \ No newline at end of file diff --git a/src/app/jams/api/content/route.ts b/src/app/jams/api/content/route.ts new file mode 100644 index 0000000..65a07ea --- /dev/null +++ b/src/app/jams/api/content/route.ts @@ -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); +} \ No newline at end of file diff --git a/src/app/jams/api/jams/[jam]/content/route.ts b/src/app/jams/api/jams/[jam]/content/route.ts new file mode 100644 index 0000000..ed562e5 --- /dev/null +++ b/src/app/jams/api/jams/[jam]/content/route.ts @@ -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 + }) +} \ No newline at end of file diff --git a/src/app/jams/api/jams/[jam]/route.ts b/src/app/jams/api/jams/[jam]/route.ts new file mode 100644 index 0000000..ce8d9fc --- /dev/null +++ b/src/app/jams/api/jams/[jam]/route.ts @@ -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 = {}; + + 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 + }); +} \ No newline at end of file diff --git a/src/app/jams/api/jams/route.ts b/src/app/jams/api/jams/route.ts new file mode 100644 index 0000000..304de8e --- /dev/null +++ b/src/app/jams/api/jams/route.ts @@ -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 + }) +} \ No newline at end of file diff --git a/src/app/jams/api/users/[user]/content/route.ts b/src/app/jams/api/users/[user]/content/route.ts new file mode 100644 index 0000000..89dd568 --- /dev/null +++ b/src/app/jams/api/users/[user]/content/route.ts @@ -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); +} \ No newline at end of file diff --git a/src/app/jams/api/users/[user]/jams/route.ts b/src/app/jams/api/users/[user]/jams/route.ts new file mode 100644 index 0000000..128a871 --- /dev/null +++ b/src/app/jams/api/users/[user]/jams/route.ts @@ -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); +} \ No newline at end of file diff --git a/src/app/jams/api/users/[user]/judgements/route.ts b/src/app/jams/api/users/[user]/judgements/route.ts new file mode 100644 index 0000000..fca7946 --- /dev/null +++ b/src/app/jams/api/users/[user]/judgements/route.ts @@ -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); +} \ No newline at end of file diff --git a/src/app/jams/api/users/[user]/route.ts b/src/app/jams/api/users/[user]/route.ts new file mode 100644 index 0000000..525e67b --- /dev/null +++ b/src/app/jams/api/users/[user]/route.ts @@ -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 = {}; + + 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 + }); +} \ No newline at end of file diff --git a/src/app/jams/api/users/route.ts b/src/app/jams/api/users/route.ts new file mode 100644 index 0000000..e719c47 --- /dev/null +++ b/src/app/jams/api/users/route.ts @@ -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); +} \ No newline at end of file diff --git a/src/app/jams/content/[content]/page.tsx b/src/app/jams/content/[content]/page.tsx new file mode 100644 index 0000000..db77a57 --- /dev/null +++ b/src/app/jams/content/[content]/page.tsx @@ -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 ( + +

{content.name}

+

{content.url}

+

Submitted by {`${contentOwner.username}@${contentOwner.instance}`} to {submittedJam.name} - {new Date(parseInt(content.submitted)).toDateString()}

+

{content.description}

+ +
+

You have the ability to modify this submission.

+

Edit submission

+

Delete submission

+

Judge this submission

+
+
+

Judgements

+ {judgements.map(async (judgement:JSONJudgementTable) => { + let judgementOwner = await db + .selectFrom('users') + .where('users.id', '=', judgement.author_id) + .selectAll() + .executeTakeFirst(); + + if (judgementOwner == null) return

An error occured.

; + + return (
+

{`${judgementOwner.username}@${judgementOwner.instance}`}

+ + + +

Published {new Date(parseInt(judgement.published)).toDateString()}

+

{judgement.content}

+
); + })} +
+ + Later + + {/* */} + Earlier + {/* */} +
+
+ ) +} \ No newline at end of file diff --git a/src/app/jams/jam/[jam]/page.tsx b/src/app/jams/jam/[jam]/page.tsx new file mode 100644 index 0000000..983abc8 --- /dev/null +++ b/src/app/jams/jam/[jam]/page.tsx @@ -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 ( + +

{jam.name}

+

{started ? "Started" : "Starts"} {new Date(parseInt(jam.date_start)).toDateString()} - {ended ? "Ended" : "Ends"} {new Date(parseInt(jam.date_end)).toDateString()}

+

Hosted by {`${jamOwner.username}@${jamOwner.instance}`}

+

{jam.description}

+ +
+

You have the ability to modify this Jam.

+

Edit jam

+

Delete jam

+

Submit something

+
+
+

Submissions

+ {content.map(async (content:JSONContentTable) => { + let contentOwner = await db + .selectFrom('users') + .where('users.id', '=', content.author_id) + .selectAll() + .executeTakeFirst(); + + if (contentOwner == null) return

An error occured.

; + + return (
+

{content.name}

+

Submitted by {`${contentOwner.username}@${contentOwner.instance}`} - {new Date(parseInt(content.submitted)).toDateString()}

+

{content.description}

+
); + })} +
+ + Later + + {/* */} + Earlier + {/* */} +
+
+ ) +} \ No newline at end of file diff --git a/src/app/jams/oauth/code/route.ts b/src/app/jams/oauth/code/route.ts new file mode 100644 index 0000000..8a73fe4 --- /dev/null +++ b/src/app/jams/oauth/code/route.ts @@ -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)); +} \ No newline at end of file diff --git a/src/app/jams/oauth/login/route.ts b/src/app/jams/oauth/login/route.ts new file mode 100644 index 0000000..01c7081 --- /dev/null +++ b/src/app/jams/oauth/login/route.ts @@ -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`); +} \ No newline at end of file diff --git a/src/app/jams/oauth/logout/route.ts b/src/app/jams/oauth/logout/route.ts new file mode 100644 index 0000000..63248c5 --- /dev/null +++ b/src/app/jams/oauth/logout/route.ts @@ -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)); +} \ No newline at end of file diff --git a/src/app/jams/page.tsx b/src/app/jams/page.tsx new file mode 100644 index 0000000..b5300f5 --- /dev/null +++ b/src/app/jams/page.tsx @@ -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 ( + + JAMS +

Some jams I host that you can participate in!

+

These serve many purposes. To create a community bond, to help creatives find their way, and most importantly, to make me popular. X3

+

Enjoy!

+ +

Log in with the Fediverse

+
+ + +
+

Tested on Mastodon, GoToSocial, Pleroma, and Misskey

+
+ +

Logged in as {existingUser?.username}@{existingUser?.instance} (Logout)

+

Create Jam

+
+

Jams

+ {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

An error occured.

; + + return (
+

{jam.name}

+

Hosted by {`${jamOwner.username}@${jamOwner.instance}`} - {started ? "Started" : "Starts"} {new Date(parseInt(jam.date_start)).toDateString()} - {ended ? "Ended" : "Ends"} {new Date(parseInt(jam.date_end)).toDateString()}

+ {jam.description} +
); + })} +
+ + Later + + {/* */} + Earlier + {/* */} +
+
+ ) +} \ No newline at end of file diff --git a/src/app/jams/user/[user]/page.tsx b/src/app/jams/user/[user]/page.tsx new file mode 100644 index 0000000..04e745a --- /dev/null +++ b/src/app/jams/user/[user]/page.tsx @@ -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 ( + +

{`${user.username}@${user.instance}`}

+

{user.banned ? <>BANNED - : <>}{user.admin ? <>ADMIN - : <>}Joined {new Date(parseInt(user.joined)).toDateString()}

+

{user.url}

+

Jams

+ {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

An error occured.

; + + return (
+

{jam.name}

+

{started ? "Started" : "Starts"} {new Date(parseInt(jam.date_start)).toDateString()} - {ended ? "Ended" : "Ends"} {new Date(parseInt(jam.date_end)).toDateString()}

+

{jam.description}

+
); + })} +

Submissions

+ {content.map(async (content:JSONContentTable) => { + let contentOwner = await db + .selectFrom('users') + .where('users.id', '=', content.author_id) + .selectAll() + .executeTakeFirst(); + + if (contentOwner == null) return

An error occured.

; + + // 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

An error occured.

; + + return (
+

{content.name}

+

Submitted {new Date(parseInt(content.submitted)).toDateString()} to {submittedJam.name}

+

{content.description}

+
); + })} +

Judgements

+ {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

An error occured.

; + + return (
+

{`${judgementOwner.username}@${judgementOwner.instance}`}

+

Published {new Date(parseInt(judgement.published)).toDateString()} on {submittedContent.name}

+

{judgement.content}

+
); + })} + {/*
+ + Later + + Earlier +
*/} +
+ ) +} \ No newline at end of file diff --git a/src/layout/MainLayout/Sidebar/Main/Main.module.css b/src/layout/MainLayout/Sidebar/Main/Main.module.css index b778100..c2d199c 100644 --- a/src/layout/MainLayout/Sidebar/Main/Main.module.css +++ b/src/layout/MainLayout/Sidebar/Main/Main.module.css @@ -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); diff --git a/src/layout/MainLayout/Sidebar/Main/Main.tsx b/src/layout/MainLayout/Sidebar/Main/Main.tsx index a149b91..fbd9220 100644 --- a/src/layout/MainLayout/Sidebar/Main/Main.tsx +++ b/src/layout/MainLayout/Sidebar/Main/Main.tsx @@ -25,6 +25,19 @@ export function SidebarMain({currentPage}:{currentPage:string}) { Root + currentPage === "/" ? impossiblePlay() : openPlay()} + // onMouseEnter={hoverPlay} + > +
+ format_paint + Jams + NEW +
+ ({ + +}); \ No newline at end of file diff --git a/src/lib/mastoauth/mastoauth.ts b/src/lib/mastoauth/mastoauth.ts new file mode 100644 index 0000000..1209c88 --- /dev/null +++ b/src/lib/mastoauth/mastoauth.ts @@ -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; + } +} \ No newline at end of file diff --git a/src/lib/mastoauth/realtypes.ts b/src/lib/mastoauth/realtypes.ts new file mode 100644 index 0000000..ae917ce --- /dev/null +++ b/src/lib/mastoauth/realtypes.ts @@ -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; +} \ No newline at end of file