From 5c8fbaa159560ae1d9d505935a34114bc0762b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jennifer=20Fern=C3=A1ndez?= Date: Fri, 28 Feb 2025 17:51:28 +0000 Subject: [PATCH 01/79] first commit home --- Pipfile.lock | 654 +++++++++++++++------------ migrations/README | 1 + migrations/alembic.ini | 50 ++ migrations/env.py | 113 +++++ migrations/script.py.mako | 24 + migrations/versions/2e3a70a695d4_.py | 35 ++ package-lock.json | 16 + package.json | 1 + src/front/js/component/card.js | 21 + src/front/js/component/footer.js | 21 +- src/front/js/component/navbar.js | 52 ++- src/front/js/pages/Producto.js | 0 src/front/js/pages/home.js | 52 ++- src/front/styles/home.css | 2 +- 14 files changed, 708 insertions(+), 334 deletions(-) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/2e3a70a695d4_.py create mode 100644 src/front/js/component/card.js create mode 100644 src/front/js/pages/Producto.js diff --git a/Pipfile.lock b/Pipfile.lock index a391864e9d..3c146e5b23 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "74f92d76f687bb774828613a3a513123fe2ffdb429b95b351d29721dddfd3fb8" + "sha256": "4f0e9a772f04b621ff0313b7ecfa468af1526aa27df8bfcacac6955d499d352d" }, "pipfile-spec": 6, "requires": { @@ -18,73 +18,91 @@ "default": { "alembic": { "hashes": [ - "sha256:6880dec4f28dd7bd999d2ed13fbe7c9d4337700a44d11a524c0ce0c59aaf0dbd", - "sha256:e8a6ff9f3b1887e1fed68bfb8fb9a000d8f61c21bdcc85b67bb9f87fcbc4fce3" + "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5", + "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213" ], - "markers": "python_version >= '3.7'", - "version": "==1.9.2" + "markers": "python_version >= '3.8'", + "version": "==1.14.1" + }, + "blinker": { + "hashes": [ + "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", + "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" + ], + "markers": "python_version >= '3.9'", + "version": "==1.9.0" }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2025.1.31" }, "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:ba223705409b2aaddd5196c2184d65f50a83dffcba3b94f3727658ff6a0172a3", + "sha256:e4191b470c5bae55542b64e0a78659af42971880294456dca480bc974fa9280a" ], "index": "pypi", - "version": "==1.31.0" + "version": "==1.42.2" }, "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-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 +114,346 @@ }, "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:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", + "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7", + "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", + "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", + "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", + "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", + "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", + "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", + "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", + "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa", + "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", + "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", + "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", + "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22", + "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9", + "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", + "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba", + "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", + "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", + "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", + "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291", + "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", + "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", + "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", + "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", + "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef", + "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c", + "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", + "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", + "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", + "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", + "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8", + "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d", + "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", + "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", + "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", + "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", + "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", + "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", + "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1", + "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef", + "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", + "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", + "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", + "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", + "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd", + "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981", + "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", + "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", + "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798", + "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", + "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", + "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", + "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", + "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af", + "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", + "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", + "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", + "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e", + "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81", + "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", + "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", + "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc", + "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de", + "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111", + "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", + "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", + "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", + "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", + "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", + "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803", + "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", + "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f" ], "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.1.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:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", + "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2" + "version": "==3.1.5" }, "mako": { "hashes": [ - "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818", - "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34" + "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", + "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac" ], - "markers": "python_version >= '3.7'", - "version": "==1.2.4" + "markers": "python_version >= '3.8'", + "version": "==1.3.9" }, "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:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + ], + "markers": "python_version >= '3.8'", + "version": "==24.2" }, "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:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", + "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" ], - "markers": "python_version >= '3.6'", - "version": "==6.0" + "index": "pypi", + "version": "==1.0.1" }, - "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 +504,35 @@ }, "typing-extensions": { "hashes": [ - "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", - "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], "index": "pypi", - "version": "==4.4.0" + "version": "==4.12.2" }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", + "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" ], - "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.3.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/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/2e3a70a695d4_.py b/migrations/versions/2e3a70a695d4_.py new file mode 100644 index 0000000000..085a84a3f5 --- /dev/null +++ b/migrations/versions/2e3a70a695d4_.py @@ -0,0 +1,35 @@ +"""empty message + +Revision ID: 2e3a70a695d4 +Revises: +Create Date: 2025-02-28 10:57:49.125716 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2e3a70a695d4' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('password', sa.String(length=80), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + # ### end Alembic commands ### diff --git a/package-lock.json b/package-lock.json index c932d7fc55..f0dfb6daf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "prop-types": "^15.6.1", "react": "^16.8.4", "react-dom": "^16.8.4", + "react-icons": "^5.5.0", "react-polyfills": "0.0.1", "react-router-dom": "^6.3.0" }, @@ -7620,6 +7621,15 @@ "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", "dev": true }, + "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", @@ -15006,6 +15016,12 @@ "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", "dev": true }, + "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..540e682ff1 100755 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "prop-types": "^15.6.1", "react": "^16.8.4", "react-dom": "^16.8.4", + "react-icons": "^5.5.0", "react-polyfills": "0.0.1", "react-router-dom": "^6.3.0" } diff --git a/src/front/js/component/card.js b/src/front/js/component/card.js new file mode 100644 index 0000000000..f712ae7578 --- /dev/null +++ b/src/front/js/component/card.js @@ -0,0 +1,21 @@ +import React, { Component } from "react"; + +export const Card = () => { + return ( +
+ ... +
+
Card title
+

Some quick example text to build on the card.

+
+ + {/*
+ Card link + Another link +
*/} +
+); +} \ No newline at end of file diff --git a/src/front/js/component/footer.js b/src/front/js/component/footer.js index 670323e091..b767ac03d8 100755 --- a/src/front/js/component/footer.js +++ b/src/front/js/component/footer.js @@ -1,10 +1,15 @@ import React, { Component } from "react"; -export const Footer = () => ( - -); +export const Footer = () => { + return ( +
+
+ Footer +
+
+
Special title treatment
+

With supporting text below as a natural lead-in to additional content.

+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index af4b01e334..2728e63577 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -1,19 +1,43 @@ import React from "react"; import { Link } from "react-router-dom"; +import { CiSearch } from "react-icons/ci"; + + export const Navbar = () => { - return ( - - ); + return ( + + ); }; diff --git a/src/front/js/pages/Producto.js b/src/front/js/pages/Producto.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/front/js/pages/home.js b/src/front/js/pages/home.js index 7b0be46a1e..d96fe50de3 100755 --- a/src/front/js/pages/home.js +++ b/src/front/js/pages/home.js @@ -1,26 +1,50 @@ import React, { useContext } from "react"; import { Context } from "../store/appContext"; -import rigoImageUrl from "../../img/rigo-baby.jpg"; +import { Card } from "../component/card"; import "../../styles/home.css"; export const Home = () => { const { store, actions } = useContext(Context); return ( -
-

Hello Rigo!!

-

- -

-
- {store.message || "Loading message from the backend (make sure your python backend is running)..."} + +
+
{/* Centra la imagen */} + Banner +
+
+

Productos top para perros

+
+ + + + +
+
+ +
+

Productos top para gatos

+
+ + + + +
+
+ +
+

Productos top para animales exóticos

+
+ + + + +
-

- This boilerplate comes with lots of documentation:{" "} - - Read documentation - -

); }; diff --git a/src/front/styles/home.css b/src/front/styles/home.css index 194b236ac4..757b881b2d 100755 --- a/src/front/styles/home.css +++ b/src/front/styles/home.css @@ -4,4 +4,4 @@ All pages share the styles on index.css but you should create one more css for each page that will contain the selected used on that page only (the ones not reused in other pages). -*/ \ No newline at end of file +*/ From 44c01a167ecef25301c6e0f2a43729f1729e8f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jennifer=20Fern=C3=A1ndez?= Date: Sat, 1 Mar 2025 11:04:24 +0000 Subject: [PATCH 02/79] first commit vista-producto --- Pipfile.lock | 654 +++++++++++++++------------ migrations/README | 1 + migrations/alembic.ini | 50 ++ migrations/env.py | 113 +++++ migrations/script.py.mako | 24 + migrations/versions/ba227b582218_.py | 35 ++ src/front/js/component/producto.js | 62 +++ src/front/js/layout.js | 2 + src/front/js/pages/VistaProducto.js | 34 ++ 9 files changed, 678 insertions(+), 297 deletions(-) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/ba227b582218_.py create mode 100644 src/front/js/component/producto.js create mode 100644 src/front/js/pages/VistaProducto.js diff --git a/Pipfile.lock b/Pipfile.lock index a391864e9d..3c146e5b23 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "74f92d76f687bb774828613a3a513123fe2ffdb429b95b351d29721dddfd3fb8" + "sha256": "4f0e9a772f04b621ff0313b7ecfa468af1526aa27df8bfcacac6955d499d352d" }, "pipfile-spec": 6, "requires": { @@ -18,73 +18,91 @@ "default": { "alembic": { "hashes": [ - "sha256:6880dec4f28dd7bd999d2ed13fbe7c9d4337700a44d11a524c0ce0c59aaf0dbd", - "sha256:e8a6ff9f3b1887e1fed68bfb8fb9a000d8f61c21bdcc85b67bb9f87fcbc4fce3" + "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5", + "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213" ], - "markers": "python_version >= '3.7'", - "version": "==1.9.2" + "markers": "python_version >= '3.8'", + "version": "==1.14.1" + }, + "blinker": { + "hashes": [ + "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", + "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" + ], + "markers": "python_version >= '3.9'", + "version": "==1.9.0" }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2025.1.31" }, "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:ba223705409b2aaddd5196c2184d65f50a83dffcba3b94f3727658ff6a0172a3", + "sha256:e4191b470c5bae55542b64e0a78659af42971880294456dca480bc974fa9280a" ], "index": "pypi", - "version": "==1.31.0" + "version": "==1.42.2" }, "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-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 +114,346 @@ }, "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:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", + "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7", + "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", + "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", + "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", + "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", + "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", + "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", + "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", + "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa", + "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", + "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", + "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", + "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22", + "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9", + "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", + "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba", + "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", + "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", + "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", + "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291", + "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", + "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", + "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", + "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", + "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef", + "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c", + "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", + "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", + "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", + "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", + "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8", + "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d", + "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", + "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", + "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", + "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", + "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", + "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", + "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1", + "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef", + "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", + "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", + "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", + "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", + "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd", + "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981", + "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", + "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", + "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798", + "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", + "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", + "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", + "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", + "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af", + "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", + "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", + "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", + "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e", + "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81", + "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", + "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", + "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc", + "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de", + "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111", + "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", + "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", + "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", + "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", + "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", + "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803", + "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", + "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f" ], "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.1.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:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", + "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2" + "version": "==3.1.5" }, "mako": { "hashes": [ - "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818", - "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34" + "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", + "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac" ], - "markers": "python_version >= '3.7'", - "version": "==1.2.4" + "markers": "python_version >= '3.8'", + "version": "==1.3.9" }, "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:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + ], + "markers": "python_version >= '3.8'", + "version": "==24.2" }, "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:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", + "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" ], - "markers": "python_version >= '3.6'", - "version": "==6.0" + "index": "pypi", + "version": "==1.0.1" }, - "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 +504,35 @@ }, "typing-extensions": { "hashes": [ - "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", - "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], "index": "pypi", - "version": "==4.4.0" + "version": "==4.12.2" }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", + "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" ], - "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.3.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/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/ba227b582218_.py b/migrations/versions/ba227b582218_.py new file mode 100644 index 0000000000..d8c4bdb055 --- /dev/null +++ b/migrations/versions/ba227b582218_.py @@ -0,0 +1,35 @@ +"""empty message + +Revision ID: ba227b582218 +Revises: +Create Date: 2025-03-01 09:14:08.397142 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ba227b582218' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('password', sa.String(length=80), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + # ### end Alembic commands ### diff --git a/src/front/js/component/producto.js b/src/front/js/component/producto.js new file mode 100644 index 0000000000..61630db4e9 --- /dev/null +++ b/src/front/js/component/producto.js @@ -0,0 +1,62 @@ +import React, { Component, useState } from "react"; + + +export const Producto = () => { + const [precio, setPrecio] = useState(29.99); // Precio inicial + + const handleFormatoChange = (e) => { + const selectedOption = e.target.options[e.target.selectedIndex]; + const nuevoPrecio = selectedOption.dataset.precio; + + // Actualizar el precio si se seleccionó un formato + if (nuevoPrecio) { + setPrecio(nuevoPrecio); + } else { + setPrecio(29.99); // Precio por defecto + } + }; + return ( + + +
+
+
+
+
+
+ Producto +
+
+
+
Nombre del Producto
+
+
+

{precio}€

+
+ + +
+
+

Descripción breve del producto que está en venta. Aquí puedes incluir características y beneficios. + Lorem Ipsum es simplemente el texto de relleno de las imprentas y archivos de texto. Lorem Ipsum ha sido el texto de relleno estándar de las industrias desde el año 1500, cuando un impresor (N. del T. persona que se dedica a la imprenta). +

+
+ +
+
+
+
+
+
+
+
+ + + + ); +} \ No newline at end of file diff --git a/src/front/js/layout.js b/src/front/js/layout.js index d42289f0ee..b1428ef531 100755 --- a/src/front/js/layout.js +++ b/src/front/js/layout.js @@ -10,6 +10,7 @@ import injectContext from "./store/appContext"; import { Navbar } from "./component/navbar"; import { Footer } from "./component/footer"; +import { VistaProducto } from "./pages/VistaProducto"; //create your first component const Layout = () => { @@ -26,6 +27,7 @@ const Layout = () => { } path="/" /> + } path="/vista-producto" /> } path="/demo" /> } path="/single/:theid" /> Not found!} /> diff --git a/src/front/js/pages/VistaProducto.js b/src/front/js/pages/VistaProducto.js new file mode 100644 index 0000000000..ae0cebb352 --- /dev/null +++ b/src/front/js/pages/VistaProducto.js @@ -0,0 +1,34 @@ +import React, { Component, useState } from "react"; +import { Producto } from "../component/producto"; + + +export const VistaProducto = () => { + + return ( + + +
+ +
+
+

Productos similares

