before art class

This commit is contained in:
MeowcaTheoRange 2024-04-24 08:54:47 -05:00
parent 868c9df315
commit ab620bdc91
34 changed files with 2557 additions and 13 deletions

288
package-lock.json generated
View file

@ -8,6 +8,10 @@
"name": "abtmtr-v9", "name": "abtmtr-v9",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@vercel/postgres-kysely": "^0.8.0",
"formik": "^2.4.5",
"kysely": "^0.27.3",
"nanoid": "^5.0.7",
"next": "14.1.4", "next": "14.1.4",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
@ -23,6 +27,14 @@
"typescript": "^5" "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": { "node_modules/@next/env": {
"version": "14.1.4", "version": "14.1.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.4.tgz", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.4.tgz",
@ -200,6 +212,15 @@
"@types/unist": "*" "@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": { "node_modules/@types/mdast": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz",
@ -217,11 +238,20 @@
"version": "20.12.2", "version": "20.12.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz",
"integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==", "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==",
"dev": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "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": { "node_modules/@types/prop-types": {
"version": "15.7.12", "version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "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", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" "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": { "node_modules/bail": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@ -264,6 +322,18 @@
"url": "https://github.com/sponsors/wooorm" "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": { "node_modules/busboy": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@ -386,6 +456,14 @@
"url": "https://github.com/sponsors/wooorm" "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": { "node_modules/dequal": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "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", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" "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": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -574,6 +676,14 @@
"url": "https://opencollective.com/unified" "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": { "node_modules/howler": {
"version": "2.2.4", "version": "2.2.4",
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz", "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", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "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": { "node_modules/longest-streak": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@ -1483,9 +1611,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "5.0.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -1493,10 +1621,10 @@
} }
], ],
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.js"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^18 || >=20"
} }
}, },
"node_modules/next": { "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": { "node_modules/parse-entities": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", "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" "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": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -1611,6 +1777,58 @@
"node": "^10 || ^12 || >=14" "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": { "node_modules/property-information": {
"version": "6.4.1", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.1.tgz",
@ -1643,6 +1861,16 @@
"react": "^18.2.0" "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": { "node_modules/react-markdown": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", "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": { "node_modules/trim-lines": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@ -1859,8 +2092,7 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
"dev": true
}, },
"node_modules/unified": { "node_modules/unified": {
"version": "11.0.4", "version": "11.0.4",
@ -1967,6 +2199,18 @@
"react": ">=16.8" "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": { "node_modules/vfile": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz",
@ -2016,6 +2260,34 @@
"url": "https://github.com/sponsors/wooorm" "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": { "node_modules/zwitch": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",

View file

@ -9,6 +9,10 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@vercel/postgres-kysely": "^0.8.0",
"formik": "^2.4.5",
"kysely": "^0.27.3",
"nanoid": "^5.0.7",
"next": "14.1.4", "next": "14.1.4",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",

BIN
public/headers/jams.kra Normal file

Binary file not shown.

BIN
public/headers/jams.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View 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>
)
}

View file

View 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
});
}

View 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
});
}

View 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
});
}

View 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);
}

View 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
})
}

View 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
});
}

View 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
})
}

View 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);
}

View 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);
}

View 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);
}

View 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
});
}

View 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);
}

View 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>
)
}

View 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>
)
}

View 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));
}

View 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`);
}

View 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
View 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>
)
}

View 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>
)
}

View file

@ -21,15 +21,21 @@
background-color: var(--bg-2); background-color: var(--bg-2);
} }
/* .Main_List_Button { .Main_List_Button_Badge {
transition: scale 0.0625s; 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 { .Main_List_CurrentLink .Main_List_Button_Badge {
scale: 0.95; display: none;
} }
*/ .Main_List_Button_Slim { .Main_List_Button_Slim {
padding: 2px 8px !important; padding: 2px 8px !important;
background-color: var(--bg-3); background-color: var(--bg-3);
color: var(--fg-2); color: var(--fg-2);

View file

@ -25,6 +25,19 @@ export function SidebarMain({currentPage}:{currentPage:string}) {
<span>Root</span> <span>Root</span>
</div> </div>
</Link> </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 <Link
href="/search/" href="/search/"
className={currentPage.includes("/search/") ? styles.Main_List_CurrentLink : ""} className={currentPage.includes("/search/") ? styles.Main_List_CurrentLink : ""}

View 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>({
});

View 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;
}
}

View 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;
}