commit 4f75b0622b66ca5f4dcbea62b7a323a1bf9b37ae Author: kosh Date: Mon Aug 26 18:37:38 2024 +0530 Complete diff --git a/.Trash-1000/files/a b/.Trash-1000/files/a new file mode 100644 index 0000000..e69de29 diff --git a/.Trash-1000/files/a.zip b/.Trash-1000/files/a.zip new file mode 100644 index 0000000..d3efa0f Binary files /dev/null and b/.Trash-1000/files/a.zip differ diff --git a/.Trash-1000/files/apifiletest_.vscode_settings.json b/.Trash-1000/files/apifiletest_.vscode_settings.json new file mode 100644 index 0000000..4798424 --- /dev/null +++ b/.Trash-1000/files/apifiletest_.vscode_settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/.Trash-1000/files/apifiletest_index.html b/.Trash-1000/files/apifiletest_index.html new file mode 100644 index 0000000..b5d3028 --- /dev/null +++ b/.Trash-1000/files/apifiletest_index.html @@ -0,0 +1,21 @@ + + + + + Document + + + +
+ + +
+ + diff --git a/.Trash-1000/files/apifiletest_index.js b/.Trash-1000/files/apifiletest_index.js new file mode 100644 index 0000000..b97454b --- /dev/null +++ b/.Trash-1000/files/apifiletest_index.js @@ -0,0 +1,31 @@ +document.getElementById("uploadButton").addEventListener("click", async () => { + const fileInput = document.getElementById("fileInput"); + const files = Array.from(fileInput.files); + + if (files.length === 0) { + alert("Please select some files first."); + return; + } + + const formData = new FormData(); + + files.forEach((file) => { + formData.append("files[]", file); // Append each file to FormData + }); + + try { + const response = await fetch("https://club.modo-dev.com/test", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("Upload successful:", data); + } catch (error) { + console.error("Error uploading files:", error); + } +}); diff --git a/.Trash-1000/files/apifiletest|.vscode|settings.json b/.Trash-1000/files/apifiletest|.vscode|settings.json new file mode 100644 index 0000000..4798424 --- /dev/null +++ b/.Trash-1000/files/apifiletest|.vscode|settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/.Trash-1000/files/booking-site.db b/.Trash-1000/files/booking-site.db new file mode 100644 index 0000000..e69de29 diff --git a/.Trash-1000/files/docs.fuck_discord b/.Trash-1000/files/docs.fuck_discord new file mode 100644 index 0000000..40ee66c --- /dev/null +++ b/.Trash-1000/files/docs.fuck_discord @@ -0,0 +1,140 @@ +Legend: + Form data: + https://developer.mozilla.org/en-US/docs/Web/API/FormData + [something] : this something is optional + + + +---------- USER HANDLING ---------- + +POST : /get-user-by-email + Form data: + email + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : email missing in request + 404 : email not found + +POST : /get-user-by-id + Form data: + id + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : id missing in request + 404 : id not found + +POST : /get-user-by-handle + Form Data: + handle + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : handle missing in request + 404 : handle not found + +POST : /register + Form Data: + email + handle + password + [display_name] + [profile_picture] + Errors: + 422 : something missing in request + 409 : User exists (email or handle) + +POST : /login + Form Data: + email + password + Errors: + 422 : something missing in request + 404 : User does not exist + 409 : Wrong password + Cookies: + sets id + sets session_id + +POST : /logout + Errors: + 401 : Not logged in + Cookies: + deletes session_id + deletes id + +POST : /change-user-profile-picture + Form Data: + image + Errors: + 401 : Not logged in + 403 : Invalid log in + 422 : image is missing + Note: + Must be logged in + +POST : /change-user-display-name + Form Data: + display_name + Errors: + 401 : Not logged in + 403 : Invalid log in + 422 : display_name is missing + Note: + Must be logged in + +GET : /profile-picture/ + Errors: + 404 : Handle not found + + + +---------- BOUNTY HANDLING ---------- + +POST : /create-bounty + Form Data: + title + description + [field] + [language] + Errors: + 422 : something missing in request + 403 : Unknown language/ field + Note: + default language = "any" + default field = "other" + +POST : /get-user-by-id + Form data: + id + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : id missing in request + 404 : id not found + +POST : /create-session-for-bounty-get-random + Cookies: + sets bounty_session_id + +POST : /bounty-get-random + Form data: + page_number + Note: + /create-session-for-bounty-get-random must be called before this + Errors: + 422 : page no. missing in request + 404 : /create-session-for-bounty-get-random not called diff --git a/.Trash-1000/files/test.html b/.Trash-1000/files/test.html new file mode 100644 index 0000000..8539955 --- /dev/null +++ b/.Trash-1000/files/test.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/.Trash-1000/files/test/subdir/test1.c b/.Trash-1000/files/test/subdir/test1.c new file mode 100644 index 0000000..20f5249 --- /dev/null +++ b/.Trash-1000/files/test/subdir/test1.c @@ -0,0 +1,6 @@ +#include + +void main() +{ + printf("Hello World\n"); +} \ No newline at end of file diff --git a/.Trash-1000/files/test/test.c b/.Trash-1000/files/test/test.c new file mode 100644 index 0000000..20f5249 --- /dev/null +++ b/.Trash-1000/files/test/test.c @@ -0,0 +1,6 @@ +#include + +void main() +{ + printf("Hello World\n"); +} \ No newline at end of file diff --git a/.Trash-1000/info/a.trashinfo b/.Trash-1000/info/a.trashinfo new file mode 100644 index 0000000..fab49ef --- /dev/null +++ b/.Trash-1000/info/a.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=src/a +DeletionDate=2024-07-24T17:11:19 diff --git a/.Trash-1000/info/a.zip.trashinfo b/.Trash-1000/info/a.zip.trashinfo new file mode 100644 index 0000000..a74bbbc --- /dev/null +++ b/.Trash-1000/info/a.zip.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=src/a.zip +DeletionDate=2024-07-24T19:40:20 diff --git a/.Trash-1000/info/apifiletest_.vscode_settings.json.trashinfo b/.Trash-1000/info/apifiletest_.vscode_settings.json.trashinfo new file mode 100644 index 0000000..8c5bb2f --- /dev/null +++ b/.Trash-1000/info/apifiletest_.vscode_settings.json.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=assets/apifiletest_.vscode_settings.json +DeletionDate=2024-07-24T20:03:05 diff --git a/.Trash-1000/info/apifiletest_index.html.trashinfo b/.Trash-1000/info/apifiletest_index.html.trashinfo new file mode 100644 index 0000000..15d4f02 --- /dev/null +++ b/.Trash-1000/info/apifiletest_index.html.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=assets/apifiletest_index.html +DeletionDate=2024-07-24T20:02:59 diff --git a/.Trash-1000/info/apifiletest_index.js.trashinfo b/.Trash-1000/info/apifiletest_index.js.trashinfo new file mode 100644 index 0000000..f6a2f97 --- /dev/null +++ b/.Trash-1000/info/apifiletest_index.js.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=assets/apifiletest_index.js +DeletionDate=2024-07-24T20:03:05 diff --git a/.Trash-1000/info/apifiletest|.vscode|settings.json.trashinfo b/.Trash-1000/info/apifiletest|.vscode|settings.json.trashinfo new file mode 100644 index 0000000..292ee9b --- /dev/null +++ b/.Trash-1000/info/apifiletest|.vscode|settings.json.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=assets/apifiletest%7C.vscode%7Csettings.json +DeletionDate=2024-07-24T20:24:13 diff --git a/.Trash-1000/info/booking-site.db.trashinfo b/.Trash-1000/info/booking-site.db.trashinfo new file mode 100644 index 0000000..798dcc4 --- /dev/null +++ b/.Trash-1000/info/booking-site.db.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=data/booking-site.db +DeletionDate=2024-07-17T19:27:01 diff --git a/.Trash-1000/info/docs.fuck_discord.trashinfo b/.Trash-1000/info/docs.fuck_discord.trashinfo new file mode 100644 index 0000000..ddb216f --- /dev/null +++ b/.Trash-1000/info/docs.fuck_discord.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=src/docs.fuck_discord +DeletionDate=2024-07-24T17:11:22 diff --git a/.Trash-1000/info/test.html.trashinfo b/.Trash-1000/info/test.html.trashinfo new file mode 100644 index 0000000..58749d6 --- /dev/null +++ b/.Trash-1000/info/test.html.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=src/test.html +DeletionDate=2024-07-24T20:25:59 diff --git a/.Trash-1000/info/test.trashinfo b/.Trash-1000/info/test.trashinfo new file mode 100644 index 0000000..19bd00f --- /dev/null +++ b/.Trash-1000/info/test.trashinfo @@ -0,0 +1,3 @@ +[Trash Info] +Path=src/test +DeletionDate=2024-07-24T19:40:41 diff --git a/.vim/coc-settings.json b/.vim/coc-settings.json new file mode 100644 index 0000000..d3833f9 --- /dev/null +++ b/.vim/coc-settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Yash's" + ] +} \ No newline at end of file diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..66e6cf7 Binary files /dev/null and b/__pycache__/main.cpython-312.pyc differ diff --git a/__pycache__/rest_api.cpython-312.pyc b/__pycache__/rest_api.cpython-312.pyc new file mode 100644 index 0000000..23004e7 Binary files /dev/null and b/__pycache__/rest_api.cpython-312.pyc differ diff --git a/assets/apifiletest/index.html b/assets/apifiletest/index.html new file mode 100644 index 0000000..a0b5be0 --- /dev/null +++ b/assets/apifiletest/index.html @@ -0,0 +1,30 @@ + + + + + Document + + + + +
+ + +
+ + diff --git a/assets/apifiletest/index.js b/assets/apifiletest/index.js new file mode 100644 index 0000000..7463aec --- /dev/null +++ b/assets/apifiletest/index.js @@ -0,0 +1,32 @@ +document.getElementById("uploadButton").addEventListener("click", async () => { + const fileInput = document.getElementById("fileInput"); + const files = Array.from(fileInput.files); + + if (files.length === 0) { + alert("Please select some files first."); + return; + } + + const formData = new FormData(); + + formData.append("bounty_id", 7); + files.forEach((file) => { + formData.append("files", file); // Append each file to FormData + }); + + try { + const response = await fetch("http://localhost:5000/submit-solution", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("Upload successful:", data); + } catch (error) { + console.error("Error uploading files:", error); + } +}); diff --git a/assets/profile_pictures/1 b/assets/profile_pictures/1 new file mode 100644 index 0000000..ac5be70 Binary files /dev/null and b/assets/profile_pictures/1 differ diff --git a/assets/profile_pictures/123 b/assets/profile_pictures/123 new file mode 100644 index 0000000..ac5be70 Binary files /dev/null and b/assets/profile_pictures/123 differ diff --git a/assets/profile_pictures/12345 b/assets/profile_pictures/12345 new file mode 100644 index 0000000..ac5be70 Binary files /dev/null and b/assets/profile_pictures/12345 differ diff --git a/assets/profile_pictures/55555 b/assets/profile_pictures/55555 new file mode 100644 index 0000000..ac5be70 Binary files /dev/null and b/assets/profile_pictures/55555 differ diff --git a/assets/profile_pictures/555555 b/assets/profile_pictures/555555 new file mode 100644 index 0000000..ac5be70 Binary files /dev/null and b/assets/profile_pictures/555555 differ diff --git a/assets/profile_pictures/ b/assets/profile_pictures/ new file mode 100644 index 0000000..ac5be70 Binary files /dev/null and b/assets/profile_pictures/ differ diff --git a/assets/profile_pictures/I21 b/assets/profile_pictures/I21 new file mode 100644 index 0000000..4450584 Binary files /dev/null and b/assets/profile_pictures/I21 differ diff --git a/assets/profile_pictures/N b/assets/profile_pictures/N new file mode 100644 index 0000000..4450584 Binary files /dev/null and b/assets/profile_pictures/N differ diff --git a/assets/profile_pictures/None b/assets/profile_pictures/None new file mode 100644 index 0000000..ac5be70 Binary files /dev/null and b/assets/profile_pictures/None differ diff --git a/assets/profile_pictures/default_profile_picture.jpg b/assets/profile_pictures/default_profile_picture.jpg new file mode 100644 index 0000000..4450584 Binary files /dev/null and b/assets/profile_pictures/default_profile_picture.jpg differ diff --git a/assets/profile_pictures/kosh b/assets/profile_pictures/kosh new file mode 100644 index 0000000..4450584 Binary files /dev/null and b/assets/profile_pictures/kosh differ diff --git a/assets/profile_pictures/kush b/assets/profile_pictures/kush new file mode 100644 index 0000000..fdabcbc Binary files /dev/null and b/assets/profile_pictures/kush differ diff --git a/assets/profile_pictures/kushi b/assets/profile_pictures/kushi new file mode 100644 index 0000000..fdabcbc Binary files /dev/null and b/assets/profile_pictures/kushi differ diff --git a/assets/profile_pictures/kushidsaddfdsfdffasfasfdsfdsfdasds b/assets/profile_pictures/kushidsaddfdsfdffasfasfdsfdsfdasds new file mode 100644 index 0000000..fdabcbc Binary files /dev/null and b/assets/profile_pictures/kushidsaddfdsfdffasfasfdsfdsfdasds differ diff --git a/assets/profile_pictures/nameless b/assets/profile_pictures/nameless new file mode 100644 index 0000000..4450584 Binary files /dev/null and b/assets/profile_pictures/nameless differ diff --git a/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|123.txt b/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|234.txt b/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2 - Copy|123.txt b/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2 - Copy|234.txt b/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2|123.txt b/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2|234.txt b/assets/solutions/0rN9ylJIoT_6LJQnxrRoKBcz86cb7ZTQxAkbIe0_1Jxf0v1ds-c4lg/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|123.txt b/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|234.txt b/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2 - Copy|123.txt b/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2 - Copy|234.txt b/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2|123.txt b/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2|234.txt b/assets/solutions/1efYDlaL5Zk9bkQcZU6iYY8m4GtnSt19z6aMH3JoMZRtPrWXvCVQkw/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|123.txt b/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|234.txt b/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2 - Copy|123.txt b/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2 - Copy|234.txt b/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2|123.txt b/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2|234.txt b/assets/solutions/2BLAbvW9CYAyGLyzNo4GsIrgiY819d_myGiqw_06IliSjkxfAH9onw/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|.jukitinfo.json b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|.jukitinfo.json new file mode 100644 index 0000000..bf52a50 --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|.jukitinfo.json @@ -0,0 +1 @@ +{"cmd": "import os\nimport secrets\nimport sqlite3\nfrom user_handler import UserHandler\nfrom werkzeug.datastructures.file_storage import FileStorage\n\n\nclass SolutionHandler:\n connection: sqlite3.Connection = sqlite3.connect(\"../data/database.db\", check_same_thread=False)\n cursor: sqlite3.Cursor = connection.cursor()\n\n # Save solution\n @classmethod\n def submit_solution(cls, files: list[FileStorage], bounty_identifier: int, creator_identifier: int):\n solution_id = secrets.token_urlsafe(40)\n for file in files:\n if not file.filename:\n file.filename = secrets.token_urlsafe(5)\n file.filename = file.filename.replace(\"/\", r\"|\")\n os.mkdir(f\"../assets/solutions/{solution_id}\")\n file.save(f\"../assets/solutions/{solution_id}/{file.filename}\")\n\n cls.cursor.execute(\n f\"\"\"\n INSERT INTO bounty_solutions(creator_identifier, bounty_identifier, identifier)\n values(?, ?, ?)\n \"\"\",\n (creator_identifier, bounty_identifier, solution_id)\n )\n cls.connection.commit()\n\n @staticmethod\n def __format_solution(paths: list[str]):\n pass\n\n # Get solution by id\n @staticmethod\n def get_solution_by_id(solution_id: str):\n path = f\"../assets/solutions/{solution_id}/\"\n if not os.path.isdir(path):\n raise KeyError(\"Solution doesn't exist\")\n formated_solutions: dict[str, str | dict] = {}\n file_paths = os.listdir(path)\n for file_path in file_paths:\n current_folder = formated_solutions\n for folder in file_path.split(\"|\")[:-1]:\n current_folder[folder] = {}\n current_folder = current_folder[folder]\n file_name = file_path.split(\"|\")[-1]\n try:\n with open(path + file_path, \"r\") as file:\n current_folder[file_name] = file.read()\n except UnicodeDecodeError:\n current_folder[file_name] = \"\"\n\n print(formated_solutions)\n \n\n @classmethod\n def get_solution_list(cls, identifier: int) -> list[dict]:\n solutions = cls.cursor.execute(\n f\"\"\"\n SELECT creator_identifier, identifier FROM bounty_solutions\n WHERE bounty_identifier = ?;\n \"\"\",\n (identifier, )\n ).fetchall()\n return [\n {\n \"creator_id\": solution[0],\n \"creator_display_name\": UserHandler.get_display_name(solution[0]),\n \"solution_id\": solution[1],\n \"likes\": cls.get_likes(solution[1])\n } for solution in solutions\n ]\n\n @classmethod\n def like(cls, solution_id: int, user_id: int):\n cls.cursor.execute(\n f\"\"\"\n INSERT INTO solution_likes(user_id, solution_id) values(?, ?)\n \"\"\",\n (user_id, solution_id)\n )\n cls.connection.commit()\n\n @classmethod\n def unlike(cls, solution_id: int, user_id: int):\n cls.cursor.execute(\n f\"\"\"\n DELETE FROM solution_likes WHERE solution_id = ? AND user_id = ?\n \"\"\",\n (solution_id, user_id)\n )\n cls.connection.commit()\n\n @classmethod\n def get_likes(cls, solution_id: int) -> int:\n likes = cls.cursor.execute(\n f\"\"\"\n SELECT COUNT(*) FROM solution_likes WHERE solution_id = ?\n \"\"\",\n (solution_id, )\n ).fetchall()[0]\n return likes[0] if likes else 0\n\n @classmethod\n def has_user_liked(cls, solution_id: int, user_id: int) -> bool:\n\n likes = cls.cursor.execute(\n f\"\"\"\n SELECT 1 FROM solution_likes WHERE solution_id = ?\n \"\"\",\n (solution_id, )\n ).fetchall()\n return bool(likes)\n\nSolutionHandler.get_solution_by_id(\"NvFlP3tsfJbYDTAOD5dHt4hEp2rLlsVEYizWhC39WwstnJWDosPKyQ\")", "cmd_opts": " --cell_id=NONE -s", "import_complete": 1, "terminal": "kitty"} \ No newline at end of file diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|bountyhandlerouthist.json b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|bountyhandlerouthist.json new file mode 100644 index 0000000..f54de93 --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|bountyhandlerouthist.json @@ -0,0 +1 @@ +{"LsrWtuhk5A": null} \ No newline at end of file diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|restapiouthist.json b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|restapiouthist.json new file mode 100644 index 0000000..3a3530f --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|restapiouthist.json @@ -0,0 +1 @@ +{"NONE": [{"output_type": "stream", "name": "stdout", "text": "{'GTXN1Una4AAvXHl.webp': '', 'test1': {'fdf.webp': 'Sfjdifjodi\\n'}, 'test2': {'test3': {'safdf.webp': 'fjidofjidfji\\n'}}}\n"}]} \ No newline at end of file diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|userhandlerouthist.json b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|userhandlerouthist.json new file mode 100644 index 0000000..7c8b470 --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|.jukit|userhandlerouthist.json @@ -0,0 +1 @@ +{"NONE": [{"output_type": "stream", "name": "stdout", "text": "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mIntegrityError\u001b[0m Traceback (most recent call last)\nCell \u001b[0;32mIn[1], line 127\u001b[0m\n\u001b[1;32m 117\u001b[0m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39mget_user_by_handle(handle)\n\u001b[1;32m 118\u001b[0m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39mcursor\u001b[38;5;241m.\u001b[39mexecute(\n\u001b[1;32m 119\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\"\"\u001b[39m\n\u001b[1;32m 120\u001b[0m \u001b[38;5;124m UPDATE users\u001b[39m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 124\u001b[0m (display_name, handle )\n\u001b[1;32m 125\u001b[0m )\u001b[38;5;241m.\u001b[39mfetchall()\n\u001b[0;32m--> 127\u001b[0m \u001b[43mUserHandler\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcreate_user\u001b[49m\u001b[43m(\u001b[49m\u001b[43mUser\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkos\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkos\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkos\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkosh\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 128\u001b[0m UserHandler\u001b[38;5;241m.\u001b[39mchange_display_name(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkos\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkosh\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\nCell \u001b[0;32mIn[1], line 38\u001b[0m, in \u001b[0;36mUserHandler.create_user\u001b[0;34m(cls, user)\u001b[0m\n\u001b[1;32m 36\u001b[0m salt \u001b[38;5;241m=\u001b[39m bcrypt\u001b[38;5;241m.\u001b[39mgensalt()\n\u001b[1;32m 37\u001b[0m encrypted_password \u001b[38;5;241m=\u001b[39m bcrypt\u001b[38;5;241m.\u001b[39mhashpw(user\u001b[38;5;241m.\u001b[39mpassword\u001b[38;5;241m.\u001b[39mencode(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m\"\u001b[39m), salt)\n\u001b[0;32m---> 38\u001b[0m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcursor\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexecute\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 39\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\"\"\u001b[39;49m\n\u001b[1;32m 40\u001b[0m \u001b[38;5;124;43m INSERT INTO users(handle, display_name, email, password)\u001b[39;49m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;124;43m values(?, ?, ?, ?)\u001b[39;49m\n\u001b[1;32m 42\u001b[0m \u001b[38;5;124;43m \u001b[39;49m\u001b[38;5;124;43m\"\"\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 43\u001b[0m \u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43muser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mhandle\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdisplay_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43memail\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencrypted_password\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 44\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 45\u001b[0m shutil\u001b[38;5;241m.\u001b[39mcopy(\n\u001b[1;32m 46\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m../assets/profile_pictures/default_profile_picture.jpg\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 47\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m../assets/profile_pictures/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00muser\u001b[38;5;241m.\u001b[39mhandle\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 48\u001b[0m )\n\u001b[1;32m 49\u001b[0m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39mconnection\u001b[38;5;241m.\u001b[39mcommit()\n\n\u001b[0;31mIntegrityError\u001b[0m: UNIQUE constraint failed: users.handle\n"}]} \ No newline at end of file diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|bountyhandler.py b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|bountyhandler.py new file mode 100644 index 0000000..1503442 --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|bountyhandler.py @@ -0,0 +1,147 @@ +from dataclasses import dataclass +import sqlite3 + + +@dataclass +class Bounty: + bounty_id: int + user_id: int + title: str + description: str + languages: str + field: str + + def __init__( + self, + bounty_id: int, + user_id: int, + title: str, + description: str, + languages: str, + field: str, + ) -> None: + self.bounty_id = bounty_id + self.user_id = user_id + self.title = title + self.description = description + self.languages = languages + self.field = field + + +class BountyHandler: + connection: sqlite3.Connection = sqlite3.connect("../data/database.db", check_same_thread=False) + cursor: sqlite3.Cursor = connection.cursor() + + # Create Bounty + @classmethod + def create_bounty( + cls, + bounty: Bounty + ) -> None: + bounty.languages.removeprefix(",") + bounty.languages.removesuffix(",") + print(bounty.languages) + assert set(bounty.languages.split(",")).issubset( + {"c", "cpp", "c#", "css", "go", "html", "java", "js", "python", "ruby", "rust", "other", "any"} + ) + assert set(bounty.field.split(",")).issubset( + {"dsa", "ai", "web", "app", "other"} + ) + cls.cursor.execute( + f""" + INSERT INTO bounties(user_id, title, description, languages, field) + values(?, ?, ?, ?, ?) + """, + (bounty.bounty_id, bounty.title, bounty.description, bounty.languages, bounty.field) + ) + cls.connection.commit() + + # Get Bounty + @classmethod + def get_bounty_by_id(cls, bounty_id: int) -> Bounty: + bounties = cls.cursor.execute( + f""" + SELECT user_id, title, description, languages, field + FROM bounties WHERE bounty_id = ?; + """, + (bounty_id, ) + ).fetchall() + if not bounties: + raise KeyError(f"{bounty_id} does not exist!") + bounty = bounties[0] + return Bounty( + bounty_id=bounty_id, + user_id=bounty[0], + title=bounty[1], + description=bounty[2], + languages=bounty[3], + field=bounty[4], + ) + + # Get random bounties + @classmethod + def get_randomized_bounty_list(cls): + responses = cls.cursor.execute( + f""" + SELECT bounty_id, user_id, title, description, languages, field FROM bounties ORDER BY RANDOM(); + """ + ).fetchall() + bounties: list[Bounty] = [] + for response in responses: + bounties.append(Bounty( + bounty_id=response[0], + user_id=response[1], + title=response[2], + description=response[3], + languages=response[4], + field=response[5], + )) + return bounties + + # Rate + @classmethod + def rate(cls, bounty_id: int, user_id: int, rating: int) -> None: + cls.cursor.execute( + f""" + INSERT INTO bounty_ratings(bounty_id, user_id, rating) + values(?, ?, ?) + """, + (bounty_id, user_id, rating) + ) + cls.connection.commit() + + # Get Rating + @classmethod + def get_sum_of_ratings(cls, bounty_id: int) -> int: + sum_of_ratings: int = cls.cursor.execute( + f""" + SELECT SUM(rating) + FROM bounty_ratings WHERE bounty_id = ?; + """, + (bounty_id,) + ).fetchall()[0][0] + return sum_of_ratings if sum_of_ratings else 0 + + # Get number of Rating + @classmethod + def get_number_of_ratings(cls, bounty_id: int) -> int: + sum_of_ratings: int = cls.cursor.execute( + f""" + SELECT COUNT() + FROM bounty_ratings WHERE bounty_id = ?; + """, + (bounty_id,) + ).fetchall()[0][0] + return sum_of_ratings if sum_of_ratings else -1 + + # Get rater's Rating + @classmethod + def get_rating_of_user(cls, user_id: int, bounty_id: int) -> int: + sum_of_ratings: int = cls.cursor.execute( + f""" + SELECT SUM(rating) + FROM bounty_ratings WHERE bounty_id = ? AND user_id = ?; + """, + (bounty_id, user_id) + ).fetchall()[0][0] + return sum_of_ratings if sum_of_ratings else -2 diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|docs.txt b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|docs.txt new file mode 100644 index 0000000..40ee66c --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|docs.txt @@ -0,0 +1,140 @@ +Legend: + Form data: + https://developer.mozilla.org/en-US/docs/Web/API/FormData + [something] : this something is optional + + + +---------- USER HANDLING ---------- + +POST : /get-user-by-email + Form data: + email + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : email missing in request + 404 : email not found + +POST : /get-user-by-id + Form data: + id + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : id missing in request + 404 : id not found + +POST : /get-user-by-handle + Form Data: + handle + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : handle missing in request + 404 : handle not found + +POST : /register + Form Data: + email + handle + password + [display_name] + [profile_picture] + Errors: + 422 : something missing in request + 409 : User exists (email or handle) + +POST : /login + Form Data: + email + password + Errors: + 422 : something missing in request + 404 : User does not exist + 409 : Wrong password + Cookies: + sets id + sets session_id + +POST : /logout + Errors: + 401 : Not logged in + Cookies: + deletes session_id + deletes id + +POST : /change-user-profile-picture + Form Data: + image + Errors: + 401 : Not logged in + 403 : Invalid log in + 422 : image is missing + Note: + Must be logged in + +POST : /change-user-display-name + Form Data: + display_name + Errors: + 401 : Not logged in + 403 : Invalid log in + 422 : display_name is missing + Note: + Must be logged in + +GET : /profile-picture/ + Errors: + 404 : Handle not found + + + +---------- BOUNTY HANDLING ---------- + +POST : /create-bounty + Form Data: + title + description + [field] + [language] + Errors: + 422 : something missing in request + 403 : Unknown language/ field + Note: + default language = "any" + default field = "other" + +POST : /get-user-by-id + Form data: + id + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : id missing in request + 404 : id not found + +POST : /create-session-for-bounty-get-random + Cookies: + sets bounty_session_id + +POST : /bounty-get-random + Form data: + page_number + Note: + /create-session-for-bounty-get-random must be called before this + Errors: + 422 : page no. missing in request + 404 : /create-session-for-bounty-get-random not called diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|bountyhandler.cpython-312.pyc b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|bountyhandler.cpython-312.pyc new file mode 100644 index 0000000..094146e Binary files /dev/null and b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|bountyhandler.cpython-312.pyc differ diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|restapi.cpython-312.pyc b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|restapi.cpython-312.pyc new file mode 100644 index 0000000..5460ab3 Binary files /dev/null and b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|restapi.cpython-312.pyc differ diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|solutionhandler.cpython-312.pyc b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|solutionhandler.cpython-312.pyc new file mode 100644 index 0000000..932749d Binary files /dev/null and b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|solutionhandler.cpython-312.pyc differ diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|userhandler.cpython-312.pyc b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|userhandler.cpython-312.pyc new file mode 100644 index 0000000..4b87815 Binary files /dev/null and b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|pycache|userhandler.cpython-312.pyc differ diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|restapi.py b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|restapi.py new file mode 100644 index 0000000..558f8d7 --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|restapi.py @@ -0,0 +1,419 @@ +import json +import flask +import sqlite3 +from werkzeug.datastructures.file_storage import FileStorage +from solution_handler import SolutionHandler +from user_handler import UserHandler, User +from bounty_handler import BountyHandler, Bounty + + +app = flask.Flask(__name__) +app.secret_key = "few`3r9i3r" +app.config['SESSION_TYPE'] = 'filesystem' + +@app.get("/") +def a(): + return flask.send_file("../assets/apifiletest/index.html") +# ---------- USER ---------- + +@app.get("/index.js") +def an(): + return flask.send_file("../assets/apifiletest/index.js") +#@app.post("/get-user-by-email") +#def get_user_by_email() -> flask.Response: +# request_data = dict(flask.request.form) +# if "email" not in request_data: +# return flask.Response("email or password missing", status=422) +# try: +# user = UserHandler.get_user_by_email( +# request_data["email"], +# ) +# except KeyError: +# return flask.Response("User does not exist", status=404) +# return flask.Response( +# response=json.dumps({ +# "handle": user.handle, +# "display_name": user.display_name, +# }), +# status=200, +# mimetype='application/json' +# ) +# +# +@app.post("/get-user-by-id") +def get_user_by_id() -> flask.Response: + request_data = dict(flask.request.form) + try: + user_id = int(request_data["user_id"]) + except KeyError: + return flask.Response("user_id missing", status=422) + except ValueError: + return flask.Response("user_id wasn't int", status=422) + try: + user = UserHandler.get_user_by_id(user_id) + except KeyError: + return flask.Response("User does not exist", status=404) + return flask.Response( + response=json.dumps({ + "handle": user.handle, + "display_name": user.display_name, + }), + status=200, + mimetype='application/json' + ) + + +@app.post("/get-user-by-handle") +def get_user_by_handle() -> flask.Response: + request_data = dict(flask.request.form) + try: + handle = request_data["handle"] + except KeyError: + return flask.Response("handle or password missing", status=422) + try: + user = UserHandler.get_user_by_handle(handle) + except KeyError: + return flask.Response("User does not exist", status=404) + return flask.Response( + response=json.dumps({ + "handle": user.handle, + "display_name": user.display_name, + }), + status=200, + mimetype='application/json' + ) + + +@app.post("/register") +def register() -> flask.Response: + request_data = dict(flask.request.form) + try: + email = request_data["email"] + handle = request_data["handle"] + password = request_data["password"] + except KeyError: + return flask.Response("email, handle or password missing", status=422) + if "display_name" not in request_data: + request_data["display_name"] = request_data["handle"] + try: + UserHandler.create_user( + User( + -1, + email, + request_data["handle"], + request_data["password"], + request_data["display_name"], + ) + ) + except sqlite3.IntegrityError as error: + if "handle" in str(error): + return flask.Response("Handle exists", status=409) + else: + return flask.Response("Email exists", status=409) + if "profile_picture" in flask.request.files: + flask.request.files["profile_picture"].save(f"../assets/profile_pictures/{request_data['handle']}") + return flask.Response(status=200) + + +@app.post("/validate-login") +def validate_login(): + if "user_id" not in flask.session: + return flask.Response("Authentication error", status=401) + if not isinstance(flask.session["user_id"], int): + return flask.Response("Authentication error", status=401) + return flask.Response(status=200) + + +@app.post("/login") +def login() -> flask.Response: + request_data = dict(flask.request.form) + try: + email = request_data["email"] + password = request_data["password"] + except KeyError: + return flask.Response("email or password missing", status=422) + try: + assert UserHandler.verify_password_by_email(email, password) + except KeyError: + return flask.Response("User does not exist", status=404) + except AssertionError: + return flask.Response("Wrong password", status=409) + user = UserHandler.get_user_by_email(request_data["email"]) + flask.session["user_id"] = user.user_id + return flask.Response(status=200) + + +@app.post("/logout") +def logout() -> flask.Response: + if "user_id" not in flask.session: + return flask.Response("Authentication error", status=401) + flask.session.pop("user_id") + return flask.Response(status=200) + + +@app.post("/change-user-profile-picture") +def change_user_profile_picture() -> flask.Response: + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + + try: + image = flask.request.files["image"] + except KeyError: + return flask.Response("image is missing", status=422) + image_name = UserHandler.get_user_by_id(flask.session["user_id"]).handle + image.save( + f"../assets/profile_pictures/{image_name}" + ) + return flask.Response(status=200) + + +@app.post("/change-user-display-name") +def change_user_display_name(): + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + request_data = dict(flask.request.form) + if "display_name" not in request_data: + return flask.Response("display_name is missing", status=422) + UserHandler.change_display_name(flask.session["user_id"], request_data["display_name"]) + return flask.Response(status=200) + + +@app.get("/profile-picture/") +def get_profile_picture(handle: str) -> flask.Response: + try: + return flask.send_file(f"../assets/profile_pictures/{handle}") + except FileNotFoundError: + return flask.Response(f"{handle} does not exist", 404) + + +# ---------- BOUNTY ---------- +@app.post("/create-bounty") +def create_bounty() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + if "title" not in request_data or \ + "description" not in request_data: + return flask.Response("title or description is missing", status=422) + if "field" not in request_data: + request_data["field"] = "other" + if "languages" not in request_data: + request_data["language"] = "any" + try: + BountyHandler.create_bounty(Bounty( + -1, + flask.session["user_id"], + request_data["title"], + request_data["description"], + languages=request_data["languages"], + field=request_data["field"] + )) + return flask.Response(status=200) + except AssertionError: + return flask.Response("Unknown language/ field", status=403) + + +@app.post("/get-bounty-by-id") +def bounty_get_by_id() -> flask.Response: + login_validation = validate_login() + user_id = -1 + if login_validation.status_code == 200: + user_id = flask.session["user_id"] + request_data = dict(flask.request.form) + if "bounty_id" not in request_data: + return flask.Response("bounty_id is missing", status=422) + try: + bounty_id = int(request_data["bounty_id"]) + except ValueError: + return flask.Response("user_id wasn't int", status=422) + try: + bounty = BountyHandler.get_bounty_by_id(bounty_id) + except KeyError: + return flask.Response("Doesn't exist", status=404) + number_of_ratings: int = BountyHandler.get_number_of_ratings(bounty_id) + return flask.Response( + response=json.dumps({ + "title": bounty.title, + "description": bounty.description, + "languages": bounty.languages, + "field": bounty.field, + "average_rating": BountyHandler.get_sum_of_ratings(bounty_id) / number_of_ratings, + "number_of_ratings": number_of_ratings, + "users_rating": BountyHandler.get_rating_of_user(user_id, bounty.bounty_id), + }), + status=200, + mimetype='application/json' + ) + + +@app.post("/create-session-for-bounty-get-random") +def create_session_for_bounty_get_random() -> flask.Response: + login_validation = validate_login() + rater_id = -1 + if login_validation.status_code == 200: + rater_id = flask.session["user_id"] + bounties = BountyHandler.get_randomized_bounty_list() + bounties = [ + { + "id": bounty.bounty_id, + "creator_identifie": bounty.user_id, + "title": bounty.title, + "description": bounty.description, + "languages": bounty.languages, + "field": bounty.field, + "average_rating": BountyHandler.get_sum_of_ratings(bounty.bounty_id) /\ + BountyHandler.get_number_of_ratings(bounty.bounty_id), + "number_of_ratings": BountyHandler.get_number_of_ratings(bounty.bounty_id), + "users_rating": BountyHandler.get_rating_of_user(rater_id, bounty.bounty_id), + } for bounty in bounties + ] + flask.session["bounties"] = bounties + response = flask.Response(status=200) + return response + + +@app.post("/bounty-get-random") +def bounty_get_random() -> flask.Response: + request_data = dict(flask.request.form) + if "bounties" not in flask.session: + return flask.Response("/create-session-for-bounty-get-random not called", status=401) + try: + page_number = int(request_data["page_number"]) + except KeyError: + return flask.Response("page_number is missing", status=422) + except ValueError: + return flask.Response("page_number wasn't int", status=422) + return flask.Response( + response=json.dumps( + flask.session["bounties"][page_number * 20 : (page_number + 1) * 20] + ), + status=200, + mimetype='application/json' + ) + + +@app.post("/bounty-rate") +def bounty_rate() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + BountyHandler.rate( + int(request_data["bounty_id"]), + flask.session["user_id"], + int(request_data["rating"]), + ) + except sqlite3.IntegrityError: + return flask.Response("Rating gotta be between 1 and 10", status=403) + except ValueError: + return flask.Response("bounty_id or rating wasn't int", status=422) + except KeyError: + return flask.Response("bounty_id or rating missing", status=422) + return flask.Response(status=200) + + +# ---------- SOLUTIONS ---------- +@app.post("/test") +def test(): + files = flask.request.files.getlist("files") + for file in files: + if file.filename: + file.filename = file.filename.replace("/", r"|") + file.save(f"../assets/{file.filename}") + else: + return flask.Response(status=400) + return flask.Response() + + +@app.post("/submit-solution") +def submit_answer() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + user_id = flask.session["user_id"] + try: + bounty_id = int(request_data["bounty_id"]) + except KeyError: + return flask.Response("Bounty id missing", status=422) + except ValueError: + return flask.Response("bounty_id wasn't int", status=422) + + files : list[FileStorage]= flask.request.files.getlist("files") + if not files: + return flask.Response("files missing", status=422) + SolutionHandler.submit_solution(files, bounty_id, user_id) + return flask.Response() + + +@app.post("/get-solution-by-id") +def get_solution_by_id() -> flask.Response: + request_data = dict(flask.request.form) + try: + solution_id = request_data["solution_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + try: + return flask.jsonify(SolutionHandler.get_solution_by_id(solution_id)) + except KeyError: + return flask.Response("Solution doesn't exist", status=404) + + +@app.post("/get-solution-list") +def get_solution_list() -> flask.Response: + request_data = dict(flask.request.form) + try: + solution_id = int(request_data["bounty_id"]) + except KeyError: + return flask.Response("Bounty id missing", status=422) + except ValueError: + return flask.Response("bounty_id wasn't int", status=422) + return flask.jsonify(SolutionHandler.get_solution_list(solution_id)) + + +@app.post("/like-solution") +def like_solution() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + solution_id = request_data["solution_id"] + user_id = flask.session["user_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + SolutionHandler.like(solution_id=solution_id, user_id=user_id) + return flask.Response(status=200) + + +@app.post("/unlike-solution") +def unlike_solution() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + solution_id = request_data["solution_id"] + user_id = flask.session["user_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + SolutionHandler.unlike(solution_id=solution_id, user_id=user_id) + return flask.Response(status=200) + + +@app.post("/has-user-liked") +def has_user_liked() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + solution_id = request_data["solution_id"] + user_id = flask.session["user_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + return flask.jsonify(SolutionHandler.has_user_liked(user_id=user_id, solution_id=solution_id)) diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|solutionhandler.py b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|solutionhandler.py new file mode 100644 index 0000000..d608d90 --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|solutionhandler.py @@ -0,0 +1,116 @@ +import os +import secrets +import sqlite3 +from user_handler import UserHandler +from werkzeug.datastructures.file_storage import FileStorage +import re + + +class SolutionHandler: + connection: sqlite3.Connection = sqlite3.connect("../data/database.db", check_same_thread=False) + cursor: sqlite3.Cursor = connection.cursor() + + # Save solution + @classmethod + def submit_solution(cls, files: list[FileStorage], bounty_identifier: int, creator_identifier: int): + solution_id = secrets.token_urlsafe(40) + os.mkdir(f"../assets/solutions/{solution_id}/") + for file in files: + print(file.filename) + if not file.filename: + file.filename = secrets.token_urlsafe(5) + file.filename = "".join(re.findall("[a-zA-Z 0-9-./]", file.filename)) + file.filename = file.filename.replace("/", r"|") + file.save(f"../assets/solutions/{solution_id}/{file.filename}") + + cls.cursor.execute( + f""" + INSERT INTO solutions(user_id, bounty_id, solution_id) + values(?, ?, ?) + """, + (creator_identifier, bounty_identifier, solution_id) + ) + cls.connection.commit() + + # Get solution by id + @staticmethod + def get_solution_by_id(solution_id: str): + path = f"../assets/solutions/{solution_id}/" + if not os.path.isdir(path): + raise KeyError("Solution doesn't exist") + formated_solutions: dict[str, str | dict] = {} + file_paths = os.listdir(path) + for file_path in file_paths: + current_folder = formated_solutions + for folder in file_path.split("|")[:-1]: + print(folder) + current_folder[folder] = {} + current_folder: dict = current_folder[folder] + file_name = file_path.split("|")[-1] + try: + with open(path + file_path, "r") as file: + current_folder[file_name] = file.read() + except UnicodeDecodeError: + current_folder[file_name] = "" + + return formated_solutions + + + @classmethod + def get_solution_list(cls, identifier: int) -> list[dict]: + solutions = cls.cursor.execute( + f""" + SELECT user_id, solution_id FROM solutions + WHERE bounty_id = ?; + """, + (identifier, ) + ).fetchall() + return [ + { + "user_id": solution[0], + "creator_display_name": UserHandler.get_display_name(solution[0]), + "solution_id": solution[1], + "likes": cls.get_likes(solution[1]) + } for solution in solutions + ] + + @classmethod + def like(cls, solution_id: int, user_id: int): + cls.cursor.execute( + f""" + INSERT INTO solution_likes(user_id, solution_id) values(?, ?) + """, + (user_id, solution_id) + ) + cls.connection.commit() + + @classmethod + def unlike(cls, solution_id: int, user_id: int): + cls.cursor.execute( + f""" + DELETE FROM solution_likes WHERE solution_id = ? AND user_id = ? + """, + (solution_id, user_id) + ) + cls.connection.commit() + + @classmethod + def get_likes(cls, solution_id: int) -> int: + likes = cls.cursor.execute( + f""" + SELECT COUNT(*) FROM solution_likes WHERE solution_id = ? + """, + (solution_id, ) + ).fetchall()[0] + return likes[0] if likes else 0 + + @classmethod + def has_user_liked(cls, solution_id: int, user_id: int) -> bool: + + likes = cls.cursor.execute( + f""" + SELECT 1 FROM solution_likes WHERE solution_id = ? + """, + (solution_id, ) + ).fetchall() + return bool(likes) diff --git a/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|userhandler.py b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|userhandler.py new file mode 100644 index 0000000..11d4580 --- /dev/null +++ b/assets/solutions/585Sh-FFkzoYGkAlCbtHQn7VdH6vzPHGmGF6MKm31gdlFeoMBbQuVA/src|userhandler.py @@ -0,0 +1,154 @@ +import bcrypt +import shutil +from dataclasses import dataclass +import sqlite3 + + +@dataclass +class User: + user_id: int + email: str + handle: str + password: str + display_name: str + + def __init__( + self, + user_id: int, + email: str, + handle: str, + password: str = "", + display_name: str = "", + ) -> None: + self.user_id: int = user_id + self.handle = handle + self.password = password + self.display_name = display_name if display_name else handle + self.email = email + + +class UserHandler: + connection: sqlite3.Connection = sqlite3.connect("../data/database.db", check_same_thread=False) + cursor: sqlite3.Cursor = connection.cursor() + + @classmethod + def create_user( + cls, + user: User + ) -> None: + salt = bcrypt.gensalt() + encrypted_password = bcrypt.hashpw(user.password.encode("utf-8"), salt) + cls.cursor.execute( + f""" + INSERT INTO users(handle, display_name, email, password) + values(?, ?, ?, ?) + """, + (user.handle, user.display_name, user.email, encrypted_password) + ) + cls.connection.commit() + shutil.copy( + "../assets/profile_pictures/default_profile_picture.jpg", + f"../assets/profile_pictures/{user.handle}" + ) + + @classmethod + def get_user_by_id(cls, user_id: int) -> User: + users = cls.cursor.execute( + f""" + SELECT handle, display_name + FROM users WHERE user_id = ?; + """, + (user_id, ) + ).fetchall() + if not users: + raise KeyError(f"{user_id} does not exist!") + user = users[0] + return User( + user_id=user_id, + handle=user[0], + display_name=user[1], + email="", + password="" + ) + + @classmethod + def get_user_by_handle(cls, handle: str) -> User: + users = cls.cursor.execute( + f""" + SELECT user_id, handle, display_name + FROM users WHERE handle = ?; + """, + (handle, ) + ).fetchall() + if not users: + raise KeyError(f"{handle} does not exist!") + users = users[0] + return User( + user_id=users[0], + handle=users[1], + display_name=users[2], + email="", + password="" + ) + + @classmethod + def get_user_by_email(cls, email: str) -> User: + users = cls.cursor.execute( + f""" + SELECT user_id, handle, display_name + FROM users WHERE email = ?; + """, + (email, ) + ).fetchall() + if not users: + raise KeyError(f"{email} does not exist!") + users = users[0] + return User( + user_id=users[0], + handle=users[1], + display_name=users[2], + email=email, + password="", + ) + + #@classmethod + #def verify_password_by_handle(cls, handle: str, password: str) -> bool: + # users = cls.cursor.execute( + # f""" + # SELECT password + # FROM users WHERE handle = ?; + # """, + # (handle, ) + # ).fetchall() + # if not users: + # raise KeyError(f"{handle} does not exist!") + # real_password = users[0][0] + # return bcrypt.checkpw(password.encode("utf-8"), real_password) + + @classmethod + def verify_password_by_email(cls, email: str, password: str) -> bool: + users = cls.cursor.execute( + f""" + SELECT password + FROM users WHERE email = ?; + """, + (email, ) + ).fetchall() + if not users: + raise KeyError(f"{email} does not exist!") + return bcrypt.checkpw(password.encode("utf-8"), users[0][0]) + + @classmethod + def change_display_name(cls, user_id: int, display_name: str): + cls.cursor.execute( + f""" + UPDATE users + SET display_name = ? + WHERE user_id = ?; + """, + (display_name, user_id) + ).fetchall() + + @classmethod + def get_display_name(cls, user_id: int) -> str: + return cls.get_user_by_id(user_id).display_name diff --git a/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|123.txt b/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|234.txt b/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2 - Copy|123.txt b/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2 - Copy|234.txt b/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2|123.txt b/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2|234.txt b/assets/solutions/G83-qgjo7wUWrwhrteC80Li-TgCSIGcJ9Rr8BG79-l_IpuKRAhKPSw/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|123.txt b/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|234.txt b/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2 - Copy|123.txt b/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2 - Copy|234.txt b/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2|123.txt b/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2|234.txt b/assets/solutions/Sd9PQMmpSXRVx3DzQhp0eblDPW57j0IkITDZUgx3No42Bw5-L3EvNw/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|123.txt b/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|234.txt b/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2 - Copy|123.txt b/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2 - Copy|234.txt b/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2|123.txt b/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2|234.txt b/assets/solutions/_WXL2tXN5gJcCu6DZxCkeAXbl3Zwjz1zGBeAnJIFozIOSuxgBzu_5Q/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|123.txt b/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|234.txt b/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2 - Copy|123.txt b/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2 - Copy|234.txt b/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2|123.txt b/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2|234.txt b/assets/solutions/eC34Fk-XnKyyrdFckBIfWEDhOfwObFNgHXVAe4TcPMXLbRZgYq2J3A/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|123.txt b/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|234.txt b/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2 - Copy|123.txt b/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2 - Copy|234.txt b/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2|123.txt b/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2|234.txt b/assets/solutions/fgISg49wTzCklFb8U3JsgziRXvABnvEyPwqPfnFh3f8i4jL5_s1yvQ/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|123.txt b/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|234.txt b/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2 - Copy|123.txt b/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2 - Copy|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2 - Copy|234.txt b/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2 - Copy|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2|123.txt b/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2|123.txt new file mode 100644 index 0000000..e69de29 diff --git a/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2|234.txt b/assets/solutions/hNKzTH0__gD_PYqT2KM0BV2HsjmZO5NUsrNF-F8wvrVoA0rStjU6Cw/test|test2|234.txt new file mode 100644 index 0000000..e69de29 diff --git a/data/database.db b/data/database.db new file mode 100644 index 0000000..07c7b4e Binary files /dev/null and b/data/database.db differ diff --git a/src/.jukit/.jukit_info.json b/src/.jukit/.jukit_info.json new file mode 100644 index 0000000..c3bfc23 --- /dev/null +++ b/src/.jukit/.jukit_info.json @@ -0,0 +1 @@ +{"cmd": "import os\nimport secrets\nimport sqlite3\nfrom user_handler import UserHandler\nfrom werkzeug.datastructures.file_storage import FileStorage\n\n\nclass SolutionHandler:\n connection: sqlite3.Connection = sqlite3.connect(\"../data/database.db\", check_same_thread=False)\n cursor: sqlite3.Cursor = connection.cursor()\n\n # Save solution\n @classmethod\n def submit_solution(cls, files: list[FileStorage], bounty_identifier: int, creator_identifier: int):\n solution_id = secrets.token_urlsafe(40)\n for file in files:\n if not file.filename:\n file.filename = secrets.token_urlsafe(5)\n file.filename = file.filename.replace(\"/\", r\"|\")\n os.mkdir(f\"../assets/solutions/{solution_id}\")\n file.save(f\"../assets/solutions/{solution_id}/{file.filename}\")\n\n cls.cursor.execute(\n f\"\"\"\n INSERT INTO bounty_solutions(creator_identifier, bounty_identifier, identifier)\n values(?, ?, ?)\n \"\"\",\n (creator_identifier, bounty_identifier, solution_id)\n )\n cls.connection.commit()\n\n @staticmethod\n def __format_solution(paths: list[str]):\n pass\n\n # Get solution by id\n @staticmethod\n def get_solution_by_id(solution_id: str):\n path = f\"../assets/solutions/{solution_id}/\"\n if not os.path.isdir(path):\n raise KeyError(\"Solution doesn't exist\")\n formated_solutions: dict[str, str | dict] = {}\n file_paths = os.listdir(path)\n for file_path in file_paths:\n current_folder = formated_solutions\n for folder in file_path.split(\"|\")[:-1]:\n current_folder[folder] = {}\n current_folder = current_folder[folder]\n file_name = file_path.split(\"|\")[-1]\n try:\n with open(path + file_path, \"r\") as file:\n current_folder[file_name] = file.read()\n except UnicodeDecodeError:\n current_folder[file_name] = \"\"\n\n print(formated_solutions)\n \n\n @classmethod\n def get_solution_list(cls, identifier: int) -> list[dict]:\n solutions = cls.cursor.execute(\n f\"\"\"\n SELECT creator_identifier, identifier FROM bounty_solutions\n WHERE bounty_identifier = ?;\n \"\"\",\n (identifier, )\n ).fetchall()\n return [\n {\n \"creator_id\": solution[0],\n \"creator_display_name\": UserHandler.get_display_name(solution[0]),\n \"solution_id\": solution[1],\n \"likes\": cls.get_likes(solution[1])\n } for solution in solutions\n ]\n\n @classmethod\n def like(cls, solution_id: int, user_id: int):\n cls.cursor.execute(\n f\"\"\"\n INSERT INTO solution_likes(user_id, solution_id) values(?, ?)\n \"\"\",\n (user_id, solution_id)\n )\n cls.connection.commit()\n\n @classmethod\n def unlike(cls, solution_id: int, user_id: int):\n cls.cursor.execute(\n f\"\"\"\n DELETE FROM solution_likes WHERE solution_id = ? AND user_id = ?\n \"\"\",\n (solution_id, user_id)\n )\n cls.connection.commit()\n\n @classmethod\n def get_likes(cls, solution_id: int) -> int:\n likes = cls.cursor.execute(\n f\"\"\"\n SELECT COUNT(*) FROM solution_likes WHERE solution_id = ?\n \"\"\",\n (solution_id, )\n ).fetchall()[0]\n return likes[0] if likes else 0\n\n @classmethod\n def has_user_liked(cls, solution_id: int, user_id: int) -> bool:\n\n likes = cls.cursor.execute(\n f\"\"\"\n SELECT 1 FROM solution_likes WHERE solution_id = ?\n \"\"\",\n (solution_id, )\n ).fetchall()\n return bool(likes)\n\nSolutionHandler.get_solution_by_id(\"NvFlP3tsfJbYDTAOD5dHt4hEp2rLlsVEYizWhC39WwstnJWDosPKyQ\")", "cmd_opts": " --cell_id=NONE -s", "import_complete": 1, "terminal": "nvimterm"} \ No newline at end of file diff --git a/src/.jukit/bounty_handler_outhist.json b/src/.jukit/bounty_handler_outhist.json new file mode 100644 index 0000000..f54de93 --- /dev/null +++ b/src/.jukit/bounty_handler_outhist.json @@ -0,0 +1 @@ +{"LsrWtuhk5A": null} \ No newline at end of file diff --git a/src/.jukit/rest_api_outhist.json b/src/.jukit/rest_api_outhist.json new file mode 100644 index 0000000..3a3530f --- /dev/null +++ b/src/.jukit/rest_api_outhist.json @@ -0,0 +1 @@ +{"NONE": [{"output_type": "stream", "name": "stdout", "text": "{'GTXN1Una4AAvXHl.webp': '', 'test1': {'fdf.webp': 'Sfjdifjodi\\n'}, 'test2': {'test3': {'safdf.webp': 'fjidofjidfji\\n'}}}\n"}]} \ No newline at end of file diff --git a/src/.jukit/user_handler_outhist.json b/src/.jukit/user_handler_outhist.json new file mode 100644 index 0000000..7c8b470 --- /dev/null +++ b/src/.jukit/user_handler_outhist.json @@ -0,0 +1 @@ +{"NONE": [{"output_type": "stream", "name": "stdout", "text": "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mIntegrityError\u001b[0m Traceback (most recent call last)\nCell \u001b[0;32mIn[1], line 127\u001b[0m\n\u001b[1;32m 117\u001b[0m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39mget_user_by_handle(handle)\n\u001b[1;32m 118\u001b[0m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39mcursor\u001b[38;5;241m.\u001b[39mexecute(\n\u001b[1;32m 119\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\"\"\u001b[39m\n\u001b[1;32m 120\u001b[0m \u001b[38;5;124m UPDATE users\u001b[39m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 124\u001b[0m (display_name, handle )\n\u001b[1;32m 125\u001b[0m )\u001b[38;5;241m.\u001b[39mfetchall()\n\u001b[0;32m--> 127\u001b[0m \u001b[43mUserHandler\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcreate_user\u001b[49m\u001b[43m(\u001b[49m\u001b[43mUser\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkos\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkos\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkos\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkosh\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 128\u001b[0m UserHandler\u001b[38;5;241m.\u001b[39mchange_display_name(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkos\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkosh\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\nCell \u001b[0;32mIn[1], line 38\u001b[0m, in \u001b[0;36mUserHandler.create_user\u001b[0;34m(cls, user)\u001b[0m\n\u001b[1;32m 36\u001b[0m salt \u001b[38;5;241m=\u001b[39m bcrypt\u001b[38;5;241m.\u001b[39mgensalt()\n\u001b[1;32m 37\u001b[0m encrypted_password \u001b[38;5;241m=\u001b[39m bcrypt\u001b[38;5;241m.\u001b[39mhashpw(user\u001b[38;5;241m.\u001b[39mpassword\u001b[38;5;241m.\u001b[39mencode(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m\"\u001b[39m), salt)\n\u001b[0;32m---> 38\u001b[0m \u001b[38;5;28;43mcls\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcursor\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexecute\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 39\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\"\"\u001b[39;49m\n\u001b[1;32m 40\u001b[0m \u001b[38;5;124;43m INSERT INTO users(handle, display_name, email, password)\u001b[39;49m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;124;43m values(?, ?, ?, ?)\u001b[39;49m\n\u001b[1;32m 42\u001b[0m \u001b[38;5;124;43m \u001b[39;49m\u001b[38;5;124;43m\"\"\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 43\u001b[0m \u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[43muser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mhandle\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdisplay_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43muser\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43memail\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencrypted_password\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 44\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 45\u001b[0m shutil\u001b[38;5;241m.\u001b[39mcopy(\n\u001b[1;32m 46\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m../assets/profile_pictures/default_profile_picture.jpg\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 47\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m../assets/profile_pictures/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00muser\u001b[38;5;241m.\u001b[39mhandle\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 48\u001b[0m )\n\u001b[1;32m 49\u001b[0m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39mconnection\u001b[38;5;241m.\u001b[39mcommit()\n\n\u001b[0;31mIntegrityError\u001b[0m: UNIQUE constraint failed: users.handle\n"}]} \ No newline at end of file diff --git a/src/__pycache__/bounty_handler.cpython-312.pyc b/src/__pycache__/bounty_handler.cpython-312.pyc new file mode 100644 index 0000000..094146e Binary files /dev/null and b/src/__pycache__/bounty_handler.cpython-312.pyc differ diff --git a/src/__pycache__/rest_api.cpython-312.pyc b/src/__pycache__/rest_api.cpython-312.pyc new file mode 100644 index 0000000..5460ab3 Binary files /dev/null and b/src/__pycache__/rest_api.cpython-312.pyc differ diff --git a/src/__pycache__/solution_handler.cpython-312.pyc b/src/__pycache__/solution_handler.cpython-312.pyc new file mode 100644 index 0000000..e053055 Binary files /dev/null and b/src/__pycache__/solution_handler.cpython-312.pyc differ diff --git a/src/__pycache__/user_handler.cpython-312.pyc b/src/__pycache__/user_handler.cpython-312.pyc new file mode 100644 index 0000000..4b87815 Binary files /dev/null and b/src/__pycache__/user_handler.cpython-312.pyc differ diff --git a/src/bounty_handler.py b/src/bounty_handler.py new file mode 100644 index 0000000..1503442 --- /dev/null +++ b/src/bounty_handler.py @@ -0,0 +1,147 @@ +from dataclasses import dataclass +import sqlite3 + + +@dataclass +class Bounty: + bounty_id: int + user_id: int + title: str + description: str + languages: str + field: str + + def __init__( + self, + bounty_id: int, + user_id: int, + title: str, + description: str, + languages: str, + field: str, + ) -> None: + self.bounty_id = bounty_id + self.user_id = user_id + self.title = title + self.description = description + self.languages = languages + self.field = field + + +class BountyHandler: + connection: sqlite3.Connection = sqlite3.connect("../data/database.db", check_same_thread=False) + cursor: sqlite3.Cursor = connection.cursor() + + # Create Bounty + @classmethod + def create_bounty( + cls, + bounty: Bounty + ) -> None: + bounty.languages.removeprefix(",") + bounty.languages.removesuffix(",") + print(bounty.languages) + assert set(bounty.languages.split(",")).issubset( + {"c", "cpp", "c#", "css", "go", "html", "java", "js", "python", "ruby", "rust", "other", "any"} + ) + assert set(bounty.field.split(",")).issubset( + {"dsa", "ai", "web", "app", "other"} + ) + cls.cursor.execute( + f""" + INSERT INTO bounties(user_id, title, description, languages, field) + values(?, ?, ?, ?, ?) + """, + (bounty.bounty_id, bounty.title, bounty.description, bounty.languages, bounty.field) + ) + cls.connection.commit() + + # Get Bounty + @classmethod + def get_bounty_by_id(cls, bounty_id: int) -> Bounty: + bounties = cls.cursor.execute( + f""" + SELECT user_id, title, description, languages, field + FROM bounties WHERE bounty_id = ?; + """, + (bounty_id, ) + ).fetchall() + if not bounties: + raise KeyError(f"{bounty_id} does not exist!") + bounty = bounties[0] + return Bounty( + bounty_id=bounty_id, + user_id=bounty[0], + title=bounty[1], + description=bounty[2], + languages=bounty[3], + field=bounty[4], + ) + + # Get random bounties + @classmethod + def get_randomized_bounty_list(cls): + responses = cls.cursor.execute( + f""" + SELECT bounty_id, user_id, title, description, languages, field FROM bounties ORDER BY RANDOM(); + """ + ).fetchall() + bounties: list[Bounty] = [] + for response in responses: + bounties.append(Bounty( + bounty_id=response[0], + user_id=response[1], + title=response[2], + description=response[3], + languages=response[4], + field=response[5], + )) + return bounties + + # Rate + @classmethod + def rate(cls, bounty_id: int, user_id: int, rating: int) -> None: + cls.cursor.execute( + f""" + INSERT INTO bounty_ratings(bounty_id, user_id, rating) + values(?, ?, ?) + """, + (bounty_id, user_id, rating) + ) + cls.connection.commit() + + # Get Rating + @classmethod + def get_sum_of_ratings(cls, bounty_id: int) -> int: + sum_of_ratings: int = cls.cursor.execute( + f""" + SELECT SUM(rating) + FROM bounty_ratings WHERE bounty_id = ?; + """, + (bounty_id,) + ).fetchall()[0][0] + return sum_of_ratings if sum_of_ratings else 0 + + # Get number of Rating + @classmethod + def get_number_of_ratings(cls, bounty_id: int) -> int: + sum_of_ratings: int = cls.cursor.execute( + f""" + SELECT COUNT() + FROM bounty_ratings WHERE bounty_id = ?; + """, + (bounty_id,) + ).fetchall()[0][0] + return sum_of_ratings if sum_of_ratings else -1 + + # Get rater's Rating + @classmethod + def get_rating_of_user(cls, user_id: int, bounty_id: int) -> int: + sum_of_ratings: int = cls.cursor.execute( + f""" + SELECT SUM(rating) + FROM bounty_ratings WHERE bounty_id = ? AND user_id = ?; + """, + (bounty_id, user_id) + ).fetchall()[0][0] + return sum_of_ratings if sum_of_ratings else -2 diff --git a/src/docs.txt b/src/docs.txt new file mode 100644 index 0000000..40ee66c --- /dev/null +++ b/src/docs.txt @@ -0,0 +1,140 @@ +Legend: + Form data: + https://developer.mozilla.org/en-US/docs/Web/API/FormData + [something] : this something is optional + + + +---------- USER HANDLING ---------- + +POST : /get-user-by-email + Form data: + email + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : email missing in request + 404 : email not found + +POST : /get-user-by-id + Form data: + id + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : id missing in request + 404 : id not found + +POST : /get-user-by-handle + Form Data: + handle + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : handle missing in request + 404 : handle not found + +POST : /register + Form Data: + email + handle + password + [display_name] + [profile_picture] + Errors: + 422 : something missing in request + 409 : User exists (email or handle) + +POST : /login + Form Data: + email + password + Errors: + 422 : something missing in request + 404 : User does not exist + 409 : Wrong password + Cookies: + sets id + sets session_id + +POST : /logout + Errors: + 401 : Not logged in + Cookies: + deletes session_id + deletes id + +POST : /change-user-profile-picture + Form Data: + image + Errors: + 401 : Not logged in + 403 : Invalid log in + 422 : image is missing + Note: + Must be logged in + +POST : /change-user-display-name + Form Data: + display_name + Errors: + 401 : Not logged in + 403 : Invalid log in + 422 : display_name is missing + Note: + Must be logged in + +GET : /profile-picture/ + Errors: + 404 : Handle not found + + + +---------- BOUNTY HANDLING ---------- + +POST : /create-bounty + Form Data: + title + description + [field] + [language] + Errors: + 422 : something missing in request + 403 : Unknown language/ field + Note: + default language = "any" + default field = "other" + +POST : /get-user-by-id + Form data: + id + return type: + { + "handle": "~", + "display_name": "~" + } + Errors: + 422 : id missing in request + 404 : id not found + +POST : /create-session-for-bounty-get-random + Cookies: + sets bounty_session_id + +POST : /bounty-get-random + Form data: + page_number + Note: + /create-session-for-bounty-get-random must be called before this + Errors: + 422 : page no. missing in request + 404 : /create-session-for-bounty-get-random not called diff --git a/src/rest_api.py b/src/rest_api.py new file mode 100644 index 0000000..558f8d7 --- /dev/null +++ b/src/rest_api.py @@ -0,0 +1,419 @@ +import json +import flask +import sqlite3 +from werkzeug.datastructures.file_storage import FileStorage +from solution_handler import SolutionHandler +from user_handler import UserHandler, User +from bounty_handler import BountyHandler, Bounty + + +app = flask.Flask(__name__) +app.secret_key = "few`3r9i3r" +app.config['SESSION_TYPE'] = 'filesystem' + +@app.get("/") +def a(): + return flask.send_file("../assets/apifiletest/index.html") +# ---------- USER ---------- + +@app.get("/index.js") +def an(): + return flask.send_file("../assets/apifiletest/index.js") +#@app.post("/get-user-by-email") +#def get_user_by_email() -> flask.Response: +# request_data = dict(flask.request.form) +# if "email" not in request_data: +# return flask.Response("email or password missing", status=422) +# try: +# user = UserHandler.get_user_by_email( +# request_data["email"], +# ) +# except KeyError: +# return flask.Response("User does not exist", status=404) +# return flask.Response( +# response=json.dumps({ +# "handle": user.handle, +# "display_name": user.display_name, +# }), +# status=200, +# mimetype='application/json' +# ) +# +# +@app.post("/get-user-by-id") +def get_user_by_id() -> flask.Response: + request_data = dict(flask.request.form) + try: + user_id = int(request_data["user_id"]) + except KeyError: + return flask.Response("user_id missing", status=422) + except ValueError: + return flask.Response("user_id wasn't int", status=422) + try: + user = UserHandler.get_user_by_id(user_id) + except KeyError: + return flask.Response("User does not exist", status=404) + return flask.Response( + response=json.dumps({ + "handle": user.handle, + "display_name": user.display_name, + }), + status=200, + mimetype='application/json' + ) + + +@app.post("/get-user-by-handle") +def get_user_by_handle() -> flask.Response: + request_data = dict(flask.request.form) + try: + handle = request_data["handle"] + except KeyError: + return flask.Response("handle or password missing", status=422) + try: + user = UserHandler.get_user_by_handle(handle) + except KeyError: + return flask.Response("User does not exist", status=404) + return flask.Response( + response=json.dumps({ + "handle": user.handle, + "display_name": user.display_name, + }), + status=200, + mimetype='application/json' + ) + + +@app.post("/register") +def register() -> flask.Response: + request_data = dict(flask.request.form) + try: + email = request_data["email"] + handle = request_data["handle"] + password = request_data["password"] + except KeyError: + return flask.Response("email, handle or password missing", status=422) + if "display_name" not in request_data: + request_data["display_name"] = request_data["handle"] + try: + UserHandler.create_user( + User( + -1, + email, + request_data["handle"], + request_data["password"], + request_data["display_name"], + ) + ) + except sqlite3.IntegrityError as error: + if "handle" in str(error): + return flask.Response("Handle exists", status=409) + else: + return flask.Response("Email exists", status=409) + if "profile_picture" in flask.request.files: + flask.request.files["profile_picture"].save(f"../assets/profile_pictures/{request_data['handle']}") + return flask.Response(status=200) + + +@app.post("/validate-login") +def validate_login(): + if "user_id" not in flask.session: + return flask.Response("Authentication error", status=401) + if not isinstance(flask.session["user_id"], int): + return flask.Response("Authentication error", status=401) + return flask.Response(status=200) + + +@app.post("/login") +def login() -> flask.Response: + request_data = dict(flask.request.form) + try: + email = request_data["email"] + password = request_data["password"] + except KeyError: + return flask.Response("email or password missing", status=422) + try: + assert UserHandler.verify_password_by_email(email, password) + except KeyError: + return flask.Response("User does not exist", status=404) + except AssertionError: + return flask.Response("Wrong password", status=409) + user = UserHandler.get_user_by_email(request_data["email"]) + flask.session["user_id"] = user.user_id + return flask.Response(status=200) + + +@app.post("/logout") +def logout() -> flask.Response: + if "user_id" not in flask.session: + return flask.Response("Authentication error", status=401) + flask.session.pop("user_id") + return flask.Response(status=200) + + +@app.post("/change-user-profile-picture") +def change_user_profile_picture() -> flask.Response: + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + + try: + image = flask.request.files["image"] + except KeyError: + return flask.Response("image is missing", status=422) + image_name = UserHandler.get_user_by_id(flask.session["user_id"]).handle + image.save( + f"../assets/profile_pictures/{image_name}" + ) + return flask.Response(status=200) + + +@app.post("/change-user-display-name") +def change_user_display_name(): + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + request_data = dict(flask.request.form) + if "display_name" not in request_data: + return flask.Response("display_name is missing", status=422) + UserHandler.change_display_name(flask.session["user_id"], request_data["display_name"]) + return flask.Response(status=200) + + +@app.get("/profile-picture/") +def get_profile_picture(handle: str) -> flask.Response: + try: + return flask.send_file(f"../assets/profile_pictures/{handle}") + except FileNotFoundError: + return flask.Response(f"{handle} does not exist", 404) + + +# ---------- BOUNTY ---------- +@app.post("/create-bounty") +def create_bounty() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + if "title" not in request_data or \ + "description" not in request_data: + return flask.Response("title or description is missing", status=422) + if "field" not in request_data: + request_data["field"] = "other" + if "languages" not in request_data: + request_data["language"] = "any" + try: + BountyHandler.create_bounty(Bounty( + -1, + flask.session["user_id"], + request_data["title"], + request_data["description"], + languages=request_data["languages"], + field=request_data["field"] + )) + return flask.Response(status=200) + except AssertionError: + return flask.Response("Unknown language/ field", status=403) + + +@app.post("/get-bounty-by-id") +def bounty_get_by_id() -> flask.Response: + login_validation = validate_login() + user_id = -1 + if login_validation.status_code == 200: + user_id = flask.session["user_id"] + request_data = dict(flask.request.form) + if "bounty_id" not in request_data: + return flask.Response("bounty_id is missing", status=422) + try: + bounty_id = int(request_data["bounty_id"]) + except ValueError: + return flask.Response("user_id wasn't int", status=422) + try: + bounty = BountyHandler.get_bounty_by_id(bounty_id) + except KeyError: + return flask.Response("Doesn't exist", status=404) + number_of_ratings: int = BountyHandler.get_number_of_ratings(bounty_id) + return flask.Response( + response=json.dumps({ + "title": bounty.title, + "description": bounty.description, + "languages": bounty.languages, + "field": bounty.field, + "average_rating": BountyHandler.get_sum_of_ratings(bounty_id) / number_of_ratings, + "number_of_ratings": number_of_ratings, + "users_rating": BountyHandler.get_rating_of_user(user_id, bounty.bounty_id), + }), + status=200, + mimetype='application/json' + ) + + +@app.post("/create-session-for-bounty-get-random") +def create_session_for_bounty_get_random() -> flask.Response: + login_validation = validate_login() + rater_id = -1 + if login_validation.status_code == 200: + rater_id = flask.session["user_id"] + bounties = BountyHandler.get_randomized_bounty_list() + bounties = [ + { + "id": bounty.bounty_id, + "creator_identifie": bounty.user_id, + "title": bounty.title, + "description": bounty.description, + "languages": bounty.languages, + "field": bounty.field, + "average_rating": BountyHandler.get_sum_of_ratings(bounty.bounty_id) /\ + BountyHandler.get_number_of_ratings(bounty.bounty_id), + "number_of_ratings": BountyHandler.get_number_of_ratings(bounty.bounty_id), + "users_rating": BountyHandler.get_rating_of_user(rater_id, bounty.bounty_id), + } for bounty in bounties + ] + flask.session["bounties"] = bounties + response = flask.Response(status=200) + return response + + +@app.post("/bounty-get-random") +def bounty_get_random() -> flask.Response: + request_data = dict(flask.request.form) + if "bounties" not in flask.session: + return flask.Response("/create-session-for-bounty-get-random not called", status=401) + try: + page_number = int(request_data["page_number"]) + except KeyError: + return flask.Response("page_number is missing", status=422) + except ValueError: + return flask.Response("page_number wasn't int", status=422) + return flask.Response( + response=json.dumps( + flask.session["bounties"][page_number * 20 : (page_number + 1) * 20] + ), + status=200, + mimetype='application/json' + ) + + +@app.post("/bounty-rate") +def bounty_rate() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + BountyHandler.rate( + int(request_data["bounty_id"]), + flask.session["user_id"], + int(request_data["rating"]), + ) + except sqlite3.IntegrityError: + return flask.Response("Rating gotta be between 1 and 10", status=403) + except ValueError: + return flask.Response("bounty_id or rating wasn't int", status=422) + except KeyError: + return flask.Response("bounty_id or rating missing", status=422) + return flask.Response(status=200) + + +# ---------- SOLUTIONS ---------- +@app.post("/test") +def test(): + files = flask.request.files.getlist("files") + for file in files: + if file.filename: + file.filename = file.filename.replace("/", r"|") + file.save(f"../assets/{file.filename}") + else: + return flask.Response(status=400) + return flask.Response() + + +@app.post("/submit-solution") +def submit_answer() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + user_id = flask.session["user_id"] + try: + bounty_id = int(request_data["bounty_id"]) + except KeyError: + return flask.Response("Bounty id missing", status=422) + except ValueError: + return flask.Response("bounty_id wasn't int", status=422) + + files : list[FileStorage]= flask.request.files.getlist("files") + if not files: + return flask.Response("files missing", status=422) + SolutionHandler.submit_solution(files, bounty_id, user_id) + return flask.Response() + + +@app.post("/get-solution-by-id") +def get_solution_by_id() -> flask.Response: + request_data = dict(flask.request.form) + try: + solution_id = request_data["solution_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + try: + return flask.jsonify(SolutionHandler.get_solution_by_id(solution_id)) + except KeyError: + return flask.Response("Solution doesn't exist", status=404) + + +@app.post("/get-solution-list") +def get_solution_list() -> flask.Response: + request_data = dict(flask.request.form) + try: + solution_id = int(request_data["bounty_id"]) + except KeyError: + return flask.Response("Bounty id missing", status=422) + except ValueError: + return flask.Response("bounty_id wasn't int", status=422) + return flask.jsonify(SolutionHandler.get_solution_list(solution_id)) + + +@app.post("/like-solution") +def like_solution() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + solution_id = request_data["solution_id"] + user_id = flask.session["user_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + SolutionHandler.like(solution_id=solution_id, user_id=user_id) + return flask.Response(status=200) + + +@app.post("/unlike-solution") +def unlike_solution() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + solution_id = request_data["solution_id"] + user_id = flask.session["user_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + SolutionHandler.unlike(solution_id=solution_id, user_id=user_id) + return flask.Response(status=200) + + +@app.post("/has-user-liked") +def has_user_liked() -> flask.Response: + request_data = dict(flask.request.form) + login_validation = validate_login() + if login_validation.status_code != 200: + return login_validation + try: + solution_id = request_data["solution_id"] + user_id = flask.session["user_id"] + except KeyError: + return flask.Response("Solution id missing", status=422) + return flask.jsonify(SolutionHandler.has_user_liked(user_id=user_id, solution_id=solution_id)) diff --git a/src/solution_handler.py b/src/solution_handler.py new file mode 100644 index 0000000..3092bdf --- /dev/null +++ b/src/solution_handler.py @@ -0,0 +1,115 @@ +import os +import secrets +import sqlite3 +from user_handler import UserHandler +from werkzeug.datastructures.file_storage import FileStorage +import re + + +class SolutionHandler: + connection: sqlite3.Connection = sqlite3.connect("../data/database.db", check_same_thread=False) + cursor: sqlite3.Cursor = connection.cursor() + + # Save solution + @classmethod + def submit_solution(cls, files: list[FileStorage], bounty_identifier: int, creator_identifier: int): + solution_id = secrets.token_urlsafe(40) + os.mkdir(f"../assets/solutions/{solution_id}/") + for file in files: + if not file.filename: + file.filename = secrets.token_urlsafe(5) + file.filename = "".join(re.findall("[a-zA-Z 0-9-./]", file.filename)) + file.filename = file.filename.replace("/", r"|") + file.save(f"../assets/solutions/{solution_id}/{file.filename}") + + cls.cursor.execute( + f""" + INSERT INTO solutions(user_id, bounty_id, solution_id) + values(?, ?, ?) + """, + (creator_identifier, bounty_identifier, solution_id) + ) + cls.connection.commit() + + # Get solution by id + @staticmethod + def get_solution_by_id(solution_id: str): + path = f"../assets/solutions/{solution_id}/" + if not os.path.isdir(path): + raise KeyError("Solution doesn't exist") + formated_solutions: dict[str, str | dict] = {} + file_paths = os.listdir(path) + for file_path in file_paths: + current_folder = formated_solutions + for folder in file_path.split("|")[:-1]: + if folder not in current_folder: + current_folder[folder] = {} + current_folder: dict = current_folder[folder] + file_name = file_path.split("|")[-1] + try: + with open(path + file_path, "r") as file: + current_folder[file_name] = file.read() + except UnicodeDecodeError: + current_folder[file_name] = "" + + return formated_solutions + + # Get solution list + @classmethod + def get_solution_list(cls, identifier: int) -> list[dict]: + solutions = cls.cursor.execute( + f""" + SELECT user_id, solution_id FROM solutions + WHERE bounty_id = ?; + """, + (identifier, ) + ).fetchall() + return [ + { + "user_id": solution[0], + "creator_display_name": UserHandler.get_display_name(solution[0]), + "solution_id": solution[1], + "likes": cls.get_likes(solution[1]) + } for solution in solutions + ] + + @classmethod + def like(cls, solution_id: str, user_id: int): + cls.cursor.execute( + f""" + INSERT INTO solution_likes(user_id, solution_id) values(?, ?) + """, + (user_id, solution_id) + ) + cls.connection.commit() + + @classmethod + def unlike(cls, solution_id: str, user_id: int): + cls.cursor.execute( + f""" + DELETE FROM solution_likes WHERE solution_id = ? AND user_id = ? + """, + (solution_id, user_id) + ) + cls.connection.commit() + + @classmethod + def get_likes(cls, solution_id: int) -> int: + likes = cls.cursor.execute( + f""" + SELECT COUNT(*) FROM solution_likes WHERE solution_id = ? + """, + (solution_id, ) + ).fetchall()[0] + return likes[0] if likes else 0 + + @classmethod + def has_user_liked(cls, solution_id: int, user_id: int) -> bool: + + likes = cls.cursor.execute( + f""" + SELECT 1 FROM solution_likes WHERE solution_id = ? AND user_id = ? + """, + (solution_id, user_id) + ).fetchall() + return bool(likes) diff --git a/src/user_handler.py b/src/user_handler.py new file mode 100644 index 0000000..11d4580 --- /dev/null +++ b/src/user_handler.py @@ -0,0 +1,154 @@ +import bcrypt +import shutil +from dataclasses import dataclass +import sqlite3 + + +@dataclass +class User: + user_id: int + email: str + handle: str + password: str + display_name: str + + def __init__( + self, + user_id: int, + email: str, + handle: str, + password: str = "", + display_name: str = "", + ) -> None: + self.user_id: int = user_id + self.handle = handle + self.password = password + self.display_name = display_name if display_name else handle + self.email = email + + +class UserHandler: + connection: sqlite3.Connection = sqlite3.connect("../data/database.db", check_same_thread=False) + cursor: sqlite3.Cursor = connection.cursor() + + @classmethod + def create_user( + cls, + user: User + ) -> None: + salt = bcrypt.gensalt() + encrypted_password = bcrypt.hashpw(user.password.encode("utf-8"), salt) + cls.cursor.execute( + f""" + INSERT INTO users(handle, display_name, email, password) + values(?, ?, ?, ?) + """, + (user.handle, user.display_name, user.email, encrypted_password) + ) + cls.connection.commit() + shutil.copy( + "../assets/profile_pictures/default_profile_picture.jpg", + f"../assets/profile_pictures/{user.handle}" + ) + + @classmethod + def get_user_by_id(cls, user_id: int) -> User: + users = cls.cursor.execute( + f""" + SELECT handle, display_name + FROM users WHERE user_id = ?; + """, + (user_id, ) + ).fetchall() + if not users: + raise KeyError(f"{user_id} does not exist!") + user = users[0] + return User( + user_id=user_id, + handle=user[0], + display_name=user[1], + email="", + password="" + ) + + @classmethod + def get_user_by_handle(cls, handle: str) -> User: + users = cls.cursor.execute( + f""" + SELECT user_id, handle, display_name + FROM users WHERE handle = ?; + """, + (handle, ) + ).fetchall() + if not users: + raise KeyError(f"{handle} does not exist!") + users = users[0] + return User( + user_id=users[0], + handle=users[1], + display_name=users[2], + email="", + password="" + ) + + @classmethod + def get_user_by_email(cls, email: str) -> User: + users = cls.cursor.execute( + f""" + SELECT user_id, handle, display_name + FROM users WHERE email = ?; + """, + (email, ) + ).fetchall() + if not users: + raise KeyError(f"{email} does not exist!") + users = users[0] + return User( + user_id=users[0], + handle=users[1], + display_name=users[2], + email=email, + password="", + ) + + #@classmethod + #def verify_password_by_handle(cls, handle: str, password: str) -> bool: + # users = cls.cursor.execute( + # f""" + # SELECT password + # FROM users WHERE handle = ?; + # """, + # (handle, ) + # ).fetchall() + # if not users: + # raise KeyError(f"{handle} does not exist!") + # real_password = users[0][0] + # return bcrypt.checkpw(password.encode("utf-8"), real_password) + + @classmethod + def verify_password_by_email(cls, email: str, password: str) -> bool: + users = cls.cursor.execute( + f""" + SELECT password + FROM users WHERE email = ?; + """, + (email, ) + ).fetchall() + if not users: + raise KeyError(f"{email} does not exist!") + return bcrypt.checkpw(password.encode("utf-8"), users[0][0]) + + @classmethod + def change_display_name(cls, user_id: int, display_name: str): + cls.cursor.execute( + f""" + UPDATE users + SET display_name = ? + WHERE user_id = ?; + """, + (display_name, user_id) + ).fetchall() + + @classmethod + def get_display_name(cls, user_id: int) -> str: + return cls.get_user_by_id(user_id).display_name