diff --git a/.vscode/settings.json b/.vscode/settings.json index 246b0419d0..c001ea6561 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,11 @@ { - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "workbench.editorAssociations": { - "*.md": "vscode.markdown.preview.editor" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - } + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + "githubPullRequests.ignoredPullRequestBranches": ["main"] } diff --git a/Pipfile b/Pipfile index 44e04f14ff..f61bd2be9f 100644 --- a/Pipfile +++ b/Pipfile @@ -6,20 +6,24 @@ verify_ssl = true [dev-packages] [packages] -flask = "*" -flask-sqlalchemy = "*" -flask-migrate = "*" -flask-swagger = "*" psycopg2-binary = "*" python-dotenv = "*" -flask-cors = "*" gunicorn = "*" -cloudinary = "*" flask-admin = "*" typing-extensions = "*" -flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" +flask-migrate = "*" +flask-swagger = "*" +flask-cors = "*" +flask-sqlalchemy = "*" +flask-jwt-extended = "*" +flask-bcrypt = "*" +email-validator = "*" +cloudinary = "*" +flask = "*" +tomli = "*" + [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index b201c3decc..2c9ddb0737 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d2e672e650278aeeee2fe49bd76d76497d8b65a50f8b5dbb121d265cbc6ef4e5" + "sha256": "684a5250b0f2ac59db78410080e8f0b4a8c0871bba20557124d95d4505581008" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,68 @@ "default": { "alembic": { "hashes": [ - "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5", - "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213" + "sha256:b05e51e8e82efc1abd14ba2af6392897e145930c3e0a2faf2b0da2f7f7fd660d", + "sha256:efab6ada0dd0fae2c92060800e0bf5c1dc26af15a10e02fb4babff164b4725e2" + ], + "markers": "python_version >= '3.9'", + "version": "==1.16.4" + }, + "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": "==1.14.1" + "version": "==4.3.0" }, "blinker": { "hashes": [ @@ -34,35 +91,53 @@ }, "certifi": { "hashes": [ - "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", - "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" + "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", + "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" ], - "markers": "python_version >= '3.6'", - "version": "==2025.1.31" + "markers": "python_version >= '3.7'", + "version": "==2025.7.14" }, "click": { "hashes": [ - "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", - "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", + "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" ], - "markers": "python_version >= '3.7'", - "version": "==8.1.8" + "markers": "python_version >= '3.10'", + "version": "==8.2.1" }, "cloudinary": { "hashes": [ - "sha256:ba223705409b2aaddd5196c2184d65f50a83dffcba3b94f3727658ff6a0172a3", - "sha256:e4191b470c5bae55542b64e0a78659af42971880294456dca480bc974fa9280a" + "sha256:62d4374b79d5476de2a86cb6a1da709a5429e02aef474bfc5d99f3e38a1a62ff", + "sha256:b4785031179a5ec7010f46665e5c8fad2cae022c18405546f01d257e02f78b1c" ], "index": "pypi", - "version": "==1.42.2" + "version": "==1.44.1" + }, + "dnspython": { + "hashes": [ + "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", + "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1" + ], + "markers": "python_version >= '3.9'", + "version": "==2.7.0" + }, + "email-validator": { + "hashes": [ + "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", + "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.2.0" }, "flask": { "hashes": [ - "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", - "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136" + "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", + "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e" ], "index": "pypi", - "version": "==3.1.0" + "markers": "python_version >= '3.9'", + "version": "==3.1.1" }, "flask-admin": { "hashes": [ @@ -70,23 +145,34 @@ "sha256:fd8190f1ec3355913a22739c46ed3623f1d82b8112cde324c60a6fc9b21c9406" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==1.6.1" }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" + ], + "index": "pypi", + "version": "==1.0.1" + }, "flask-cors": { "hashes": [ - "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", - "sha256:fa5cb364ead54bbf401a26dbf03030c6b18fb2fcaf70408096a572b409586b0c" + "sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c", + "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db" ], "index": "pypi", - "version": "==5.0.1" + "markers": "python_version >= '3.9' and python_version < '4.0'", + "version": "==6.0.1" }, "flask-jwt-extended": { "hashes": [ - "sha256:63a28fc9731bcc6c4b8815b6f954b5904caa534fc2ae9b93b1d3ef12930dca95", - "sha256:9215d05a9413d3855764bcd67035e75819d23af2fafb6b55197eb5a3313fdfb2" + "sha256:52f35bf0985354d7fb7b876e2eb0e0b141aaff865a22ff6cc33d9a18aa987978", + "sha256:8085d6757505b6f3291a2638c84d207e8f0ad0de662d1f46aa2f77e658a0c976" ], "index": "pypi", - "version": "==4.6.0" + "markers": "python_version >= '3.9' and python_version < '4'", + "version": "==4.7.1" }, "flask-migrate": { "hashes": [ @@ -94,6 +180,7 @@ "sha256:24d8051af161782e0743af1b04a152d007bad9772b2bca67b7ec1e8ceeb3910d" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==4.1.0" }, "flask-sqlalchemy": { @@ -102,6 +189,7 @@ "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==3.1.1" }, "flask-swagger": { @@ -114,82 +202,63 @@ }, "greenlet": { "hashes": [ - "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.14' 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": "==3.1.1" + "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26", + "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", + "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", + "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83", + "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", + "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be", + "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4", + "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", + "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5", + "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", + "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", + "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", + "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", + "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64", + "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57", + "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b", + "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", + "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", + "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", + "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147", + "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db", + "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", + "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c", + "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00", + "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", + "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba", + "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac", + "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822", + "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da", + "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", + "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805", + "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34", + "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", + "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", + "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", + "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", + "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", + "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72", + "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", + "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc", + "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", + "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302", + "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", + "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392", + "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322", + "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", + "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", + "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b", + "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904", + "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf", + "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7", + "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", + "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712", + "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728" + ], + "markers": "python_version >= '3.9'", + "version": "==3.2.3" }, "gunicorn": { "hashes": [ @@ -197,8 +266,17 @@ "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==23.0.0" }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, "itsdangerous": { "hashes": [ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", @@ -209,19 +287,19 @@ }, "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": [ - "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", - "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac" + "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", + "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59" ], "markers": "python_version >= '3.8'", - "version": "==1.3.9" + "version": "==1.3.10" }, "markupsafe": { "hashes": [ @@ -292,11 +370,11 @@ }, "packaging": { "hashes": [ - "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", - "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], "markers": "python_version >= '3.8'", - "version": "==24.2" + "version": "==25.0" }, "psycopg2-binary": { "hashes": [ @@ -370,6 +448,7 @@ "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==2.9.10" }, "pyjwt": { @@ -382,11 +461,12 @@ }, "python-dotenv": { "hashes": [ - "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", - "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" + "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", + "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" ], "index": "pypi", - "version": "==1.0.1" + "markers": "python_version >= '3.9'", + "version": "==1.1.1" }, "pyyaml": { "hashes": [ @@ -457,82 +537,123 @@ }, "sqlalchemy": { "hashes": [ - "sha256:0398361acebb42975deb747a824b5188817d32b5c8f8aba767d51ad0cc7bb08d", - "sha256:0561832b04c6071bac3aad45b0d3bb6d2c4f46a8409f0a7a9c9fa6673b41bc03", - "sha256:07258341402a718f166618470cde0c34e4cec85a39767dce4e24f61ba5e667ea", - "sha256:0a826f21848632add58bef4f755a33d45105d25656a0c849f2dc2df1c71f6f50", - "sha256:1052723e6cd95312f6a6eff9a279fd41bbae67633415373fdac3c430eca3425d", - "sha256:12d5b06a1f3aeccf295a5843c86835033797fea292c60e72b07bcb5d820e6dd3", - "sha256:12f5c9ed53334c3ce719155424dc5407aaa4f6cadeb09c5b627e06abb93933a1", - "sha256:2a0ef3f98175d77180ffdc623d38e9f1736e8d86b6ba70bff182a7e68bed7727", - "sha256:2f2951dc4b4f990a4b394d6b382accb33141d4d3bd3ef4e2b27287135d6bdd68", - "sha256:3868acb639c136d98107c9096303d2d8e5da2880f7706f9f8c06a7f961961149", - "sha256:386b7d136919bb66ced64d2228b92d66140de5fefb3c7df6bd79069a269a7b06", - "sha256:3d3043375dd5bbcb2282894cbb12e6c559654c67b5fffb462fda815a55bf93f7", - "sha256:3e35d5565b35b66905b79ca4ae85840a8d40d31e0b3e2990f2e7692071b179ca", - "sha256:402c2316d95ed90d3d3c25ad0390afa52f4d2c56b348f212aa9c8d072a40eee5", - "sha256:40310db77a55512a18827488e592965d3dec6a3f1e3d8af3f8243134029daca3", - "sha256:40e9cdbd18c1f84631312b64993f7d755d85a3930252f6276a77432a2b25a2f3", - "sha256:49aa2cdd1e88adb1617c672a09bf4ebf2f05c9448c6dbeba096a3aeeb9d4d443", - "sha256:57dd41ba32430cbcc812041d4de8d2ca4651aeefad2626921ae2a23deb8cd6ff", - "sha256:5dba1cdb8f319084f5b00d41207b2079822aa8d6a4667c0f369fce85e34b0c86", - "sha256:5e1d9e429028ce04f187a9f522818386c8b076723cdbe9345708384f49ebcec6", - "sha256:63178c675d4c80def39f1febd625a6333f44c0ba269edd8a468b156394b27753", - "sha256:6493bc0eacdbb2c0f0d260d8988e943fee06089cd239bd7f3d0c45d1657a70e2", - "sha256:64aa8934200e222f72fcfd82ee71c0130a9c07d5725af6fe6e919017d095b297", - "sha256:665255e7aae5f38237b3a6eae49d2358d83a59f39ac21036413fab5d1e810578", - "sha256:6db316d6e340f862ec059dc12e395d71f39746a20503b124edc255973977b728", - "sha256:70065dfabf023b155a9c2a18f573e47e6ca709b9e8619b2e04c54d5bcf193178", - "sha256:8455aa60da49cb112df62b4721bd8ad3654a3a02b9452c783e651637a1f21fa2", - "sha256:8b0ac78898c50e2574e9f938d2e5caa8fe187d7a5b69b65faa1ea4648925b096", - "sha256:8bf312ed8ac096d674c6aa9131b249093c1b37c35db6a967daa4c84746bc1bc9", - "sha256:92f99f2623ff16bd4aaf786ccde759c1f676d39c7bf2855eb0b540e1ac4530c8", - "sha256:9c8bcad7fc12f0cc5896d8e10fdf703c45bd487294a986903fe032c72201596b", - "sha256:9cd136184dd5f58892f24001cdce986f5d7e96059d004118d5410671579834a4", - "sha256:9eb4fa13c8c7a2404b6a8e3772c17a55b1ba18bc711e25e4d6c0c9f5f541b02a", - "sha256:a2bc4e49e8329f3283d99840c136ff2cd1a29e49b5624a46a290f04dff48e079", - "sha256:a5645cd45f56895cfe3ca3459aed9ff2d3f9aaa29ff7edf557fa7a23515a3725", - "sha256:a9afbc3909d0274d6ac8ec891e30210563b2c8bdd52ebbda14146354e7a69373", - "sha256:aa498d1392216fae47eaf10c593e06c34476ced9549657fca713d0d1ba5f7248", - "sha256:afd776cf1ebfc7f9aa42a09cf19feadb40a26366802d86c1fba080d8e5e74bdd", - "sha256:b335a7c958bc945e10c522c069cd6e5804f4ff20f9a744dd38e748eb602cbbda", - "sha256:b3c4817dff8cef5697f5afe5fec6bc1783994d55a68391be24cb7d80d2dbc3a6", - "sha256:b79ee64d01d05a5476d5cceb3c27b5535e6bb84ee0f872ba60d9a8cd4d0e6579", - "sha256:b87a90f14c68c925817423b0424381f0e16d80fc9a1a1046ef202ab25b19a444", - "sha256:bf89e0e4a30714b357f5d46b6f20e0099d38b30d45fa68ea48589faf5f12f62d", - "sha256:c058b84c3b24812c859300f3b5abf300daa34df20d4d4f42e9652a4d1c48c8a4", - "sha256:c09a6ea87658695e527104cf857c70f79f14e9484605e205217aae0ec27b45fc", - "sha256:c57b8e0841f3fce7b703530ed70c7c36269c6d180ea2e02e36b34cb7288c50c7", - "sha256:c9cea5b756173bb86e2235f2f871b406a9b9d722417ae31e5391ccaef5348f2c", - "sha256:cb39ed598aaf102251483f3e4675c5dd6b289c8142210ef76ba24aae0a8f8aba", - "sha256:e036549ad14f2b414c725349cce0772ea34a7ab008e9cd67f9084e4f371d1f32", - "sha256:e185ea07a99ce8b8edfc788c586c538c4b1351007e614ceb708fd01b095ef33e", - "sha256:e5a4d82bdb4bf1ac1285a68eab02d253ab73355d9f0fe725a97e1e0fa689decb", - "sha256:eae27ad7580529a427cfdd52c87abb2dfb15ce2b7a3e0fc29fbb63e2ed6f8120", - "sha256:ecef029b69843b82048c5b347d8e6049356aa24ed644006c9a9d7098c3bd3bfd", - "sha256:ee3bee874cb1fadee2ff2b79fc9fc808aa638670f28b2145074538d4a6a5028e", - "sha256:f0d3de936b192980209d7b5149e3c98977c3810d401482d05fb6d668d53c1c63", - "sha256:f53c0d6a859b2db58332e0e6a921582a02c1677cc93d4cbb36fdf49709b327b2", - "sha256:f9d57f1b3061b3e21476b0ad5f0397b112b94ace21d1f439f2db472e568178ae" + "sha256:023b3ee6169969beea3bb72312e44d8b7c27c75b347942d943cf49397b7edeb5", + "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582", + "sha256:05132c906066142103b83d9c250b60508af556982a385d96c4eaa9fb9720ac2b", + "sha256:087b6b52de812741c27231b5a3586384d60c353fbd0e2f81405a814b5591dc8b", + "sha256:0b3dbf1e7e9bc95f4bac5e2fb6d3fb2f083254c3fdd20a1789af965caf2d2348", + "sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda", + "sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5", + "sha256:1e3f196a0c59b0cae9a0cd332eb1a4bda4696e863f4f1cf84ab0347992c548c2", + "sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29", + "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8", + "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f", + "sha256:34ea30ab3ec98355235972dadc497bb659cc75f8292b760394824fab9cf39826", + "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504", + "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae", + "sha256:4d44522480e0bf34c3d63167b8cfa7289c1c54264c2950cc5fc26e7850967e45", + "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", + "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", + "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", + "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1", + "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0", + "sha256:5e22575d169529ac3e0a120cf050ec9daa94b6a9597993d1702884f6954a7d71", + "sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11", + "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e", + "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f", + "sha256:6854175807af57bdb6425e47adbce7d20a4d79bbfd6f6d6519cd10bb7109a7f8", + "sha256:6ab60a5089a8f02009f127806f777fca82581c49e127f08413a66056bd9166dd", + "sha256:725875a63abf7c399d4548e686debb65cdc2549e1825437096a0af1f7e374814", + "sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08", + "sha256:81965cc20848ab06583506ef54e37cf15c83c7e619df2ad16807c03100745dea", + "sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30", + "sha256:81eedafa609917040d39aa9332e25881a8e7a0862495fcdf2023a9667209deda", + "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9", + "sha256:8280856dd7c6a68ab3a164b4a4b1c51f7691f6d04af4d4ca23d6ecf2261b7923", + "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", + "sha256:8b4af17bda11e907c51d10686eda89049f9ce5669b08fbe71a29747f1e876036", + "sha256:90144d3b0c8b139408da50196c5cad2a6909b51b23df1f0538411cd23ffa45d3", + "sha256:906e6b0d7d452e9a98e5ab8507c0da791856b2380fdee61b765632bb8698026f", + "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6", + "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04", + "sha256:9a420a91913092d1e20c86a2f5f1fc85c1a8924dbcaf5e0586df8aceb09c9cc2", + "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560", + "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70", + "sha256:a373a400f3e9bac95ba2a06372c4fd1412a7cee53c37fc6c05f829bf672b8769", + "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", + "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6", + "sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b", + "sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747", + "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078", + "sha256:b50eab9994d64f4a823ff99a0ed28a6903224ddbe7fef56a6dd865eec9243440", + "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", + "sha256:c0b0e5e1b5d9f3586601048dd68f392dc0cc99a59bb5faf18aab057ce00d00b2", + "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", + "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", + "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", + "sha256:dd5ec3aa6ae6e4d5b5de9357d2133c07be1aff6405b136dad753a16afb6717dd", + "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", + "sha256:ff8e80c4c4932c10493ff97028decfdb622de69cae87e0f127a7ebe32b4069c6" ], "index": "pypi", - "version": "==2.0.38" + "markers": "python_version >= '3.7'", + "version": "==2.0.41" + }, + "tomli": { + "hashes": [ + "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", + "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", + "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", + "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", + "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", + "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", + "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", + "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", + "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", + "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", + "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", + "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", + "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", + "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", + "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", + "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", + "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", + "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", + "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", + "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", + "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", + "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", + "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", + "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", + "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", + "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", + "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", + "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", + "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", + "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", + "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", + "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.2.1" }, "typing-extensions": { "hashes": [ - "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", - "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", + "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" ], "index": "pypi", - "version": "==4.12.2" + "markers": "python_version >= '3.9'", + "version": "==4.14.1" }, "urllib3": { "hashes": [ - "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", - "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], "markers": "python_version >= '3.9'", - "version": "==2.3.0" + "version": "==2.5.0" }, "werkzeug": { "hashes": [ @@ -548,6 +669,7 @@ "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==3.1.2" } }, diff --git a/migrations/versions/0763d677d453_.py b/migrations/versions/0763d677d453_.py deleted file mode 100644 index 88964176f1..0000000000 --- a/migrations/versions/0763d677d453_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: 0763d677d453 -Revises: -Create Date: 2025-02-25 14:47:16.337069 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '0763d677d453' -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(), 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/11dcc99ec91a_.py b/migrations/versions/11dcc99ec91a_.py new file mode 100644 index 0000000000..3763678d69 --- /dev/null +++ b/migrations/versions/11dcc99ec91a_.py @@ -0,0 +1,92 @@ +"""empty message + +Revision ID: 11dcc99ec91a +Revises: +Create Date: 2025-07-31 14:59:39.412614 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '11dcc99ec91a' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('category', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('pet_type', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=True), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('password', sa.String(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_table('order', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('status', sa.Enum('CART', 'PENDING', 'COMPLETED', 'CANCELLED', name='status'), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('product', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=120), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.Column('photo', sa.String(), nullable=False), + sa.Column('coste', sa.Float(), nullable=False), + sa.Column('price', sa.Float(), nullable=False), + sa.Column('pet_type_id', sa.Integer(), nullable=False), + sa.Column('stock', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['pet_type_id'], ['pet_type.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('order_item', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('order_id', sa.Integer(), nullable=False), + sa.Column('product_id', sa.Integer(), nullable=False), + sa.Column('cant', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['order_id'], ['order.id'], ), + sa.ForeignKeyConstraint(['product_id'], ['product.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('product_category', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('category_id', sa.Integer(), nullable=False), + sa.Column('product_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['category_id'], ['category.id'], ), + sa.ForeignKeyConstraint(['product_id'], ['product.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('product_category') + op.drop_table('order_item') + op.drop_table('product') + op.drop_table('order') + op.drop_table('user') + op.drop_table('pet_type') + op.drop_table('category') + # ### end Alembic commands ### diff --git a/package-lock.json b/package-lock.json index 8d43d98ab7..25c110e281 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "1.0.1", "license": "ISC", "dependencies": { + "@cloudinary/react": "^1.14.3", + "@cloudinary/url-gen": "^1.21.0", + "cloudinary": "^2.7.0", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -338,6 +341,58 @@ "node": ">=6.9.0" } }, + "node_modules/@cloudinary/html": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@cloudinary/html/-/html-1.13.4.tgz", + "integrity": "sha512-noBk9D2VZgZkQIs5/y29OsJDmwp0FtgAlKrrp1+0Jp2HMu+68sdDJ3zi4/ZuLCnbdqthGuxP83trowG+Zsa68Q==", + "dependencies": { + "@types/lodash.clonedeep": "^4.5.6", + "@types/lodash.debounce": "^4.0.6", + "@types/node": "^14.14.10", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "typescript": "^4.1.2" + } + }, + "node_modules/@cloudinary/html/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@cloudinary/react": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@cloudinary/react/-/react-1.14.3.tgz", + "integrity": "sha512-AJ662pNAGEapRdkOOQstQHiKtXHBdoNkduWU3levugZ34ZiabBxmViISo16MtxZuBkzj96sV9ppmcq6foPqJ2w==", + "license": "MIT", + "dependencies": { + "@cloudinary/html": "^1.13.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@cloudinary/transformation-builder-sdk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@cloudinary/transformation-builder-sdk/-/transformation-builder-sdk-1.18.0.tgz", + "integrity": "sha512-cUl+RoQLirwCuLo5sHlRkftAQ/bmE4GTgpue1ur0YgxGlQy05vRZwXDQ+gcbMneFYMy6BjTDUs4nVVxL/hk2WA==", + "license": "MIT", + "dependencies": { + "@cloudinary/url-gen": "^1.7.0" + } + }, + "node_modules/@cloudinary/url-gen": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@cloudinary/url-gen/-/url-gen-1.21.0.tgz", + "integrity": "sha512-ctYcCzX3G3vcgnESTU2ET3K1XsBiXcEnBddCGV0QbR3fJhLLrIShjSMEwZoepgh4LAFOHJu9DzvLFr+E8R7c7g==", + "license": "MIT", + "dependencies": { + "@cloudinary/transformation-builder-sdk": "^1.15.1" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -997,6 +1052,30 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" + }, + "node_modules/@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "16.11.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", @@ -1395,6 +1474,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cloudinary": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.7.0.tgz", + "integrity": "sha512-qrqDn31+qkMCzKu1GfRpzPNAO86jchcNwEHCUiqvPHNSFqu7FTNF9FuAkBUyvM1CFFgFPu64NT0DyeREwLwK0w==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "q": "^1.5.1" + }, + "engines": { + "node": ">=9" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3113,6 +3205,24 @@ "node": ">= 0.8.0" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3461,6 +3571,17 @@ "node": ">=6" } }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4213,6 +4334,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -4687,6 +4821,50 @@ "@babel/helper-validator-identifier": "^7.25.9" } }, + "@cloudinary/html": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@cloudinary/html/-/html-1.13.4.tgz", + "integrity": "sha512-noBk9D2VZgZkQIs5/y29OsJDmwp0FtgAlKrrp1+0Jp2HMu+68sdDJ3zi4/ZuLCnbdqthGuxP83trowG+Zsa68Q==", + "requires": { + "@types/lodash.clonedeep": "^4.5.6", + "@types/lodash.debounce": "^4.0.6", + "@types/node": "^14.14.10", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "typescript": "^4.1.2" + }, + "dependencies": { + "@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + } + } + }, + "@cloudinary/react": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@cloudinary/react/-/react-1.14.3.tgz", + "integrity": "sha512-AJ662pNAGEapRdkOOQstQHiKtXHBdoNkduWU3levugZ34ZiabBxmViISo16MtxZuBkzj96sV9ppmcq6foPqJ2w==", + "requires": { + "@cloudinary/html": "^1.13.4" + } + }, + "@cloudinary/transformation-builder-sdk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@cloudinary/transformation-builder-sdk/-/transformation-builder-sdk-1.18.0.tgz", + "integrity": "sha512-cUl+RoQLirwCuLo5sHlRkftAQ/bmE4GTgpue1ur0YgxGlQy05vRZwXDQ+gcbMneFYMy6BjTDUs4nVVxL/hk2WA==", + "requires": { + "@cloudinary/url-gen": "^1.7.0" + } + }, + "@cloudinary/url-gen": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@cloudinary/url-gen/-/url-gen-1.21.0.tgz", + "integrity": "sha512-ctYcCzX3G3vcgnESTU2ET3K1XsBiXcEnBddCGV0QbR3fJhLLrIShjSMEwZoepgh4LAFOHJu9DzvLFr+E8R7c7g==", + "requires": { + "@cloudinary/transformation-builder-sdk": "^1.15.1" + } + }, "@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -5044,6 +5222,27 @@ "@babel/types": "^7.20.7" } }, + "@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==" + }, + "@types/lodash.clonedeep": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", + "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { "version": "16.11.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", @@ -5304,6 +5503,15 @@ "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==", "dev": true }, + "cloudinary": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.7.0.tgz", + "integrity": "sha512-qrqDn31+qkMCzKu1GfRpzPNAO86jchcNwEHCUiqvPHNSFqu7FTNF9FuAkBUyvM1CFFgFPu64NT0DyeREwLwK0w==", + "requires": { + "lodash": "^4.17.21", + "q": "^1.5.1" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6461,6 +6669,21 @@ "type-check": "~0.4.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6692,6 +6915,11 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7192,6 +7420,11 @@ "reflect.getprototypeof": "^1.0.6" } }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + }, "unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/package.json b/package.json index 0caab10749..8cb23a23bc 100755 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "main": "index.js", "scripts": { "dev": "vite", - "start": "vite", - "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "start": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" }, "author": { "name": "Alejandro Sanchez", @@ -30,13 +30,13 @@ "license": "ISC", "devDependencies": { "@types/react": "^18.2.18", - "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.46.0", - "eslint-plugin-react": "^7.33.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.8" + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.0.4", + "eslint": "^8.46.0", + "eslint-plugin-react": "^7.33.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "vite": "^4.4.8" }, "babel": { "presets": [ @@ -54,9 +54,12 @@ ] }, "dependencies": { + "@cloudinary/react": "^1.14.3", + "@cloudinary/url-gen": "^1.21.0", + "cloudinary": "^2.7.0", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.18.0" } } diff --git a/public/Carrusel1.png b/public/Carrusel1.png new file mode 100644 index 0000000000..94598457a8 Binary files /dev/null and b/public/Carrusel1.png differ diff --git a/public/Carrusel2.png b/public/Carrusel2.png new file mode 100644 index 0000000000..1e2a9c9540 Binary files /dev/null and b/public/Carrusel2.png differ diff --git a/public/Carrusel3.png b/public/Carrusel3.png new file mode 100644 index 0000000000..5160dd6f0a Binary files /dev/null and b/public/Carrusel3.png differ diff --git a/public/dalmata.png b/public/dalmata.png new file mode 100644 index 0000000000..e06d8c9be4 Binary files /dev/null and b/public/dalmata.png differ diff --git a/public/fotoPrincipal.png b/public/fotoPrincipal.png new file mode 100644 index 0000000000..c4a93feb3d Binary files /dev/null and b/public/fotoPrincipal.png differ diff --git a/public/logoPatitas.png b/public/logoPatitas.png new file mode 100644 index 0000000000..7d65fdc5d1 Binary files /dev/null and b/public/logoPatitas.png differ diff --git a/public/rigo-baby.jpg b/public/rigo-baby.jpg deleted file mode 100644 index da566a74a0..0000000000 Binary files a/public/rigo-baby.jpg and /dev/null differ diff --git a/src/api/admin.py b/src/api/admin.py index 3eecb64140..f3d6648321 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,7 +1,7 @@ import os from flask_admin import Admin -from .models import db, User +from .models import db, User, Category, PetType, Product from flask_admin.contrib.sqla import ModelView def setup_admin(app): @@ -12,6 +12,8 @@ def setup_admin(app): # Add your models here, for example this is how we add a the User model to the admin admin.add_view(ModelView(User, db.session)) - + admin.add_view(ModelView(Category, db.session)) + admin.add_view(ModelView(PetType, db.session)) + admin.add_view(ModelView(Product, db.session)) # You can duplicate that line to add mew models # admin.add_view(ModelView(YourModelName, db.session)) \ No newline at end of file diff --git a/src/api/commands.py b/src/api/commands.py index 19806164d3..c30e297592 100644 --- a/src/api/commands.py +++ b/src/api/commands.py @@ -7,15 +7,16 @@ Flask commands are usefull to run cronjobs or tasks outside of the API but sill in integration with youy database, for example: Import the price of bitcoin every night as 12am """ + + def setup_commands(app): - """ This is an example command "insert-test-users" that you can run from the command line by typing: $ flask insert-test-users 5 Note: 5 is the number of users to add """ - @app.cli.command("insert-test-users") # name of our command - @click.argument("count") # argument of out command + @app.cli.command("insert-test-users") # name of our command + @click.argument("count") # argument of out command def insert_test_users(count): print("Creating test users") for x in range(1, int(count) + 1): @@ -29,6 +30,6 @@ def insert_test_users(count): print("All test users created") - @app.cli.command("insert-test-data") + @app.cli.command("insert-test-users") def insert_test_data(): - pass \ No newline at end of file + pass diff --git a/src/api/models.py b/src/api/models.py index da515f6a1a..dd240900ba 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,170 @@ from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import String, ForeignKey, Integer, Float, Boolean +from sqlalchemy.orm import Mapped, mapped_column, relationship +from typing import List +import enum db = SQLAlchemy() +class Status(enum.Enum): + CART = 'cart' + PENDING = 'pending' + COMPLETED = 'completed' + CANCELLED = 'cancelled' + + class User(db.Model): id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120),nullable=True) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + is_active: Mapped [bool] = mapped_column(Boolean(), nullable=False) + + order: Mapped [List["Order"]] = relationship( + back_populates= "user", cascade= "all, delete-orphan" + ) def serialize(self): return { "id": self.id, + "name":self.name, "email": self.email, # do not serialize the password, its a security breach + } + +class Order(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id")) + status: Mapped[Status] = mapped_column(default= Status.CART) + + + user: Mapped ["User"] = relationship( + back_populates= "order" + ) + order_item: Mapped [List["OrderItem"]] = relationship( + back_populates= "order", cascade= "all, delete-orphan" + ) + + + def serialize(self): + return { + "id": self.id, + "user_id": self.id, + + # do not serialize the password, its a security breach + } + +class Category(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) + description: Mapped[str] = mapped_column(String(), nullable=False) + + + + product_category: Mapped[List["ProductCategory"]] = relationship( + back_populates="category", cascade= "all, delete-orphan" + ) + + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + # do not serialize the password, its a security breach + } + +class PetType(db.Model): + id: Mapped[int] = mapped_column(primary_key= True) + name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) + products: Mapped[List["Product"]] = relationship( + back_populates = "pet_type", cascade = "all, delete-orphan" + ) + + def serialize(self): + return { + "id": self.id, + "name": self.name, + } + +class Product(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) + description: Mapped[str] = mapped_column(String(), nullable=False) + photo: Mapped[str] = mapped_column(String(), nullable=False) + coste: Mapped[float] = mapped_column(Float(), nullable=False) + price: Mapped[float] = mapped_column(Float(), nullable=False) + pet_type_id: Mapped[int] = mapped_column(ForeignKey("pet_type.id")) + stock: Mapped[int] = mapped_column(Integer(), nullable=True) + + + + pet_type: Mapped["PetType"] = relationship( + back_populates="products") + + order_items: Mapped[List["OrderItem"]] = relationship( + back_populates= "product", cascade= "all, delete-orphan" + ) + + list_product_category: Mapped[List["ProductCategory"]] = relationship( + back_populates="product", cascade= "all, delete-orphan" + ) + + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "description": self.description, + "photo": self.photo, + "coste": self.coste, + "price": self.price, + "stock": self.stock, + + # do not serialize the password, its a security breach + } + +class ProductCategory(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + + category_id: Mapped[int] = mapped_column(ForeignKey("category.id")) + product_id: Mapped[int] = mapped_column(ForeignKey("product.id")) + + product: Mapped["Product"] = relationship ( + back_populates="list_product_category" + ) + category: Mapped["Category"] = relationship( + back_populates="product_category" + ) + + def serialize(self): + return { + "id": self.id, + "category_id": self.id, + "product": self.product, + "product_id": self.produc_id, + + # do not serialize the password, its a security breach + } + +class OrderItem(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + order_id: Mapped[int] = mapped_column(ForeignKey("order.id")) + product_id: Mapped[int] = mapped_column(ForeignKey("product.id")) + cant: Mapped[int] = mapped_column(Integer(), nullable=False) + order: Mapped ["Order"] = relationship( + back_populates= "order_item" + ) + product: Mapped["Product"]= relationship( + back_populates= "order_items" + ) + + def serialize(self): + return { + "id": self.id, + "order_id": self.order_id, + "product_id": self.product_id, + "cant": self.cant, + # do not serialize the password, its a security breach } \ No newline at end of file diff --git a/src/api/routes.py b/src/api/routes.py index 029589a3a1..7472651f82 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -1,22 +1,211 @@ """ This module takes care of starting the API Server, Loading the DB and Adding the endpoints """ -from flask import Flask, request, jsonify, url_for, Blueprint -from api.models import db, User +from flask import Flask, request, jsonify, url_for, Blueprint, make_response from api.utils import generate_sitemap, APIException from flask_cors import CORS +from api.models import User, db, Product +from flask_jwt_extended import jwt_required, create_access_token, get_jwt_identity +from flask_bcrypt import Bcrypt + + api = Blueprint('api', __name__) -# Allow CORS requests to this API + CORS(api) -@api.route('/hello', methods=['POST', 'GET']) -def handle_hello(): +@api.route('/private-hello', methods=['GET']) +@jwt_required() +def handle_private_hello(): + user_id = get_jwt_identity() + user = User.query.get(user_id) + if user: + response_body = { + "message": "Hola, soy una ruta privada", + "user": user.serialize() + } + return jsonify(response_body), 200 + else: + return jsonify({"message": "Usuario no encontrado"}), 404 + + +@api.route('/login', methods=['POST']) +def login(): + data_request = request.get_json() + print("Login request data:", data_request) + + email = data_request.get('email') + password = data_request.get('password') + print(f"Email: {email}, Password received: {'Yes' if password else 'No'}") + + if not email or not password: + print("Error: email or password missing") + return jsonify({"message": "Los campos email,password son obligatorios"}), 400 + + user = User.query.filter_by(email=email).first() + print("User found:", user) + + if not user: + print("Error: user not found") + return jsonify({"message": "Verifique sus credenciales"}), 401 + + if bcrypt.check_password_hash(user.password, password): + print("Password match, creating access token") + access_token = create_access_token(identity=str(user.id)) + return jsonify({"message": "Login exitoso", "token": access_token, "user": user.serialize()}), 200 + + print("Error: password incorrect") + return jsonify({"message": "Verifique sus credenciales"}), 401 + + +@api.route('/register', methods=["POST"]) +def register(): + data_request = request.get_json() + email = data_request.get('email') + password = data_request.get('password') + name = data_request.get('username') + + if not email or not password: + return jsonify({"message": "Los campos email,password son obligatorios"}), 400 + + hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') + + new_user = User( + email=email, + password=hashed_password, + name=name, + is_active=True + ) + + try: + db.session.add(new_user) + db.session.commit() + return jsonify({"message": "usuario creado con exito"}), 201 + except Exception as e: + db.session.rollback() + print("Error saving user:", e) + return jsonify({"error": "Error en el servidor"}),500 + + + + + + +# logica para que el administrador haga CRUD de los productos +@api.route('/hello') +def home(): + return make_response(jsonify({"msg":"¡Funcionando Correctamente!"}), 200) + +#Producto +@api.route('/product', methods=['GET']) +def get_product(): + list_products= Product.query.all() + products=[Product.serialize() for Product in list_products] + print(list_products[0].name) + return make_response(jsonify({"msg":"¡Producto cargado exitosamente!", "products":products }), 200) + +@api.route('/product/', methods=['GET']) +def get_product_id(id): + product = Product.query.filter_by(id=id).first() + print(product) + + + if not product: + return make_response(jsonify({"messaje":f"producto con el id #{id} no encontrado"}), 404) + return jsonify(product.serialize()) + + +@api.route('/new', methods=['POST']) +def new_product(): + data_request= request.get_json() + + #inicio de la validacion + + required_Add=['name','description','photo','coste','price','pet_type_id','stock'] + error={} + + for Add in required_Add: + + if Add not in data_request or data_request[Add] is None: + error[Add]= f"El campo {Add} es obligatorio" + if error: + return make_response(jsonify({"error":"¡Revisa los Detalles!"}), 422) + try: + name=data_request.get('name') + description=data_request.get('description') + photo=data_request.get('photo') + coste=data_request.get('coste') + price=data_request.get('price') + pet_type_id=data_request.get('pet_type_id') + stock=data_request.get('stock') + + product_new= Product( + name=name, + description=description, + photo=photo, + coste=coste, + price=price, + pet_type_id=pet_type_id, + stock=stock + ) + + db.session.add(product_new) + db.session.commit() + + return make_response(jsonify({"msg":"¡Producto agregado exitosamente!"}), 201) + + except Exception as e: + print(e) + return make_response(jsonify({"error": "Error en el servidor"}), 500) + + + + +@api.route('/update/', methods=['PUT']) +def update_product(id): + try: + product = Product.query.get(id) + + if not product: + return make_response(jsonify({"messaje":f"producto con el id #{id} no encontrado"}), 404) + + data_request = request.get_json() + + if 'name' in data_request: + product.name = data_request['name'] + if 'description' in data_request: + product.description = data_request['description'] + if 'photo' in data_request: + product.photo = data_request['photo'] + if 'coste' in data_request: + product.coste = data_request['coste'] + if 'price' in data_request: + product.price = data_request['price'] + if 'pet_type_id' in data_request: + product.pet_type_id = data_request['pet_type_id'] + if 'stock' in data_request: + product.stock = data_request['stock'] + + db.session.commit() + + return make_response(jsonify({"msg":"¡Producto actualizado exitosamente!"}), 200) + + except Exception as e: + print(e) + return make_response(jsonify({"msg":"Error interno del servidor"}), 500) + + + +@api.route('/delete/', methods=['DELETE']) +def delete_product(id): + product= db.get_or_404(Product,id) + + db.session.delete(product) + db.session.commit() + + return make_response(jsonify({"msg": "Se ha eliminado exitosamente"}), 200) - response_body = { - "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request" - } + - return jsonify(response_body), 200 diff --git a/src/app.py b/src/app.py index 1b3340c0fa..0a986415f8 100644 --- a/src/app.py +++ b/src/app.py @@ -10,15 +10,22 @@ from api.routes import api from api.admin import setup_admin from api.commands import setup_commands +from flask_jwt_extended import JWTManager +from flask_cors import CORS + -# from models import Person ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production" static_file_dir = os.path.join(os.path.dirname( os.path.realpath(__file__)), '../dist/') app = Flask(__name__) +CORS(app) app.url_map.strict_slashes = False + + + + # database condiguration db_url = os.getenv("DATABASE_URL") if db_url is not None: @@ -30,7 +37,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False MIGRATE = Migrate(app, db, compare_type=True) db.init_app(app) - +jwt = JWTManager(app) # add the admin setup_admin(app) @@ -57,6 +64,8 @@ def sitemap(): return send_from_directory(static_file_dir, 'index.html') # any other endpoint will try to serve it like a static file + + @app.route('/', methods=['GET']) def serve_any_other_file(path): if not os.path.isfile(os.path.join(static_file_dir, path)): diff --git a/src/front/components/ConsejoModal.jsx b/src/front/components/ConsejoModal.jsx new file mode 100644 index 0000000000..12f571f08b --- /dev/null +++ b/src/front/components/ConsejoModal.jsx @@ -0,0 +1,32 @@ +import { useEffect, useState } from "react"; +import { consejosPerros, consejosGatos } from "../data/consejos"; + +export default function ConsejoModal({ tipo, show, onClose }) { + const [consejo, setConsejo] = useState(""); + + useEffect(() => { + if (show) { + const consejos = tipo === "gatos" ? consejosGatos : consejosPerros; + const randomIndex = Math.floor(Math.random() * consejos.length); + setConsejo(consejos[randomIndex]); + } + }, [show, tipo]); + + if (!show) return null; + + return ( + +
+
+

¡Consejo para {tipo}!

+

{consejo}

+ +
+
+ ); +} diff --git a/src/front/components/CurvedText.jsx b/src/front/components/CurvedText.jsx new file mode 100644 index 0000000000..ebb4599109 --- /dev/null +++ b/src/front/components/CurvedText.jsx @@ -0,0 +1,33 @@ +import React from "react"; + +const CurvedText = ({ text, direction = "up" }) => { + const pathId = direction === "up" ? "curveUp" : "curveDown"; + const pathD = + direction === "up" + ? "M 50 80 Q 250 0 450 80" + : "M 50 20 Q 250 100 450 20"; + + const dominantBaseline = direction === "down" ? "text-after-edge" : "auto"; + + return ( +
+ + + + + + + {text} + + + +
+ ); +}; + +export default CurvedText; diff --git a/src/front/components/Footer.jsx b/src/front/components/Footer.jsx index f06302dbd2..2c9ed11bec 100644 --- a/src/front/components/Footer.jsx +++ b/src/front/components/Footer.jsx @@ -1,11 +1,22 @@ export const Footer = () => ( - -); + <> +
+
+ +

¡Contáctanos!

+
+ © {new Date().getFullYear()} PatitasClub. Todos los derechos reservados. +
+
+ +); \ No newline at end of file diff --git a/src/front/components/Navbar.jsx b/src/front/components/Navbar.jsx index 30d43a2636..dfeb512c5d 100644 --- a/src/front/components/Navbar.jsx +++ b/src/front/components/Navbar.jsx @@ -1,19 +1,99 @@ -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; +import logoPatitas from "/logoPatitas.png"; export const Navbar = () => { + const navigate = useNavigate(); + const token = localStorage.getItem("token"); - return ( - - ); -}; \ No newline at end of file + const handleUserClick = () => { + if (token) { + navigate("/dashboard"); + } else { + navigate("/registro"); + } + }; + + return ( + + ); +}; diff --git a/src/front/components/Pagos.jsx b/src/front/components/Pagos.jsx new file mode 100644 index 0000000000..ae1755e73c --- /dev/null +++ b/src/front/components/Pagos.jsx @@ -0,0 +1,74 @@ +import React from "react"; + +const Pagos = () => { + const handleSubmit = (e) => { + e.preventDefault(); + alert("Pago procesado"); + }; + + return ( +
+

Formulario de Pago

+
+
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ ); +}; + +export default Pagos; diff --git a/src/front/components/Prouductos.jsx b/src/front/components/Prouductos.jsx new file mode 100644 index 0000000000..f031700af2 --- /dev/null +++ b/src/front/components/Prouductos.jsx @@ -0,0 +1,90 @@ +import { Link } from "react-router-dom"; + +export const Productos = () => { + return ( +
+
+
+
+ Producto 1 +
+
Título del producto
+

Descripción

+

+ Precio +

+ + Ver Detalles + +
+
+
+ +
+
+ Producto 2 +
+
Título del producto
+

Descripción

+

+ Precio +

+ + Ver Detalles + +
+
+
+ +
+
+ Producto 3 +
+
Título del producto
+

Descripción

+

+ Precio +

+ + Ver Detalles + +
+
+
+ +
+
+ Producto 4 +
+
Título del producto
+

Descripción

+

+ Precio +

+ + Ver Detalles + +
+
+
+ +
+
+ Producto 5 +
+
Título del producto
+

Descripción

+

+ Precio +

+ + Ver Detalles + +
+
+
+ +
+
+ ); +}; diff --git a/src/front/components/VistaProducto.jsx b/src/front/components/VistaProducto.jsx new file mode 100644 index 0000000000..216704f170 --- /dev/null +++ b/src/front/components/VistaProducto.jsx @@ -0,0 +1,19 @@ +export const VistaProducto = () =>{ + return( +
+
+ Titulo Producto +
+
+

Aqui viene la imagen del producto

+ +
+
+

Aqui viene la descripcion del Producto

+

aqui debajo el precio €

+ + +
+
+ ) +} \ No newline at end of file diff --git a/src/front/data/consejos.js b/src/front/data/consejos.js new file mode 100644 index 0000000000..c06b69b273 --- /dev/null +++ b/src/front/data/consejos.js @@ -0,0 +1,35 @@ +export const consejosPerros = [ + "Proporciónale ejercicio diario adecuado según su raza y edad.", + "Socialízalo desde cachorro para que se relacione bien con otros perros y personas.", + "Llévalo al veterinario al menos una vez al año para chequeos y vacunas.", + "Dale una alimentación balanceada y de buena calidad.", + "No lo dejes solo por periodos prolongados para evitar ansiedad por separación.", + "Cepíllalo regularmente para mantener su pelaje y piel saludables.", + "Enséñale órdenes básicas como sentarse, venir o quedarse.", + "Protégelo de temperaturas extremas.", + "Revisa y limpia sus oídos y dientes con frecuencia.", + "Dale cariño, atención y tiempo de calidad todos los días.", + "Colócale un chip de identificación y asegúrate de que lleve placa con tus datos.", + "Evita el castigo físico; usa refuerzo positivo para educarlo.", + "Ten siempre agua fresca disponible para él.", + "Cuida sus patas, sobre todo si camina por superficies calientes o ásperas.", + "Hazle chequeos regulares de parásitos internos y externos." +]; + +export const consejosGatos = [ + "Proporciónale un ambiente enriquecido con rascadores, juguetes y lugares elevados.", + "Mantenle su arenero limpio y en un lugar tranquilo.", + "Cepíllalo con regularidad, especialmente si tiene pelo largo.", + "Visita al veterinario al menos una vez al año para revisión y vacunas.", + "Esterilízalo para prevenir problemas de salud y comportamiento.", + "Proporciónale alimento de calidad y agua fresca a diario.", + "Permítele tener momentos de juego y caza simulada.", + "Respeta su espacio personal: los gatos necesitan momentos de soledad.", + "Mantén las ventanas seguras si vive en un piso alto.", + "Colócale identificación por si se extravía.", + "Evita los cambios bruscos en su entorno: los gatos son sensibles al estrés.", + "No uses productos tóxicos para limpiar donde él camina o lame.", + "Asegúrate de que tenga acceso a lugares donde pueda esconderse o descansar.", + "Proporciónale variedad en su dieta, pero sin hacer cambios repentinos.", + "Juega con él todos los días, aunque sea solo 10 minutos." +]; diff --git a/src/front/index.css b/src/front/index.css index e69de29bb2..e36eb0af5b 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -0,0 +1,45 @@ +.navbar { + background-color: #eae164; +} + +.search-bar input.form-control { + box-shadow: none !important; + outline: none; +} + +.navbar-brand { + width: 200px; +} + +.footer { + background-color: #EAE164; +} +.custom-hr{ + border: 1px solid #000; + height: 3px; + background-color: #000; + margin: 0; +} + +icon-container { + color: #3c6ca8; +} + +.title1{ + color:#3c6ca8 +} + +.title2{ + color:#EAE164; +} + +.container1{ + background-color: #3c6ca8; +} + +@media (min-width: 992px) { + .col-lg-custom { + flex: 0 0 auto; + width: 20%; + } +} diff --git a/src/front/main.jsx b/src/front/main.jsx index a5a3c781dc..e76c4c7697 100644 --- a/src/front/main.jsx +++ b/src/front/main.jsx @@ -6,6 +6,7 @@ import { router } from "./routes"; // Import the router configuration import { StoreProvider } from './hooks/useGlobalReducer'; // Import the StoreProvider for global state management import { BackendURL } from './components/BackendURL'; + const Main = () => { if(! import.meta.env.VITE_BACKEND_URL || import.meta.env.VITE_BACKEND_URL == "") return ( @@ -25,5 +26,8 @@ const Main = () => { ); } + + + // Render the Main component into the root DOM element. ReactDOM.createRoot(document.getElementById('root')).render(
) diff --git a/src/front/pages/Carrito.jsx b/src/front/pages/Carrito.jsx new file mode 100644 index 0000000000..9bdb689b53 --- /dev/null +++ b/src/front/pages/Carrito.jsx @@ -0,0 +1,11 @@ +import { Link } from "react-router-dom"; + +export const Carrito = () => { + return ( +
+

Carrito de Compras

+

Esta es la página del carrito de compras.

+ Volver a la página principal +
+ ); +}; diff --git a/src/front/pages/Dashboard.jsx b/src/front/pages/Dashboard.jsx new file mode 100644 index 0000000000..d72888d9a3 --- /dev/null +++ b/src/front/pages/Dashboard.jsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +export const Dashboard = () => { + const [user, setUser] = useState(null); + const [error, setError] = useState(""); + const navigate = useNavigate(); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (!token) { + setError("No se encontró el token de autenticación."); + return; + } + fetch(`${import.meta.env.VITE_BACKEND_URL}api/private-hello`, { + method: "GET", + headers: { + "Authorization": `Bearer ${token}`, + "Content-Type": "application/json" + } + }) + .then(resp => resp.json()) + .then(data => { + if (data.user) { + setUser(data.user); + } else { + setError(data.message || "No se pudo obtener la información del usuario."); + } + }) + .catch(() => setError("Error al conectar con el servidor.")); + }, []); + + const handleLogout = () => { + localStorage.removeItem("token"); + navigate("/login"); + }; + + return ( +
+

Bienvenido al Dashboard

+ {error &&
{error}
} + {user ? ( +
+

Información personal:

+
    +
  • Nombre: {user.name}
  • +
  • Email: {user.email}
  • +
+
+ +
+
+ ) : ( + !error &&

Cargando información...

+ )} +
+ ); +}; diff --git a/src/front/pages/Gatos.jsx b/src/front/pages/Gatos.jsx new file mode 100644 index 0000000000..55832108c1 --- /dev/null +++ b/src/front/pages/Gatos.jsx @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; +import ConsejoModal from "../components/ConsejoModal"; +import { Productos } from "../components/Prouductos"; + +export function Gatos() { + const [showModal, setShowModal] = useState(true); // mostrar al cargar + + return ( +
+

Productos para gatos

+ setShowModal(false)} /> +

Comida

+ +

Juguetes

+ +

Accesorios

+ +

Cuidados

+ +
+ ); +} diff --git a/src/front/pages/Home.jsx b/src/front/pages/Home.jsx index 341ed21768..f32b4b7e43 100644 --- a/src/front/pages/Home.jsx +++ b/src/front/pages/Home.jsx @@ -1,52 +1,139 @@ -import React, { useEffect } from "react" -import rigoImageUrl from "../assets/img/rigo-baby.jpg"; -import useGlobalReducer from "../hooks/useGlobalReducer.jsx"; - -export const Home = () => { - - const { store, dispatch } = useGlobalReducer() - - const loadMessage = async () => { - try { - const backendUrl = import.meta.env.VITE_BACKEND_URL - - if (!backendUrl) throw new Error("VITE_BACKEND_URL is not defined in .env file") - - const response = await fetch(backendUrl + "/api/hello") - const data = await response.json() - - if (response.ok) dispatch({ type: "set_hello", payload: data.message }) - - return data - - } catch (error) { - if (error.message) throw new Error( - `Could not fetch the message from the backend. - Please check if the backend is running and the backend port is public.` - ); - } - - } - - useEffect(() => { - loadMessage() - }, []) - - return ( -
-

Hello Rigo!!

-

- Rigo Baby -

-
- {store.message ? ( - {store.message} - ) : ( - - Loading message from the backend (make sure your python 🐍 backend is running)... - - )} -
-
- ); -}; \ No newline at end of file +import React, { useState } from "react"; +import CurvedText from "../components/CurvedText"; +import { Link } from "react-router-dom"; +import dalmata from "/dalmata.png"; +import fotoPrincipal from "/fotoPrincipal.png"; +import Carrusel1 from "/Carrusel1.png"; +import Carrusel2 from "/Carrusel2.png"; +import Carrusel3 from "/Carrusel3.png"; + +const Home = () => { + const [destacados, setDestacados] = useState([ + { id: 1, name: "Pienso premium", precio: 18, image: Carrusel1 }, + { id: 2, name: "Champú especial", precio: 15, image: Carrusel2 }, + { id: 3, name: "Pienso para gatos", precio: 21, image: Carrusel3 }, + ]); + + return ( +
+
+
+
+ + + +
+ +
+ {destacados.map((producto) => ( +
+ {producto.name} +
+
+ {producto.name} + €{producto.precio} +
+ + Ver producto + +
+
+ ))} +
+ + + +
+ +
+ + + Foto Principal + + +
+ +
+ Dálmata +
+
+
+ ); +}; + +export default Home; diff --git a/src/front/pages/Login.jsx b/src/front/pages/Login.jsx new file mode 100644 index 0000000000..1fb1e2ac4b --- /dev/null +++ b/src/front/pages/Login.jsx @@ -0,0 +1,94 @@ +import { Link, useNavigate } from "react-router-dom"; +import React, { useState } from "react"; + +export const Login = () => { + const [form, setForm] = useState({ + email: "", + password: "" + }); + const [error, setError] = useState(""); + const navigate = useNavigate(); + + const handleChange = (e) => { + setForm({ + ...form, + [e.target.name]: e.target.value + }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(""); + + try { + const resp = await fetch(`${import.meta.env.VITE_BACKEND_URL}api/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: form.email, + password: form.password + }) + }); + const data = await resp.json(); + console.log("Login response:", data); + + if (resp.ok && data.token) { + localStorage.setItem("token", data.token); + navigate("/dashboard"); + } else { + setError(data.message || "Error en el inicio de sesión."); + } + } catch (err) { + setError("Error de conexión."); + } + }; + + return ( +
+

¡Bienvenido de nuevo a PatitasClub!

+ +
+
+ + +
+
+ + +
+
+ +
+
+ + {error && ( +
+ {error} +
+ )} + +

+ ¿No tienes una cuenta? Regístrate aquí +

+

+ Volver a la página principal +

+
+ ); +} \ No newline at end of file diff --git a/src/front/pages/Perros.jsx b/src/front/pages/Perros.jsx new file mode 100644 index 0000000000..bb8dd46312 --- /dev/null +++ b/src/front/pages/Perros.jsx @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; +import ConsejoModal from "../components/ConsejoModal"; +import { Productos } from "../components/Prouductos"; + +export function Perros() { + const [showModal, setShowModal] = useState(true); + + return ( +
+

Productos para perros

+ setShowModal(false)} /> +

Comida

+ +

Juguetes

+ +

Accesorios

+ +

Cuidados

+ +
+ ); +} diff --git a/src/front/pages/Registro.jsx b/src/front/pages/Registro.jsx new file mode 100644 index 0000000000..52331525a1 --- /dev/null +++ b/src/front/pages/Registro.jsx @@ -0,0 +1,157 @@ +import { color } from "@cloudinary/url-gen/qualifiers/background"; +import React, { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; + + +export const Registro = () => { + + const [form, setform] = useState({ + username: "", + email: "", + password: "", + confirmPassword: "" + }); + + const [error, setError] = useState("") + const [success, setSuccess] = useState("") + const navigate = useNavigate(); + const handleChange = (e) => { + setform({ + ...form, + [e.target.name]: e.target.value + }); + } + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(""); + setSuccess(""); + + if (!form.username || !form.email || !form.password || !form.confirmPassword) { + setError("Todos los campos son obligatorios"); + return; + } + if (form.username.length < 3) { + setError("El nombre de usuario debe tener al menos 3 caracteres"); + return; + } + if (!/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(form.email)) { + setError("Correo electrónico inválido"); + return; + } + if (form.password.length < 6) { + setError("La contraseña debe tener al menos 6 caracteres"); + return; + } + if (form.confirmPassword.length < 6) { + setError("La confirmación de contraseña debe tener al menos 6 caracteres"); + return; + } + if (!/[A-Z]/.test(form.password)) { + setError("Debe contener al menos una mayúscula"); + return; + } + if (!/[a-z]/.test(form.password)) { + setError("Debe contener al menos una minúscula"); + return; + } + if (!/[0-9]/.test(form.password)) { + setError("Debe contener al menos un número"); + return; + } + if (form.password !== form.confirmPassword) { + setError("Las contraseñas no coinciden"); + return; + } + try { + const resp = await fetch(`${import.meta.env.VITE_BACKEND_URL}api/register`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: form.email, + password: form.password, + username: form.username + }) + }); + const data = await resp.json(); + if (resp.ok) { + setSuccess("¡Registro exitoso! Redirigiendo..."); + setTimeout(() => navigate("/login"), 1500); + } else { + setError(data.message || "Error en el registro."); + } + } catch (err) { + setError("Error de conexión."); + } + }; + + return ( +
+

¡Bienvenido a PatitasClub!

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {error &&
{error}
} + {success &&
{success}
} +
+ +
+
+

+ ¿Ya tienes una cuenta? Inicia sesión aquí +

+

+ Volver a la página principal +

+
+ ); +} + diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 0557df6141..b795fd90f2 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -1,30 +1,43 @@ // Import necessary components and functions from react-router-dom. import { - createBrowserRouter, - createRoutesFromElements, - Route, + createBrowserRouter, + createRoutesFromElements, + Route, } from "react-router-dom"; import { Layout } from "./pages/Layout"; -import { Home } from "./pages/Home"; +import Home from "../front/pages/Home.jsx"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; +import React from "react"; +import CurvedText from "./components/CurvedText"; // Importing the CurvedText component +import { Perros } from "./pages/Perros.jsx"; +import { Gatos } from "./pages/Gatos.jsx"; +import { Registro } from "./pages/Registro.jsx"; // Importing the Registro component +import { Carrito } from "./pages/Carrito.jsx"; // Importing the Carrito component +import { Login } from "./pages/Login.jsx"; // Importing the Login component +import { Dashboard } from "./pages/Dashboard"; // Importing the Dashboard component +import { VistaProducto } from "./components/VistaProducto.jsx"; export const router = createBrowserRouter( - createRoutesFromElements( - // CreateRoutesFromElements function allows you to build route elements declaratively. - // Create your routes here, if you want to keep the Navbar and Footer in all views, add your new routes inside the containing Route. - // Root, on the contrary, create a sister Route, if you have doubts, try it! - // Note: keep in mind that errorElement will be the default page when you don't get a route, customize that page to make your project more attractive. - // Note: The child paths of the Layout element replace the Outlet component with the elements contained in the "element" attribute of these child paths. + createRoutesFromElements( + } errorElement={

Not found!

} > - // Root Route: All navigation will start from here. - } errorElement={

Not found!

} > + {/* Nested Routes: Defines sub-routes within the BaseHome component. */} + } /> + } /> {/* Dynamic route for single items */} + } /> + } /> {/* Route for CurvedText component */} + } /> {/* Route for Perros page */} + } /> {/* Route for Gatos page */} + } /> {/* Route for RegistroModal */} + } /> {/* Route for Carrito page */} + } /> {/* Route for Login page */} + } /> {/* Route for Dashboard page */} + } /> {/* Route for Dashboard page */} - {/* Nested Routes: Defines sub-routes within the BaseHome component. */} - } /> - } /> {/* Dynamic route for single items */} - } /> - - ) + + + + ) ); \ No newline at end of file