+
+ {/* AQUI IRIAN LAS CARDS + + + + */} + +
+
+
+
+
+
+ +
+ + + + ); +} \ No newline at end of file From a627d1bae1a227670ee2a6c227d3b6f5942e3572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jennifer=20Fern=C3=A1ndez?= Date: Sat, 1 Mar 2025 11:40:50 +0000 Subject: [PATCH 03/79] second commit home --- src/api/routes.py | 1 + src/front/js/component/card.js | 24 ++++++++++++------------ src/front/js/component/navbar.js | 2 +- src/front/js/pages/Producto.js | 0 4 files changed, 14 insertions(+), 13 deletions(-) delete mode 100644 src/front/js/pages/Producto.js diff --git a/src/api/routes.py b/src/api/routes.py index 029589a3a1..45d8de4e19 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -20,3 +20,4 @@ def handle_hello(): } return jsonify(response_body), 200 + diff --git a/src/front/js/component/card.js b/src/front/js/component/card.js index f712ae7578..67d45a0d3d 100644 --- a/src/front/js/component/card.js +++ b/src/front/js/component/card.js @@ -2,20 +2,20 @@ import React, { Component } from "react"; export const Card = () => { return ( -
- ... -
-
Card title
+
+ ... +
+
Título

Some quick example text to build on the card.

-
    -
  • An item
  • -
  • A second item
  • -
- {/* */} +
+ +
); } \ No newline at end of file diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index 2728e63577..028c97b84b 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -6,7 +6,7 @@ import { CiSearch } from "react-icons/ci"; export const Navbar = () => { return ( -
+ +
); }; diff --git a/src/front/js/store/flux.js b/src/front/js/store/flux.js index cc56951a22..b6d75a1234 100755 --- a/src/front/js/store/flux.js +++ b/src/front/js/store/flux.js @@ -13,7 +13,10 @@ const getState = ({ getStore, getActions, setStore }) => { background: "white", initial: "white" } - ] + ], + dogFood: [], + catFood: [] , + exoticFood: [] }, actions: { // Use getActions to call a function within a fuction @@ -21,18 +24,103 @@ const getState = ({ getStore, getActions, setStore }) => { getActions().changeColor(0, "green"); }, + getDogFood: async () => { + const myHeaders = new Headers(); + myHeaders.append("Cookie", ".Tunnels.Relay.WebForwarding.Cookies=CfDJ8Cs4yarcs6pKkdu0hlKHsZs0q_CxPIxRcsYOazLvQz4rP7s5FWFmvGJndFqy0N7fvoY5B6Jou5i4ZPgwsQZsEYGDV4DoaNhJP3xwIvv1aGmoRIVdScF1G2c_hWBWqeCTHFNCvGD1Dy0sm3kBmaNdXiMsSO0myHKUFvlWHCed2AdtyCiC6CHBqk9DHs32cYjJV4GQr4cxW2IXl6QDukWwCPSYuzTnP699Rz_4pCbB8OPOQNBDyDtdks_LUoMZR2Qt6IWKmUnLGt-n3JLFjeQMZiSeKEXKNTcJknrz4p25p9-5rh3BY2FBX_kg6MtH3cLbqOyS6yqrG4cjJPpyZbVfN3iEYSR6lzEGiGZDFPvokcj_PM8fq32HR1_olrhti8nYtDctNR_8YRewW5quhBNW6mtF_-SqGTbCQVH1CiLUF2UKK_H_nGmwvWAce2n4Cdw1BLUCZhlCr3GAKWJWHqLI1K5n9OekmI4zs0TI_60R6urTRxIQx2IgkRPYizg-AUdyr6bORhYr7s3c6oFBrdA4yBShqyJFOo4fuMkQuHflmg717cZeB1MDnWgSm9Xnl4qmOlcta0fCSq15GNUPvXhAwvclIoK9NTrmSSd1wvRhoqz7ypodxTSOafQx0ybhJZwTxDeS_gv-4KjbyFngwj7Bj1TluB2qE5Hijbi4uhZb2KILE9AGYHKWSW-IWoSmEXW71c7HhH8mBEbBxidpsSi_Rip3CdXL2oUO-8TGtLx2HgJtExj_7AyTD1trSanlKgDurPSEfDuXuwtqxawfWf0a1sbp9BGk0pRLOA-tVKrxmMiFAPiNCVC1W2EXb95TKydzKIwtbcC70YyDJ4dFwnHrQmgPYxzIz7PYzICzKqZ-VDpO4lf7C3jf4OBJ9ZMV4JRvPiR2kUwMNX_c5CuMLaKrZNzqCFwZBUa4N4AUyTT83mtzFQGAZjMXDeZNok7mjTYBp121qquiSKX8ft05b1MTsVtRfg3ETiFdOHmYQ2-1EcSwBY-VhjWIlkQiFr1yBWXk-g"); + + const requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow" + }; + + try { + const response = await fetch("https://silver-meme-jj4x947jg76ghqjg9-3001.app.github.dev/api/foods/dog", requestOptions); + + if (!response.ok) { + throw new Error('Network response was not ok'); + } + + const data = await response.json(); + setStore({ dogFood: data }); // Asegúrate de que data.result sea la estructura correcta + console.log(data); + } catch (error) { + console.error('Error fetching dog food:', error); + } + } + + + + + , + getCatFood: async ()=>{ + + const myHeaders = new Headers(); + myHeaders.append("Cookie", ".Tunnels.Relay.WebForwarding.Cookies=CfDJ8Cs4yarcs6pKkdu0hlKHsZsFuWJUB8pT978IgBH2YtuNtjrSfloWcBx8Cb4ny9waQSc6YIxDXdFsnq7Re1iGbqt37RMJ8vfb6ibKLFqvloDFsbiBuxDcqwi_MvYRhr2Z463usLkj3RbhHXQfm0TDnuAtNaE3deMSqRX9yv4T0Ui6indSlqpCNa-EX0tvUPEUYniNtvBPGilr_PC-U4inRAN5RaUZvcfD9vs4fenjYjUAaDpz49MnUZT8uGDozBeAZynLSYkXtpY9Rb2GuG-eOvk-8noe3tIekVKBOTjnaeis5QUJJpYcv73D6GOrYSIpavdb29mY6QWEsir-s4vxW2wE0dCb8n5g4MldUigDSxrKnndHoBleQih8VqWGo5AZKTYLT3Y735A7ZsmFGvFjRGrrGCPQ6YU28RddH4ieKxoVlTt2PG2uD17_QI5FmUNuvz3Z8YDAK878jEXK-_jXm5SIi4oLvfCr9sezmoc8NgcvJBAH0lVlJCf3kz9Z7_y_5wwU9a4Nk6-rH2p__Jb49iHxfOuRu0EzOLdNHiPfznR8iEp41VsK7ie7ECmM5Q6SANKFM9AtYkbXqCmn5tAdmrCFaGKNAu8w7gZ_r97mDMzcHVOwUktErNC1y2j6yUPv6SAU9hVk4_xFev-2wI7h5PEaPCY3khPgI6Z1UPNC_SkodFGHbEdRUSMc8ICYoAv4llTD8xtfOqzMDms4HG1xcbJrE1NTWh4rpfv7KiEIi6iqE6uizX7_CmWJkcA9SGig9hk9A9abtYb2Pg8Z8GG2cEan7FFpl7kbvTobFfSGn_N9ukSCqYuuoldgSno8yyH5P3E7oqqVnO1eT3kpRGZ9EdtmL5zXv8Fs-x3GDrQ2z1_V6sqMxy6LlXHXJ7m6MYUy4sET4nISyUJ1H2eBbC-Ttk7_7om7ByATu_oEZjPFefBf7sXaCZ2ng9ZBYYOOZHtbUEs2rrFAiL6dmrgjKVaXUyz3KiiP73Dy5OR7g9ccYBD5t74jdyVYPLmgoasQocdBEA"); + + const requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow" + }; + + try { + const response = await fetch("https://silver-meme-jj4x947jg76ghqjg9-3001.app.github.dev/api/foods/cat", requestOptions); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + + const data = await response.json(); + setStore({ catFood: data }); + console.log(data); + } catch (error) { + console.error('Error fetching cat food:', error); + } + } + , + + getExoticFood: async ()=>{ + const myHeaders = new Headers(); + myHeaders.append("Cookie", ".Tunnels.Relay.WebForwarding.Cookies=CfDJ8Cs4yarcs6pKkdu0hlKHsZuqp5-e7T1vVCCdEQ0C1wStd1vPjajnZKjnPSA5Hq6rqD8KnBNHzrxCrSBqwnlunA0rkm36DsRzpvbpKLHVNklCfX8GBsMZKstrANOENBUzsSi1PBxW5_TibtPeRePzgNe5pBHuYx5F-1zm7-FoplLOZKZUFeRu1ua1DgiPeOiqvYSnKMQISiWoLb64DlQlSfXIGkFzVDFsFNPJ5lN-NHmI9dQ4O8b8EtoLAvXug1GQ5WkthHTcarTf_lXY5fcoEJbvhObGkUw7LC07jfKn2BTk27bZGx_F2kXNKq2N32CJ_46MMiulhV3sVpuuBUenZ-P7eLMcyMYuz6LcVrybPlrmHBiCnIbgcmgn-jnL-P3rcWL49L8RpPU84Ul4CaOZTFZ7eFCUqa1m1YKjX21YEdJlEm8fyYpFYcFp_9mGziKaxChcel7DevH4J4NV-E1mEUnQP-wDBMVq1IH0wdb4_HSnZ8aBhyyb8GvuEuLIhlH4soY8xtKNlaM73kynXCEhJ2OH-eKM6XIkWQHVIENwRoWzjj8G-8jQcJjz1Clyo9lMuFB98AWfsJgqe_PO7PgIw_Ms8_hRKyGG_aej9UuBo8rS-u4vTGaj5v_1vp6_PrZ-qEp8iyGGK_0OERl5OH0S3_mcXOd1wQcpFQ76RR6qtXqju2G8WkN9e3xC9XvP3SWVDIc5195fVj_1gRGLUCC20Uu5uPC0yzuE2X_TrcjKq0-SeWiyVIphc1T7AoJ2PvAu74js-66UPPcjXqoQvGXCzwrwhPdzXAp8n6sVwGcfRthrk2AOD4U8XJz68aBA5aRx77sqsMKKFIbCRXjRfOcAoa214qR5Oz7-nnKj1dII9Rx_g9ZD7SS_Mjm5Sk3M_Mr0zZNDzN7YWd_j3WhN9Gvr1MOeU6aFKssi2wzLgyNAVHNaXxjKms7cV1bDDwVzfYcHa6L-oLvvPk1FRmoRsD4J_3I7TgE1iO2LOtWD4ZLYilAcmmiHh3OPqNpQrKkl5uK2zg"); + + const requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow" + }; + + try { + const response = await fetch("https://silver-meme-jj4x947jg76ghqjg9-3001.app.github.dev/api/foods/exotic", requestOptions); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + + const data = await response.json(); + setStore({ exoticFood: data }); + console.log(data); + } catch (error) { + console.error('Error fetching cat food:', error); + } + } + , + + + + getMessage: async () => { try{ // fetching data from the backend const resp = await fetch(process.env.BACKEND_URL + "/api/hello") const data = await resp.json() - setStore({ message: data.message }) + setStore({ exoticFood: data }) // don't forget to return something, that is how the async resolves return data; }catch(error){ console.log("Error loading message from backend", error) } }, + + changeColor: (index, color) => { //get the store const store = getStore(); From e5de680a09450cdc35b26dead564690270bd0bea Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:58:01 +0000 Subject: [PATCH 10/79] inicio de usuario y nuevos piensos y mascotas --- Pipfile | 2 +- Pipfile.lock | 22 +++++++++++----------- src/api/commands.py | 27 ++++++++++++++++++++------- src/api/models.py | 2 +- src/api/routes.py | 25 +++++++++++++++++++++---- src/app.py | 6 ++++++ 6 files changed, 60 insertions(+), 24 deletions(-) diff --git a/Pipfile b/Pipfile index b461e2e4ee..ff6b238176 100644 --- a/Pipfile +++ b/Pipfile @@ -18,7 +18,7 @@ gunicorn = "*" cloudinary = "*" flask-admin = "*" typing-extensions = "*" -flask-jwt-extended = "==4.6.0" +flask-jwt-extended = "*" wtforms = "==3.1.2" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index 3c146e5b23..91123c2485 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4f0e9a772f04b621ff0313b7ecfa468af1526aa27df8bfcacac6955d499d352d" + "sha256": "94a6703b952d22b68d3a5c0a0ba5acfe0104b92d0f991a972ff1bc9d0b15c43b" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "alembic": { "hashes": [ - "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5", - "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213" + "sha256:197de710da4b3e91cf66a826a5b31b5d59a127ab41bd0fc42863e2902ce2bbbe", + "sha256:e1a1c738577bca1f27e68728c910cd389b9a92152ff91d902da649c192e30c49" ], - "markers": "python_version >= '3.8'", - "version": "==1.14.1" + "markers": "python_version >= '3.9'", + "version": "==1.15.1" }, "blinker": { "hashes": [ @@ -82,11 +82,11 @@ }, "flask-jwt-extended": { "hashes": [ - "sha256:63a28fc9731bcc6c4b8815b6f954b5904caa534fc2ae9b93b1d3ef12930dca95", - "sha256:9215d05a9413d3855764bcd67035e75819d23af2fafb6b55197eb5a3313fdfb2" + "sha256:52f35bf0985354d7fb7b876e2eb0e0b141aaff865a22ff6cc33d9a18aa987978", + "sha256:8085d6757505b6f3291a2638c84d207e8f0ad0de662d1f46aa2f77e658a0c976" ], "index": "pypi", - "version": "==4.6.0" + "version": "==4.7.1" }, "flask-migrate": { "hashes": [ @@ -209,11 +209,11 @@ }, "jinja2": { "hashes": [ - "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", - "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], "markers": "python_version >= '3.7'", - "version": "==3.1.5" + "version": "==3.1.6" }, "mako": { "hashes": [ diff --git a/src/api/commands.py b/src/api/commands.py index fda0a74295..bb661d2da1 100644 --- a/src/api/commands.py +++ b/src/api/commands.py @@ -42,8 +42,8 @@ def insert_data_catfood(): catfood.weight = 1. catfood.price = 1. catfood.animal_type = "gato" - catfood.age = "sd" - catfood.pathologies = "asd" + catfood.age = "cachorro" + catfood.pathologies = "renal" db.session.add(catfood) db.session.commit() @@ -85,10 +85,10 @@ def insert_data_food(): dogfood.description = "asd" dogfood.ingredients = "ads" dogfood.price = 1. - dogfood.pathologies = "renal" + dogfood.pathologies = "diabetes" dogfood.animal_type = "perro" - dogfood.age = "senior" - dogfood.size = "medium" + dogfood.age = "cachorro" + dogfood.size = "grande" dogfood.weight = 1. db.session.add(dogfood) db.session.commit() @@ -129,12 +129,12 @@ def insert_data_accessories(): @app.cli.command("insert_data_pet") def insert_data_pet(): pet= Pet() - pet.name = "asd" + pet.name = "" pet.size="" pet.breed= "asd" pet.age= "cachorro" pet.animal_type = "gato" - pet.pathologies="obesidad, diabetes" + pet.pathologies="diabetes" pet.user_id = 1 db.session.add(pet) db.session.commit() @@ -150,6 +150,17 @@ def insert_data_pet(): db.session.add(pet) db.session.commit() + pet= Pet() + pet.name = "peluso" + pet.size="grande" + pet.breed= "asd" + pet.age="cachorro" + pet.animal_type = "perro" + pet.pathologies="diabetes" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + pet= Pet() pet.name = "asd" pet.size="oxbow" @@ -157,6 +168,7 @@ def insert_data_pet(): pet.age= "2" pet.animal_type = "cobaya" pet.pathologies="escorbuto" + pet.user_id = 1 db.session.add(pet) db.session.commit() @@ -167,6 +179,7 @@ def insert_data_pet(): pet.age= "60" pet.animal_type = "loro" pet.pathologies="" + pet.user_id = 1 db.session.add(pet) db.session.commit() diff --git a/src/api/models.py b/src/api/models.py index 24597a509c..1978e4e56c 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -155,7 +155,7 @@ class Pet(db.Model): breed = db.Column(db.String(100), nullable=True) # cambiar raza por tamaño age = db.Column(db.String, nullable=False) animal_type= db.Column(db.String, nullable=False) - pathologies = db.Column(db.Text, nullable=False) # patología contemple peso + pathologies = db.Column(db.Text, nullable=True) # patología contemple peso user_id = db.Column(db.ForeignKey("user.id")) # is_hypoallergenic = db.Column(db.Boolean, default=False) diff --git a/src/api/routes.py b/src/api/routes.py index 4915dd28e2..3f4add878c 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -5,8 +5,9 @@ from api.models import db, User, Food, Pet, Accessories from api.utils import generate_sitemap, APIException from flask_cors import CORS -from sqlalchemy import select +from sqlalchemy import select, and_, or_ import json +from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity api = Blueprint('api', __name__) @@ -89,10 +90,10 @@ def get_pet_suggestions(pet_id): # Solucion simple: limitar a 1 patologia cada animal por ahora #if para pet# anymal_type == perro, animal size #si no no hace falta size if pet["animal_type"] == "perro": - food_suggestions = db.session.execute(select(Food).where(Food.animal_type==pet["animal_type"]), + food_suggestions = db.session.execute(select(Food).where(and_(Food.animal_type==pet["animal_type"]), Food.size==pet["size"], Food.age==pet["age"], - Food.pathologies==pet["pathologies"]).all() + Food.pathologies==pet["pathologies"])).all() else: food_suggestions = db.session.execute(select(Food).where(Food.animal_type==pet["animal_type"]), Food.age==pet["age"], @@ -172,7 +173,7 @@ def create_food(): db.session.commit() return jsonify(new_food.serialize()), 201 -#crear nuevo usuario +#registrar nuevo usuario @api.route('/users', methods=['POST']) def create_user(): data = request.get_json() @@ -182,10 +183,26 @@ def create_user(): password=data["password"], is_active=data["is_active"] ) + if User.query.filter_by(email=data["email"]).first(): + return jsonify({"msg": "El usuario ya existe"}), 400 + db.session.add(new_user) db.session.commit() return jsonify(new_user.serialize()), 201 +# iniciar sesion +@api.route('loging/user', methods=['POST']) +def logging_user(): + data = request.get_json() + user = User.query.filter_by(email=data["email"]).first() + + + if User.query.filter_by(email=data["email"]).first() and User.query.filter_by(password=data["password"]).first(): + access_token=create_access_token(identity=user.email) + return jsonify(access_token=access_token), 200 + return jsonify ({"nsg":"credenciales invalidas"}), 400 + + #crear un nuevo accesorio @api.route('/accessories', methods=['POST']) def create_accessory(): diff --git a/src/app.py b/src/app.py index 0ea8351d5f..727d9c2c3b 100644 --- a/src/app.py +++ b/src/app.py @@ -10,6 +10,8 @@ from api.routes import api from api.admin import setup_admin from api.commands import setup_commands +from flask_jwt_extended import JWTManager +from datetime import timedelta # from models import Person @@ -31,6 +33,10 @@ MIGRATE = Migrate(app, db, compare_type=True) db.init_app(app) +app.config["JWT_ACCESS_TOKEN_EXPIRE"] = timedelta(hours=1) +jwt = JWTManager(app) + + # add the admin setup_admin(app) From 9259f15449aa2edcefe5c2d3ada470aa7cc143fa Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Fri, 7 Mar 2025 16:20:57 +0000 Subject: [PATCH 11/79] Develop actualizado --- src/api/models.py | 1 + src/api/routes.py | 7 +- src/front/js/component/navbar.js | 29 ++++-- src/front/js/layout.js | 4 +- src/front/js/pages/loginSignup.js | 101 ++++++++++++++++++++ src/front/js/store/flux.js | 99 ++++++++++++++++++- src/front/styles/home.css | 152 ++++++++++++++++++++++++++++++ 7 files changed, 375 insertions(+), 18 deletions(-) create mode 100644 src/front/js/pages/loginSignup.js diff --git a/src/api/models.py b/src/api/models.py index 1978e4e56c..ab0d5a79f1 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -10,6 +10,7 @@ db = SQLAlchemy() class User(db.Model): + id = db.Column(db.Integer, primary_key=True) name= db.Column(db.String(80), nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) diff --git a/src/api/routes.py b/src/api/routes.py index 829e3cc67e..0e71e7e916 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -113,16 +113,13 @@ def get_all_cat_food(): if not food_cat: return jsonify({"error": "No cat food found"}), 404 - -# #obtener todos los alimentos según tipo de animal -@api.route('/foods/cat', methods=['GET']) -def get_all_cat_food(): - food_cat = db.session.query(Food).filter(Food.animal_type.ilike("%gato%")).all() print("Datos obtenidos:", food_cat) if not food_cat: return jsonify({"error": "No cat food found"}), 404 return jsonify([food.serialize() for food in food_cat]), 200 + + @api.route('/foods/dog', methods=['GET']) def get_all_dog_food(): diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index 028c97b84b..fd4e38ff8f 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -1,14 +1,21 @@ -import React from "react"; +import React, {useContext, useEffect} from "react"; import { Link } from "react-router-dom"; import { CiSearch } from "react-icons/ci"; +import { Context } from "../store/appContext"; export const Navbar = () => { + const { store, actions } = useContext(Context); + const { user } = store; // Obtenemos el usuario del store + + useEffect(() => { + console.log("Usuario en navbar:", user); + }, [user]); return (
diff --git a/src/front/js/layout.js b/src/front/js/layout.js index 0fc5453a74..b8b89a045f 100755 --- a/src/front/js/layout.js +++ b/src/front/js/layout.js @@ -4,13 +4,13 @@ import ScrollToTop from "./component/scrollToTop"; import { BackendURL } from "./component/backendURL"; import { Home } from "./pages/home"; - import { Demo } from "./pages/demo"; import { Single } from "./pages/single"; import injectContext from "./store/appContext"; import { Navbar } from "./component/navbar"; import { Footer } from "./component/footer"; +import { LoginSignup } from "./pages/loginSignup"; //create your first component @@ -28,7 +28,7 @@ const Layout = () => { } path="/" /> - + } path="/loginSignup" /> } path="/demo" /> } path="/single/:theid" /> diff --git a/src/front/js/pages/loginSignup.js b/src/front/js/pages/loginSignup.js new file mode 100644 index 0000000000..b3a876d327 --- /dev/null +++ b/src/front/js/pages/loginSignup.js @@ -0,0 +1,101 @@ +import React, { useState, useContext} from 'react'; +import { Context } from '../store/appContext'; +import { Link, useNavigate } from 'react-router-dom'; +import "../../styles/home.css"; + +export const LoginSignup = () => { + + const { actions } = useContext(Context); + const navigate = useNavigate(); + + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [isSignup, setIsSignup] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + setError(''); + + try { + const dataUser = { username, email, password }; + if (isSignup) { + await actions.signup(dataUser, navigate); + } else { + await actions.login(email, password, navigate); + } + setUsername(''); + setEmail(''); + setPassword(''); + } catch (err) { + setError('Error al procesar la solicitud. Por favor, intenta nuevamente.'); + } finally { + setLoading(false); + } + + }; + return ( +
+
+ {!isSignup && ( +
+

Iniciar sesión

+ setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + + {error &&

{error}

} +

setIsSignup(true)}>¿No tienes cuenta? Regístrate

+
+ )} + {isSignup && ( +
+

Regístrate

+ setUsername(e.target.value)} + required + /> + setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + + {error &&

{error}

} +

setIsSignup(false)}>¿Ya tienes cuenta? Inicia sesión

