diff --git a/.env.example b/.env.example index 740bd4f8e8..982fe1dfa2 100644 --- a/.env.example +++ b/.env.example @@ -12,3 +12,6 @@ DEBUG=TRUE # Front-End Variables BASENAME=/ #BACKEND_URL= +#CLOUDINARY_API_KEY= +#CLOUDINARY_API_SECRET= +#PAYPAL_CLIENT_ID= \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 24da33c3e2..7f1bff12d5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,11 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "workbench.editorAssociations": { "*.md": "vscode.markdown.preview.editor" + }, + "[javascript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "vscode.typescript-language-features" } } diff --git a/Pipfile b/Pipfile index b461e2e4ee..0604642e3a 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ flask-admin = "*" typing-extensions = "*" flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" +flask-bcrypt = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index a391864e9d..88c4e489ed 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "74f92d76f687bb774828613a3a513123fe2ffdb429b95b351d29721dddfd3fb8" + "sha256": "06f49fbe748ce19a68c3a8afe126e8b2968391a733f159165157c2b63292e99e" }, "pipfile-spec": 6, "requires": { @@ -18,73 +18,156 @@ "default": { "alembic": { "hashes": [ - "sha256:6880dec4f28dd7bd999d2ed13fbe7c9d4337700a44d11a524c0ce0c59aaf0dbd", - "sha256:e8a6ff9f3b1887e1fed68bfb8fb9a000d8f61c21bdcc85b67bb9f87fcbc4fce3" + "sha256:1c72391bbdeffccfe317eefba686cb9a3c078005478885413b95c3b26c57a8a7", + "sha256:2e76bd916d547f6900ec4bb5a90aeac1485d2c92536923d0b138c02b126edc53" ], - "markers": "python_version >= '3.7'", - "version": "==1.9.2" + "markers": "python_version >= '3.9'", + "version": "==1.15.2" + }, + "bcrypt": { + "hashes": [ + "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", + "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", + "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", + "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", + "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", + "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d", + "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", + "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", + "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", + "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", + "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", + "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", + "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", + "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", + "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", + "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", + "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", + "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", + "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", + "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", + "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", + "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", + "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", + "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", + "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", + "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", + "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", + "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", + "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", + "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", + "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", + "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", + "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", + "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", + "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", + "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", + "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", + "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", + "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90", + "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492", + "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", + "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", + "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", + "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1", + "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", + "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", + "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", + "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", + "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", + "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", + "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.0" + }, + "blinker": { + "hashes": [ + "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", + "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" + ], + "markers": "python_version >= '3.9'", + "version": "==1.9.0" }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", + "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2025.4.26" }, "click": { "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", + "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" ], "markers": "python_version >= '3.7'", - "version": "==8.1.3" + "version": "==8.1.8" }, "cloudinary": { "hashes": [ - "sha256:f52a1f5eb2c6820f13aa01c109caa5937ad3fd6caf5967817d0ef6c113403afc" + "sha256:8b2e5ac3f17068e79e58934b8836e6fea5d96536c1b85c3b4c3b9ebac44151ce", + "sha256:eb2dcf08f5fa69a1476dd357071d05089b9c8219617dacdb1cb1294719c7f633" ], "index": "pypi", - "version": "==1.31.0" + "version": "==1.44.0" }, "flask": { "hashes": [ - "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", - "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" + "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", + "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136" ], "index": "pypi", - "version": "==2.2.2" + "version": "==3.1.0" }, "flask-admin": { "hashes": [ - "sha256:424ffc79b7b0dfff051555686ea12e86e48dffacac14beaa319fb4502ac40988" + "sha256:24cae2af832b6a611a01d7dc35f42d266c1d6c75a426b869d8cb241b78233369", + "sha256:fd8190f1ec3355913a22739c46ed3623f1d82b8112cde324c60a6fc9b21c9406" ], "index": "pypi", - "version": "==1.6.0" + "version": "==1.6.1" + }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" + ], + "index": "pypi", + "version": "==1.0.1" }, "flask-cors": { "hashes": [ - "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", - "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de" + "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", + "sha256:fa5cb364ead54bbf401a26dbf03030c6b18fb2fcaf70408096a572b409586b0c" + ], + "index": "pypi", + "version": "==5.0.1" + }, + "flask-jwt-extended": { + "hashes": [ + "sha256:63a28fc9731bcc6c4b8815b6f954b5904caa534fc2ae9b93b1d3ef12930dca95", + "sha256:9215d05a9413d3855764bcd67035e75819d23af2fafb6b55197eb5a3313fdfb2" ], "index": "pypi", - "version": "==3.0.10" + "version": "==4.6.0" }, "flask-migrate": { "hashes": [ - "sha256:8662a9dd391ce36deeaf3265987319c20fdb4c8a45306a32ba4f8224459abed4", - "sha256:a0062c8d3f32de02847086b46cfc389412f78c71c89a619ebd7097e89d72ea4b" + "sha256:1a336b06eb2c3ace005f5f2ded8641d534c18798d64061f6ff11f79e1434126d", + "sha256:24d8051af161782e0743af1b04a152d007bad9772b2bca67b7ec1e8ceeb3910d" ], "index": "pypi", - "version": "==4.0.3" + "version": "==4.1.0" }, "flask-sqlalchemy": { "hashes": [ - "sha256:2764335f3c9d7ebdc9ed6044afaf98aae9fa50d7a074cef55dde307ec95903ec", - "sha256:add5750b2f9cd10512995261ee2aa23fab85bd5626061aa3c564b33bb4aa780a" + "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1", + "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283" ], "index": "pypi", - "version": "==3.0.3" + "version": "==3.0.5" }, "flask-swagger": { "hashes": [ @@ -96,304 +179,328 @@ }, "greenlet": { "hashes": [ - "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a", - "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a", - "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43", - "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33", - "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8", - "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088", - "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca", - "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343", - "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645", - "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db", - "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df", - "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3", - "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86", - "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2", - "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a", - "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf", - "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7", - "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394", - "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40", - "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3", - "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6", - "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74", - "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0", - "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3", - "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91", - "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5", - "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9", - "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8", - "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b", - "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6", - "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb", - "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73", - "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b", - "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df", - "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9", - "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f", - "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0", - "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857", - "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a", - "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249", - "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30", - "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292", - "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b", - "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d", - "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b", - "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c", - "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca", - "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7", - "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75", - "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae", - "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b", - "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470", - "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564", - "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9", - "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099", - "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0", - "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5", - "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19", - "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1", - "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526" + "sha256:04b4ec7f65f0e4a1500ac475c9343f6cc022b2363ebfb6e94f416085e40dea15", + "sha256:05a7490f74e8aabc5f29256765a99577ffde979920a2db1f3676d265a3adba41", + "sha256:063bcf7f8ee28eb91e7f7a8148c65a43b73fbdc0064ab693e024b5a940070145", + "sha256:0ba2811509a30e5f943be048895a983a8daf0b9aa0ac0ead526dfb5d987d80ea", + "sha256:0c68bbc639359493420282d2f34fa114e992a8724481d700da0b10d10a7611b8", + "sha256:0ddda0197c5b46eedb5628d33dad034c455ae77708c7bf192686e760e26d6a0c", + "sha256:175d583f7d5ee57845591fc30d852b75b144eb44b05f38b67966ed6df05c8526", + "sha256:17964c246d4f6e1327edd95e2008988a8995ae3a7732be2f9fc1efed1f1cdf8c", + "sha256:1a750f1046994b9e038b45ae237d68153c29a3a783075211fb1414a180c8324b", + "sha256:1c472adfca310f849903295c351d297559462067f618944ce2650a1878b84123", + "sha256:2273586879affca2d1f414709bb1f61f0770adcabf9eda8ef48fd90b36f15d12", + "sha256:24a496479bc8bd01c39aa6516a43c717b4cee7196573c47b1f8e1011f7c12495", + "sha256:2530bfb0abcd451ea81068e6d0a1aac6dabf3f4c23c8bd8e2a8f579c2dd60d95", + "sha256:3059c6f286b53ea4711745146ffe5a5c5ff801f62f6c56949446e0f6461f8157", + "sha256:3227c6ec1149d4520bc99edac3b9bc8358d0034825f3ca7572165cb502d8f29a", + "sha256:374ffebaa5fbd10919cd599e5cf8ee18bae70c11f9d61e73db79826c8c93d6f9", + "sha256:3ecc9d33ca9428e4536ea53e79d781792cee114d2fa2695b173092bdbd8cd6d5", + "sha256:3f56382ac4df3860ebed8ed838f268f03ddf4e459b954415534130062b16bc32", + "sha256:4245246e72352b150a1588d43ddc8ab5e306bef924c26571aafafa5d1aaae4e8", + "sha256:4339b202ac20a89ccd5bde0663b4d00dc62dd25cb3fb14f7f3034dec1b0d9ece", + "sha256:4818116e75a0dd52cdcf40ca4b419e8ce5cb6669630cb4f13a6c384307c9543f", + "sha256:5193135b3a8d0017cb438de0d49e92bf2f6c1c770331d24aa7500866f4db4017", + "sha256:51a2f49da08cff79ee42eb22f1658a2aed60c72792f0a0a95f5f0ca6d101b1fb", + "sha256:5c12f0d17a88664757e81a6e3fc7c2452568cf460a2f8fb44f90536b2614000b", + "sha256:6079ae990bbf944cf66bea64a09dcb56085815630955109ffa98984810d71565", + "sha256:639a94d001fe874675b553f28a9d44faed90f9864dc57ba0afef3f8d76a18b04", + "sha256:64a4d0052de53ab3ad83ba86de5ada6aeea8f099b4e6c9ccce70fb29bc02c6a2", + "sha256:6dcc6d604a6575c6225ac0da39df9335cc0c6ac50725063fa90f104f3dbdb2c9", + "sha256:7132e024ebeeeabbe661cf8878aac5d2e643975c4feae833142592ec2f03263d", + "sha256:72c9b668454e816b5ece25daac1a42c94d1c116d5401399a11b77ce8d883110c", + "sha256:777c1281aa7c786738683e302db0f55eb4b0077c20f1dc53db8852ffaea0a6b0", + "sha256:7abc0545d8e880779f0c7ce665a1afc3f72f0ca0d5815e2b006cafc4c1cc5840", + "sha256:7b0f3a0a67786facf3b907a25db80efe74310f9d63cc30869e49c79ee3fcef7e", + "sha256:852ef432919830022f71a040ff7ba3f25ceb9fe8f3ab784befd747856ee58530", + "sha256:8b89e5d44f55372efc6072f59ced5ed1efb7b44213dab5ad7e0caba0232c6545", + "sha256:8fe303381e7e909e42fb23e191fc69659910909fdcd056b92f6473f80ef18543", + "sha256:9afa05fe6557bce1642d8131f87ae9462e2a8e8c46f7ed7929360616088a3975", + "sha256:9f4dd4b4946b14bb3bf038f81e1d2e535b7d94f1b2a59fdba1293cd9c1a0a4d7", + "sha256:aa30066fd6862e1153eaae9b51b449a6356dcdb505169647f69e6ce315b9468b", + "sha256:b38d53cf268da963869aa25a6e4cc84c1c69afc1ae3391738b2603d110749d01", + "sha256:b7503d6b8bbdac6bbacf5a8c094f18eab7553481a1830975799042f26c9e101b", + "sha256:c07a0c01010df42f1f058b3973decc69c4d82e036a951c3deaf89ab114054c07", + "sha256:cb5ee928ce5fedf9a4b0ccdc547f7887136c4af6109d8f2fe8e00f90c0db47f5", + "sha256:cc45a7189c91c0f89aaf9d69da428ce8301b0fd66c914a499199cfb0c28420fc", + "sha256:d6668caf15f181c1b82fb6406f3911696975cc4c37d782e19cb7ba499e556189", + "sha256:dbb4e1aa2000852937dd8f4357fb73e3911da426df8ca9b8df5db231922da474", + "sha256:de62b542e5dcf0b6116c310dec17b82bb06ef2ceb696156ff7bf74a7a498d982", + "sha256:e1967882f0c42eaf42282a87579685c8673c51153b845fde1ee81be720ae27ac", + "sha256:e1a40a17e2c7348f5eee5d8e1b4fa6a937f0587eba89411885a36a8e1fc29bd2", + "sha256:e63cd2035f49376a23611fbb1643f78f8246e9d4dfd607534ec81b175ce582c2", + "sha256:e775176b5c203a1fa4be19f91da00fd3bff536868b77b237da3f4daa5971ae5d", + "sha256:e77ae69032a95640a5fe8c857ec7bee569a0997e809570f4c92048691ce4b437", + "sha256:e934591a7a4084fa10ee5ef50eb9d2ac8c4075d5c9cf91128116b5dca49d43b1", + "sha256:e98328b8b8f160925d6b1c5b1879d8e64f6bd8cf11472b7127d579da575b77d9", + "sha256:ff38c869ed30fff07f1452d9a204ece1ec6d3c0870e0ba6e478ce7c1515acf22" ], "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", - "version": "==2.0.2" + "version": "==3.2.1" }, "gunicorn": { "hashes": [ - "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", - "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" ], "index": "pypi", - "version": "==20.1.0" + "version": "==23.0.0" }, "itsdangerous": { "hashes": [ - "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", - "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", + "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "markers": "python_version >= '3.8'", + "version": "==2.2.0" }, "jinja2": { "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2" + "version": "==3.1.6" }, "mako": { "hashes": [ - "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818", - "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34" + "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", + "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59" ], - "markers": "python_version >= '3.7'", - "version": "==1.2.4" + "markers": "python_version >= '3.8'", + "version": "==1.3.10" }, "markupsafe": { "hashes": [ - "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", - "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", - "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", - "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", - "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", - "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", - "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", - "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", - "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", - "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", - "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", - "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", - "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", - "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", - "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", - "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", - "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", - "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", - "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", - "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", - "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", - "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", - "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", - "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", - "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", - "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", - "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", - "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", - "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", - "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", - "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", - "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", - "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", - "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", - "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", - "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", - "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", - "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", - "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", - "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", - "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", - "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", - "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", - "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", - "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", - "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", - "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", - "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", - "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "markers": "python_version >= '3.9'", + "version": "==3.0.2" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" }, "psycopg2-binary": { "hashes": [ - "sha256:00475004e5ed3e3bf5e056d66e5dcdf41a0dc62efcd57997acd9135c40a08a50", - "sha256:01ad49d68dd8c5362e4bfb4158f2896dc6e0c02e87b8a3770fc003459f1a4425", - "sha256:024030b13bdcbd53d8a93891a2cf07719715724fc9fee40243f3bd78b4264b8f", - "sha256:02551647542f2bf89073d129c73c05a25c372fc0a49aa50e0de65c3c143d8bd0", - "sha256:043a9fd45a03858ff72364b4b75090679bd875ee44df9c0613dc862ca6b98460", - "sha256:05b3d479425e047c848b9782cd7aac9c6727ce23181eb9647baf64ffdfc3da41", - "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85", - "sha256:1764546ffeaed4f9428707be61d68972eb5ede81239b46a45843e0071104d0dd", - "sha256:1e491e6489a6cb1d079df8eaa15957c277fdedb102b6a68cfbf40c4994412fd0", - "sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd", - "sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147", - "sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c", - "sha256:2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903", - "sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba", - "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632", - "sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577", - "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c", - "sha256:3520d7af1ebc838cc6084a3281145d5cd5bdd43fdef139e6db5af01b92596cb7", - "sha256:3d790f84201c3698d1bfb404c917f36e40531577a6dda02e45ba29b64d539867", - "sha256:3fc33295cfccad697a97a76dec3f1e94ad848b7b163c3228c1636977966b51e2", - "sha256:422e3d43b47ac20141bc84b3d342eead8d8099a62881a501e97d15f6addabfe9", - "sha256:426c2ae999135d64e6a18849a7d1ad0e1bd007277e4a8f4752eaa40a96b550ff", - "sha256:46512486be6fbceef51d7660dec017394ba3e170299d1dc30928cbedebbf103a", - "sha256:46850a640df62ae940e34a163f72e26aca1f88e2da79148e1862faaac985c302", - "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1", - "sha256:4e7904d1920c0c89105c0517dc7e3f5c20fb4e56ba9cdef13048db76947f1d79", - "sha256:56b2957a145f816726b109ee3d4e6822c23f919a7d91af5a94593723ed667835", - "sha256:5c6527c8efa5226a9e787507652dd5ba97b62d29b53c371a85cd13f957fe4d42", - "sha256:5cbc554ba47ecca8cd3396ddaca85e1ecfe3e48dd57dc5e415e59551affe568e", - "sha256:5d28ecdf191db558d0c07d0f16524ee9d67896edf2b7990eea800abeb23ebd61", - "sha256:5fc447058d083b8c6ac076fc26b446d44f0145308465d745fba93a28c14c9e32", - "sha256:63e318dbe52709ed10d516a356f22a635e07a2e34c68145484ed96a19b0c4c68", - "sha256:68d81a2fe184030aa0c5c11e518292e15d342a667184d91e30644c9d533e53e1", - "sha256:6e63814ec71db9bdb42905c925639f319c80e7909fb76c3b84edc79dadef8d60", - "sha256:6f8a9bcab7b6db2e3dbf65b214dfc795b4c6b3bb3af922901b6a67f7cb47d5f8", - "sha256:70831e03bd53702c941da1a1ad36c17d825a24fbb26857b40913d58df82ec18b", - "sha256:74eddec4537ab1f701a1647214734bc52cee2794df748f6ae5908e00771f180a", - "sha256:7b3751857da3e224f5629400736a7b11e940b5da5f95fa631d86219a1beaafec", - "sha256:7cf1d44e710ca3a9ce952bda2855830fe9f9017ed6259e01fcd71ea6287565f5", - "sha256:7d07f552d1e412f4b4e64ce386d4c777a41da3b33f7098b6219012ba534fb2c2", - "sha256:7d88db096fa19d94f433420eaaf9f3c45382da2dd014b93e4bf3215639047c16", - "sha256:7ee3095d02d6f38bd7d9a5358fcc9ea78fcdb7176921528dd709cc63f40184f5", - "sha256:902844f9c4fb19b17dfa84d9e2ca053d4a4ba265723d62ea5c9c26b38e0aa1e6", - "sha256:937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1", - "sha256:95076399ec3b27a8f7fa1cc9a83417b1c920d55cf7a97f718a94efbb96c7f503", - "sha256:9c38d3869238e9d3409239bc05bc27d6b7c99c2a460ea337d2814b35fb4fea1b", - "sha256:9e32cedc389bcb76d9f24ea8a012b3cb8385ee362ea437e1d012ffaed106c17d", - "sha256:9ffdc51001136b699f9563b1c74cc1f8c07f66ef7219beb6417a4c8aaa896c28", - "sha256:a0adef094c49f242122bb145c3c8af442070dc0e4312db17e49058c1702606d4", - "sha256:a36a0e791805aa136e9cbd0ffa040d09adec8610453ee8a753f23481a0057af5", - "sha256:a7e518a0911c50f60313cb9e74a169a65b5d293770db4770ebf004245f24b5c5", - "sha256:af0516e1711995cb08dc19bbd05bec7dbdebf4185f68870595156718d237df3e", - "sha256:b8104f709590fff72af801e916817560dbe1698028cd0afe5a52d75ceb1fce5f", - "sha256:b911dfb727e247340d36ae20c4b9259e4a64013ab9888ccb3cbba69b77fd9636", - "sha256:b9a794cef1d9c1772b94a72eec6da144c18e18041d294a9ab47669bc77a80c1d", - "sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64", - "sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb", - "sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882", - "sha256:bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720", - "sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896", - "sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267", - "sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7", - "sha256:c5e65c6ac0ae4bf5bef1667029f81010b6017795dcb817ba5c7b8a8d61fab76f", - "sha256:d4c7b3a31502184e856df1f7bbb2c3735a05a8ce0ade34c5277e1577738a5c91", - "sha256:d892bfa1d023c3781a3cab8dd5af76b626c483484d782e8bd047c180db590e4c", - "sha256:dbc332beaf8492b5731229a881807cd7b91b50dbbbaf7fe2faf46942eda64a24", - "sha256:dc85b3777068ed30aff8242be2813038a929f2084f69e43ef869daddae50f6ee", - "sha256:e59137cdb970249ae60be2a49774c6dfb015bd0403f05af1fe61862e9626642d", - "sha256:e67b3c26e9b6d37b370c83aa790bbc121775c57bfb096c2e77eacca25fd0233b", - "sha256:e72c91bda9880f097c8aa3601a2c0de6c708763ba8128006151f496ca9065935", - "sha256:f95b8aca2703d6a30249f83f4fe6a9abf2e627aa892a5caaab2267d56be7ab69" + "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", + "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5", + "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", + "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", + "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", + "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", + "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", + "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", + "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", + "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", + "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", + "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", + "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", + "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", + "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", + "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", + "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", + "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", + "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", + "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", + "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", + "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", + "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", + "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", + "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", + "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", + "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", + "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", + "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", + "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44", + "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", + "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", + "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", + "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa", + "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", + "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", + "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", + "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", + "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", + "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", + "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", + "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", + "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", + "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", + "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3", + "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", + "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92", + "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", + "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", + "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", + "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", + "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", + "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", + "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", + "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", + "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", + "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", + "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", + "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", + "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", + "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", + "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", + "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", + "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", + "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", + "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", + "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", + "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" ], "index": "pypi", - "version": "==2.9.5" + "version": "==2.9.10" }, - "python-dotenv": { + "pyjwt": { "hashes": [ - "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49", - "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a" + "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", + "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb" ], - "index": "pypi", - "version": "==0.21.1" + "markers": "python_version >= '3.9'", + "version": "==2.10.1" }, - "pyyaml": { + "python-dotenv": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", + "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d" ], - "markers": "python_version >= '3.6'", - "version": "==6.0" + "index": "pypi", + "version": "==1.1.0" }, - "setuptools": { + "pyyaml": { "hashes": [ - "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378", - "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.7'", - "version": "==67.1.0" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -444,35 +551,35 @@ }, "typing-extensions": { "hashes": [ - "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", - "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", + "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" ], "index": "pypi", - "version": "==4.4.0" + "version": "==4.13.2" }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", + "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.14" + "markers": "python_version >= '3.9'", + "version": "==2.4.0" }, "werkzeug": { "hashes": [ - "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", - "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" + "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", + "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], - "markers": "python_version >= '3.7'", - "version": "==2.2.2" + "markers": "python_version >= '3.9'", + "version": "==3.1.3" }, "wtforms": { "hashes": [ - "sha256:6b351bbb12dd58af57ffef05bc78425d08d1914e0fd68ee14143b7ade023c5bc", - "sha256:837f2f0e0ca79481b92884962b914eba4e72b7a2daaf1f939c890ed0124b834b" + "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07", + "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9" ], - "markers": "python_version >= '3.7'", - "version": "==3.0.1" + "index": "pypi", + "version": "==3.1.2" } }, "develop": {} diff --git a/dist/index.html b/dist/index.html index 1e8cb81dfe..c73f99cd71 100644 --- a/dist/index.html +++ b/dist/index.html @@ -8,7 +8,7 @@ -
+ diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000000..0e04844159 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000000..ec9d45c26a --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000000..4c9709271b --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000000..2c0156303a --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/a8a59e6331ba_.py b/migrations/versions/a8a59e6331ba_.py new file mode 100644 index 0000000000..ed9ebf7099 --- /dev/null +++ b/migrations/versions/a8a59e6331ba_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: a8a59e6331ba +Revises: e2ca046dde9e +Create Date: 2025-05-09 22:02:35.938934 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a8a59e6331ba' +down_revision = 'e2ca046dde9e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('comments', schema=None) as batch_op: + batch_op.create_unique_constraint(None, ['id']) + + with op.batch_alter_table('orders', schema=None) as batch_op: + batch_op.create_unique_constraint(None, ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('orders', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='unique') + + with op.batch_alter_table('comments', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='unique') + + # ### end Alembic commands ### diff --git a/migrations/versions/b7278cf88f23_.py b/migrations/versions/b7278cf88f23_.py new file mode 100644 index 0000000000..da446ecb7d --- /dev/null +++ b/migrations/versions/b7278cf88f23_.py @@ -0,0 +1,95 @@ +"""empty message + +Revision ID: b7278cf88f23 +Revises: +Create Date: 2025-05-08 23:42:06.432792 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b7278cf88f23' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=50), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('last_name', sa.String(length=50), nullable=False), + sa.Column('password', sa.String(length=120), nullable=False), + sa.Column('phone', sa.String(length=20), nullable=False), + sa.Column('address', sa.String(length=200), nullable=False), + sa.Column('rol', sa.String(length=20), nullable=False), + sa.Column('service_description', sa.String(length=300), nullable=True), + sa.Column('service_title', sa.String(length=100), nullable=True), + sa.Column('profile_description', sa.String(length=800), nullable=True), + sa.Column('balance', sa.Integer(), nullable=True), + sa.Column('img_url', sa.String(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_table('comments', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('text', sa.String(length=200), nullable=False), + sa.Column('stars', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('freelance_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['freelance_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id') + ) + op.create_table('favoritos', + sa.Column('usuario_id', sa.Integer(), nullable=False), + sa.Column('favorito_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['favorito_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['usuario_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('usuario_id', 'favorito_id') + ) + op.create_table('services', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=100), nullable=True), + sa.Column('price', sa.DECIMAL(precision=10, scale=2), nullable=True), + sa.Column('time', sa.String(length=50), nullable=True), + sa.Column('description', sa.String(length=200), nullable=True), + sa.Column('img_url', sa.String(), nullable=True), + sa.Column('category', sa.String(length=50), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('orders', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('is_payed', sa.Boolean(), nullable=True), + sa.Column('price', sa.Float(), nullable=True), + sa.Column('user_name', sa.String(length=30), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('service_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('orders') + op.drop_table('services') + op.drop_table('favoritos') + op.drop_table('comments') + op.drop_table('users') + # ### end Alembic commands ### diff --git a/migrations/versions/e2ca046dde9e_.py b/migrations/versions/e2ca046dde9e_.py new file mode 100644 index 0000000000..9f90203c85 --- /dev/null +++ b/migrations/versions/e2ca046dde9e_.py @@ -0,0 +1,96 @@ +"""empty message + +Revision ID: e2ca046dde9e +Revises: +Create Date: 2025-05-09 22:02:13.173178 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e2ca046dde9e' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=50), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('last_name', sa.String(length=50), nullable=False), + sa.Column('password', sa.String(length=120), nullable=False), + sa.Column('phone', sa.String(length=20), nullable=False), + sa.Column('address', sa.String(length=200), nullable=False), + sa.Column('rol', sa.String(length=20), nullable=False), + sa.Column('service_description', sa.String(length=300), nullable=True), + sa.Column('service_title', sa.String(length=100), nullable=True), + sa.Column('profile_description', sa.String(length=800), nullable=True), + sa.Column('balance', sa.Integer(), nullable=True), + sa.Column('img_url', sa.String(), nullable=True), + sa.Column('cover_img_url', sa.String(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_table('comments', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('text', sa.String(length=200), nullable=False), + sa.Column('stars', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('freelance_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['freelance_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id') + ) + op.create_table('favoritos', + sa.Column('usuario_id', sa.Integer(), nullable=False), + sa.Column('favorito_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['favorito_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['usuario_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('usuario_id', 'favorito_id') + ) + op.create_table('services', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=100), nullable=True), + sa.Column('price', sa.Integer(), nullable=True), + sa.Column('time', sa.String(length=50), nullable=True), + sa.Column('description', sa.String(length=200), nullable=True), + sa.Column('img_url', sa.String(), nullable=True), + sa.Column('category', sa.String(length=50), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('orders', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('is_payed', sa.Boolean(), nullable=True), + sa.Column('price', sa.Float(), nullable=True), + sa.Column('user_name', sa.String(length=30), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('service_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('orders') + op.drop_table('services') + op.drop_table('favoritos') + op.drop_table('comments') + op.drop_table('users') + # ### end Alembic commands ### diff --git a/package-lock.json b/package-lock.json index c932d7fc55..ef7e122283 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,13 @@ "version": "1.0.1", "license": "ISC", "dependencies": { + "@paypal/react-paypal-js": "^8.8.3", "prop-types": "^15.6.1", "react": "^16.8.4", "react-dom": "^16.8.4", + "react-hook-form": "^7.56.2", + "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", "react-polyfills": "0.0.1", "react-router-dom": "^6.3.0" }, @@ -1850,6 +1854,38 @@ "node": ">= 8" } }, + "node_modules/@paypal/paypal-js": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@paypal/paypal-js/-/paypal-js-8.2.0.tgz", + "integrity": "sha512-hLg5wNORW3WiyMiRNJOm6cN2IqjPlClpxd971bEdm0LNpbbejQZYtesb0/0arTnySSbGcxg7MxjkZ/N5Z5qBNQ==", + "license": "Apache-2.0", + "dependencies": { + "promise-polyfill": "^8.3.0" + } + }, + "node_modules/@paypal/react-paypal-js": { + "version": "8.8.3", + "resolved": "https://registry.npmjs.org/@paypal/react-paypal-js/-/react-paypal-js-8.8.3.tgz", + "integrity": "sha512-H5s3EU7S+RFaLad3BmV9nSAmD3iaJI14mCtbngpqMm4ruMNGHjOTaSTX3jAAk/ghmzGAda2GMfyiYondO4F21Q==", + "license": "Apache-2.0", + "dependencies": { + "@paypal/paypal-js": "^8.1.2", + "@paypal/sdk-constants": "^1.0.122" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/@paypal/sdk-constants": { + "version": "1.0.153", + "resolved": "https://registry.npmjs.org/@paypal/sdk-constants/-/sdk-constants-1.0.153.tgz", + "integrity": "sha512-wrPXK9ckwYBOOLyHfsGGtYjf9vOQgPSOh08DEeGRr/KBamQgvHYEG2soSMPlJuLfker32n1x5wvnWCcYP+ea9w==", + "license": "Apache-2.0", + "dependencies": { + "hi-base32": "^0.5.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -3427,6 +3463,12 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -5217,6 +5259,15 @@ "node": ">=0.10.0" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -5331,6 +5382,12 @@ "he": "bin/he" } }, + "node_modules/hi-base32": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", + "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==", + "license": "MIT" + }, "node_modules/history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -7200,6 +7257,12 @@ "node": ">=0.4.0" } }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "license": "MIT" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -7620,6 +7683,48 @@ "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", "dev": true }, + "node_modules/react-hook-form": { + "version": "7.56.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.2.tgz", + "integrity": "sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -10619,6 +10724,31 @@ "fastq": "^1.6.0" } }, + "@paypal/paypal-js": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@paypal/paypal-js/-/paypal-js-8.2.0.tgz", + "integrity": "sha512-hLg5wNORW3WiyMiRNJOm6cN2IqjPlClpxd971bEdm0LNpbbejQZYtesb0/0arTnySSbGcxg7MxjkZ/N5Z5qBNQ==", + "requires": { + "promise-polyfill": "^8.3.0" + } + }, + "@paypal/react-paypal-js": { + "version": "8.8.3", + "resolved": "https://registry.npmjs.org/@paypal/react-paypal-js/-/react-paypal-js-8.8.3.tgz", + "integrity": "sha512-H5s3EU7S+RFaLad3BmV9nSAmD3iaJI14mCtbngpqMm4ruMNGHjOTaSTX3jAAk/ghmzGAda2GMfyiYondO4F21Q==", + "requires": { + "@paypal/paypal-js": "^8.1.2", + "@paypal/sdk-constants": "^1.0.122" + } + }, + "@paypal/sdk-constants": { + "version": "1.0.153", + "resolved": "https://registry.npmjs.org/@paypal/sdk-constants/-/sdk-constants-1.0.153.tgz", + "integrity": "sha512-wrPXK9ckwYBOOLyHfsGGtYjf9vOQgPSOh08DEeGRr/KBamQgvHYEG2soSMPlJuLfker32n1x5wvnWCcYP+ea9w==", + "requires": { + "hi-base32": "^0.5.0" + } + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -11929,6 +12059,11 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -13249,6 +13384,12 @@ "pinkie-promise": "^2.0.0" } }, + "goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "requires": {} + }, "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -13329,6 +13470,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hi-base32": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", + "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==" + }, "history": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", @@ -14703,6 +14849,11 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==" + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -15006,6 +15157,27 @@ "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", "dev": true }, + "react-hook-form": { + "version": "7.56.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.2.tgz", + "integrity": "sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==", + "requires": {} + }, + "react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "requires": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + } + }, + "react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 3c8d47cba7..bf05facc47 100755 --- a/package.json +++ b/package.json @@ -73,9 +73,13 @@ ] }, "dependencies": { + "@paypal/react-paypal-js": "^8.8.3", "prop-types": "^15.6.1", "react": "^16.8.4", "react-dom": "^16.8.4", + "react-hook-form": "^7.56.2", + "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", "react-polyfills": "0.0.1", "react-router-dom": "^6.3.0" } diff --git a/public/index.html b/public/index.html index 9462644fe9..058d1ccbdd 100644 --- a/public/index.html +++ b/public/index.html @@ -1 +1,4 @@ -Hello Rigo with Vanilla.js
\ No newline at end of file +Hello Rigo with Vanilla.js
+ + + \ No newline at end of file diff --git a/src/api/admin.py b/src/api/admin.py index 3eecb64140..a5cf2ea0e3 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,7 +1,7 @@ import os from flask_admin import Admin -from .models import db, User +from .models import db, User, Service,Order,Comment from flask_admin.contrib.sqla import ModelView def setup_admin(app): @@ -12,6 +12,10 @@ def setup_admin(app): # Add your models here, for example this is how we add a the User model to the admin admin.add_view(ModelView(User, db.session)) + admin.add_view(ModelView(Service, db.session)) + admin.add_view(ModelView(Order, db.session)) + admin.add_view(ModelView(Comment, db.session)) + # You can duplicate that line to add mew models # admin.add_view(ModelView(YourModelName, db.session)) \ No newline at end of file diff --git a/src/api/models.py b/src/api/models.py index dccd8421ee..bde2913942 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,161 @@ from flask_sqlalchemy import SQLAlchemy +from datetime import datetime, timezone +from sqlalchemy import Column, Integer, ForeignKey +from sqlalchemy.orm import relationship +from decimal import Decimal db = SQLAlchemy() +favoritos = db.Table('favoritos', + db.Column('usuario_id', db.Integer, db.ForeignKey('users.id'), primary_key=True), + db.Column('favorito_id', db.Integer, db.ForeignKey('users.id'), primary_key=True) +) + class User(db.Model): + __tablename__="users" id = db.Column(db.Integer, primary_key=True) - email = db.Column(db.String(120), unique=True, nullable=False) - password = db.Column(db.String(80), unique=False, nullable=False) - is_active = db.Column(db.Boolean(), unique=False, nullable=False) + email = db.Column(db.String(50), unique=True, nullable=False) + name = db.Column(db.String(50), nullable=False) + last_name = db.Column(db.String(50), nullable=False) + password = db.Column(db.String(120), nullable=False) + phone = db.Column(db.String(20), nullable=False) + address = db.Column(db.String(200), nullable=False) + rol = db.Column(db.String(20), nullable=False) + service_description = db.Column(db.String(300), nullable=True) + service_title = db.Column(db.String(100), nullable=True) + profile_description = db.Column(db.String(800), nullable=True) + balance=db.Column(db.Integer(), nullable=True ,default=0) + img_url=db.Column(db.String(), nullable=True) + cover_img_url=db.Column(db.String(), nullable=True) + + is_active = db.Column(db.Boolean(), nullable=False,default=True) + created_at = db.Column(db.DateTime(timezone=True), default=datetime.now(timezone.utc), nullable=False) + + services = relationship("Service", back_populates="user") + orders = relationship("Order",back_populates="user") + + favoritos_agregados = db.relationship( + 'User', + secondary=favoritos, + primaryjoin="User.id==favoritos.c.usuario_id", + secondaryjoin="User.id==favoritos.c.favorito_id", + backref=db.backref('favoritos_de', lazy='dynamic') + ) + + comments_made = relationship("Comment", foreign_keys="[Comment.user_id]",back_populates="author") + comments_received = relationship("Comment",foreign_keys="[Comment.freelance_id]",back_populates="recipient") + + def agregar_favorito(self, usuario): + """Agregar un usuario como favorito""" + if usuario not in self.favoritos_agregados: + self.favoritos_agregados.append(usuario) + + def eliminar_favorito(self, usuario): + """Eliminar un usuario de la lista de favoritos""" + if usuario in self.favoritos_agregados: + self.favoritos_agregados.remove(usuario) + + def tiene_favorito(self, usuario): + """Verificar si un usuario está en la lista de favoritos""" + return usuario in self.favoritos_agregados - def __repr__(self): - return f'' def serialize(self): return { "id": self.id, "email": self.email, + "name": self.name, + "last_name": self.last_name, + "phone": self.phone, + "address": self.address, + "rol": self.rol, + "is_active": self.is_active, + "balance": self.balance, + "services": self.services, + "img_url": self.img_url, + "cover_img_url" : self.cover_img_url, + "service_description": self.service_description, + "service_title": self.service_title, + "profile_description": self.profile_description, + # do not serialize the password, its a security breach + } + +class Service(db.Model): + __tablename__="services" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(100), nullable=True) + price = db.Column(db.Integer, nullable=True) + time = db.Column(db.String(50), nullable=True) + description = db.Column(db.String(200), nullable=True) + img_url=db.Column(db.String(), nullable=True) + category=db.Column(db.String(50), nullable=True) + + created_at = db.Column(db.DateTime(timezone=True), default=datetime.now(timezone.utc), nullable=False) + + user_id = Column(Integer, ForeignKey('users.id')) + user = relationship("User", back_populates="services") + orders = relationship("Order",back_populates="services") + + def serialize(self): + return { + "id": self.id, + "title": self.title, + "price": self.price, + "time": self.time, + "description": self.description, + "img_url": self.img_url, + "user_id": self.user_id, + "category": self.category, # do not serialize the password, its a security breach - } \ No newline at end of file + } + +class Order(db.Model): + __tablename__="orders" + id = db.Column(db.Integer, unique=True, primary_key=True) + status = db.Column(db.String(20), nullable=False) + is_payed = db.Column(db.Boolean(),default=False) + price = db.Column(db.Float) + user_name = db.Column(db.String(30), nullable=False) + + user_id = Column(Integer, ForeignKey('users.id')) + service_id = Column(Integer, ForeignKey('services.id')) + + created_at = db.Column(db.DateTime(timezone=True), default=datetime.now(timezone.utc), nullable=False) + + services = relationship("Service", back_populates="orders") + user = relationship("User", back_populates="orders") + + def serialize(self): + return { + "id": self.id, + "status": self.status, + "price": self.price, + "is_payed": self.is_payed, + "user_id": self.user_id, + "service_id": self.service_id, + "user_name": self.user_name + # do not serialize the password, its a security breach + } + +class Comment(db.Model): + __tablename__="comments" + id = db.Column(db.Integer, unique=True, primary_key=True) + text = db.Column(db.String(200), nullable=False) + stars=db.Column(db.Integer, nullable=False,default=1) + + + user_id = db.Column(db.Integer, ForeignKey('users.id')) + freelance_id = db.Column(db.Integer, ForeignKey('users.id')) + author = relationship("User",foreign_keys=[user_id],back_populates="comments_made") + recipient = relationship("User",foreign_keys=[freelance_id],back_populates="comments_received") + + def serialize(self): + return { + "id": self.id, + "text": self.text, + "stars": self.stars, + "user_id":self.user_id, + "freelance_id":self.freelance_id, + + } + diff --git a/src/api/routes.py b/src/api/routes.py index 029589a3a1..4e85d2c559 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -1,22 +1,772 @@ """ This module takes care of starting the API Server, Loading the DB and Adding the endpoints """ +import os from flask import Flask, request, jsonify, url_for, Blueprint -from api.models import db, User +from api.models import db, User,Service,Order,Comment from api.utils import generate_sitemap, APIException from flask_cors import CORS +from datetime import timedelta +from flask_jwt_extended import JWTManager,create_access_token, jwt_required, get_jwt_identity +from flask_bcrypt import Bcrypt + +import cloudinary +from cloudinary.uploader import upload +from cloudinary.utils import cloudinary_url + +cloudinary.config( + cloud_name = os.getenv("CLOUDINARY_CLOUD_NAME"), + api_key = os.getenv("CLOUDINARY_API_KEY"), + api_secret = os.getenv("CLOUDINARY_API_SECRET"), # Click 'View API Keys' above to copy your API secret + secure=True +) api = Blueprint('api', __name__) # Allow CORS requests to this API CORS(api) +jwt = JWTManager() +bcrypt = Bcrypt() + +IMAGE_PROFILE_URL="https://res.cloudinary.com/dph121s7p/image/upload/v1746471079/image_profile_placceholder_dfzbln.jpg" + +def photo_uploader(photo,height=300,width=300): + upload_result = upload(photo) + cloud_url, options = cloudinary_url(upload_result['public_id'], format="jpg", crop="fill", width=height,height=height) + + return cloud_url + +@api.route('/test/sign-up', methods=['POST']) +def sign_up(): + + try: + data = request.get_json() + + name = data.get("name") + last_name = data.get("last_name") + email = data.get("email") + password = data.get("password") + phone = data.get("phone") + rol = data.get("rol") + address = data.get("address") + + + required_fields = ["name", "last_name", "email", "password", "phone", "rol","address"] + missing_fields = [field for field in required_fields if not data.get(field)] + + if missing_fields: + return jsonify({ + "msj": f"faltan campos necesarios: {', '.join(missing_fields)}", + "result": [] + }), 400 + + existe_usuario = User.query.filter(User.email == data["email"]).first() + + if existe_usuario: + return jsonify({"msj":"Correo ya registardo","result":[]}),400 + + password_hasheada=bcrypt.generate_password_hash(data["password"]).decode("utf-8") + + + nuevo_usuario=User(name=name,last_name=last_name,email=email,password=password_hasheada,phone=phone ,rol=rol,address=address) + + db.session.add(nuevo_usuario) + db.session.commit() + + return jsonify({"msg":"Craedo exitosamente","result":{ + "name":name, + "last_name":last_name, + "email":email, + "phone":phone , + "rol":rol, + "address":address + }}), 201 + + except Exception as e: + return jsonify({ + "error":str(e) + }) + +@api.route('/log-in', methods=['POST']) +def log_in(): + + try: + data = request.get_json() + + email = data.get("email") + password = data.get("password") + + required_fields = ["email", "password"] + missing_fields = [field for field in required_fields if not data.get(field)] + + if missing_fields: + return jsonify({ + "msj": f"Faltan campos necesarios: {', '.join(missing_fields)}", + "result": [] + }), 400 + + existe_usuario = User.query.filter_by(email=email).first() + + if not existe_usuario: + return jsonify({"msj":"Correo no esta registardo","result":[]}),400 + + hasheada_password=existe_usuario.password + its_valid_password=bcrypt.check_password_hash(hasheada_password,password) + + if its_valid_password: + services = [service.serialize() for service in existe_usuario.services] + resultado={**existe_usuario.serialize(), "services": services} + expires=timedelta(days=1) + token=create_access_token(identity=str(existe_usuario.id),expires_delta=expires) + return jsonify({ 'token':token,"user_info":resultado,"msj":"Inicio de sesion exitosa"}), 200 + else : + return jsonify({"msj":"Contraseña equivocada"}),404 + + except Exception as e: + return jsonify({ + "error":str(e) + }) + + +@api.route("/search",methods=["POST"]) +def search_results(): + try: + busqueda=request.get_json() + + if not busqueda: + return jsonify({ + 'msj': "El termino de busqueda no debe de estar vacio.", + "result":[] + }),400 + + resultados = User.query.filter( + User.service_description.ilike(f'%{busqueda}%') + ).all() + + data = [{ + 'id': resultado.id, + 'title': resultado.service_description, + "user_name":f'{resultado.name} {resultado.last_name}' + # Agrega otros campos que necesites mostrar + } for resultado in resultados] + + return jsonify({ + 'msj': True, + "result":data + + }),200 + + + except Exception as e: + return jsonify({ + "error":str(e) + }) + + +@api.route('/user', methods=['GET']) +@jwt_required() +def get_user(): + try: + user_id=get_jwt_identity() + if user_id: + user=User.query.filter_by(id=user_id).first() + services = [service.serialize() for service in user.services] + resultado={**user.serialize(), "services": services} + return jsonify(resultado), 200 + + else: + return {"Error": "Token inválido o no proporcionado"}, 401 + + + except Exception as e: + return jsonify({ + "error":str(e) + }) + + + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@api.route('/service', methods=['POST']) +@jwt_required() +def post_service(): + try: + user_id= get_jwt_identity() + user = User.query.get(user_id) + if not user: + return jsonify({"msg":"Freelance no encontrado"}), 404 + + title = request.form.get("title") + price = request.form.get("price") + description = request.form.get("description") + time = request.form.get("time") + img_file = request.files.get("img_url") + category = request.form.get("category") + + if not title or not price or not description or not time or not category: + return jsonify({"error": "Todos los campos son obligatorios"}),400 + + + image_url = None + if img_file: + upload_result = cloudinary.uploader.upload(img_file) + image_url = upload_result.get("secure_url") + + new_service=Service( + user_id=int (user.id), + title=title, + description=description, + price=int(price), + time=time, + category=category, + img_url=image_url, + ) + + db.session.add(new_service) + db.session.commit() + + return jsonify({ + 'msj': 'Servicio creado exitosamente', + 'result': new_service.serialize()}), 201 + + + + except Exception as e: + return jsonify({"error":f"Error interno del servidor: {str(e)}" + } + ) + + + + + +@api.route('/service/', methods=['GET']) +def get_single_service(service_id): + try: + servicio = Service.query.filter(Service.id == int(service_id)).first() + + if not servicio: + return jsonify({"msj":"No existe servicio"}), 400 + + + usuario = User.query.filter(User.id == int(servicio.user_id)).first() + + if not usuario: + return jsonify({"msj":"No existe usuario"}), 400 + + result={ + "description":servicio.serialize()["description"] , + "id": servicio.serialize()["id"] , + "img_url":servicio.serialize()["img_url"] , + "price":servicio.serialize()["price"], + "time":servicio.serialize()["time"], + "title": servicio.serialize()["title"], + "user": { + "id":usuario.serialize()["id"], + "name":usuario.serialize()["name"] + } + } + + + return jsonify({ + 'result': result}), 200 + + + except Exception as e: + return jsonify({ + "error":str(e) + }) + + +@api.route('/create-order', methods=['POST']) +@jwt_required() +def create_order(): + try: + print('Attempting to create new order...') + data = request.get_json() + + user_id = get_jwt_identity() + + user_dict = User.query.filter_by(id=user_id).first() + #print(user_dict.serialize()["name"]) + user_name= user_dict.serialize()["name"] + + + status = data.get("status") + is_payed = data.get("is_payed") + service_id = data.get("service_id") + + #user_id = data.get("user_id") + price = data.get("price") + + + required_fields = ["status", "is_payed","price","user_id","service_id"] + #required_fields = ["status", "is_payed","service_id","user_id"] + # missing_fields = [field for field in required_fields if not data.get(field)] + missing_fields = [field for field in required_fields if field not in data] + + if missing_fields: + return jsonify({ + "msj": f"faltan campos necesarios: {', '.join(missing_fields)}", + "result": [] + }), 400 + + #Buscar si existe user_id en db + usuario = User.query.filter_by(id=user_id).first() + + #service_user_name = data.get() + + if not usuario: + return jsonify({"msj":"Usuario no encontrado","result":[]}),400 + + #Buscar si existe service_id en db + servicio = Service.query.filter_by(id=service_id).first() + + + if not servicio: + return jsonify({"msj":"Servicio no encontrado","result":[]}),400 + + #nueva_orden=Order(status=status,is_payed=is_payed,price=price,service_id=service_id,user_id=user_id) + nueva_orden=Order(status=status,is_payed=is_payed,price=float(price),service_id=int(service_id),user_id=int(user_id),user_name=user_name) + + db.session.add(nueva_orden) + db.session.commit() + + return jsonify({"msg":"Craeda exitosamente","result": nueva_orden.serialize()}), 201 + + except Exception as e: + return jsonify({ + "error":str(e) + }) + + + +@api.route('/order', methods=['GET']) +@jwt_required() +def get_order(): + try: + orders = Order.query.filter_by(user_id=get_jwt_identity()).all() + + data = [order.serialize() for order in orders] + + def info(x): + service_id=x["service_id"] + freelance_user_id=Service.query.filter_by(id=service_id).first().serialize()["user_id"] + frelance_name=User.query.filter_by(id=freelance_user_id).first().serialize() + + + return { + "id":x["id"], + "freelance_name":frelance_name["name"], + "freelance_email":frelance_name["email"], + "freelance_phone":frelance_name["phone"], + "user_name":x["user_name"], + "price":x["price"], + "is_payed":x["is_payed"], + } + + raw= list(map(info, data)) + + return jsonify({"result": raw} ),200 + + except Exception as e: + return jsonify({ + "error":str(e) + }), + +@api.route('/freelance/', methods=['GET']) +def get_freelance(freelance_id): + try: + + user_dict = User.query.filter_by(id=freelance_id).first() + + if not user_dict: + return jsonify({"msj": "Freelance no encontrado", "result": []}), 404 + services = [service.serialize() for service in user_dict.services] + freelance_with ={ + "name": user_dict.name, + "service_title": user_dict.service_title, + "service_description": user_dict.service_description, + "profile_description": user_dict.profile_description, + "services": services + } + return jsonify({ + "result": freelance_with + }), 200 + + except Exception as e: + return jsonify({ + "error":str(e) + }), +# ruta para actualizr los datos solo de freelance +@api.route('/freelance', methods=['PUT']) +@jwt_required() +def update_freelance(): + try: + form_data = request.form + photo_profile= request.files.get('photo_profile') + photo_cover =request.files.get('photo_cover') + + user_id = get_jwt_identity() + current_user = User.query.filter_by(id=user_id).first() + + if not current_user: + return jsonify({"msj": "Perfil de Freelance no encontrado", "result": []}), 404 + + + + if photo_profile: + + current_user.img_url = photo_uploader(photo_profile) + if photo_cover: + + current_user.cover_img_url = photo_uploader(photo_cover) + + current_user.name =form_data.get("name", current_user.name) + current_user.last_name =form_data.get("last_name", current_user.last_name) + current_user.email =form_data.get("email", current_user.email) + current_user.phone =form_data.get("phone", current_user.phone) + current_user.address =form_data.get("address", current_user.address) + current_user.service_title =form_data.get("service_title", current_user.service_title) + current_user.service_description =form_data.get("service_description", current_user.service_description) + current_user.profile_description =form_data.get("profile_description", current_user.profile_description) + + + db.session.commit() + + return jsonify({ + "msj": "Freelance actualizado correctamente", + "result": current_user.serialize() + }), 200 + + except Exception as e: + print(f"Error al actualizar freelance: {e}") + return jsonify({ + "error": f"Error interno del servidor: {str(e)}" + }), 500 + + +# ruta para actualizar solo los datos del user + + + +@api.route('/sign-up', methods=['POST']) +def sign_up_img(): + try: + data=request.form + + name = request.form.get('name') + last_name = request.form.get('last_name') + email = request.form.get('email') + password = request.form.get('password') + rol = request.form.get('rol') + address = request.form.get('address') + phone = request.form.get('phone') + + photo = request.files.get('photo') + + required_fields = ["name", "last_name", "email", "password", "phone", "rol","address"] + missing_fields = [field for field in required_fields if not data.get(field)] + + if missing_fields: + return jsonify({ + "msj": f"Faltan campos necesarios: {', '.join(missing_fields)}", + "result": [] + }), 400 + + if not photo : + photo=IMAGE_PROFILE_URL + else: + + photo=photo_uploader(photo) + + existe_usuario = User.query.filter(User.email == email).first() + + if existe_usuario: + return jsonify({"msj":"Correo ya registrado","result":[]}),400 + + password_hasheada=bcrypt.generate_password_hash(password).decode("utf-8") + nuevo_usuario=User(name=name,last_name=last_name,email=email,password=password_hasheada,phone=phone ,rol=rol,address=address,img_url=photo) + + db.session.add(nuevo_usuario) + db.session.commit() + + return jsonify({ + "msj":"Usuario creado correctamente", + "result":nuevo_usuario.serialize() + }), 200 + + except Exception as e: + return jsonify({ + "msj":"Problemas al crear el usuario", + "error":str(e) + }) + +#/favorite/check +@api.route("/favorite/check", methods=['POST']) +@jwt_required() +def check_favorite(): + try: + # Obtener el ID del usuario + current_user_id = get_jwt_identity() + data = request.get_json() + + # Validar que se haya proporcionado un favorite_id + favorite_id = data.get('favorite_id') + + # Obtener los usuarios involucrados + user = User.query.get(current_user_id) + favorite_user = User.query.get(favorite_id) + + # Verificar si los usuarios existen + if not user or not favorite_user: + return jsonify({"error": "Usuario o favorito no encontrado"}), 404 + + # Verificar si el usuario está en la lista de favoritos + is_favorite = user.tiene_favorito(favorite_user) + + return jsonify({ + "result": is_favorite, + "message": "El usuario está en la lista de favoritos" if is_favorite else "El usuario no está en la lista de favoritos" + }), 200 + + except Exception as e: + return jsonify({"error": str(e)}), 500 + +@api.route('/favorites/all', methods=['GET']) +@jwt_required() +def get_user_favorites(): + user_id = int(get_jwt_identity()) + print(user_id) + if user_id <= 0: + return jsonify({"error": "El ID del usuario debe ser un número positivo"}), 400 + + user = User.query.get(int(user_id)) + if not user: + return jsonify({"error": f"Usuario con ID {user_id} no encontrado"}), 404 + + def nombre(objeto): + data = objeto.serialize() + name = data["name"] + last_name = data["last_name"] + id = data["id"] + return {"name": name,"last_name": last_name,"id": id} + + favorites = [nombre(fav) for fav in user.favoritos_agregados] + + + print(favorites) + + return jsonify({ + "result": favorites + }), 200 + + +@api.route('/favorite/change', methods=['POST']) +@jwt_required() +def create_favorite(): + try: + current_user_id = get_jwt_identity() + print(current_user_id) + data = request.get_json() + print(data) + favorite_status = data.get('favorite_status') + + favorite_id = data.get('favorite_id') + + user = User.query.get(current_user_id) + favorite_user = User.query.get(favorite_id) + print(user.serialize()) + print(favorite_user.serialize()) + + if not user or not favorite_user: + return jsonify({"error": "Usuario o favorito no encontrado"}), 404 + print(favorite_status) + print(favorite_id) + print(favorite_status == False) + if favorite_status == False: + # Verificar si el favorito ya existe + #print("A punto de agregar1") + #print(user.favoritos_agregados) + #print("A punto de agregar2") + if favorite_user in user.favoritos_agregados: + return jsonify({"message": "El usuario ya está en la lista de favoritos"}), 200 + + #print("A punto de agregar3") + # Agregar el favorito + user.agregar_favorito(favorite_user) + db.session.commit() + return jsonify({ + "result": True, + "message": "Favorito agregado correctamente" + # "favoritos": [fav.id for fav in user.favoritos_agregados] + }),201 + else: + #print("A punto de eliminar") + # Eliminar el favorito + user.eliminar_favorito(favorite_user) + db.session.commit() + return jsonify({ + "result": False, + "message": "Favorito eliminado correctamente" + }),200 + + + except Exception as e: + db.session.rollback() + + return jsonify({"error": str(e)}), 500 + + + +@api.route("/comment/delete",methods=["DELETE"]) +@jwt_required() +def delete_comment(): + try: + data = request.get_json() + user_id=int(get_jwt_identity()) + + # freelance_id=int(data.get("freelance_id")) + comment_id=int(data.get("comment_id")) + + user=User.query.filter_by(id=user_id).first() + if not user: + return jsonify({"msj": "No se encuentra el usuario"}), 400 + + # freelance=User.query.filter_by(id=freelance_id).first() + # if not freelance: + # return jsonify({"msj": "No se encuentra el freelance"}), 400 + + comment=Comment.query.filter_by(id=comment_id).first() + # comment_length=len(user.comments_made) + + # if comment_length == 0: + # return jsonify({"msj": "No hay commentarios que borrar"}), 400 + + if not comment: + return jsonify({"msj": "No se encuentra el comentario"}), 400 + + db.session.delete(comment) + db.session.commit() + + return jsonify({ + "msj":"Comentario eliminado correctamente", + "result":comment.serialize() + }),200 + + except Exception as e: + db.session.rollback() + return jsonify({"msj": str(e)}), 500 + +@api.route("/comment/add",methods=["POST"]) +@jwt_required() +def add_comment(): + try: + data = request.get_json() + user_id=int(get_jwt_identity()) + text=data.get("text") + stars=data.get("stars") + freelance_id=int(data.get("freelance_id")) + print(data,user_id) + + user=User.query.filter_by(id=user_id).first() + + if not user: + return jsonify({"msj": "No se encuentra el usuario"}), 400 + freelance=User.query.filter_by(id=freelance_id).first() + if not freelance: + return jsonify({"msj": "No se encuentra el freelance"}), 400 + + comment = Comment( + text=text, + stars=stars, + user_id=user_id, + freelance_id=freelance_id + ) + + db.session.add(comment) + db.session.commit() + + return jsonify({ + "msj":"Comentario creado correctamente", + "result":comment.serialize() + }),200 + + except Exception as e: + db.session.rollback() + return jsonify({"msj": str(e)}), 500 + +@api.route("/comment/user",methods=["GET"]) +@jwt_required() +def get_comment_user(): + try: + user_id=int(get_jwt_identity()) + user=User.query.filter_by(id=user_id).first() + comments_len=len(user.comments_made) + + if comments_len ==0: + return jsonify({"msj": "No se han hecho comentarios","result":[]}), 200 + + if not user: + return jsonify({"msj": "No se encuentra el usuario","result":[]}), 400 + + def extrat_data(com): + data=com.serialize() + author=User.query.filter_by(id=data["user_id"]).first().serialize() + autor_full_name=author["name"]+" "+ author["last_name"] + autor_img_url=author["img_url"] + result= { + "id":data["id"], + "text":data["text"], + "stars":data["stars"], + "author_full_name":autor_full_name, + "author_img_url":autor_img_url + } + return result + + comentarios_hechos =[extrat_data(com) for com in user.comments_made] + + return jsonify({ + "msj":"Comentarios", + "result":comentarios_hechos + }),200 + + except Exception as e: + db.session.rollback() + return jsonify({"error": str(e)}), 500 + + +@api.route("/comment/freelance/",methods=["GET"]) +def get_comment_freelance(freelance_id): + try: + # user_id=int(get_jwt_identity()) + user=User.query.filter_by(id=freelance_id).first() + + if not user: + return jsonify({"msj": "No se encuentra el usuario"}), 400 + + def extrat_data(com): + data=com.serialize() + author=User.query.filter_by(id=data["user_id"]).first().serialize() + autor_full_name=author["name"]+" "+ author["last_name"] + autor_img_url=author["img_url"] + result= { + "id":data["id"], + "text":data["text"], + "stars":data["stars"], + "author_full_name":autor_full_name, + "author_img_url":autor_img_url + } + return result -@api.route('/hello', methods=['POST', 'GET']) -def handle_hello(): + comentarios_recibidos =[extrat_data(com) for com in user.comments_received] + + return jsonify({ + "result":comentarios_recibidos + }),200 - response_body = { - "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request" - } + except Exception as e: + db.session.rollback() + return jsonify({"error": str(e)}), 500 - return jsonify(response_body), 200 diff --git a/src/app.py b/src/app.py index 0ea8351d5f..dddd380358 100644 --- a/src/app.py +++ b/src/app.py @@ -11,6 +11,10 @@ from api.admin import setup_admin from api.commands import setup_commands +from flask_jwt_extended import JWTManager +from flask_bcrypt import Bcrypt + + # from models import Person ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production" @@ -49,6 +53,11 @@ def handle_invalid_usage(error): # generate sitemap with all your endpoints +jwt = JWTManager(app) +bcrypt = Bcrypt(app) +app.config["JWT_SECRET_KEY"]=os.getenv('FLASK_APP_KEY') + + @app.route('/') def sitemap(): diff --git a/src/front/js/component/CommentBox.jsx b/src/front/js/component/CommentBox.jsx new file mode 100644 index 0000000000..9e7e1e369a --- /dev/null +++ b/src/front/js/component/CommentBox.jsx @@ -0,0 +1,117 @@ +import React, { useContext, useState, } from 'react' +import { Context } from "../store/appContext.js"; +import { useForm } from "react-hook-form" +import ImageLoader from './ImageLoader.jsx'; +import { toastExito, toastFallo } from './Toaster/toasterIndex.jsx'; +import { useNavigate } from 'react-router-dom'; + +const StarComponent = ({ value, stars, setStarsState }) => { + const [hover, setHover] = useState(false) + + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + onClick={() => setStarsState(value)} + style={{ cursor: "pointer" }} + className='d-flex justify-content-start align-content-center '> + stars ? "black" : "yellow", scale: hover ? "1.50" : "1" }} className="fa-solid fa-star m-1"> +
+ ) +} + +const CommentBox = ({freelance_id,setModalOpen}) => { + const { store, actions } = useContext(Context) + const [starsState, setStarsState] = useState(1) + const navigate=useNavigate() + const { + register, + handleSubmit, + watch, + formState: { errors }, + } = useForm() + + const MAX_CHARACTERS = 200 + const MIN_CHARACTERS = 0 + const msj = watch("text") + + const onSubmit = (data) =>{ + + actions.postComment({ + text:data.text, + stars:starsState, + freelance_id:freelance_id + }) + .then((res)=>{ + console.log(res) + toastExito(res.msj) + setModalOpen(false) + + }) + .catch((err)=>toastFallo(err.msj)) + .finally(()=> navigate("/ordenes")) + + } + + return ( +
+
+
+
+ + +
+ +

+ {`${store.userProfile.name} ${store.userProfile.last_name} `} +

+ + +
+
+
+
+
+ { + [1, 2, 3, 4, 5].map((elem) => ) + } +
+
+
+
+ + +
+ +
+ { + errors.text && errors.text.type === "required" && Esto debe de ser incluido. + } + { + errors.text && errors.text.type === "pattern" && Minimo un caracter. + } + { + errors.text && errors.text.type === "maxLength" && Sobrepasa el limite de carateres. + } +
+ +
+
+
+
+ ) +} + +export default CommentBox \ No newline at end of file diff --git a/src/front/js/component/CommentCard.jsx b/src/front/js/component/CommentCard.jsx new file mode 100644 index 0000000000..850c622f5a --- /dev/null +++ b/src/front/js/component/CommentCard.jsx @@ -0,0 +1,87 @@ +import React, { useContext, useEffect, useState } from 'react' +import ImageLoader from './ImageLoader.jsx' +import { Context } from '../store/appContext.js' +import { toastExito, toastFallo} from './Toaster/toasterIndex.jsx' + + +const StarComponentShell = ({ value }) => { + if (value) { + return ( +
+ +
+ ) + } + return ( +
+ +
+ ) +} + +const CommentCard = ({ userName, stars, img_url, text, id, deleteButton,setData }) => { + const {store,actions}=useContext(Context) + const miArray2 = Array(5).fill(0); + const resultado = miArray2.map((_, index) => + index < stars ? 1 : 0 + ); + + const deleteHandle=(id)=>{ + actions.deleteComment({comment_id:id}) + .then((res)=>{ + toastExito(res.msj) + actions.getAllCommentsMade().then((res)=>setData(res.result)) + }) + .catch((err)=>toastFallo(err.msj)) + } + + return ( +
+
+
+
+
+ +
+ +

+ {`${userName} `} +

+ +
+ { + deleteButton && + + } + + +
+
+
+
+ { + resultado.map((elem, index) => ) + } +
+

+ {text} +

+ +
+
+
+ ) +} + +export default CommentCard \ No newline at end of file diff --git a/src/front/js/component/CommentSection.jsx b/src/front/js/component/CommentSection.jsx new file mode 100644 index 0000000000..aeb9c58dcd --- /dev/null +++ b/src/front/js/component/CommentSection.jsx @@ -0,0 +1,51 @@ +import React,{useContext,useState,useEffect} from "react" +import CommentCard from '../component/CommentCard.jsx' +import { Context } from "../store/appContext.js"; + +const CommentSection = ({ freelance_id }) => { + const { store, actions } = useContext(Context) + const [data, setData] = useState({}); + const [isLoading,setIsLoading]=useState(true) + + useEffect(()=>{ + actions.getAllComments(freelance_id) + .then((res)=>{ + + setData(res.result) + }) + .finally(()=>{ + setIsLoading(false) + }) + },[]) + + if (isLoading==false) { + + return (
+

+ Comentarios. +

+
+ { + data?.length !== 0 ? + data?.map(({ author_full_name, id, stars, author_img_url,text }) => ) + : +
No hay comentarios
+ } + +
) + + } else { + return ( +
+
No hay comentarios
+
+ +
+ ) + + } + + +} + +export default CommentSection \ No newline at end of file diff --git a/src/front/js/component/DetalleDeOrden.jsx b/src/front/js/component/DetalleDeOrden.jsx new file mode 100644 index 0000000000..aa756b4c50 --- /dev/null +++ b/src/front/js/component/DetalleDeOrden.jsx @@ -0,0 +1,34 @@ +import React from "react" + +const DetalleDeOrden = ({service,price,fullName,description}) => { + + + const handlePago = ()=>{ + alert("Continuaremos con su pago, por favor espere un momento....") + } + + return ( +
+

Detallado de orden

+
+ +

Cliente: {fullName}

+

Freelance: {service}

+

Descripcion de Orden : {description}

+
+ {/*

Producto:

+

Pagina web

*/} +
+
+ {/*

Estado: pending

*/} +

Precio: {`$ ${price}`}

+
+ {/*
+ +
*/} +
+
+ ) +} + +export default DetalleDeOrden \ No newline at end of file diff --git a/src/front/js/component/FormularioFreelance.jsx b/src/front/js/component/FormularioFreelance.jsx new file mode 100644 index 0000000000..98d587137f --- /dev/null +++ b/src/front/js/component/FormularioFreelance.jsx @@ -0,0 +1,150 @@ +import React, { useContext, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { Context } from '../store/appContext'; + + +const DEFAULT_IMAGE_URL = "https://res.cloudinary.com/dph121s7p/image/upload/v1746471079/image_profile_placceholder_dfzbln.jpg"; +const FormularioFreelance = () => { + const { store, actions } = useContext(Context); + const navigate = useNavigate(); + const [user, setUser]= useState(store.userProfile); + const [photoFile, setPhotoFile] = useState(null); + const [photoPreview, setPhotoPreview] = useState(null); + + if (!user) return

Cargando datos del usuario...

; + + const handleChange = (e) => { + const { name, value } = e.target; + setUser({ ...user, [name]: value }); + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file){ + setPhotoFile(file); + setPhotoPreview(URL.createObjectURL(file)); + + } + + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const formData = new FormData() + formData.append("name", user.name); + formData.append("last_name", user.last_name); + formData.append("phone", user.phone); + formData.append("address", user.address); + formData.append("email", user.email); + + if (photoFile) { + formData.append("photo_profile", photoFile); + + } + await actions.updateFreelanceProfile(formData); + navigate(-1); + }; + + return ( +
+
+
+

Edita los datos de tu Perfil

+
+ Imagen de perfil + +
+
+ + + Sube una imagen de perfil (opcional) +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+ + + + +
+
+
+ ); +}; + +export default FormularioFreelance \ No newline at end of file diff --git a/src/front/js/component/FreelanceCard.jsx b/src/front/js/component/FreelanceCard.jsx new file mode 100644 index 0000000000..ce68d3ed08 --- /dev/null +++ b/src/front/js/component/FreelanceCard.jsx @@ -0,0 +1,26 @@ +import React from 'react' +import { useNavigate } from 'react-router-dom' + +const FreelanceCard = ({user_name,title,id}) => { + const navigate=useNavigate() + return ( +
+ alt image +
+
{user_name}
+

+ { + title + } +

+ + +
+
+ ) +} + +export default FreelanceCard \ No newline at end of file diff --git a/src/front/js/component/ImageLoader.jsx b/src/front/js/component/ImageLoader.jsx new file mode 100644 index 0000000000..5af5648661 --- /dev/null +++ b/src/front/js/component/ImageLoader.jsx @@ -0,0 +1,68 @@ +import React, { useState, useEffect } from 'react'; + +// Hook personalizado para manejar la carga de imágenes +const useImageLoader = (src) => { + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(false); + + useEffect(() => { + if (!src) return; + + const img = new Image(); + img.src = src; + + img.onload = () => { + setIsLoading(false); + setError(false); + }; + + img.onerror = () => { + setIsLoading(false); + setError(true); + }; + + return () => { + img.onload = null; + img.onerror = null; + }; + }, [src]); + + return { isLoading, error }; +}; + +// Componente principal +const ImageLoader = ({ + src, + alt = 'Imagen por defecto', + width = '100%', + height = 'auto' +}) => { + const { isLoading, error } = useImageLoader(src); + + if (error) { + return ( +
+ +
+ ); + } + + return ( +
+ {isLoading && ( +
+ +
+ )} + + {alt} +
+ ); +}; + +export default ImageLoader; \ No newline at end of file diff --git a/src/front/js/component/ModalCommponent.jsx b/src/front/js/component/ModalCommponent.jsx new file mode 100644 index 0000000000..2349353818 --- /dev/null +++ b/src/front/js/component/ModalCommponent.jsx @@ -0,0 +1,51 @@ +import React from 'react' +import CommentBox from "./CommentBox.jsx" +import { useNavigate } from 'react-router-dom'; + +const ModalCommponent = ({modalOpen,setModalOpen,freelance_id}) => { + const navigate=useNavigate() + + const styleModal={ + position: "absolute", + display: modalOpen?"flex": "none", + minWidth: "100vw", + minHeight: "100vh", + backgroundColor:"rgba(0, 0, 0, 0.5)", + boxSizing:"content-box", + zIndex:100, + justifyContent:"center", + alignItems:"center" + } + const styleButton={ + position:"absolute", + width: "50px", + height: "50px", + backgroundColor:"rgb(68, 64, 64)", + top:20, + right:20 + } + + return ( +
+
+ +
+
Agrega un comentario.
+ +
+
+ + +
+ ) +} + +export default ModalCommponent \ No newline at end of file diff --git a/src/front/js/component/NavbarLateral.jsx b/src/front/js/component/NavbarLateral.jsx new file mode 100644 index 0000000000..93ab55c22d --- /dev/null +++ b/src/front/js/component/NavbarLateral.jsx @@ -0,0 +1,91 @@ + +import React from 'react'; +import { Link } from 'react-router-dom'; + +import styles from "./navbarLateral.module.css" + + +const NavbarLateral = (props) => { + useEffect(() => { + const timer = setTimeout(() => { + setLoading(false) + }, 1000); + return () => clearTimeout(timer); + }, []) + + + return ( +
+ + + {/* Botón de colapso para dispositivos pequeños */} + +
+ ); +}; + +export default NavbarLateral; + + diff --git a/src/front/js/component/PrivateRoute.jsx b/src/front/js/component/PrivateRoute.jsx new file mode 100644 index 0000000000..08df1e88f7 --- /dev/null +++ b/src/front/js/component/PrivateRoute.jsx @@ -0,0 +1,20 @@ +import React, {useContext} from "react"; +import { Navigate } from "react-router-dom"; +import { Context } from "../store/appContext"; + + +const PrivateRoute =({children, allowedRoles }) =>{ + const {store} = useContext (Context); + const userRole = store.userProfile?.rol; + console.log(allowedRoles.includes(userRole)); + console.log(allowedRoles); + console.log("Is Authorized:", allowedRoles.includes(userRole)); + + // if (!userRole) return ; + if (userRole === "user") return ; + if (!allowedRoles.includes(userRole)) return ; + + return children; +}; + +export default PrivateRoute; \ No newline at end of file diff --git a/src/front/js/component/SearchBar.jsx b/src/front/js/component/SearchBar.jsx new file mode 100644 index 0000000000..393dc50326 --- /dev/null +++ b/src/front/js/component/SearchBar.jsx @@ -0,0 +1,45 @@ +import React, { useState, useEffect,useContext } from 'react' +import { useNavigate } from 'react-router-dom' +import styles from "./SearchBar.module.css" +import {Context} from "../store/appContext.js" +import { toastFallo } from './Toaster/toasterIndex.jsx' + +const SearchBar = () => { + const {store,actions}=useContext(Context) + const [terminoBuqueda, setTerminoBuqueda] = useState(store.terminoBusqueda) + const handleChange = (e) => { + setTerminoBuqueda(e.target.value) + } + + const handleKeyDown = (e) => { + + if (e.key === "Enter") { + actions.busquedaFreelancers(terminoBuqueda) + setTerminoBuqueda("") + } + }; + return ( +
+ +
{ + + actions.busquedaFreelancers(terminoBuqueda) + }} + style={{ width: "50px", height: "100%",cursor:"pointer" }} + className={`d-flex justify-content-center align-items-center `} + > + +
+
+ ) +} + +export default SearchBar \ No newline at end of file diff --git a/src/front/js/component/SearchBar.module.css b/src/front/js/component/SearchBar.module.css new file mode 100644 index 0000000000..b2c92d2352 --- /dev/null +++ b/src/front/js/component/SearchBar.module.css @@ -0,0 +1,24 @@ +.searchbar_container{ + box-sizing: content-box; + + width: 500px; + border-radius: 50px; + border: 2px solid black; +} +.searchbar_input{ + + width: 100%; + height: 50px; + border-radius: 50px; + padding-left: 30px; + border: none; +} +.searchbar_input:focus{ + outline: none; +} + +.searchbar_icon{ + border-radius: 50px; + + +} \ No newline at end of file diff --git a/src/front/js/component/Sidebar.jsx b/src/front/js/component/Sidebar.jsx new file mode 100644 index 0000000000..b721eb0eef --- /dev/null +++ b/src/front/js/component/Sidebar.jsx @@ -0,0 +1,116 @@ +import React, { use, useContext, useEffect, useState } from "react"; +import { useLocation } from "react-router-dom" +import { Context } from "../store/appContext.js"; + +// function deshabilitarScroll() { +// document.body.style.overflowX = 'hidden'; +// } + +// function habilitarScroll() { +// document.body.style.overflowX = 'auto'; +// } + +const Sidebar = () => { + const location = useLocation() + const { store, actions } = useContext(Context) + const sideBar = store.sidebarOpen + const [loading, setLoading] = useState(true) + + useEffect(() => { + const timer = setTimeout(() => { + + setLoading(false) + console.log('¡Terminado!'); + }, 1000); + + return () => clearTimeout(timer); + }, []) + + const userLinks = [ + { + id: 1, + name: "Ordenes", + pathName: "/ordenes" + }, + { + id: 2, + name: "Busqueda", + pathName: "/busqueda" + }, + ] + const freelanceLinks = [ + { + id: 1, + name: "Freeelance 1", + pathName: "/ordenes" + }, + { + id: 2, + name: "Freelance 2", + pathName: "/busqueda" + }, + ] + + + + + + + const handleSidebar = () => { + actions.toggleSideBar() + } + // useEffect(()=>{ + // if(sideBar==true){ + // deshabilitarScroll() + // }else{ + // habilitarScroll() + // } + // },[ + // store.sidebarOpen + // ]) + + + + const styleSidebar = { + width: "300px", + // height:"auto", + // minHeight:"100vh", + position: "absolute", + zIndex: 99, + left: sideBar ? 0 : -300, + bottom: 0, + top: 0 + } + + if (loading==true) { + return ( + + ) + } + + return ( + + ) + + +} +export default Sidebar \ No newline at end of file diff --git a/src/front/js/component/Tabla.jsx b/src/front/js/component/Tabla.jsx new file mode 100644 index 0000000000..494a04d7ca --- /dev/null +++ b/src/front/js/component/Tabla.jsx @@ -0,0 +1,74 @@ +import React from 'react' +import styles from "./Tabla.module.css" + +const Row = ({ data }) => { + // Definimos el orden exacto de las columnas + const columnOrder = ['id', 'freelance_name', 'user_name', 'price', 'is_payed',"freelance_email","freelance_phone"]; + + return ( + + {columnOrder.map(column => { + const valor = data[column]; + // Si es la columna id, usamos th, de lo contrario td + if (column === "id") { + return ( + + {`${valor}`} + + ); + } + return ( + + {`${valor}`} + + ); + })} + + ); +}; + +const Tabla = ({ lista }) => { + return ( + + + + + + + + + + + + + + {lista.map((item) => { + return ; + })} + +
+ id + + Nombre de freelance + + Nombre de consumidor + + precio + + status + + correo + + telefono +
+ ) +} + +export default Tabla \ No newline at end of file diff --git a/src/front/js/component/Tabla.module.css b/src/front/js/component/Tabla.module.css new file mode 100644 index 0000000000..623c7ffd08 --- /dev/null +++ b/src/front/js/component/Tabla.module.css @@ -0,0 +1,5 @@ +.tabla_td{ + + text-align: center; + border: 1px black solid; +} \ No newline at end of file diff --git a/src/front/js/component/Toaster/toasterIndex.jsx b/src/front/js/component/Toaster/toasterIndex.jsx new file mode 100644 index 0000000000..5b60377216 --- /dev/null +++ b/src/front/js/component/Toaster/toasterIndex.jsx @@ -0,0 +1,5 @@ +import React from "react" +import toast from "react-hot-toast"; + +export const toastExito=(mensaje)=>toast.success(mensaje,{duration:4000}); +export const toastFallo=(mensaje)=>toast.error(mensaje,{duration:4000}); \ No newline at end of file diff --git a/src/front/js/component/footer.js b/src/front/js/component/footer.js index 670323e091..1d3086fc79 100755 --- a/src/front/js/component/footer.js +++ b/src/front/js/component/footer.js @@ -3,8 +3,8 @@ import React, { Component } from "react"; export const Footer = () => (

- Made with by{" "} - 4Geeks Academy + Made with by{" Irvin, Fredy, Sebas "} +

); diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index af4b01e334..7ab73612b5 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -1,17 +1,104 @@ -import React from "react"; -import { Link } from "react-router-dom"; +import React, { useEffect, useContext } from "react"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { Context } from "../store/appContext.js"; + +//styles +import styles from "./navbar.module.css" + export const Navbar = () => { + const { store, actions } = useContext(Context) + + const handleSidebar = () => { + actions.toggleSideBar() + } + + const navigate = useNavigate() + + const handleLogOut = () => { + actions.logOut() + navigate("/") + } + return ( -