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..2647385541 100644 --- a/Pipfile +++ b/Pipfile @@ -6,20 +6,22 @@ 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 = "*" +flask-cors = "*" +flask-sqlalchemy = "*" +flask-jwt-extended = "*" +flask-bcrypt = "*" +email-validator = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index b201c3decc..58ca00a1a3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d2e672e650278aeeee2fe49bd76d76497d8b65a50f8b5dbb121d265cbc6ef4e5" + "sha256": "40d252ffa830ca377695397de60c42d47aebe6623398965da883b48b4b163be9" }, "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,84 @@ }, "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" }, "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 +630,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/ec3dc653e85e_.py b/migrations/versions/ec3dc653e85e_.py new file mode 100644 index 0000000000..efc74815c9 --- /dev/null +++ b/migrations/versions/ec3dc653e85e_.py @@ -0,0 +1,92 @@ +"""empty message + +Revision ID: ec3dc653e85e +Revises: +Create Date: 2025-07-23 19:05:41.212111 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ec3dc653e85e' +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..516f848a2b 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..787eb0cd60 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..51ca0ec2cd 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/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..39cba2073a 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,165 @@ 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..beba6cb35b 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -2,21 +2,85 @@ 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 api.utils import generate_sitemap, APIException from flask_cors import CORS +from flask_jwt_extended import jwt_required, create_access_token, get_jwt_identity +from api.models import User, db +from flask_bcrypt import Bcrypt api = Blueprint('api', __name__) +bcrypt = Bcrypt() -# 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 - 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 +@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() + return jsonify({"error": "Error en el servidor"}), 500 diff --git a/src/app.py b/src/app.py index 1b3340c0fa..eed9956882 100644 --- a/src/app.py +++ b/src/app.py @@ -10,13 +10,15 @@ 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 @@ -30,7 +32,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 +59,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/CurvedText.jsx b/src/front/components/CurvedText.jsx new file mode 100644 index 0000000000..1a7e505be9 --- /dev/null +++ b/src/front/components/CurvedText.jsx @@ -0,0 +1,32 @@ +import React from "react"; + +const CurvedText = () => { + return ( +
+ + + + + + + Donde cada huellita cuenta... + + + +
+ ); +}; + +export default CurvedText; \ No newline at end of file diff --git a/src/front/components/Footer.jsx b/src/front/components/Footer.jsx index f06302dbd2..5dc30fde02 100644 --- a/src/front/components/Footer.jsx +++ b/src/front/components/Footer.jsx @@ -1,11 +1,19 @@ export const Footer = () => ( - -); + <> +
+ + +); \ No newline at end of file diff --git a/src/front/components/Navbar.jsx b/src/front/components/Navbar.jsx index 30d43a2636..283be26e83 100644 --- a/src/front/components/Navbar.jsx +++ b/src/front/components/Navbar.jsx @@ -1,19 +1,86 @@ import { Link } from "react-router-dom"; - +import logoPatitas from "/logoPatitas.png"; export const Navbar = () => { + return ( + + ); +}; + + + + + - return ( - - ); -}; \ No newline at end of file diff --git a/src/front/index.css b/src/front/index.css index e69de29bb2..6c1a4a70c0 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -0,0 +1,38 @@ +.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; +} \ No newline at end of file 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..a02409cbea --- /dev/null +++ b/src/front/pages/Dashboard.jsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from "react"; + +export const Dashboard = () => { + const [user, setUser] = useState(null); + const [error, setError] = useState(""); + + 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.")); + }, []); + + return ( +
+

Bienvenido al Dashboard

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

Información personal:

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

Cargando información...

+ )} +
+ ); +} \ No newline at end of file diff --git a/src/front/pages/Gatos.jsx b/src/front/pages/Gatos.jsx new file mode 100644 index 0000000000..8a9af0a806 --- /dev/null +++ b/src/front/pages/Gatos.jsx @@ -0,0 +1,8 @@ +export const Gatos = () => { + return ( +
+

Gatos

+
+ + ); +}; diff --git a/src/front/pages/Home.jsx b/src/front/pages/Home.jsx index 341ed21768..a4d12cec87 100644 --- a/src/front/pages/Home.jsx +++ b/src/front/pages/Home.jsx @@ -1,52 +1,89 @@ -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: "Producto 1", precio: 23, image: Carrusel1 }, + { id: 2, name: "Producto 2", precio: 21, image: Carrusel2 }, + { id: 3, name: "Producto 3", precio: 18, image: Carrusel3 } + ]); + return ( +
+
+ + +
+
+ + + +
+ +
+ {destacados.map((producto) => ( +
+ {producto.name} +
+
{producto.name}
+

${producto.precio}

+ Ver producto +
+
+ ))} + +
+ + + +
+ + +
+ + + + +
+

Conectando corazones y patitas

+
+
+ +
+ +
+
+
+ ); +}; + +export default Home; \ No newline at end of file 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..066b9b1226 --- /dev/null +++ b/src/front/pages/Perros.jsx @@ -0,0 +1,7 @@ +export const Perros = () => { + return ( +
+

Perros

+
+ ); +}; diff --git a/src/front/pages/Registro.jsx b/src/front/pages/Registro.jsx new file mode 100644 index 0000000000..8e8c4286c7 --- /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..62364ff696 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -1,30 +1,40 @@ // 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 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 */} - {/* Nested Routes: Defines sub-routes within the BaseHome component. */} - } /> - } /> {/* Dynamic route for single items */} - } /> - - ) + + + ) ); \ No newline at end of file