+
+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/js/store/flux.js b/src/front/js/store/flux.js index b6d75a1234..260f005a46 100755 --- a/src/front/js/store/flux.js +++ b/src/front/js/store/flux.js @@ -1,6 +1,8 @@ const getState = ({ getStore, getActions, setStore }) => { return { store: { + user: null, + token: null, message: null, demo: [ { @@ -24,6 +26,95 @@ const getState = ({ getStore, getActions, setStore }) => { getActions().changeColor(0, "green"); }, + login: async (email, password, navigate) => { + try { + const resp = await fetch(`${process.env.BACKEND_URL}api/login/user`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ email, password }) + }); + + if (!resp.ok) { + throw new Error("Error al iniciar sesión"); + } + + const data = await resp.json(); + console.log("Inicio de sesión exitoso:", data); + + const token = data.token; + if (!token) { + throw new Error("No se recibió el token"); + } + + localStorage.setItem("token", token); + setStore({ token }); + + const actions = getActions(); + actions.getUser(); + navigate("/"); + } catch (error) { + console.log("Error al iniciar sesión", error); + alert("Error al iniciar sesión"); + } + }, + signup: async (dataUser, navigate) => { + try { + const resp = await fetch(`${process.env.BACKEND_URL}api/signup`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(dataUser) + }); + + if (!resp.ok) { + throw new Error("Error en el registro"); + } + + const data = await resp.json(); + console.log("Usuario registrado exitosamente", data); + + const token = data.token; + if (!token) { + throw new Error("No se recibió el token"); + } + + localStorage.setItem("token", token); + setStore({ token }); + + const actions = getActions(); + actions.getUser(); + navigate("/"); + } catch (error) { + } + }, + getUser: async () => { + try { + const token = localStorage.getItem("token"); + if (!token) throw new Error("No token found"); + + const resp = await fetch(`${process.env.BACKEND_URL}api/user`, { + headers: { + "Authorization": `Bearer ${token}` + } + }); + + if (!resp.ok) throw new Error("Error al obtener el usuario"); + + const data = await resp.json(); + setStore({ user: data }); + } catch (error) { + console.log("Error al obtener usuario", error); + } + }, + logout: () => { + localStorage.removeItem('token'); + setStore({token: null, user: null}) + + }, + getDogFood: async () => { const myHeaders = new Headers(); myHeaders.append("Cookie", ".Tunnels.Relay.WebForwarding.Cookies=CfDJ8Cs4yarcs6pKkdu0hlKHsZs0q_CxPIxRcsYOazLvQz4rP7s5FWFmvGJndFqy0N7fvoY5B6Jou5i4ZPgwsQZsEYGDV4DoaNhJP3xwIvv1aGmoRIVdScF1G2c_hWBWqeCTHFNCvGD1Dy0sm3kBmaNdXiMsSO0myHKUFvlWHCed2AdtyCiC6CHBqk9DHs32cYjJV4GQr4cxW2IXl6QDukWwCPSYuzTnP699Rz_4pCbB8OPOQNBDyDtdks_LUoMZR2Qt6IWKmUnLGt-n3JLFjeQMZiSeKEXKNTcJknrz4p25p9-5rh3BY2FBX_kg6MtH3cLbqOyS6yqrG4cjJPpyZbVfN3iEYSR6lzEGiGZDFPvokcj_PM8fq32HR1_olrhti8nYtDctNR_8YRewW5quhBNW6mtF_-SqGTbCQVH1CiLUF2UKK_H_nGmwvWAce2n4Cdw1BLUCZhlCr3GAKWJWHqLI1K5n9OekmI4zs0TI_60R6urTRxIQx2IgkRPYizg-AUdyr6bORhYr7s3c6oFBrdA4yBShqyJFOo4fuMkQuHflmg717cZeB1MDnWgSm9Xnl4qmOlcta0fCSq15GNUPvXhAwvclIoK9NTrmSSd1wvRhoqz7ypodxTSOafQx0ybhJZwTxDeS_gv-4KjbyFngwj7Bj1TluB2qE5Hijbi4uhZb2KILE9AGYHKWSW-IWoSmEXW71c7HhH8mBEbBxidpsSi_Rip3CdXL2oUO-8TGtLx2HgJtExj_7AyTD1trSanlKgDurPSEfDuXuwtqxawfWf0a1sbp9BGk0pRLOA-tVKrxmMiFAPiNCVC1W2EXb95TKydzKIwtbcC70YyDJ4dFwnHrQmgPYxzIz7PYzICzKqZ-VDpO4lf7C3jf4OBJ9ZMV4JRvPiR2kUwMNX_c5CuMLaKrZNzqCFwZBUa4N4AUyTT83mtzFQGAZjMXDeZNok7mjTYBp121qquiSKX8ft05b1MTsVtRfg3ETiFdOHmYQ2-1EcSwBY-VhjWIlkQiFr1yBWXk-g"); @@ -35,7 +126,7 @@ const getState = ({ getStore, getActions, setStore }) => { }; try { - const response = await fetch("https://silver-meme-jj4x947jg76ghqjg9-3001.app.github.dev/api/foods/dog", requestOptions); + const response = await fetch(process.env.BACKEND_URL +"/api/foods/dog", requestOptions); if (!response.ok) { throw new Error('Network response was not ok'); @@ -65,7 +156,7 @@ const getState = ({ getStore, getActions, setStore }) => { }; try { - const response = await fetch("https://silver-meme-jj4x947jg76ghqjg9-3001.app.github.dev/api/foods/cat", requestOptions); + const response = await fetch(process.env.BACKEND_URL + "/api/foods/cat", requestOptions); if (!response.ok) { throw new Error('Network response was not ok'); } @@ -90,7 +181,7 @@ const getState = ({ getStore, getActions, setStore }) => { }; try { - const response = await fetch("https://silver-meme-jj4x947jg76ghqjg9-3001.app.github.dev/api/foods/exotic", requestOptions); + const response = await fetch(process.env.BACKEND_URL +"/api/foods/exotic", requestOptions); if (!response.ok) { throw new Error('Network response was not ok'); } @@ -139,4 +230,4 @@ const getState = ({ getStore, getActions, setStore }) => { }; }; -export default getState; +export default getState; \ No newline at end of file diff --git a/src/front/styles/home.css b/src/front/styles/home.css index 757b881b2d..68c7b7f067 100755 --- a/src/front/styles/home.css +++ b/src/front/styles/home.css @@ -5,3 +5,155 @@ one more css for each page that will contain the selected used on that page only (the ones not reused in other pages). */ +body { + font-family: 'Arial', sans-serif; + background-color: #f5f7fa; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + } + + .registration-view-container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #e2efff, #ffffff); + padding: 20px; + } + + /* Form container */ + .form-container { + background-color: #ffffff; + border-radius: 12px; + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1); + padding: 30px 40px; + width: 100%; + max-width: 400px; + transition: all 0.3s ease; + } + + /* Form title */ + form h2 { + font-size: 24px; + font-weight: bold; + text-align: center; + color: #333; + margin-bottom: 20px; + text-transform: uppercase; + letter-spacing: 1.2px; + } + + /* Input fields */ + input { + width: 100%; + padding: 12px; + margin: 10px 0; + border-radius: 8px; + border: 2px solid #d1d1d1; + background-color: #f9f9f9; + font-size: 16px; + transition: border-color 0.3s ease, background-color 0.3s ease; + } + + /* Focus effect on inputs */ + input:focus { + outline: none; + border-color: #5b8fcb; + background-color: #e8f0fe; + } + + /* Submit button */ + button { + width: 100%; + padding: 12px; + background-color: #5b8fcb; + color: white; + border: none; + border-radius: 8px; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s ease; + } + + /* Hover effect on button */ + button:hover { + background-color: #3d6ea5; + } + + button:disabled { + background-color: #a0c6f1; + cursor: not-allowed; + } + + /* Error message styling */ + .error-message { + color: #e74c3c; + font-size: 14px; + margin-top: 10px; + text-align: center; + } + + /* Link styling for switching between login and signup */ + p { + color: #5b8fcb; + text-align: center; + cursor: pointer; + font-size: 14px; + margin-top: 10px; + transition: color 0.3s ease; + } + + /* Hover effect on links */ + p:hover { + color: #3d6ea5; + } + + /* Animations for form entry */ + .auth-form { + opacity: 0; + transform: translateY(20px); + animation: slideIn 0.5s forwards; + } + + .auth-form.login-form { + animation-delay: 0.3s; + } + + .auth-form.signup-form { + animation-delay: 0.3s; + } + + /* Slide-in animation for forms */ + @keyframes slideIn { + to { + opacity: 1; + transform: translateY(0); + } + } + + /* Animation for form container on hover */ + .form-container:hover { + box-shadow: 0px 6px 30px rgba(0, 0, 0, 0.1); + transform: translateY(-5px); + } + + /* Responsive styles */ + @media (max-width: 600px) { + .form-container { + padding: 20px; + } + + form h2 { + font-size: 20px; + } + + input, + button { + font-size: 14px; + } + } \ No newline at end of file From fe0430512e322aba7b8f9ea8e23e1c0e12796927 Mon Sep 17 00:00:00 2001 From: Alejandrobv25 Date: Sat, 8 Mar 2025 01:10:13 +0000 Subject: [PATCH 12/79] 1. eliminado is_active de los modelos 2. migraciones 3. adaptacion de vista loginSignup a rutas 4. modificacion de endpoint login 5. creacion de endpoint jwt_required para vista privada 6. actualizacion de flux para adaptar funciones a las rutas --- migrations/versions/7b0104ace168_.py | 42 +++++++++ src/api/models.py | 4 +- src/api/routes.py | 126 ++++++++++++++++++--------- src/front/js/pages/loginSignup.js | 16 ++-- src/front/js/store/flux.js | 6 +- 5 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 migrations/versions/7b0104ace168_.py diff --git a/migrations/versions/7b0104ace168_.py b/migrations/versions/7b0104ace168_.py new file mode 100644 index 0000000000..71e4da8032 --- /dev/null +++ b/migrations/versions/7b0104ace168_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: 7b0104ace168 +Revises: a9b7f08f1a62 +Create Date: 2025-03-07 22:43:54.218242 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7b0104ace168' +down_revision = 'a9b7f08f1a62' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('pet', schema=None) as batch_op: + batch_op.alter_column('pathologies', + existing_type=sa.TEXT(), + nullable=True) + + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_column('is_active') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('is_active', sa.BOOLEAN(), autoincrement=False, nullable=False)) + + with op.batch_alter_table('pet', schema=None) as batch_op: + batch_op.alter_column('pathologies', + existing_type=sa.TEXT(), + nullable=False) + + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index ab0d5a79f1..61181b6525 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -15,7 +15,6 @@ class User(db.Model): name= db.Column(db.String(80), nullable=False) 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) def __repr__(self): return f'' @@ -24,8 +23,7 @@ def serialize(self): return { "id": self.id, "email": self.email, - "name": self.name, - "is_active": self.is_active + "name": self.name # do not serialize the password, its a security breach } diff --git a/src/api/routes.py b/src/api/routes.py index 0e71e7e916..7294e6c396 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -35,7 +35,7 @@ def get_foods(): foods = Food.query.all() if not foods: return "food not found", 404 - else: + else: return jsonify([food.serialize() for food in foods]), 200 @@ -47,7 +47,7 @@ def get_food(food_id): return jsonify({"error": "Food not found"}), 404 return jsonify(food.serialize()), 200 - +#obtener todos los usuarios @api.route('/users', methods=['GET']) def get_users(): users = User.query.all() @@ -68,7 +68,7 @@ def get_user(user_id): @api.route('/pets', methods=['GET']) def get_pets(): pets = Pet.query.all() - if not pets: + if not pets: return "no pets found", 404 return jsonify([pet.serialize() for pet in pets]), 200 @@ -85,11 +85,11 @@ def get_pet(pet_id): @api.route('/foods/suggestions/', methods=['GET']) def get_pet_suggestions(pet_id): pet = Pet.query.get(pet_id).serialize() - # Problema: Un animal puede tener varias patologias en su campo, habría que coger este campo y tratarlo, - # separar las patologias en una lista y hacer la query para cada patologia. + # Problema: Un animal puede tener varias patologias en su campo, habría que coger este campo y tratarlo, + # separar las patologias en una lista y hacer la query para cada patologia. # Solucion simple: limitar a 1 patologia cada animal por ahora #if para pet# anymal_type == perro, animal size #si no no hace falta size - if pet["animal_type"] == "perro": + if pet["animal_type"] == "perro": food_suggestions = db.session.execute(select(Food).where(and_(Food.animal_type==pet["animal_type"]), Food.size==pet["size"], Food.age==pet["age"], @@ -99,7 +99,7 @@ def get_pet_suggestions(pet_id): Food.age==pet["age"], Food.pathologies==pet["pathologies"]).all() if not food_suggestions : - return "no suggestions found", 404 + return "no suggestions found", 404 return [food[0].serialize() for food in food_suggestions], 200 @@ -107,30 +107,30 @@ def get_pet_suggestions(pet_id): @api.route('/foods/cat', methods=['GET']) def get_all_cat_food(): food_cat = db.session.query(Food).filter(Food.animal_type.ilike("%gato%")).all() - - print("Datos obtenidos:", food_cat) - + + print("Datos obtenidos:", food_cat) + if not food_cat: - return jsonify({"error": "No cat food found"}), 404 - + return jsonify({"error": "No cat food found"}), 404 + print("Datos obtenidos:", food_cat) if not food_cat: return jsonify({"error": "No cat food found"}), 404 return jsonify([food.serialize() for food in food_cat]), 200 - + @api.route('/foods/dog', methods=['GET']) def get_all_dog_food(): food_dog = db.session.query(Food).filter(Food.animal_type.ilike("%perro%")).all() - - print("Datos obtenidos:", food_dog) - + + print("Datos obtenidos:", food_dog) + if not food_dog: - return jsonify({"error": "No dog food found"}), 404 - + return jsonify({"error": "No dog food found"}), 404 + print("Datos obtenidos:", food_dog) if not food_dog: @@ -142,12 +142,12 @@ def get_all_dog_food(): def get_all_exotic_food(): food_exotic = db.session.query(Food).filter(Food.animal_type.ilike("%exótico%")).all() - - print("Datos obtenidos:", food_exotic) - + + print("Datos obtenidos:", food_exotic) + if not food_exotic: - return jsonify({"error": "No exotic food found"}), 404 - + return jsonify({"error": "No exotic food found"}), 404 + print("Datos obtenidos:", food_exotic) if not food_exotic: @@ -160,7 +160,7 @@ def get_all_exotic_food(): def get_accessories(): accessories = Accessories.query.all() if not accessories: - return "no accessories found", 404 + return "no accessories found", 404 return jsonify([accessory.serialize() for accessory in accessories]), 200 @@ -192,34 +192,76 @@ def create_food(): db.session.commit() return jsonify(new_food.serialize()), 201 -#registrar nuevo usuario -@api.route('/users', methods=['POST']) + +#registrar nuevo usuario(signup) +@api.route('/signup', methods=['POST']) def create_user(): data = request.get_json() new_user = User( name=data["name"], email=data["email"], - password=data["password"], - is_active=data["is_active"] + password=data["password"] ) if User.query.filter_by(email=data["email"]).first(): return jsonify({"msg": "El usuario ya existe"}), 400 - + db.session.add(new_user) db.session.commit() return jsonify(new_user.serialize()), 201 -# iniciar sesion -@api.route('loging/user', methods=['POST']) -def logging_user(): - data = request.get_json() - user = User.query.filter_by(email=data["email"]).first() +# iniciar sesion(login) +# @api.route('/login', methods=['POST']) +# def logging_user(): +# data = request.get_json() +# user = User.query.filter_by(email=data["email"]).first() + + +# if User.query.filter_by(email=data["email"]).first() and User.query.filter_by(password=data["password"]).first(): +# access_token=create_access_token(identity=user.email) +# return jsonify(access_token=access_token), 200 +# return jsonify ({"nsg":"credenciales invalidas"}), 400 + +@api.route('/login', methods=['POST']) +def login_user(): - if User.query.filter_by(email=data["email"]).first() and User.query.filter_by(password=data["password"]).first(): - access_token=create_access_token(identity=user.email) - return jsonify(access_token=access_token), 200 - return jsonify ({"nsg":"credenciales invalidas"}), 400 + body = request.get_json() + + if not body or "email" not in body or "password" not in body: + return jsonify({"msg": "credenciales no validas"}), 400 + + email = body["email"] + password = body["password"] + + user = User().query.filter_by(email=email, password=password).first() + token=create_access_token(identity=user.email) + user_data = { + "id": user.id, + "email": user.email, + "name": user.name + } + + return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + +#Vista privada del usuario CON el token +@api.route('/user', methods=['GET']) +@jwt_required() +def get_user_info(): + + current_user_email = get_jwt_identity() + + user = User().query.filter_by(email=current_user_email).first() + + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + + user_data = { + "id": user.id, + "email": user.email, + "name": user.name + } + + return jsonify(user_data), 200 #crear un nuevo accesorio @@ -251,8 +293,8 @@ def create_pet(): animal_type=data["animal_type"], pathologies=data["pathologies"], user_id=data["user_id"], - - + + ) db.session.add(new_accessory) db.session.commit() @@ -265,7 +307,7 @@ def create_pet(): # return jsonify({"message": "Food not found"}), 404 # data = request.get_json() - + # food.name = data.get("name", food.name) # food.brand = data.get("brand", food.brand) # food.description = data.get("description", food.description) @@ -288,7 +330,7 @@ def create_pet(): # "animal_type": food.animal_type, # "price": food.price, # "weight": food.weight, -# "size" : food.size, +# "size" : food.size, # "pathologies": food.pathologies, # "url": food.url # }) diff --git a/src/front/js/pages/loginSignup.js b/src/front/js/pages/loginSignup.js index b3a876d327..ab5f18d544 100644 --- a/src/front/js/pages/loginSignup.js +++ b/src/front/js/pages/loginSignup.js @@ -5,10 +5,10 @@ import "../../styles/home.css"; export const LoginSignup = () => { - const { actions } = useContext(Context); + const { actions, store } = useContext(Context); const navigate = useNavigate(); - const [username, setUsername] = useState(''); + const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isSignup, setIsSignup] = useState(false); @@ -21,13 +21,13 @@ export const LoginSignup = () => { setError(''); try { - const dataUser = { username, email, password }; + const dataUser = { name, email, password }; if (isSignup) { await actions.signup(dataUser, navigate); } else { await actions.login(email, password, navigate); } - setUsername(''); + setName(''); setEmail(''); setPassword(''); } catch (err) { @@ -68,10 +68,10 @@ export const LoginSignup = () => {

Regístrate

setUsername(e.target.value)} + type="name" + placeholder="Nombre" + value={name} + onChange={(e) => setName(e.target.value)} required /> { login: async (email, password, navigate) => { try { - const resp = await fetch(`${process.env.BACKEND_URL}api/login/user`, { + const resp = await fetch(`${process.env.BACKEND_URL}/api/login`, { method: "POST", headers: { "Content-Type": "application/json" @@ -61,7 +61,7 @@ const getState = ({ getStore, getActions, setStore }) => { }, signup: async (dataUser, navigate) => { try { - const resp = await fetch(`${process.env.BACKEND_URL}api/signup`, { + const resp = await fetch(`${process.env.BACKEND_URL}/api/signup`, { method: "POST", headers: { "Content-Type": "application/json" @@ -95,7 +95,7 @@ const getState = ({ getStore, getActions, setStore }) => { const token = localStorage.getItem("token"); if (!token) throw new Error("No token found"); - const resp = await fetch(`${process.env.BACKEND_URL}api/user`, { + const resp = await fetch(`${process.env.BACKEND_URL}/api/user`, { headers: { "Authorization": `Bearer ${token}` } From c9c847731484805ecbd2e7cfa7952d9d25ba42a6 Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Sat, 8 Mar 2025 11:12:45 +0000 Subject: [PATCH 13/79] login actualizado --- migrations/versions/7b0104ace168_.py | 42 ------------------- .../{a9b7f08f1a62_.py => 7f479f7b82b2_.py} | 9 ++-- 2 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 migrations/versions/7b0104ace168_.py rename migrations/versions/{a9b7f08f1a62_.py => 7f479f7b82b2_.py} (93%) diff --git a/migrations/versions/7b0104ace168_.py b/migrations/versions/7b0104ace168_.py deleted file mode 100644 index 71e4da8032..0000000000 --- a/migrations/versions/7b0104ace168_.py +++ /dev/null @@ -1,42 +0,0 @@ -"""empty message - -Revision ID: 7b0104ace168 -Revises: a9b7f08f1a62 -Create Date: 2025-03-07 22:43:54.218242 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7b0104ace168' -down_revision = 'a9b7f08f1a62' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('pet', schema=None) as batch_op: - batch_op.alter_column('pathologies', - existing_type=sa.TEXT(), - nullable=True) - - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.drop_column('is_active') - - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.add_column(sa.Column('is_active', sa.BOOLEAN(), autoincrement=False, nullable=False)) - - with op.batch_alter_table('pet', schema=None) as batch_op: - batch_op.alter_column('pathologies', - existing_type=sa.TEXT(), - nullable=False) - - # ### end Alembic commands ### diff --git a/migrations/versions/a9b7f08f1a62_.py b/migrations/versions/7f479f7b82b2_.py similarity index 93% rename from migrations/versions/a9b7f08f1a62_.py rename to migrations/versions/7f479f7b82b2_.py index aa78ad4f0c..945e5e1d6d 100644 --- a/migrations/versions/a9b7f08f1a62_.py +++ b/migrations/versions/7f479f7b82b2_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: a9b7f08f1a62 +Revision ID: 7f479f7b82b2 Revises: -Create Date: 2025-03-06 10:20:42.876362 +Create Date: 2025-03-08 10:50:02.759160 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'a9b7f08f1a62' +revision = '7f479f7b82b2' down_revision = None branch_labels = None depends_on = None @@ -49,7 +49,6 @@ def upgrade(): sa.Column('name', sa.String(length=80), nullable=False), sa.Column('email', sa.String(length=120), nullable=False), sa.Column('password', sa.String(length=80), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('email') ) @@ -60,7 +59,7 @@ def upgrade(): sa.Column('breed', sa.String(length=100), nullable=True), sa.Column('age', sa.String(), nullable=False), sa.Column('animal_type', sa.String(), nullable=False), - sa.Column('pathologies', sa.Text(), nullable=False), + sa.Column('pathologies', sa.Text(), nullable=True), sa.Column('user_id', sa.Integer(), nullable=True), sa.Column('url', sa.String(length=255), nullable=True), sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), From 83725332bc7260f2efdb79f5d0bc58d2c6d04252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jennifer=20Fern=C3=A1ndez?= Date: Sun, 9 Mar 2025 10:11:36 +0000 Subject: [PATCH 14/79] first commit --- migrations/versions/ba227b582218_.py | 35 ----- .../{7f479f7b82b2_.py => dac9e4bf37de_.py} | 6 +- src/front/js/layout.js | 3 +- src/front/js/pages/RegistroMascota.js | 125 ++++++++++++++++++ src/front/styles/registroMascota.css | 9 ++ 5 files changed, 139 insertions(+), 39 deletions(-) delete mode 100644 migrations/versions/ba227b582218_.py rename migrations/versions/{7f479f7b82b2_.py => dac9e4bf37de_.py} (96%) create mode 100644 src/front/js/pages/RegistroMascota.js create mode 100644 src/front/styles/registroMascota.css diff --git a/migrations/versions/ba227b582218_.py b/migrations/versions/ba227b582218_.py deleted file mode 100644 index d8c4bdb055..0000000000 --- a/migrations/versions/ba227b582218_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: ba227b582218 -Revises: -Create Date: 2025-03-01 09:14:08.397142 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'ba227b582218' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password', sa.String(length=80), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - # ### end Alembic commands ### diff --git a/migrations/versions/7f479f7b82b2_.py b/migrations/versions/dac9e4bf37de_.py similarity index 96% rename from migrations/versions/7f479f7b82b2_.py rename to migrations/versions/dac9e4bf37de_.py index 945e5e1d6d..7c7e05a737 100644 --- a/migrations/versions/7f479f7b82b2_.py +++ b/migrations/versions/dac9e4bf37de_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 7f479f7b82b2 +Revision ID: dac9e4bf37de Revises: -Create Date: 2025-03-08 10:50:02.759160 +Create Date: 2025-03-09 09:51:14.460794 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '7f479f7b82b2' +revision = 'dac9e4bf37de' down_revision = None branch_labels = None depends_on = None diff --git a/src/front/js/layout.js b/src/front/js/layout.js index c20bcab3d0..1643b9aca1 100755 --- a/src/front/js/layout.js +++ b/src/front/js/layout.js @@ -15,6 +15,7 @@ import { Footer } from "./component/footer"; import { VistaProducto } from "./pages/VistaProducto"; import { LoginSignup } from "./pages/loginSignup"; +import { RegistroMascota } from "./pages/RegistroMascota"; @@ -39,7 +40,7 @@ const Layout = () => { } path="/loginSignup" /> - + } path="/registro-mascota" /> } path="/demo" /> } path="/single/:theid" /> Not found!} /> diff --git a/src/front/js/pages/RegistroMascota.js b/src/front/js/pages/RegistroMascota.js new file mode 100644 index 0000000000..534edcf4bd --- /dev/null +++ b/src/front/js/pages/RegistroMascota.js @@ -0,0 +1,125 @@ +import React, { useState } from "react"; +import { Context } from "../store/appContext"; +import "../../styles/registroMascota.css"; + +export const RegistroMascota = () => { + const [nuevaMascota, setNuevaMascota] = useState({ + nombre: "", + especie: "", + tamaño: "", + etapaVital: "", + patología: "" + }); + + const [fotoMascota, setFotoMascota] = useState(null); + + const handleChange = (e) => { + const { name, value } = e.target; + setNuevaMascota({ + ...nuevaMascota, + [name]: value, + }); + }; + + const handleFileChange = (e) => { + setFotoMascota(e.target.files[0]); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + const formMascota = new formMascota(); + formMascota.append("mascota", JSON.stringify(nuevaMascota)); + if (fotoMascota) { + formMascota.append("foto", fotoMascota); + } + console.log('Datos enviados:', formData); + // Aquí puedo enviar formData a la API o manejarlo según sea necesario + }; + + return ( +
+

Registro de Mascota

+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + + +
+

Sube una foto de tu compañero/a :

+ + {fotoMascota && ( +
+ Vista previa +

Archivo: {fotoMascota.name}

+
+ )} + + +
+
+
+ + + ); + } \ No newline at end of file diff --git a/src/front/styles/registroMascota.css b/src/front/styles/registroMascota.css new file mode 100644 index 0000000000..5df0aeafe8 --- /dev/null +++ b/src/front/styles/registroMascota.css @@ -0,0 +1,9 @@ +.fotoMascota { + display: flex; + flex-direction: column; + align-items: flex-start; /* Alinear elementos al inicio */ +} + +.fotoMascota img { + object-fit: cover; /* Para mantener la proporción de la imagen */ +} \ No newline at end of file From 8715c04e503d82221782f4b1d1cafe729661688a Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Sun, 9 Mar 2025 21:26:30 +0000 Subject: [PATCH 15/79] =?UTF-8?q?bcrypt=20a=C3=B1adadio=20al=20login=20y?= =?UTF-8?q?=20al=20signup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 1 + Pipfile.lock | 67 ++++++++++++++++- .../{7f479f7b82b2_.py => 7988effcddd5_.py} | 6 +- migrations/versions/ba227b582218_.py | 35 --------- src/api/models.py | 4 +- src/api/routes.py | 72 ++++++++++++++----- src/app.py | 2 + 7 files changed, 128 insertions(+), 59 deletions(-) rename migrations/versions/{7f479f7b82b2_.py => 7988effcddd5_.py} (96%) delete mode 100644 migrations/versions/ba227b582218_.py diff --git a/Pipfile b/Pipfile index ff6b238176..bdd43c634b 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ flask-admin = "*" typing-extensions = "*" flask-jwt-extended = "*" wtforms = "==3.1.2" +flask-bcrypt = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index 91123c2485..3f91abb23b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "94a6703b952d22b68d3a5c0a0ba5acfe0104b92d0f991a972ff1bc9d0b15c43b" + "sha256": "962f6b9c1214f196ffbb77af32268c9ca3e3e2dff9105182dc0aa1f56879dcfb" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,63 @@ "markers": "python_version >= '3.9'", "version": "==1.15.1" }, + "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", @@ -72,6 +129,14 @@ "index": "pypi", "version": "==1.6.1" }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" + ], + "index": "pypi", + "version": "==1.0.1" + }, "flask-cors": { "hashes": [ "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", diff --git a/migrations/versions/7f479f7b82b2_.py b/migrations/versions/7988effcddd5_.py similarity index 96% rename from migrations/versions/7f479f7b82b2_.py rename to migrations/versions/7988effcddd5_.py index 945e5e1d6d..2455308d0b 100644 --- a/migrations/versions/7f479f7b82b2_.py +++ b/migrations/versions/7988effcddd5_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 7f479f7b82b2 +Revision ID: 7988effcddd5 Revises: -Create Date: 2025-03-08 10:50:02.759160 +Create Date: 2025-03-08 13:29:06.947775 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '7f479f7b82b2' +revision = '7988effcddd5' down_revision = None branch_labels = None depends_on = None diff --git a/migrations/versions/ba227b582218_.py b/migrations/versions/ba227b582218_.py deleted file mode 100644 index d8c4bdb055..0000000000 --- a/migrations/versions/ba227b582218_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: ba227b582218 -Revises: -Create Date: 2025-03-01 09:14:08.397142 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'ba227b582218' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password', sa.String(length=80), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 61181b6525..e5a1a8a56e 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -9,6 +9,7 @@ db = SQLAlchemy() + class User(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -155,7 +156,7 @@ class Pet(db.Model): age = db.Column(db.String, nullable=False) animal_type= db.Column(db.String, nullable=False) pathologies = db.Column(db.Text, nullable=True) # patología contemple peso - user_id = db.Column(db.ForeignKey("user.id")) + user_id = db.Column(db.ForeignKey("user.id"), nullable=False) # is_hypoallergenic = db.Column(db.Boolean, default=False) # is_gluten_free = db.Column(db.Boolean, default=False) @@ -181,6 +182,7 @@ def serialize(self): "breed": self.breed, "animal_type": self.animal_type, "pathologies": self.pathologies, + "user_id": self.user_id, "url": self.url } diff --git a/src/api/routes.py b/src/api/routes.py index 7294e6c396..7d3a1e120d 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -8,8 +8,11 @@ from sqlalchemy import select, and_, or_ import json from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity +from flask_bcrypt import Bcrypt api = Blueprint('api', __name__) +bcrypt = Bcrypt() + # Allow CORS requests to this API CORS(api) @@ -81,6 +84,8 @@ def get_pet(pet_id): return jsonify({"error": "pet not found"}), 404 return jsonify(pet.serialize()), 200 + + #obtener sugerencias de comida según mascota @api.route('/foods/suggestions/', methods=['GET']) def get_pet_suggestions(pet_id): @@ -103,6 +108,33 @@ def get_pet_suggestions(pet_id): return [food[0].serialize() for food in food_suggestions], 200 +# #obtener sugerencias de comida según mascota +# @api.route('/foods/suggestions/', methods=['GET']) +# def get_pet_suggestions(pet_id): +# pet = Pet.query.get(pet_id) +# if not pet: +# return jsonify({"error": "pet not found"}), 404 + +# pet_data = pet.serialize() +# filters = [Food.animal_type == pet_data["animal_type"], Food.age == pet_data["age"]] + +# if pet_data["animal_type"] == "perro": +# filters.append(Food.size == pet_data["size"]) + +# # Si la mascota tiene patologías, agregarlas al filtro +# if pet_data["pathologies"]: +# pathologies_list = pet_data["pathologies"].split(",") +# filters.append(Food.pathologies.in_(pathologies_list)) +# else: +# filters.append(or_(Food.pathologies != None, Food.pathologies == None)) +# food_suggestions = db.session.execute(select(Food).where(and_(*filters))).all() + +# if not food_suggestions: +# return jsonify({"error": "no suggestions found"}), 404 + +# return jsonify([food[0].serialize() for food in food_suggestions]), 200 + + #obtener todos los alimentos según tipo de animal @api.route('/foods/cat', methods=['GET']) def get_all_cat_food(): @@ -194,13 +226,16 @@ def create_food(): #registrar nuevo usuario(signup) + @api.route('/signup', methods=['POST']) def create_user(): data = request.get_json() + hashed_password = bcrypt.generate_password_hash(data["password"]).decode('utf-8') + new_user = User( name=data["name"], email=data["email"], - password=data["password"] + password=hashed_password ) if User.query.filter_by(email=data["email"]).first(): return jsonify({"msg": "El usuario ya existe"}), 400 @@ -232,16 +267,17 @@ def login_user(): email = body["email"] password = body["password"] - - user = User().query.filter_by(email=email, password=password).first() - token=create_access_token(identity=user.email) - user_data = { - "id": user.id, - "email": user.email, - "name": user.name - } - - return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + user = User.query.filter_by(email=email).first() + if bcrypt.check_password_hash(user.password, body["password"]): + token=create_access_token(identity=user.email) + user_data = { + "id": user.id, + "email": user.email, + "name": user.name + } + + return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + return jsonify({"msg": "credenciales no validas"}), 400 #Vista privada del usuario CON el token @api.route('/user', methods=['GET']) @@ -283,22 +319,20 @@ def create_accessory(): #crear una nueva mascota @api.route('/pets', methods=['POST']) -def create_pet(): +def create_pet(user_id): data = request.get_json() - new_accessory = Pet( + new_pet = Pet( name=data["name"], size=data["size"], breed=data["breed"], age=data["age"], animal_type=data["animal_type"], pathologies=data["pathologies"], - user_id=data["user_id"], - - -) - db.session.add(new_accessory) + user_id=data["user_id"] + ) + db.session.add(new_pet) db.session.commit() - return jsonify(new_accessory.serialize()), 201 + return jsonify(new_pet.serialize()), 201 # @api.route('/foods/', methods=['PUT']) # def update_food(food_id): diff --git a/src/app.py b/src/app.py index 727d9c2c3b..6501953e09 100644 --- a/src/app.py +++ b/src/app.py @@ -11,6 +11,7 @@ from api.admin import setup_admin from api.commands import setup_commands from flask_jwt_extended import JWTManager +from flask_bcrypt import Bcrypt from datetime import timedelta # from models import Person @@ -35,6 +36,7 @@ app.config["JWT_ACCESS_TOKEN_EXPIRE"] = timedelta(hours=1) jwt = JWTManager(app) +bcrypt = Bcrypt(app) # add the admin From 4acca4e007c5f59664fb3b927696ea6ba5cb03ae Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Mon, 10 Mar 2025 11:14:16 +0000 Subject: [PATCH 16/79] Endpoint creacion mascota --- .../{7988effcddd5_.py => f64325b81a40_.py} | 8 ++++---- src/api/routes.py | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) rename migrations/versions/{7988effcddd5_.py => f64325b81a40_.py} (94%) diff --git a/migrations/versions/7988effcddd5_.py b/migrations/versions/f64325b81a40_.py similarity index 94% rename from migrations/versions/7988effcddd5_.py rename to migrations/versions/f64325b81a40_.py index 2455308d0b..7bd040efa5 100644 --- a/migrations/versions/7988effcddd5_.py +++ b/migrations/versions/f64325b81a40_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 7988effcddd5 +Revision ID: f64325b81a40 Revises: -Create Date: 2025-03-08 13:29:06.947775 +Create Date: 2025-03-10 09:54:10.208182 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '7988effcddd5' +revision = 'f64325b81a40' down_revision = None branch_labels = None depends_on = None @@ -60,7 +60,7 @@ def upgrade(): sa.Column('age', sa.String(), nullable=False), sa.Column('animal_type', sa.String(), nullable=False), sa.Column('pathologies', sa.Text(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), sa.Column('url', sa.String(length=255), nullable=True), sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') diff --git a/src/api/routes.py b/src/api/routes.py index 7d3a1e120d..75835a9107 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -268,7 +268,9 @@ def login_user(): email = body["email"] password = body["password"] user = User.query.filter_by(email=email).first() - if bcrypt.check_password_hash(user.password, body["password"]): + print(user) + #if bcrypt.check_password_hash(user.password, body["password"]): + if user != None: token=create_access_token(identity=user.email) user_data = { "id": user.id, @@ -304,6 +306,7 @@ def get_user_info(): @api.route('/accessories', methods=['POST']) def create_accessory(): data = request.get_json() + new_accessory = Accessories( name=data["name"], brand=data["brand"], @@ -319,8 +322,15 @@ def create_accessory(): #crear una nueva mascota @api.route('/pets', methods=['POST']) -def create_pet(user_id): +@jwt_required() +def create_pet(): data = request.get_json() + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + new_pet = Pet( name=data["name"], size=data["size"], @@ -328,7 +338,7 @@ def create_pet(user_id): age=data["age"], animal_type=data["animal_type"], pathologies=data["pathologies"], - user_id=data["user_id"] + user_id=user.id ) db.session.add(new_pet) db.session.commit() From 3c85381e3effdc79cf529be9552ee31cce5e63be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jennifer=20Fern=C3=A1ndez?= Date: Mon, 10 Mar 2025 12:26:00 +0000 Subject: [PATCH 17/79] fecth creacion mascota --- package-lock.json | 104 ++++++++++++++------------ package.json | 2 +- src/front/js/component/card.js | 21 ++++-- src/front/js/component/producto.js | 23 +++--- src/front/js/layout.js | 2 +- src/front/js/pages/RegistroMascota.js | 45 +++++++---- src/front/js/pages/VistaProducto.js | 49 ++++++++++-- src/front/js/pages/home.js | 6 ++ src/front/js/store/flux.js | 10 ++- 9 files changed, 172 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0dfb6daf0..06bcedc84f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "react-dom": "^16.8.4", "react-icons": "^5.5.0", "react-polyfills": "0.0.1", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.0" }, "devDependencies": { "@babel/cli": "^7.16.0", @@ -1685,6 +1685,7 @@ "version": "7.16.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz", "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -1851,6 +1852,15 @@ "node": ">= 8" } }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -5332,14 +5342,6 @@ "he": "bin/he" } }, - "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -7640,28 +7642,36 @@ "resolved": "https://registry.npmjs.org/react-polyfills/-/react-polyfills-0.0.1.tgz", "integrity": "sha1-vxRjUVy/d1VuZSj6TelYRy3IJq0=" }, - "node_modules/react-router-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", - "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "node_modules/react-router": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "license": "MIT", "dependencies": { - "history": "^5.2.0", - "react-router": "6.3.0" + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "react": ">=16.8" } }, - "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", - "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "node_modules/react-router-dom": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "license": "MIT", "dependencies": { - "history": "^5.2.0" + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { - "react": ">=16.8" + "react": ">=16.8", + "react-dom": ">=16.8" } }, "node_modules/readable-stream": { @@ -7735,7 +7745,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.14.5", @@ -10497,6 +10508,7 @@ "version": "7.16.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz", "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==", + "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -10629,6 +10641,11 @@ "fastq": "^1.6.0" } }, + "@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==" + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -13339,14 +13356,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "requires": { - "@babel/runtime": "^7.7.6" - } - }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -15032,23 +15041,21 @@ "resolved": "https://registry.npmjs.org/react-polyfills/-/react-polyfills-0.0.1.tgz", "integrity": "sha1-vxRjUVy/d1VuZSj6TelYRy3IJq0=" }, + "react-router": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "requires": { + "@remix-run/router": "1.23.0" + } + }, "react-router-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", - "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", "requires": { - "history": "^5.2.0", - "react-router": "6.3.0" - }, - "dependencies": { - "react-router": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", - "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", - "requires": { - "history": "^5.2.0" - } - } + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" } }, "readable-stream": { @@ -15107,7 +15114,8 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true }, "regenerator-transform": { "version": "0.14.5", diff --git a/package.json b/package.json index 540e682ff1..a8114efbd8 100755 --- a/package.json +++ b/package.json @@ -78,6 +78,6 @@ "react-dom": "^16.8.4", "react-icons": "^5.5.0", "react-polyfills": "0.0.1", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.0" } } diff --git a/src/front/js/component/card.js b/src/front/js/component/card.js index 8466a282d5..d681e10e69 100644 --- a/src/front/js/component/card.js +++ b/src/front/js/component/card.js @@ -1,26 +1,37 @@ import React, { Component } from "react"; import { useContext } from "react"; import { Context } from "../store/appContext"; +import { Link } from "react-router-dom"; -export const Card = ({name, id, description}) => { + + +export const Card = ({name, id, description, price, url}) => { const { store, actions } = useContext(Context); return ( +
- {/* + ... */} + />
{name}

{description}

+
{price}€
+
- + + + Añadir carrito. +
+ ); } \ No newline at end of file diff --git a/src/front/js/component/producto.js b/src/front/js/component/producto.js index 61630db4e9..6467f796ea 100644 --- a/src/front/js/component/producto.js +++ b/src/front/js/component/producto.js @@ -1,7 +1,8 @@ import React, { Component, useState } from "react"; +import { Link } from "react-router-dom"; -export const Producto = () => { +export const Producto = ({id, data}) => { const [precio, setPrecio] = useState(29.99); // Precio inicial const handleFormatoChange = (e) => { @@ -24,15 +25,15 @@ export const Producto = () => {
- Producto -
+ {data.url} +
-
Nombre del Producto
+
{data.name}

-

{precio}€

-
+

{data.price}€

+ {/*
-
+
*/}
-

Descripción breve del producto que está en venta. Aquí puedes incluir características y beneficios. - Lorem Ipsum es simplemente el texto de relleno de las imprentas y archivos de texto. Lorem Ipsum ha sido el texto de relleno estándar de las industrias desde el año 1500, cuando un impresor (N. del T. persona que se dedica a la imprenta). +

{data.description}. +

{data.price}

- + + Añadir carrito. +
diff --git a/src/front/js/layout.js b/src/front/js/layout.js index 1643b9aca1..7760e80587 100755 --- a/src/front/js/layout.js +++ b/src/front/js/layout.js @@ -36,7 +36,7 @@ const Layout = () => { } path="/" /> - } path="/vista-producto" /> + } path="/vista-producto/:id" /> } path="/loginSignup" /> diff --git a/src/front/js/pages/RegistroMascota.js b/src/front/js/pages/RegistroMascota.js index 534edcf4bd..3dfad60d02 100644 --- a/src/front/js/pages/RegistroMascota.js +++ b/src/front/js/pages/RegistroMascota.js @@ -1,5 +1,4 @@ import React, { useState } from "react"; -import { Context } from "../store/appContext"; import "../../styles/registroMascota.css"; export const RegistroMascota = () => { @@ -25,15 +24,40 @@ export const RegistroMascota = () => { setFotoMascota(e.target.files[0]); }; - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); - const formMascota = new formMascota(); - formMascota.append("mascota", JSON.stringify(nuevaMascota)); + + const formData = new FormData(); + formData.append("mascota", JSON.stringify(nuevaMascota)); if (fotoMascota) { - formMascota.append("foto", fotoMascota); + formData.append("foto", fotoMascota); + } + + console.log('Datos enviados:', nuevaMascota, fotoMascota); + + try { + const response = await crearMascota(formData); + console.log('Respuesta de la API:', response); + } catch (error) { + console.error('Error al crear la mascota:', error); } - console.log('Datos enviados:', formData); - // Aquí puedo enviar formData a la API o manejarlo según sea necesario + }; + + const crearMascota = async (formData) => { + const myHeaders = new Headers(); + myHeaders.append("Authorization",` ${token}`); // Cambia esto por tu token real + + const requestOptions = { + method: "POST", + headers: myHeaders, + body: formData, + }; + + const response = await fetch(`${process.env.BACKEND_URL}/api/pets`, requestOptions); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); }; return ( @@ -104,22 +128,17 @@ export const RegistroMascota = () => { -

Sube una foto de tu compañero/a :

- {fotoMascota && (
Vista previa

Archivo: {fotoMascota.name}

)} -
- - ); - } \ No newline at end of file +}; diff --git a/src/front/js/pages/VistaProducto.js b/src/front/js/pages/VistaProducto.js index ae0cebb352..a6b3d103a9 100644 --- a/src/front/js/pages/VistaProducto.js +++ b/src/front/js/pages/VistaProducto.js @@ -1,24 +1,57 @@ -import React, { Component, useState } from "react"; +import React, { Component, useState, useEffect } from "react"; import { Producto } from "../component/producto"; +import { useParams } from "react-router"; +import { Link } from "react-router-dom"; export const VistaProducto = () => { + const [detallesProducto, setDetallesProduto] = useState({}); + const {id} = useParams(); + + + const getInfoProducto = async ()=>{ + const myHeaders = new Headers(); + myHeaders.append("Cookie", ".Tunnels.Relay.WebForwarding.Cookies=CfDJ8Cs4yarcs6pKkdu0hlKHsZsWXlaTeJPKj71_qrJMXXteXpAWDSbXFuI8hriwQa8_KlAdToRFJL_4mcenP4X4lC1xYLHse-Zrcw7IevLi5xnS9znqeCHpxUlyRHLluajJvGgFv8wARVF0uf_cGTHBLtTfvxpD3r2DwlfB8AxXhLUusi9kUuJincYZr9pqfHL1enZbtSzhz0eRbLEhXf-7a69TbqvkbJ5uVRIm-QfWBqimk8G8PzULPd4i_0LPP7N4hoIETPgfaQ9HOXkG0Dl66hwNLGVk0y3uMh_9yiNHV1_7USXJFnVnWdoqrWEd-VtqMQG2FSxXbQzJ2USae1J6XKfdMjt3Gtf0c96vJkVmZdraL548Rr49j_0LIcxBhHW65KFuIQijKjrUCNSPg9vy_25fNNL_TagO-IEg0NhKfAJTsKTdzBFUmklwQ6SI1_74WtvJYSHJID5dLjsVd9DqCAtuiHFvLFGrH0yZm44buKQYlTscQX0n5q8-BVUxOahYcVY5rrXTazqfBYBkWZtWr-_qsPkxqiwio1AavDxPhNLK22voSrkDJy5X2NN69Z2gbafvs6cq0ZXmidyVml1CRrVi3mTXwjU4MhSOzav57YNinionWCFkFR_kkZZAGXVAlYPshndJSAOCyraX5wNoSeleefe-KFmN2R2akzIUoeExvoKu0uZwvAXamxLSyjVpFjyCO6IkCIqgeeQoUIVg6ZCBvMIbFV4-WPHTHEPauQk6NJoE2SReXwIzgGEAafuDRT5u6XO0IAhAHW1_Iml5OrezrpcFah06gSTQGQNYaD2pVWGOVmRoYSCxF2YsCTDAKW9QyD6eFWm9w2a4rNN53IkbbBRwWLSW4aQha9f8DphV-Ym5Ldx9pIHjjAOkiSWUuQyeik-W8NVzwCaH-RBUTFCSxMdhtS68RPgxnqo5A1ZOO0icHMDQOJ5wvKc7hOCnPV20qTDsnIo8p3YApPQtjLRAuqybqx3mGf5hp9EWlApF"); + + const requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow" + }; + + try { + const response = await fetch(`${process.env.BACKEND_URL}/api/foods/${id}`, requestOptions); + const data= await response.json(); + setDetallesProduto(data) + console.log(result) + } catch (error) { + console.error(error); + } + } + + + + + + useEffect(() => { + getInfoProducto(); + }, []); return (
- +

Productos similares

- {/* AQUI IRIAN LAS CARDS - - - - */} - +
diff --git a/src/front/js/pages/home.js b/src/front/js/pages/home.js index 81003f8d74..1f0eb64e0c 100755 --- a/src/front/js/pages/home.js +++ b/src/front/js/pages/home.js @@ -43,6 +43,8 @@ export const Home = () => { category={"dogFood"} id={dogFood.id} description={dogFood.description} + price={dogFood.price} + url={dogFood.url} key={index}/> ); })}
@@ -60,6 +62,8 @@ export const Home = () => { category={"catFood"} id={catFood.id} description={catFood.description} + price={catFood.price} + url={catFood.url} key={index}/> ); })}
@@ -77,6 +81,8 @@ export const Home = () => { category={"exóticFood"} id={exoticFood.id} description={exoticFood.description} + price={exoticFood.price} + url={exoticFood.url} key={index}/> ); })}
diff --git a/src/front/js/store/flux.js b/src/front/js/store/flux.js index 7e256fa69a..f227f4a6b7 100755 --- a/src/front/js/store/flux.js +++ b/src/front/js/store/flux.js @@ -114,7 +114,7 @@ const getState = ({ getStore, getActions, setStore }) => { setStore({token: null, user: null}) }, - + //TRAER ALIMENTO POR GRUPOS getDogFood: async () => { const myHeaders = new Headers(); myHeaders.append("Cookie", ".Tunnels.Relay.WebForwarding.Cookies=CfDJ8Cs4yarcs6pKkdu0hlKHsZs0q_CxPIxRcsYOazLvQz4rP7s5FWFmvGJndFqy0N7fvoY5B6Jou5i4ZPgwsQZsEYGDV4DoaNhJP3xwIvv1aGmoRIVdScF1G2c_hWBWqeCTHFNCvGD1Dy0sm3kBmaNdXiMsSO0myHKUFvlWHCed2AdtyCiC6CHBqk9DHs32cYjJV4GQr4cxW2IXl6QDukWwCPSYuzTnP699Rz_4pCbB8OPOQNBDyDtdks_LUoMZR2Qt6IWKmUnLGt-n3JLFjeQMZiSeKEXKNTcJknrz4p25p9-5rh3BY2FBX_kg6MtH3cLbqOyS6yqrG4cjJPpyZbVfN3iEYSR6lzEGiGZDFPvokcj_PM8fq32HR1_olrhti8nYtDctNR_8YRewW5quhBNW6mtF_-SqGTbCQVH1CiLUF2UKK_H_nGmwvWAce2n4Cdw1BLUCZhlCr3GAKWJWHqLI1K5n9OekmI4zs0TI_60R6urTRxIQx2IgkRPYizg-AUdyr6bORhYr7s3c6oFBrdA4yBShqyJFOo4fuMkQuHflmg717cZeB1MDnWgSm9Xnl4qmOlcta0fCSq15GNUPvXhAwvclIoK9NTrmSSd1wvRhoqz7ypodxTSOafQx0ybhJZwTxDeS_gv-4KjbyFngwj7Bj1TluB2qE5Hijbi4uhZb2KILE9AGYHKWSW-IWoSmEXW71c7HhH8mBEbBxidpsSi_Rip3CdXL2oUO-8TGtLx2HgJtExj_7AyTD1trSanlKgDurPSEfDuXuwtqxawfWf0a1sbp9BGk0pRLOA-tVKrxmMiFAPiNCVC1W2EXb95TKydzKIwtbcC70YyDJ4dFwnHrQmgPYxzIz7PYzICzKqZ-VDpO4lf7C3jf4OBJ9ZMV4JRvPiR2kUwMNX_c5CuMLaKrZNzqCFwZBUa4N4AUyTT83mtzFQGAZjMXDeZNok7mjTYBp121qquiSKX8ft05b1MTsVtRfg3ETiFdOHmYQ2-1EcSwBY-VhjWIlkQiFr1yBWXk-g"); @@ -131,9 +131,8 @@ const getState = ({ getStore, getActions, setStore }) => { if (!response.ok) { throw new Error('Network response was not ok'); } - - const data = await response.json(); - setStore({ dogFood: data }); // Asegúrate de que data.result sea la estructura correcta + const data = await response.json(); + setStore({ dogFood: data }); console.log(data); } catch (error) { console.error('Error fetching dog food:', error); @@ -195,6 +194,9 @@ const getState = ({ getStore, getActions, setStore }) => { } , + //TRAER ALIMENTO A VISTA CARRITO POR ID + + From 39ac17f89b65faf5e1602ed2efa519d8592f0019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jennifer=20Fern=C3=A1ndez?= Date: Tue, 11 Mar 2025 11:39:55 +0000 Subject: [PATCH 18/79] =?UTF-8?q?funci=C3=B3n=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 1 + Pipfile.lock | 67 ++++++++++++++++- src/api/routes.py | 103 ++++++++++++++++++++------ src/app.py | 4 +- src/front/js/component/card.js | 2 +- src/front/js/pages/RegistroMascota.js | 67 ++++++++++++----- 6 files changed, 201 insertions(+), 43 deletions(-) diff --git a/Pipfile b/Pipfile index ff6b238176..bdd43c634b 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ flask-admin = "*" typing-extensions = "*" flask-jwt-extended = "*" wtforms = "==3.1.2" +flask-bcrypt = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index 91123c2485..3f91abb23b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "94a6703b952d22b68d3a5c0a0ba5acfe0104b92d0f991a972ff1bc9d0b15c43b" + "sha256": "962f6b9c1214f196ffbb77af32268c9ca3e3e2dff9105182dc0aa1f56879dcfb" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,63 @@ "markers": "python_version >= '3.9'", "version": "==1.15.1" }, + "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", @@ -72,6 +129,14 @@ "index": "pypi", "version": "==1.6.1" }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" + ], + "index": "pypi", + "version": "==1.0.1" + }, "flask-cors": { "hashes": [ "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", diff --git a/src/api/routes.py b/src/api/routes.py index 7294e6c396..bfcb760e7f 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -8,8 +8,11 @@ from sqlalchemy import select, and_, or_ import json from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity +from flask_bcrypt import Bcrypt api = Blueprint('api', __name__) +bcrypt = Bcrypt() + # Allow CORS requests to this API CORS(api) @@ -81,6 +84,8 @@ def get_pet(pet_id): return jsonify({"error": "pet not found"}), 404 return jsonify(pet.serialize()), 200 + + #obtener sugerencias de comida según mascota @api.route('/foods/suggestions/', methods=['GET']) def get_pet_suggestions(pet_id): @@ -103,6 +108,33 @@ def get_pet_suggestions(pet_id): return [food[0].serialize() for food in food_suggestions], 200 +# #obtener sugerencias de comida según mascota +# @api.route('/foods/suggestions/', methods=['GET']) +# def get_pet_suggestions(pet_id): +# pet = Pet.query.get(pet_id) +# if not pet: +# return jsonify({"error": "pet not found"}), 404 + +# pet_data = pet.serialize() +# filters = [Food.animal_type == pet_data["animal_type"], Food.age == pet_data["age"]] + +# if pet_data["animal_type"] == "perro": +# filters.append(Food.size == pet_data["size"]) + +# # Si la mascota tiene patologías, agregarlas al filtro +# if pet_data["pathologies"]: +# pathologies_list = pet_data["pathologies"].split(",") +# filters.append(Food.pathologies.in_(pathologies_list)) +# else: +# filters.append(or_(Food.pathologies != None, Food.pathologies == None)) +# food_suggestions = db.session.execute(select(Food).where(and_(*filters))).all() + +# if not food_suggestions: +# return jsonify({"error": "no suggestions found"}), 404 + +# return jsonify([food[0].serialize() for food in food_suggestions]), 200 + + #obtener todos los alimentos según tipo de animal @api.route('/foods/cat', methods=['GET']) def get_all_cat_food(): @@ -194,13 +226,16 @@ def create_food(): #registrar nuevo usuario(signup) + @api.route('/signup', methods=['POST']) def create_user(): data = request.get_json() + hashed_password = bcrypt.generate_password_hash(data["password"]).decode('utf-8') + new_user = User( name=data["name"], email=data["email"], - password=data["password"] + password=hashed_password ) if User.query.filter_by(email=data["email"]).first(): return jsonify({"msg": "El usuario ya existe"}), 400 @@ -232,16 +267,19 @@ def login_user(): email = body["email"] password = body["password"] - - user = User().query.filter_by(email=email, password=password).first() - token=create_access_token(identity=user.email) - user_data = { - "id": user.id, - "email": user.email, - "name": user.name - } - - return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + user = User.query.filter_by(email=email).first() + print(user) + #if bcrypt.check_password_hash(user.password, body["password"]): + if user != None and bcrypt.check_password_hash(user.password, body["password"]): + token=create_access_token(identity=user.email) + user_data = { + "id": user.id, + "email": user.email, + "name": user.name + } + + return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + return jsonify({"msg": "credenciales no validas"}), 400 #Vista privada del usuario CON el token @api.route('/user', methods=['GET']) @@ -264,10 +302,29 @@ def get_user_info(): return jsonify(user_data), 200 +#Vista privada del usuario a sus mascotas +@api.route('/user_pets', methods=['GET']) +@jwt_required() +def get_user_pets_info(): + + current_user_email = get_jwt_identity() + + user = User().query.filter_by(email=current_user_email).first() + + pets = Pet().query.filter_by(user_id=user.id).all() + + if not pets: + return jsonify({"msg": "mascota no econtrada"}), 400 + + return jsonify([pet.serialize() for pet in pets]), 200 + + + #crear un nuevo accesorio @api.route('/accessories', methods=['POST']) def create_accessory(): data = request.get_json() + new_accessory = Accessories( name=data["name"], brand=data["brand"], @@ -283,22 +340,27 @@ def create_accessory(): #crear una nueva mascota @api.route('/pets', methods=['POST']) +@jwt_required() def create_pet(): data = request.get_json() - new_accessory = Pet( + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + + new_pet = Pet( name=data["name"], size=data["size"], breed=data["breed"], age=data["age"], animal_type=data["animal_type"], pathologies=data["pathologies"], - user_id=data["user_id"], - - -) - db.session.add(new_accessory) + user_id=user.id + ) + db.session.add(new_pet) db.session.commit() - return jsonify(new_accessory.serialize()), 201 + return jsonify(new_pet.serialize()), 201 # @api.route('/foods/', methods=['PUT']) # def update_food(food_id): @@ -333,7 +395,4 @@ def create_pet(): # "size" : food.size, # "pathologies": food.pathologies, # "url": food.url -# }) - - - +# }) \ No newline at end of file diff --git a/src/app.py b/src/app.py index 727d9c2c3b..73df54a838 100644 --- a/src/app.py +++ b/src/app.py @@ -11,6 +11,7 @@ from api.admin import setup_admin from api.commands import setup_commands from flask_jwt_extended import JWTManager +from flask_bcrypt import Bcrypt from datetime import timedelta # from models import Person @@ -35,6 +36,7 @@ app.config["JWT_ACCESS_TOKEN_EXPIRE"] = timedelta(hours=1) jwt = JWTManager(app) +bcrypt = Bcrypt(app) # add the admin @@ -75,4 +77,4 @@ def serve_any_other_file(path): # this only runs if `$ python src/main.py` is executed if __name__ == '__main__': PORT = int(os.environ.get('PORT', 3001)) - app.run(host='0.0.0.0', port=PORT, debug=True) + app.run(host='0.0.0.0', port=PORT, debug=True) \ No newline at end of file diff --git a/src/front/js/component/card.js b/src/front/js/component/card.js index d681e10e69..a5d5e0b75c 100644 --- a/src/front/js/component/card.js +++ b/src/front/js/component/card.js @@ -15,7 +15,7 @@ return ( ... diff --git a/src/front/js/pages/RegistroMascota.js b/src/front/js/pages/RegistroMascota.js index 3dfad60d02..16e4973958 100644 --- a/src/front/js/pages/RegistroMascota.js +++ b/src/front/js/pages/RegistroMascota.js @@ -1,16 +1,19 @@ -import React, { useState } from "react"; +import React, { useState, useContext } from "react"; import "../../styles/registroMascota.css"; +import { Context } from "../store/appContext"; export const RegistroMascota = () => { + const { actions, store} = useContext (Context) const [nuevaMascota, setNuevaMascota] = useState({ nombre: "", especie: "", tamaño: "", etapaVital: "", - patología: "" + patologia: "" }); const [fotoMascota, setFotoMascota] = useState(null); + const token = store.token; // Define tu token aquí const handleChange = (e) => { const { name, value } = e.target; @@ -26,39 +29,47 @@ export const RegistroMascota = () => { const handleSubmit = async (e) => { e.preventDefault(); - - const formData = new FormData(); - formData.append("mascota", JSON.stringify(nuevaMascota)); + + const datosMascota = new FormData(); + datosMascota.append("mascota", JSON.stringify(nuevaMascota)); if (fotoMascota) { - formData.append("foto", fotoMascota); + datosMascota.append("foto", fotoMascota); } - + console.log('Datos enviados:', nuevaMascota, fotoMascota); - + try { - const response = await crearMascota(formData); + const response = await crearMascota(datosMascota); console.log('Respuesta de la API:', response); } catch (error) { console.error('Error al crear la mascota:', error); } }; - const crearMascota = async (formData) => { - const myHeaders = new Headers(); - myHeaders.append("Authorization",` ${token}`); // Cambia esto por tu token real + const crearMascota = async (datosMascota) => { + const myHeaders = new Headers(); + myHeaders.append("Authorization", `Bearer ${token}`); + const requestOptions = { method: "POST", headers: myHeaders, - body: formData, + body: datosMascota, + redirect: "follow" }; - - const response = await fetch(`${process.env.BACKEND_URL}/api/pets`, requestOptions); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + + try { + const response = await fetch(`${process.env.BACKEND_URL}/api/pets`, requestOptions); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error(error); + throw error; } - return await response.json(); }; + return (
@@ -125,6 +136,26 @@ export const RegistroMascota = () => {
+ +
+ + +
From d01d7fa1c99f52521f62d8fc0f2b1df74fdd8f5c Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Tue, 11 Mar 2025 18:44:57 +0000 Subject: [PATCH 19/79] Actualizacion a nuevos datos desde Develop --- migrations/versions/ba227b582218_.py | 35 ---------- .../{7f479f7b82b2_.py => e2840c4c5201_.py} | 6 +- package-lock.json | 16 +++++ package.json | 1 + src/front/js/component/navbar.js | 2 +- src/front/js/layout.js | 5 +- src/front/js/pages/perfilUsuario.js | 67 +++++++++++++++++++ src/front/js/store/flux.js | 45 ++++++++++++- src/front/styles/home.css | 2 +- 9 files changed, 135 insertions(+), 44 deletions(-) delete mode 100644 migrations/versions/ba227b582218_.py rename migrations/versions/{7f479f7b82b2_.py => e2840c4c5201_.py} (96%) create mode 100644 src/front/js/pages/perfilUsuario.js diff --git a/migrations/versions/ba227b582218_.py b/migrations/versions/ba227b582218_.py deleted file mode 100644 index d8c4bdb055..0000000000 --- a/migrations/versions/ba227b582218_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: ba227b582218 -Revises: -Create Date: 2025-03-01 09:14:08.397142 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'ba227b582218' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password', sa.String(length=80), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - # ### end Alembic commands ### diff --git a/migrations/versions/7f479f7b82b2_.py b/migrations/versions/e2840c4c5201_.py similarity index 96% rename from migrations/versions/7f479f7b82b2_.py rename to migrations/versions/e2840c4c5201_.py index 945e5e1d6d..4c6f1f3525 100644 --- a/migrations/versions/7f479f7b82b2_.py +++ b/migrations/versions/e2840c4c5201_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 7f479f7b82b2 +Revision ID: e2840c4c5201 Revises: -Create Date: 2025-03-08 10:50:02.759160 +Create Date: 2025-03-10 09:27:56.857391 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '7f479f7b82b2' +revision = 'e2840c4c5201' down_revision = None branch_labels = None depends_on = None diff --git a/package-lock.json b/package-lock.json index f0dfb6daf0..30b2bff07d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.1", "license": "ISC", "dependencies": { + "lucide-react": "^0.479.0", "prop-types": "^15.6.1", "react": "^16.8.4", "react-dom": "^16.8.4", @@ -6284,6 +6285,15 @@ "node": ">=10" } }, + "node_modules/lucide-react": { + "version": "0.479.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", + "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -14038,6 +14048,12 @@ "yallist": "^4.0.0" } }, + "lucide-react": { + "version": "0.479.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", + "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", + "requires": {} + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", diff --git a/package.json b/package.json index 540e682ff1..ef48c1f3a4 100755 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ ] }, "dependencies": { + "lucide-react": "^0.479.0", "prop-types": "^15.6.1", "react": "^16.8.4", "react-dom": "^16.8.4", diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index fd4e38ff8f..42ad6602e2 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -41,7 +41,7 @@ export const Navbar = () => { {user ? ( // Si el usuario está autenticado, mostramos Perfil y Cesta <> - Perfil + Perfil Cesta diff --git a/src/front/js/layout.js b/src/front/js/layout.js index c20bcab3d0..af731d8f8b 100755 --- a/src/front/js/layout.js +++ b/src/front/js/layout.js @@ -4,7 +4,7 @@ import ScrollToTop from "./component/scrollToTop"; import { BackendURL } from "./component/backendURL"; import { Home } from "./pages/home"; - +import { PerfilUsuario} from "./pages/perfilUsuario"; import { Demo } from "./pages/demo"; import { Single } from "./pages/single"; import injectContext from "./store/appContext"; @@ -34,12 +34,11 @@ const Layout = () => { } path="/" /> - } path="/vista-producto" /> } path="/loginSignup" /> - + } path="/perfilUsuario" /> } path="/demo" /> } path="/single/:theid" /> Not found!} /> diff --git a/src/front/js/pages/perfilUsuario.js b/src/front/js/pages/perfilUsuario.js new file mode 100644 index 0000000000..b1558a9b6a --- /dev/null +++ b/src/front/js/pages/perfilUsuario.js @@ -0,0 +1,67 @@ +import React,{ useEffect, useContext } from "react"; +import { Context } from "../store/appContext"; +import { User, MapPin, PlusCircle } from "lucide-react"; + +export const PerfilUsuario =() => { + const { store, actions } = useContext(Context); + + useEffect(() => { + actions.getUser(); // Obtiene el usuario y sus mascotas + }, []); + + if (!store.user) return
Cargando...
; + + return ( +
+ {/* Header */} +
+
+
+ +
+ +
+
+ + +
+
+ + {/* Perfil de Usuario */} +
+

Hola @{store.user.name}

+
+
{store.user.name}
+
{store.user.email}
+ +
+
+ + {/* Mis Mascotas */} +

Mis mascotas

+
+ + {store.pets && store.pets.length > 0 ? ( + store.pets.map((pet, index) => ( +
+ {pet.name} + {pet.name} + {pet.breed || pet.animal_type} + {pet.age} años + {pet.size} +
+ )) + ) : ( +

No tienes mascotas registradas

+ )} +
+
+ ); +} diff --git a/src/front/js/store/flux.js b/src/front/js/store/flux.js index 7e256fa69a..3aae2263cc 100755 --- a/src/front/js/store/flux.js +++ b/src/front/js/store/flux.js @@ -111,9 +111,11 @@ const getState = ({ getStore, getActions, setStore }) => { }, logout: () => { localStorage.removeItem('token'); - setStore({token: null, user: null}) + setStore({token: null, user: null, pets: [] }) }, + + getDogFood: async () => { const myHeaders = new Headers(); @@ -194,6 +196,47 @@ const getState = ({ getStore, getActions, setStore }) => { } } , + + getUser: async () => { + try { + const token = localStorage.getItem("token"); + if (!token) throw new Error("No token found"); + + const resp = await fetch(`${process.env.BACKEND_URL}/api/user`, { + headers: { + "Authorization": `Bearer ${token}` + } + }); + + if (!resp.ok) throw new Error("Error al obtener el usuario"); + + const data = await resp.json(); + setStore({ user: data }); + + // Una vez que tenemos el usuario, obtenemos sus mascotas + getActions().getPets(data.id); + + } catch (error) { + console.log("Error al obtener usuario", error); + } + }, + + getPets: async (userId) => { + try { + const resp = await fetch(`${process.env.BACKEND_URL}/api/pets`); + if (!resp.ok) throw new Error("Error obteniendo mascotas"); + + const allPets = await resp.json(); + + // Filtrar mascotas solo del usuario autenticado + const userPets = allPets.filter(pet => pet.user_id === userId); + setStore({ pets: userPets }); + + } catch (error) { + console.log("Error al obtener mascotas", error); + } + }, + diff --git a/src/front/styles/home.css b/src/front/styles/home.css index 68c7b7f067..bc9a653a8d 100755 --- a/src/front/styles/home.css +++ b/src/front/styles/home.css @@ -5,7 +5,7 @@ one more css for each page that will contain the selected used on that page only (the ones not reused in other pages). */ -body { +.registration-view-container { font-family: 'Arial', sans-serif; background-color: #f5f7fa; display: flex; From dfe752c785538667ea75e7c392c14df9b4be910a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jennifer=20Fern=C3=A1ndez?= Date: Tue, 11 Mar 2025 18:51:04 +0000 Subject: [PATCH 20/79] formulario registro con user ligado a pets --- src/front/js/pages/RegistroMascota.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/front/js/pages/RegistroMascota.js b/src/front/js/pages/RegistroMascota.js index 16e4973958..8788c97506 100644 --- a/src/front/js/pages/RegistroMascota.js +++ b/src/front/js/pages/RegistroMascota.js @@ -3,7 +3,9 @@ import "../../styles/registroMascota.css"; import { Context } from "../store/appContext"; export const RegistroMascota = () => { + const { actions, store} = useContext (Context) + const [nuevaMascota, setNuevaMascota] = useState({ nombre: "", especie: "", @@ -13,8 +15,9 @@ export const RegistroMascota = () => { }); const [fotoMascota, setFotoMascota] = useState(null); - const token = store.token; // Define tu token aquí + const token = store.token; // Definir token aquí?? + //setear datos mascota formulario const handleChange = (e) => { const { name, value } = e.target; setNuevaMascota({ @@ -23,10 +26,12 @@ export const RegistroMascota = () => { }); }; + //cargar foto formulario const handleFileChange = (e) => { setFotoMascota(e.target.files[0]); }; + //enviar al formulario const handleSubmit = async (e) => { e.preventDefault(); @@ -46,7 +51,7 @@ export const RegistroMascota = () => { } }; - + //Creación mascota const crearMascota = async (datosMascota) => { const myHeaders = new Headers(); myHeaders.append("Authorization", `Bearer ${token}`); From 374da117bd4b2e9708fb486d20ab88c60ae205da Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:06:25 +0000 Subject: [PATCH 21/79] =?UTF-8?q?creacion=20de=20mascota=20seg=C3=BAn=20us?= =?UTF-8?q?uario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{7988effcddd5_.py => 174f8e955473_.py} | 8 ++--- src/api/routes.py | 34 +++++++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) rename migrations/versions/{7988effcddd5_.py => 174f8e955473_.py} (94%) diff --git a/migrations/versions/7988effcddd5_.py b/migrations/versions/174f8e955473_.py similarity index 94% rename from migrations/versions/7988effcddd5_.py rename to migrations/versions/174f8e955473_.py index 2455308d0b..73bb9799fb 100644 --- a/migrations/versions/7988effcddd5_.py +++ b/migrations/versions/174f8e955473_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 7988effcddd5 +Revision ID: 174f8e955473 Revises: -Create Date: 2025-03-08 13:29:06.947775 +Create Date: 2025-03-11 15:05:58.104834 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '7988effcddd5' +revision = '174f8e955473' down_revision = None branch_labels = None depends_on = None @@ -60,7 +60,7 @@ def upgrade(): sa.Column('age', sa.String(), nullable=False), sa.Column('animal_type', sa.String(), nullable=False), sa.Column('pathologies', sa.Text(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), sa.Column('url', sa.String(length=255), nullable=True), sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') diff --git a/src/api/routes.py b/src/api/routes.py index 7d3a1e120d..082e176802 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -268,7 +268,9 @@ def login_user(): email = body["email"] password = body["password"] user = User.query.filter_by(email=email).first() - if bcrypt.check_password_hash(user.password, body["password"]): + print(user) + #if bcrypt.check_password_hash(user.password, body["password"]): + if user != None and bcrypt.check_password_hash(user.password, body["password"]): token=create_access_token(identity=user.email) user_data = { "id": user.id, @@ -300,10 +302,29 @@ def get_user_info(): return jsonify(user_data), 200 +#Vista privada del usuario a sus mascotas +@api.route('/user_pets', methods=['GET']) +@jwt_required() +def get_user_pets_info(): + + current_user_email = get_jwt_identity() + + user = User().query.filter_by(email=current_user_email).first() + + pets = Pet().query.filter_by(user_id=user.id).all() + + if not pets: + return jsonify({"msg": "mascota no econtrada"}), 400 + + return jsonify([pet.serialize() for pet in pets]), 200 + + + #crear un nuevo accesorio @api.route('/accessories', methods=['POST']) def create_accessory(): data = request.get_json() + new_accessory = Accessories( name=data["name"], brand=data["brand"], @@ -319,8 +340,15 @@ def create_accessory(): #crear una nueva mascota @api.route('/pets', methods=['POST']) -def create_pet(user_id): +@jwt_required() +def create_pet(): data = request.get_json() + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + new_pet = Pet( name=data["name"], size=data["size"], @@ -328,7 +356,7 @@ def create_pet(user_id): age=data["age"], animal_type=data["animal_type"], pathologies=data["pathologies"], - user_id=data["user_id"] + user_id=user.id ) db.session.add(new_pet) db.session.commit() From c88f9df4b70a6a07189a6d2c4b6c2773c2ef8fc4 Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Tue, 11 Mar 2025 19:22:02 +0000 Subject: [PATCH 22/79] consultar mascota por usuario y bcrypt --- migrations/versions/{174f8e955473_.py => 667238548494_.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename migrations/versions/{174f8e955473_.py => 667238548494_.py} (96%) diff --git a/migrations/versions/174f8e955473_.py b/migrations/versions/667238548494_.py similarity index 96% rename from migrations/versions/174f8e955473_.py rename to migrations/versions/667238548494_.py index 73bb9799fb..7e9b61eab6 100644 --- a/migrations/versions/174f8e955473_.py +++ b/migrations/versions/667238548494_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 174f8e955473 +Revision ID: 667238548494 Revises: -Create Date: 2025-03-11 15:05:58.104834 +Create Date: 2025-03-11 19:19:38.314131 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '174f8e955473' +revision = '667238548494' down_revision = None branch_labels = None depends_on = None From dfa62e8daee3f2386210845e3905961b1f973e8a Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Wed, 12 Mar 2025 08:53:50 +0000 Subject: [PATCH 23/79] Bcrypt y primeros puts --- .../{667238548494_.py => 2c9cc9571917_.py} | 6 +- src/api/routes.py | 111 +++++++++++++----- 2 files changed, 82 insertions(+), 35 deletions(-) rename migrations/versions/{667238548494_.py => 2c9cc9571917_.py} (96%) diff --git a/migrations/versions/667238548494_.py b/migrations/versions/2c9cc9571917_.py similarity index 96% rename from migrations/versions/667238548494_.py rename to migrations/versions/2c9cc9571917_.py index 7e9b61eab6..5652af478d 100644 --- a/migrations/versions/667238548494_.py +++ b/migrations/versions/2c9cc9571917_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 667238548494 +Revision ID: 2c9cc9571917 Revises: -Create Date: 2025-03-11 19:19:38.314131 +Create Date: 2025-03-11 20:33:29.382955 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '667238548494' +revision = '2c9cc9571917' down_revision = None branch_labels = None depends_on = None diff --git a/src/api/routes.py b/src/api/routes.py index 082e176802..01374c4321 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -362,40 +362,87 @@ def create_pet(): db.session.commit() return jsonify(new_pet.serialize()), 201 -# @api.route('/foods/', methods=['PUT']) -# def update_food(food_id): -# food = Food.query.get(food_id) -# if not food: -# return jsonify({"message": "Food not found"}), 404 +@api.route('/foods/', methods=['PUT']) +def update_food(food_id): + food = Food.query.get(food_id) + if not food: + return jsonify({"message": "Food not found"}), 404 -# data = request.get_json() + data = request.get_json() + + food.name = data.get("name", food.name) + food.brand = data.get("brand", food.brand) + food.description = data.get("description", food.description) + food.ingredients = data.get("ingredients", food.ingredients) + food.weight = data.get("weight", food.weight) + food.price = data.get("price", food.price) + food.animal_type = data.get("animal_type", food.animal_type) + food.size = data.get("size", food.size) + food.pathologies = data.get("pathologies", food.pathologies) + food.url = data.get("url", food.url) + + db.session.commit() + + return jsonify({ + "id": food.id, + "name": food.name, + "brand": food.brand, + "description": food.description, + "ingredients": food.ingredients, + "animal_type": food.animal_type, + "price": food.price, + "weight": food.weight, + "size" : food.size, + "pathologies": food.pathologies, + "url": food.url + }) + + + +@api.route('/users', methods=['PUT']) +@jwt_required() +def update_user(): + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + if not user: + return jsonify({"message": "user not found"}), 404 + + data = request.get_json() + + user.name = data.get("name", user.name) + user.password = data.get("password" , user.password) -# food.name = data.get("name", food.name) -# food.brand = data.get("brand", food.brand) -# food.description = data.get("description", food.description) -# food.ingredients = data.get("ingredients", food.ingredients) -# food.weight = data.get("weight", food.weight) -# food.price = data.get("price", food.price) -# food.animal_type = data.get("animal_type", food.animal_type) -# food.size = data.get("size", food.size) -# food.pathologies = data.get("pathologies", food.patologies) -# food.url = data.get("url", food.url) - -# db.session.commit() - -# return jsonify({ -# "id": food.id, -# "name": food.name, -# "brand": food.brand, -# "description": food.description, -# "ingredients": food.ingredients, -# "animal_type": food.animal_type, -# "price": food.price, -# "weight": food.weight, -# "size" : food.size, -# "pathologies": food.pathologies, -# "url": food.url -# }) + db.session.commit() + + return jsonify({ + "id": user.id, + "name": user.name, + "password":user.password + }) + + +@api.route('/pets/', methods=['PUT']) +@jwt_required() +def new_pet(pet_id): + data = request.get_json() + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + + data = request.get_json() + + pet=Pet().query.filter_by(id=pet_id).first() + + pet.name = data.get("name", pet.name) + pet.size = data.get("size", pet.size) + pet.age = data.get("age", pet.age) + pet.animal_type = data.get("animal_type", pet.animal_type) + pet.pathologies = data.get("pathologies", pet.pathologies) + pet.url = data.get("url", pet.url) + + db.session.commit() + return jsonify(new_pet.serialize()), 201 From 9374d16d838bd4bea0bda524cf85429c307d816e Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Wed, 12 Mar 2025 14:05:10 +0000 Subject: [PATCH 24/79] =?UTF-8?q?Dise=C3=B1o1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- migrations/versions/a997e61e9374_.py | 87 ------------ .../{185795368384_.py => ebbe4006ab2c_.py} | 16 +-- package-lock.json | 14 ++ src/front/img/Icono puppereats.png | Bin 0 -> 17031 bytes ...blanco-al-generado_866663-5304 (1) (1).jpg | Bin 0 -> 49980 bytes ...ndo-blanco-al-generado_866663-5304 (1).jpg | Bin 0 -> 52485 bytes src/front/js/component/navbar.js | 75 +++++++---- src/front/js/layout.js | 2 +- src/front/js/pages/home.js | 126 ++++++++---------- src/front/js/pages/loginSignup.js | 18 ++- src/front/js/pages/perfilUsuario.js | 40 +++--- src/front/styles/home.css | 1 + 12 files changed, 154 insertions(+), 225 deletions(-) delete mode 100644 migrations/versions/a997e61e9374_.py rename migrations/versions/{185795368384_.py => ebbe4006ab2c_.py} (85%) create mode 100644 src/front/img/Icono puppereats.png create mode 100644 src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg create mode 100644 src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1).jpg diff --git a/migrations/versions/a997e61e9374_.py b/migrations/versions/a997e61e9374_.py deleted file mode 100644 index 1efde50579..0000000000 --- a/migrations/versions/a997e61e9374_.py +++ /dev/null @@ -1,87 +0,0 @@ -"""empty message - -<<<<<<<< HEAD:migrations/versions/a997e61e9374_.py -Revision ID: a997e61e9374 -Revises: -Create Date: 2025-03-11 18:55:12.245047 -======== -Revision ID: 185795368384 -Revises: -Create Date: 2025-03-12 10:21:06.585696 ->>>>>>>> 1c0f2d321c59e95e79681da621e3960f05830eea:migrations/versions/185795368384_.py - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -<<<<<<<< HEAD:migrations/versions/a997e61e9374_.py -revision = 'a997e61e9374' -======== -revision = '185795368384' ->>>>>>>> 1c0f2d321c59e95e79681da621e3960f05830eea:migrations/versions/185795368384_.py -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('accessories', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('brand', sa.String(length=100), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('animal_type', sa.String(length=50), nullable=False), - sa.Column('pathologies', sa.Text(), nullable=True), - sa.Column('price', sa.Float(), nullable=False), - sa.Column('url', sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('foods', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('brand', sa.String(length=100), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('ingredients', sa.Text(), nullable=False), - sa.Column('weight', sa.Float(), nullable=False), - sa.Column('price', sa.Float(), nullable=False), - sa.Column('age', sa.String(), nullable=False), - sa.Column('animal_type', sa.String(length=50), nullable=False), - sa.Column('size', sa.String(length=30), nullable=True), - sa.Column('pathologies', sa.Text(), nullable=True), - sa.Column('url', sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=80), nullable=False), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password', sa.String(length=80), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - op.create_table('pet', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('size', sa.String(length=100), nullable=True), - sa.Column('breed', sa.String(length=100), nullable=True), - sa.Column('age', sa.String(), nullable=False), - sa.Column('animal_type', sa.String(), nullable=False), - sa.Column('pathologies', sa.Text(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('url', sa.String(length=255), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('pet') - op.drop_table('user') - op.drop_table('foods') - op.drop_table('accessories') - # ### end Alembic commands ### diff --git a/migrations/versions/185795368384_.py b/migrations/versions/ebbe4006ab2c_.py similarity index 85% rename from migrations/versions/185795368384_.py rename to migrations/versions/ebbe4006ab2c_.py index 1efde50579..0bf694cba7 100644 --- a/migrations/versions/185795368384_.py +++ b/migrations/versions/ebbe4006ab2c_.py @@ -1,14 +1,8 @@ """empty message -<<<<<<<< HEAD:migrations/versions/a997e61e9374_.py -Revision ID: a997e61e9374 +Revision ID: ebbe4006ab2c Revises: -Create Date: 2025-03-11 18:55:12.245047 -======== -Revision ID: 185795368384 -Revises: -Create Date: 2025-03-12 10:21:06.585696 ->>>>>>>> 1c0f2d321c59e95e79681da621e3960f05830eea:migrations/versions/185795368384_.py +Create Date: 2025-03-12 11:34:26.411051 """ from alembic import op @@ -16,11 +10,7 @@ # revision identifiers, used by Alembic. -<<<<<<<< HEAD:migrations/versions/a997e61e9374_.py -revision = 'a997e61e9374' -======== -revision = '185795368384' ->>>>>>>> 1c0f2d321c59e95e79681da621e3960f05830eea:migrations/versions/185795368384_.py +revision = 'ebbe4006ab2c' down_revision = None branch_labels = None depends_on = None diff --git a/package-lock.json b/package-lock.json index 53419d48c6..a1d2e74c5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1865,6 +1865,15 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -10679,6 +10688,11 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "peer": true }, + "@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==" + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", diff --git a/src/front/img/Icono puppereats.png b/src/front/img/Icono puppereats.png new file mode 100644 index 0000000000000000000000000000000000000000..d8b02f0ced86818787aeaa4d2b2291711c320814 GIT binary patch literal 17031 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 literal 0 HcmV?d00001 diff --git a/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg b/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg new file mode 100644 index 0000000000000000000000000000000000000000..7303dcf0364fbf2ec00b23a9615b000a10b88e70 GIT binary patch literal 49980 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 ZIsBb^pK}$NtEhkTH~a|vzn7_x|Jj@t8bbg8 literal 0 HcmV?d00001 diff --git a/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1).jpg b/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1).jpg new file mode 100644 index 0000000000000000000000000000000000000000..dcdfd1082461a057d76e14f7c27a0d9a36025f23 GIT binary patch literal 52485 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

XR#CK_P?q%jHKrdjIEsX=T(bD+72skJ)Q*) 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%0D@ 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 L^&bn { const { store, actions } = useContext(Context); - const { user } = store; // Obtenemos el usuario del store + const { user } = store; + + useEffect(() => { + console.log("Usuario en navbar:", user); + }, [user]); - useEffect(() => { - console.log("Usuario en navbar:", user); - }, [user]); return ( -

); -}; \ No newline at end of file +}; From 58fcb5f9484dafe0ccb799694ffbc79d00a5ccda Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Wed, 19 Mar 2025 11:45:37 +0100 Subject: [PATCH 63/79] Update perfilUsuario.js --- src/front/js/pages/perfilUsuario.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/front/js/pages/perfilUsuario.js b/src/front/js/pages/perfilUsuario.js index 852fdae0a0..8bd6a85771 100644 --- a/src/front/js/pages/perfilUsuario.js +++ b/src/front/js/pages/perfilUsuario.js @@ -1,5 +1,5 @@ import React, { useEffect, useContext, useState } from "react"; -import logo from "/workspaces/PupperEatsAppreact-flask-hello-deprecated/src/front/img/Icono puppereats.png"; +import logo from "../../img/Icono puppereats.png"; import {EdicionPerfil} from "../component/edicionPerfil"; import { Context } from "../store/appContext"; import { User, MapPin, PlusCircle } from "lucide-react"; From fcacca62a25127931984423d200e5540dd7bb01a Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Wed, 19 Mar 2025 11:46:46 +0100 Subject: [PATCH 64/79] Update home.js --- src/front/js/pages/home.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/front/js/pages/home.js b/src/front/js/pages/home.js index 9863bfe6d1..712fbdad00 100755 --- a/src/front/js/pages/home.js +++ b/src/front/js/pages/home.js @@ -1,5 +1,5 @@ import React, { useContext, useEffect } from "react"; -import logo from "/workspaces/PupperEatsAppreact-flask-hello-deprecated/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg"; +import logo from "../../img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg"; import { Context } from "../store/appContext"; import { Card } from "../component/card"; import "../../styles/home.css"; @@ -119,4 +119,4 @@ export const Home = ({ activeCategory }) => { ); -}; \ No newline at end of file +}; From 6d045f3f3370e2109968a4c439f8bcd69b9229bc Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Wed, 19 Mar 2025 11:48:13 +0100 Subject: [PATCH 65/79] Update producto.js --- src/front/js/component/producto.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/front/js/component/producto.js b/src/front/js/component/producto.js index 007ab541ce..8749eaf5d2 100644 --- a/src/front/js/component/producto.js +++ b/src/front/js/component/producto.js @@ -69,9 +69,9 @@ export const Producto = ({ id, data }) => {
- +
@@ -81,4 +81,4 @@ export const Producto = ({ id, data }) => { ); -}; \ No newline at end of file +}; From bb6f680568560ec14418808ac89f8a7af0e2b294 Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Wed, 19 Mar 2025 11:50:00 +0100 Subject: [PATCH 66/79] Update commands.py --- src/api/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/commands.py b/src/api/commands.py index 0dfd944a5b..0072ae37ad 100644 --- a/src/api/commands.py +++ b/src/api/commands.py @@ -48,7 +48,7 @@ def insert_data_catfood(): catfood.price = 6.42 catfood.animal_type = "gato" catfood.age = "adulto" - catfood.pathologies = "hipoalergénico" + catfood.pathologies = "hipoalergenico" catfood.url = "https://era2vrmzk5n.exactdn.com/wp-content/uploads/2022/09/Pienso-15K-croqPEZ-Ayurveda-gato-kasaludintegral-1080x1080pix.jpg?strip=all&lossy=1&w=648&ssl=1" db.session.add(catfood) db.session.commit() @@ -381,4 +381,4 @@ def insert_data_pet(): - \ No newline at end of file + From 7124deca2d7f3ed880e488ace48f6b910175d0d4 Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Wed, 19 Mar 2025 12:21:48 +0100 Subject: [PATCH 67/79] Update render_build.sh --- render_build.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/render_build.sh b/render_build.sh index 82c64b8772..2e6ddb2573 100644 --- a/render_build.sh +++ b/render_build.sh @@ -8,5 +8,12 @@ npm run build cd ../.. pipenv install +pipenv run reset_db +pipenv run flask insert-test-users 5 +pipenv run flask insert_data_catfood +pipenv run flask insert_data_dogfood +pipenv run flask insert_data_exoticfood +pipenv run flask insert_data_accessories +pipenv run flask insert_data_pet pipenv run upgrade From 864585cb56621460f2125bca3bc32caf8526d181 Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Wed, 19 Mar 2025 12:36:41 +0100 Subject: [PATCH 68/79] Update render_build.sh --- render_build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/render_build.sh b/render_build.sh index 2e6ddb2573..ac84b0aef1 100644 --- a/render_build.sh +++ b/render_build.sh @@ -15,5 +15,6 @@ pipenv run flask insert_data_dogfood pipenv run flask insert_data_exoticfood pipenv run flask insert_data_accessories pipenv run flask insert_data_pet +pipenv run migrate pipenv run upgrade From 1740b8803587ea57406c414ae76cb0abe10b41b1 Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Wed, 19 Mar 2025 12:47:20 +0100 Subject: [PATCH 69/79] Update render_build.sh --- render_build.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/render_build.sh b/render_build.sh index ac84b0aef1..8311ab343c 100644 --- a/render_build.sh +++ b/render_build.sh @@ -8,13 +8,14 @@ npm run build cd ../.. pipenv install -pipenv run reset_db + + + + +pipenv run upgrade pipenv run flask insert-test-users 5 pipenv run flask insert_data_catfood pipenv run flask insert_data_dogfood pipenv run flask insert_data_exoticfood pipenv run flask insert_data_accessories pipenv run flask insert_data_pet -pipenv run migrate - -pipenv run upgrade From e02061bbfb8cc4f7f098593fde5311f6f083938a Mon Sep 17 00:00:00 2001 From: BeaNovo <168178819+BeaNovo@users.noreply.github.com> Date: Wed, 19 Mar 2025 12:50:47 +0100 Subject: [PATCH 70/79] Update render_build.sh --- render_build.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/render_build.sh b/render_build.sh index ac84b0aef1..a303b4d692 100644 --- a/render_build.sh +++ b/render_build.sh @@ -8,13 +8,7 @@ npm run build cd ../.. pipenv install -pipenv run reset_db -pipenv run flask insert-test-users 5 -pipenv run flask insert_data_catfood -pipenv run flask insert_data_dogfood -pipenv run flask insert_data_exoticfood -pipenv run flask insert_data_accessories -pipenv run flask insert_data_pet -pipenv run migrate + + pipenv run upgrade From 401a3ecddfd91bd52e5517f04a3b612e11cdbcbe Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Wed, 19 Mar 2025 13:11:11 +0100 Subject: [PATCH 71/79] Update render_build.sh --- render_build.sh | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/render_build.sh b/render_build.sh index 7f851e60d4..82c64b8772 100644 --- a/render_build.sh +++ b/render_build.sh @@ -7,13 +7,6 @@ npm install npm run build cd ../.. -pipenv run flask db upgrade - -pipenv run flask insert-test-users 5 -pipenv run flask insert_data_catfood -pipenv run flask insert_data_dogfood -pipenv run flask insert_data_exoticfood -pipenv run flask insert_data_accessories -pipenv run flask insert_data_pet - pipenv install + +pipenv run upgrade From 1faa1f59ec5b9723a1e62fefa33f767d1422803d Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Wed, 19 Mar 2025 13:22:28 +0100 Subject: [PATCH 72/79] Update render_build.sh --- render_build.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/render_build.sh b/render_build.sh index 82c64b8772..e7f5fd6f0e 100644 --- a/render_build.sh +++ b/render_build.sh @@ -8,5 +8,11 @@ npm run build cd ../.. pipenv install - +pipenv run flask insert-test-users 5 +pipenv run flask insert_data_catfood +pipenv run flask insert_data_dogfood +pipenv run flask insert_data_exoticfood +pipenv run flask insert_data_accessories +pipenv run flask insert_data_pet +pipenv run migrate pipenv run upgrade From 7ec5bb7bad6396b9ab9eb84d21d066f41794b3e3 Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Thu, 20 Mar 2025 14:41:01 +0000 Subject: [PATCH 73/79] =?UTF-8?q?Dise=C3=B1o=20logi=20y=20recuperacion=20c?= =?UTF-8?q?ontrase=C3=B1a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{83fb8f773037_.py => 2309db853ee5_.py} | 6 +- src/front/js/component/navbar.js | 8 +- .../recuperacionContrase\303\261a.js" | 130 ++++++++---- src/front/js/layout.js | 2 +- src/front/js/pages/loginSignup.js | 196 +++++++++++------- src/front/styles/home.css | 160 -------------- 6 files changed, 226 insertions(+), 276 deletions(-) rename migrations/versions/{83fb8f773037_.py => 2309db853ee5_.py} (97%) diff --git a/migrations/versions/83fb8f773037_.py b/migrations/versions/2309db853ee5_.py similarity index 97% rename from migrations/versions/83fb8f773037_.py rename to migrations/versions/2309db853ee5_.py index 1dbc687610..27d785e2e1 100644 --- a/migrations/versions/83fb8f773037_.py +++ b/migrations/versions/2309db853ee5_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 83fb8f773037 +Revision ID: 2309db853ee5 Revises: -Create Date: 2025-03-19 01:32:37.695224 +Create Date: 2025-03-20 14:23:41.224215 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '83fb8f773037' +revision = '2309db853ee5' down_revision = None branch_labels = None depends_on = None diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index 6673f30114..1bdf738680 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -42,16 +42,16 @@ export const Navbar = ({ setActiveCategory }) => { // Recibimos setActiveCatego
  • - + setActiveCategory("dogFood")}>Caninos
  • - + setActiveCategory("catFood")}>Felinos
  • - + setActiveCategory("exoticFood")}>Exóticos
  • - + setActiveCategory("accesories")}>Accesorios
diff --git "a/src/front/js/component/recuperacionContrase\303\261a.js" "b/src/front/js/component/recuperacionContrase\303\261a.js" index 75d04d9eb4..c381632c6c 100644 --- "a/src/front/js/component/recuperacionContrase\303\261a.js" +++ "b/src/front/js/component/recuperacionContrase\303\261a.js" @@ -1,46 +1,100 @@ -import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; // ✅ Importamos useNavigate +import React, { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; export const RecuperacionContraseña = () => { - const [email, setEmail] = useState(''); - const navigate = useNavigate(); // ✅ Inicializamos el hook de navegación + const [email, setEmail] = useState(""); + const navigate = useNavigate(); - const handleSubmit = async (e) => { - e.preventDefault(); - - try { - const response = await fetch(`${process.env.BACKEND_URL}/api/forgotpassword`, { // ✅ Asegúrate de que esta ruta es correcta - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email }) - }); + const handleSubmit = async (e) => { + e.preventDefault(); - const data = await response.json(); - - if (response.ok) { - alert("Correo enviado correctamente. Revisa tu bandeja de entrada."); // ✅ Alert con el mensaje - navigate("/loginSignup"); // ✅ Redirigir al inicio de sesión después de aceptar el alert - } else { - alert(`Error: ${data.msg}`); - } - } catch (error) { - alert("Error de conexión con el servidor."); + try { + const response = await fetch( + `${process.env.BACKEND_URL}/api/forgotpassword`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), } - }; + ); - return ( -
-

Recuperar contraseña

-
- setEmail(e.target.value)} - required - /> - -
+ const data = await response.json(); + + if (response.ok) { + alert("Correo enviado correctamente. Revisa tu bandeja de entrada."); + navigate("/loginSignup"); + } else { + alert(`Error: ${data.msg}`); + } + } catch (error) { + alert("Error de conexión con el servidor."); + } + }; + + return ( +
+
+
+

Recuperar Contraseña

+

+ Ingresa tu correo y te enviaremos instrucciones para restablecer tu contraseña. +

+
+
+ setEmail(e.target.value)} + required + onFocus={(e) => e.target.style.borderColor = "#007bff"} + onBlur={(e) => e.target.style.borderColor = "#ddd"} + /> +
+ +
+ + Volver a la página principal +
- ); +
+
+ ); }; diff --git a/src/front/js/layout.js b/src/front/js/layout.js index 7029175067..01ea293517 100755 --- a/src/front/js/layout.js +++ b/src/front/js/layout.js @@ -74,7 +74,7 @@ const Layout = () => { // Nuevo componente para manejar el Navbar const PageWithNavbar = ({ activeCategory, setActiveCategory }) => { const location = useLocation(); - const hideNavbarRoutes = ["/perfilUsuario", "/loginSignup"]; // Rutas donde ocultamos el Navbar + const hideNavbarRoutes = ["/perfilUsuario","/RecuperacionContraseña",]; // Rutas donde ocultamos el Navbar return ( <> diff --git a/src/front/js/pages/loginSignup.js b/src/front/js/pages/loginSignup.js index 59ffd5bff0..12f2841b16 100644 --- a/src/front/js/pages/loginSignup.js +++ b/src/front/js/pages/loginSignup.js @@ -1,18 +1,17 @@ import React, { useState, useContext } from 'react'; import { Context } from '../store/appContext'; import { Link, useNavigate } from 'react-router-dom'; -import "../../styles/home.css"; export const LoginSignup = () => { - const { actions, store } = useContext(Context); + const { actions } = useContext(Context); const navigate = useNavigate(); const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isSignup, setIsSignup] = useState(false); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); const [successMessage, setSuccessMessage] = useState(''); const handleSubmit = async (e) => { @@ -25,7 +24,7 @@ export const LoginSignup = () => { const dataUser = { name, email, password }; if (isSignup) { await actions.signup(dataUser, navigate); - setSuccessMessage('¡Registro exitoso! Redirigiendo a inicio de sesión...'); + setSuccessMessage('¡Registro exitoso! Redirigiendo...'); setTimeout(() => { setIsSignup(false); },); @@ -35,79 +34,136 @@ export const LoginSignup = () => { setName(''); setEmail(''); setPassword(''); - } catch (err) { - setError('Error al procesar la solicitud. Por favor, intenta nuevamente.'); + } catch (error) { + setError('Error al procesar la solicitud.'); } finally { setLoading(false); } }; return ( -
-
- {!isSignup && ( -
-

Iniciar sesión

- setEmail(e.target.value)} - required - /> - setPassword(e.target.value)} - required - /> - - {error &&

{error}

} -

setIsSignup(true)}>¿No tienes cuenta? Regístrate

- - {/* NUEVO: Enlace para recuperación de contraseña */} -

- ¿Olvidaste tu contraseña? Recupérala aquí -

-
- )} - {isSignup && ( -
-

Regístrate

- setName(e.target.value)} - required - /> - setEmail(e.target.value)} - required - /> - setPassword(e.target.value)} - required - /> - - {error &&

{error}

} - {successMessage &&

{successMessage}

} -

setIsSignup(false)}>¿Ya tienes cuenta? Inicia sesión

+ + {error &&

{error}

} + {successMessage &&

{successMessage}

}
- )} - -

Volver a la página principal

+ +

+ {isSignup ? ( + <> + ¿Ya tienes cuenta? setIsSignup(false)}>Inicia sesión + + ) : ( + <> + ¿No tienes cuenta? setIsSignup(true)}>Regístrate + + )} +

+ + {!isSignup && ( +

+ ¿Olvidaste tu contraseña? +

+ )} + + + Volver a la página principal + +
); diff --git a/src/front/styles/home.css b/src/front/styles/home.css index 9d39d50a24..e69de29bb2 100755 --- a/src/front/styles/home.css +++ b/src/front/styles/home.css @@ -1,160 +0,0 @@ -/* - home.css: This website contains selectors only used in home.css - - All pages share the styles on index.css but you should create - one more css for each page that will contain the selected used - on that page only (the ones not reused in other pages). -*/ - -.registration-view-container { - font-family: 'Arial', sans-serif; - background-color: #f5f7fa; - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - margin: 0; - } - - .registration-view-container { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; - background: linear-gradient(135deg, #e2efff, #ffffff); - padding: 20px; - } - - /* Form container */ - .form-container { - background-color: #ffffff; - border-radius: 12px; - box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1); - padding: 30px 40px; - width: 100%; - max-width: 400px; - transition: all 0.3s ease; - } - - /* Form title */ - form h2 { - font-size: 24px; - font-weight: bold; - text-align: center; - color: #333; - margin-bottom: 20px; - text-transform: uppercase; - letter-spacing: 1.2px; - } - - /* Input fields */ - input { - width: 100%; - padding: 12px; - margin: 10px 0; - border-radius: 8px; - border: 2px solid #d1d1d1; - background-color: #f9f9f9; - font-size: 16px; - transition: border-color 0.3s ease, background-color 0.3s ease; - } - - /* Focus effect on inputs */ - input:focus { - outline: none; - border-color: #5b8fcb; - background-color: #e8f0fe; - } - - /* Submit button */ - button { - width: 100%; - padding: 12px; - background-color: #5b8fcb; - color: white; - border: none; - border-radius: 8px; - font-size: 16px; - font-weight: bold; - cursor: pointer; - transition: background-color 0.3s ease; - } - - /* Hover effect on button */ - button:hover { - background-color: #3d6ea5; - } - - button:disabled { - background-color: #a0c6f1; - cursor: not-allowed; - } - - /* Error message styling */ - .error-message { - color: #e74c3c; - font-size: 14px; - margin-top: 10px; - text-align: center; - } - - /* Link styling for switching between login and signup */ - p { - color: #5b8fcb; - text-align: center; - cursor: pointer; - font-size: 14px; - margin-top: 10px; - transition: color 0.3s ease; - } - - /* Hover effect on links */ - p:hover { - color: #3d6ea5; - } - - /* Animations for form entry */ - .auth-form { - opacity: 0; - transform: translateY(20px); - animation: slideIn 0.5s forwards; - } - - .auth-form.login-form { - animation-delay: 0.3s; - } - - .auth-form.signup-form { - animation-delay: 0.3s; - } - - /* Slide-in animation for forms */ - @keyframes slideIn { - to { - opacity: 1; - transform: translateY(0); - } - } - - /* Animation for form container on hover */ - .form-container:hover { - box-shadow: 0px 6px 30px rgba(0, 0, 0, 0.1); - transform: translateY(-5px); - } - - /* Responsive styles */ - @media (max-width: 600px) { - .form-container { - padding: 20px; - } - - form h2 { - font-size: 20px; - } - - input, - button { - font-size: 14px; - } - } \ No newline at end of file From 24dea745318966982c8b3c1092cc7968fbf91ca5 Mon Sep 17 00:00:00 2001 From: Sergio Brenes Date: Thu, 20 Mar 2025 14:54:47 +0000 Subject: [PATCH 74/79] color navbar --- src/front/js/component/navbar.js | 2 +- "src/front/js/component/recuperacionContrase\303\261a.js" | 2 +- src/front/js/pages/loginSignup.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index 1bdf738680..e0385f8f7f 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -23,7 +23,7 @@ export const Navbar = ({ setActiveCategory }) => { // Recibimos setActiveCatego return (