diff --git a/.vscode/settings.json b/.vscode/settings.json index 24da33c3e2..7bd07e2e82 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,8 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "workbench.editorAssociations": { "*.md": "vscode.markdown.preview.editor" + }, + "[javascript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" } } diff --git a/Pipfile b/Pipfile index b461e2e4ee..43e766f2d1 100644 --- a/Pipfile +++ b/Pipfile @@ -18,8 +18,13 @@ gunicorn = "*" cloudinary = "*" flask-admin = "*" typing-extensions = "*" -flask-jwt-extended = "==4.6.0" +flask-jwt-extended = "*" wtforms = "==3.1.2" +flask-bcrypt = "*" +stripe = "*" +flask-mail = "*" +unicode = "*" +unidecode = "*" [requires] python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock index a391864e9d..b2bb96e1e5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "74f92d76f687bb774828613a3a513123fe2ffdb429b95b351d29721dddfd3fb8" + "sha256": "152d436528500a9fd66a44cfda61bef264f87ffc0a69415d18e6ce93d83dd5fb" }, "pipfile-spec": 6, "requires": { @@ -18,73 +18,262 @@ "default": { "alembic": { "hashes": [ - "sha256:6880dec4f28dd7bd999d2ed13fbe7c9d4337700a44d11a524c0ce0c59aaf0dbd", - "sha256:e8a6ff9f3b1887e1fed68bfb8fb9a000d8f61c21bdcc85b67bb9f87fcbc4fce3" + "sha256:197de710da4b3e91cf66a826a5b31b5d59a127ab41bd0fc42863e2902ce2bbbe", + "sha256:e1a1c738577bca1f27e68728c910cd389b9a92152ff91d902da649c192e30c49" ], - "markers": "python_version >= '3.7'", - "version": "==1.9.2" + "markers": "python_version >= '3.9'", + "version": "==1.15.1" + }, + "bcrypt": { + "hashes": [ + "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", + "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", + "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", + "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", + "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", + "sha256:107d53b5c67e0bbc3f03ebf5b030e0403d24dda980f8e244795335ba7b4a027d", + "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", + "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", + "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", + "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", + "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", + "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", + "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", + "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", + "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", + "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", + "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", + "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", + "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", + "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", + "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", + "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", + "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", + "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", + "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", + "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", + "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", + "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", + "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", + "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", + "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", + "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", + "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", + "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", + "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", + "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", + "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", + "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", + "sha256:b6354d3760fcd31994a14c89659dee887f1351a06e5dac3c1142307172a79f90", + "sha256:b693dbb82b3c27a1604a3dff5bfc5418a7e6a781bb795288141e5f80cf3a3492", + "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", + "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", + "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", + "sha256:c950d682f0952bafcceaf709761da0a32a942272fad381081b51096ffa46cea1", + "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", + "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", + "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", + "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", + "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", + "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", + "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.0" + }, + "blinker": { + "hashes": [ + "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", + "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" + ], + "markers": "python_version >= '3.9'", + "version": "==1.9.0" }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2025.1.31" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", + "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", + "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", + "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", + "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", + "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", + "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", + "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", + "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", + "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", + "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", + "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", + "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", + "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", + "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", + "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", + "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", + "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", + "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", + "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", + "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", + "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", + "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", + "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", + "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", + "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", + "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", + "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", + "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", + "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", + "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", + "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", + "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", + "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", + "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", + "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", + "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", + "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", + "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", + "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", + "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", + "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", + "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", + "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", + "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", + "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", + "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", + "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", + "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", + "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", + "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", + "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", + "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", + "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", + "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", + "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", + "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", + "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", + "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", + "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", + "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", + "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", + "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", + "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", + "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", + "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", + "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", + "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", + "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", + "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", + "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", + "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", + "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", + "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", + "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", + "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", + "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", + "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", + "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", + "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", + "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", + "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", + "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", + "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", + "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", + "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", + "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", + "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", + "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", + "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", + "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", + "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.1" }, "click": { "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", + "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" ], "markers": "python_version >= '3.7'", - "version": "==8.1.3" + "version": "==8.1.8" }, "cloudinary": { "hashes": [ - "sha256:f52a1f5eb2c6820f13aa01c109caa5937ad3fd6caf5967817d0ef6c113403afc" + "sha256:cdfe53473fd9564cfb3ff99f93fc6d3a1928f81e7e81920d1b3373bc9768cbd4", + "sha256:d2b4ce247e0fff558fd8326df5db7c94bce15521c5ff53edafbe1bb2e8128cef" ], "index": "pypi", - "version": "==1.31.0" + "version": "==1.43.0" }, "flask": { "hashes": [ - "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", - "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" + "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", + "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136" ], "index": "pypi", - "version": "==2.2.2" + "version": "==3.1.0" }, "flask-admin": { "hashes": [ - "sha256:424ffc79b7b0dfff051555686ea12e86e48dffacac14beaa319fb4502ac40988" + "sha256:24cae2af832b6a611a01d7dc35f42d266c1d6c75a426b869d8cb241b78233369", + "sha256:fd8190f1ec3355913a22739c46ed3623f1d82b8112cde324c60a6fc9b21c9406" + ], + "index": "pypi", + "version": "==1.6.1" + }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" ], "index": "pypi", - "version": "==1.6.0" + "version": "==1.0.1" }, "flask-cors": { "hashes": [ - "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", - "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de" + "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", + "sha256:fa5cb364ead54bbf401a26dbf03030c6b18fb2fcaf70408096a572b409586b0c" + ], + "index": "pypi", + "version": "==5.0.1" + }, + "flask-jwt-extended": { + "hashes": [ + "sha256:52f35bf0985354d7fb7b876e2eb0e0b141aaff865a22ff6cc33d9a18aa987978", + "sha256:8085d6757505b6f3291a2638c84d207e8f0ad0de662d1f46aa2f77e658a0c976" + ], + "index": "pypi", + "version": "==4.7.1" + }, + "flask-mail": { + "hashes": [ + "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d", + "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7" ], "index": "pypi", - "version": "==3.0.10" + "version": "==0.10.0" }, "flask-migrate": { "hashes": [ - "sha256:8662a9dd391ce36deeaf3265987319c20fdb4c8a45306a32ba4f8224459abed4", - "sha256:a0062c8d3f32de02847086b46cfc389412f78c71c89a619ebd7097e89d72ea4b" + "sha256:1a336b06eb2c3ace005f5f2ded8641d534c18798d64061f6ff11f79e1434126d", + "sha256:24d8051af161782e0743af1b04a152d007bad9772b2bca67b7ec1e8ceeb3910d" ], "index": "pypi", - "version": "==4.0.3" + "version": "==4.1.0" }, "flask-sqlalchemy": { "hashes": [ - "sha256:2764335f3c9d7ebdc9ed6044afaf98aae9fa50d7a074cef55dde307ec95903ec", - "sha256:add5750b2f9cd10512995261ee2aa23fab85bd5626061aa3c564b33bb4aa780a" + "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1", + "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283" ], "index": "pypi", - "version": "==3.0.3" + "version": "==3.0.5" }, "flask-swagger": { "hashes": [ @@ -96,304 +285,362 @@ }, "greenlet": { "hashes": [ - "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a", - "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a", - "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43", - "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33", - "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8", - "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088", - "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca", - "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343", - "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645", - "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db", - "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df", - "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3", - "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86", - "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2", - "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a", - "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf", - "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7", - "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394", - "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40", - "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3", - "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6", - "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74", - "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0", - "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3", - "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91", - "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5", - "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9", - "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8", - "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b", - "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6", - "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb", - "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73", - "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b", - "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df", - "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9", - "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f", - "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0", - "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857", - "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a", - "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249", - "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30", - "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292", - "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b", - "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d", - "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b", - "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c", - "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca", - "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7", - "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75", - "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae", - "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b", - "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470", - "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564", - "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9", - "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099", - "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0", - "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5", - "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19", - "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1", - "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526" + "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", + "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7", + "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", + "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", + "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", + "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", + "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", + "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", + "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", + "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa", + "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", + "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", + "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", + "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22", + "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9", + "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", + "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba", + "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", + "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", + "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", + "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291", + "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", + "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", + "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", + "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", + "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef", + "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c", + "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", + "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", + "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", + "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", + "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8", + "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d", + "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", + "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", + "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", + "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", + "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", + "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", + "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1", + "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef", + "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", + "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", + "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", + "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", + "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd", + "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981", + "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", + "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", + "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798", + "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", + "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", + "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", + "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", + "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af", + "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", + "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", + "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", + "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e", + "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81", + "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", + "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", + "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc", + "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de", + "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111", + "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", + "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", + "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", + "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", + "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", + "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803", + "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", + "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f" ], "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", - "version": "==2.0.2" + "version": "==3.1.1" }, "gunicorn": { "hashes": [ - "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", - "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", + "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" ], "index": "pypi", - "version": "==20.1.0" + "version": "==23.0.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "itsdangerous": { "hashes": [ - "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", - "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", + "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "markers": "python_version >= '3.8'", + "version": "==2.2.0" }, "jinja2": { "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2" + "version": "==3.1.6" }, "mako": { "hashes": [ - "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818", - "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34" + "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", + "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac" ], - "markers": "python_version >= '3.7'", - "version": "==1.2.4" + "markers": "python_version >= '3.8'", + "version": "==1.3.9" }, "markupsafe": { "hashes": [ - "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", - "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", - "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", - "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", - "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", - "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", - "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", - "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", - "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", - "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", - "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", - "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", - "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", - "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", - "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", - "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", - "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", - "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", - "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", - "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", - "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", - "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", - "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", - "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", - "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", - "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", - "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", - "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", - "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", - "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", - "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", - "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", - "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", - "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", - "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", - "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", - "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", - "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", - "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", - "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", - "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", - "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", - "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", - "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", - "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", - "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", - "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", - "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", - "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "markers": "python_version >= '3.9'", + "version": "==3.0.2" + }, + "packaging": { + "hashes": [ + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + ], + "markers": "python_version >= '3.8'", + "version": "==24.2" }, "psycopg2-binary": { "hashes": [ - "sha256:00475004e5ed3e3bf5e056d66e5dcdf41a0dc62efcd57997acd9135c40a08a50", - "sha256:01ad49d68dd8c5362e4bfb4158f2896dc6e0c02e87b8a3770fc003459f1a4425", - "sha256:024030b13bdcbd53d8a93891a2cf07719715724fc9fee40243f3bd78b4264b8f", - "sha256:02551647542f2bf89073d129c73c05a25c372fc0a49aa50e0de65c3c143d8bd0", - "sha256:043a9fd45a03858ff72364b4b75090679bd875ee44df9c0613dc862ca6b98460", - "sha256:05b3d479425e047c848b9782cd7aac9c6727ce23181eb9647baf64ffdfc3da41", - "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85", - "sha256:1764546ffeaed4f9428707be61d68972eb5ede81239b46a45843e0071104d0dd", - "sha256:1e491e6489a6cb1d079df8eaa15957c277fdedb102b6a68cfbf40c4994412fd0", - "sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd", - "sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147", - "sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c", - "sha256:2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903", - "sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba", - "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632", - "sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577", - "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c", - "sha256:3520d7af1ebc838cc6084a3281145d5cd5bdd43fdef139e6db5af01b92596cb7", - "sha256:3d790f84201c3698d1bfb404c917f36e40531577a6dda02e45ba29b64d539867", - "sha256:3fc33295cfccad697a97a76dec3f1e94ad848b7b163c3228c1636977966b51e2", - "sha256:422e3d43b47ac20141bc84b3d342eead8d8099a62881a501e97d15f6addabfe9", - "sha256:426c2ae999135d64e6a18849a7d1ad0e1bd007277e4a8f4752eaa40a96b550ff", - "sha256:46512486be6fbceef51d7660dec017394ba3e170299d1dc30928cbedebbf103a", - "sha256:46850a640df62ae940e34a163f72e26aca1f88e2da79148e1862faaac985c302", - "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1", - "sha256:4e7904d1920c0c89105c0517dc7e3f5c20fb4e56ba9cdef13048db76947f1d79", - "sha256:56b2957a145f816726b109ee3d4e6822c23f919a7d91af5a94593723ed667835", - "sha256:5c6527c8efa5226a9e787507652dd5ba97b62d29b53c371a85cd13f957fe4d42", - "sha256:5cbc554ba47ecca8cd3396ddaca85e1ecfe3e48dd57dc5e415e59551affe568e", - "sha256:5d28ecdf191db558d0c07d0f16524ee9d67896edf2b7990eea800abeb23ebd61", - "sha256:5fc447058d083b8c6ac076fc26b446d44f0145308465d745fba93a28c14c9e32", - "sha256:63e318dbe52709ed10d516a356f22a635e07a2e34c68145484ed96a19b0c4c68", - "sha256:68d81a2fe184030aa0c5c11e518292e15d342a667184d91e30644c9d533e53e1", - "sha256:6e63814ec71db9bdb42905c925639f319c80e7909fb76c3b84edc79dadef8d60", - "sha256:6f8a9bcab7b6db2e3dbf65b214dfc795b4c6b3bb3af922901b6a67f7cb47d5f8", - "sha256:70831e03bd53702c941da1a1ad36c17d825a24fbb26857b40913d58df82ec18b", - "sha256:74eddec4537ab1f701a1647214734bc52cee2794df748f6ae5908e00771f180a", - "sha256:7b3751857da3e224f5629400736a7b11e940b5da5f95fa631d86219a1beaafec", - "sha256:7cf1d44e710ca3a9ce952bda2855830fe9f9017ed6259e01fcd71ea6287565f5", - "sha256:7d07f552d1e412f4b4e64ce386d4c777a41da3b33f7098b6219012ba534fb2c2", - "sha256:7d88db096fa19d94f433420eaaf9f3c45382da2dd014b93e4bf3215639047c16", - "sha256:7ee3095d02d6f38bd7d9a5358fcc9ea78fcdb7176921528dd709cc63f40184f5", - "sha256:902844f9c4fb19b17dfa84d9e2ca053d4a4ba265723d62ea5c9c26b38e0aa1e6", - "sha256:937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1", - "sha256:95076399ec3b27a8f7fa1cc9a83417b1c920d55cf7a97f718a94efbb96c7f503", - "sha256:9c38d3869238e9d3409239bc05bc27d6b7c99c2a460ea337d2814b35fb4fea1b", - "sha256:9e32cedc389bcb76d9f24ea8a012b3cb8385ee362ea437e1d012ffaed106c17d", - "sha256:9ffdc51001136b699f9563b1c74cc1f8c07f66ef7219beb6417a4c8aaa896c28", - "sha256:a0adef094c49f242122bb145c3c8af442070dc0e4312db17e49058c1702606d4", - "sha256:a36a0e791805aa136e9cbd0ffa040d09adec8610453ee8a753f23481a0057af5", - "sha256:a7e518a0911c50f60313cb9e74a169a65b5d293770db4770ebf004245f24b5c5", - "sha256:af0516e1711995cb08dc19bbd05bec7dbdebf4185f68870595156718d237df3e", - "sha256:b8104f709590fff72af801e916817560dbe1698028cd0afe5a52d75ceb1fce5f", - "sha256:b911dfb727e247340d36ae20c4b9259e4a64013ab9888ccb3cbba69b77fd9636", - "sha256:b9a794cef1d9c1772b94a72eec6da144c18e18041d294a9ab47669bc77a80c1d", - "sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64", - "sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb", - "sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882", - "sha256:bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720", - "sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896", - "sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267", - "sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7", - "sha256:c5e65c6ac0ae4bf5bef1667029f81010b6017795dcb817ba5c7b8a8d61fab76f", - "sha256:d4c7b3a31502184e856df1f7bbb2c3735a05a8ce0ade34c5277e1577738a5c91", - "sha256:d892bfa1d023c3781a3cab8dd5af76b626c483484d782e8bd047c180db590e4c", - "sha256:dbc332beaf8492b5731229a881807cd7b91b50dbbbaf7fe2faf46942eda64a24", - "sha256:dc85b3777068ed30aff8242be2813038a929f2084f69e43ef869daddae50f6ee", - "sha256:e59137cdb970249ae60be2a49774c6dfb015bd0403f05af1fe61862e9626642d", - "sha256:e67b3c26e9b6d37b370c83aa790bbc121775c57bfb096c2e77eacca25fd0233b", - "sha256:e72c91bda9880f097c8aa3601a2c0de6c708763ba8128006151f496ca9065935", - "sha256:f95b8aca2703d6a30249f83f4fe6a9abf2e627aa892a5caaab2267d56be7ab69" + "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", + "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5", + "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", + "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", + "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", + "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", + "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", + "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", + "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", + "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", + "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", + "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", + "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", + "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", + "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", + "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", + "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", + "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", + "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", + "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", + "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", + "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", + "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", + "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", + "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", + "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", + "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", + "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", + "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", + "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44", + "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", + "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", + "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", + "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa", + "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", + "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", + "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", + "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", + "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", + "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", + "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", + "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", + "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", + "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", + "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3", + "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", + "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92", + "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", + "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", + "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", + "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", + "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", + "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", + "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", + "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", + "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", + "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", + "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", + "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", + "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", + "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", + "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", + "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", + "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", + "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", + "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", + "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", + "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" ], "index": "pypi", - "version": "==2.9.5" + "version": "==2.9.10" + }, + "pyjwt": { + "hashes": [ + "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", + "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb" + ], + "markers": "python_version >= '3.9'", + "version": "==2.10.1" }, "python-dotenv": { "hashes": [ - "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49", - "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a" + "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", + "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" ], "index": "pypi", - "version": "==0.21.1" + "version": "==1.0.1" }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.6'", - "version": "==6.0" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, - "setuptools": { + "requests": { "hashes": [ - "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378", - "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300" + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" ], - "markers": "python_version >= '3.7'", - "version": "==67.1.0" + "markers": "python_version >= '3.0'", + "version": "==2.32.3" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -442,37 +689,61 @@ "index": "pypi", "version": "==1.4.46" }, + "stripe": { + "hashes": [ + "sha256:0ced7cce23a6cb1a393c86a1f7f9435c9d83ae7cbd556362868caf62cb44a92c", + "sha256:6e6cf09ebb6d5fc2d708401cb8868fd7bff987a6d09a0433caaa92c62f97dbc5" + ], + "index": "pypi", + "version": "==11.6.0" + }, "typing-extensions": { "hashes": [ - "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", - "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], "index": "pypi", - "version": "==4.4.0" + "version": "==4.12.2" + }, + "unicode": { + "hashes": [ + "sha256:39d67799f4795823122192a1f8dd58d476bf8b80c863fb7d530ad990dd2ebc9a", + "sha256:42b6c3b953dce84a25bc9e5d11942c7d34b4d70d8e2d54b455c074770eb753b2" + ], + "index": "pypi", + "version": "==2.9" + }, + "unidecode": { + "hashes": [ + "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4", + "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39" + ], + "index": "pypi", + "version": "==1.3.8" }, "urllib3": { "hashes": [ - "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72", - "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1" + "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", + "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.14" + "markers": "python_version >= '3.9'", + "version": "==2.3.0" }, "werkzeug": { "hashes": [ - "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", - "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" + "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", + "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], - "markers": "python_version >= '3.7'", - "version": "==2.2.2" + "markers": "python_version >= '3.9'", + "version": "==3.1.3" }, "wtforms": { "hashes": [ - "sha256:6b351bbb12dd58af57ffef05bc78425d08d1914e0fd68ee14143b7ade023c5bc", - "sha256:837f2f0e0ca79481b92884962b914eba4e72b7a2daaf1f939c890ed0124b834b" + "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07", + "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9" ], - "markers": "python_version >= '3.7'", - "version": "==3.0.1" + "index": "pypi", + "version": "==3.1.2" } }, "develop": {} diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000000..0e04844159 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000000..ec9d45c26a --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000000..4c9709271b --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000000..2c0156303a --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/af42dc76fa61_.py b/migrations/versions/af42dc76fa61_.py new file mode 100644 index 0000000000..7d8d33354d --- /dev/null +++ b/migrations/versions/af42dc76fa61_.py @@ -0,0 +1,96 @@ +"""empty message + +Revision ID: af42dc76fa61 +Revises: +Create Date: 2025-03-20 19:50:30.031235 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'af42dc76fa61' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('accessories', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('brand', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('animal_type', sa.String(length=50), nullable=False), + sa.Column('pathologies', sa.Text(), nullable=True), + sa.Column('price', sa.Float(), nullable=False), + sa.Column('url', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('foods', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('brand', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('ingredients', sa.Text(), nullable=False), + sa.Column('weight', sa.Float(), nullable=False), + sa.Column('price', sa.Float(), nullable=False), + sa.Column('age', sa.String(), nullable=False), + sa.Column('animal_type', sa.String(length=50), nullable=False), + sa.Column('size', sa.String(length=30), nullable=True), + sa.Column('pathologies', sa.Text(), nullable=True), + sa.Column('url', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('password_reset', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('token', sa.String(length=255), nullable=False), + sa.Column('expires_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('token') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=80), nullable=False), + sa.Column('email', sa.String(length=120), nullable=False), + sa.Column('password', sa.String(length=80), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_table('order', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('ordered_food', sa.String(), nullable=True), + sa.Column('ordered_accessories', sa.String(), nullable=True), + sa.Column('status', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('pet', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('size', sa.String(length=100), nullable=True), + sa.Column('breed', sa.String(length=100), nullable=True), + sa.Column('age', sa.String(), nullable=False), + sa.Column('animal_type', sa.String(), nullable=False), + sa.Column('pathologies', sa.Text(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('url', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('pet') + op.drop_table('order') + op.drop_table('user') + op.drop_table('password_reset') + op.drop_table('foods') + op.drop_table('accessories') + # ### end Alembic commands ### diff --git a/package-lock.json b/package-lock.json index c932d7fc55..21a36caf28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,20 @@ "version": "1.0.1", "license": "ISC", "dependencies": { + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@stripe/react-stripe-js": "^3.5.0", + "@stripe/stripe-js": "^6.1.0", + "bootstrap": "^5.3.3", + "jwt-decode": "^4.0.0", + "lucide-react": "^0.479.0", "prop-types": "^15.6.1", "react": "^16.8.4", + "react-bootstrap": "^2.10.9", "react-dom": "^16.8.4", + "react-icons": "^5.5.0", "react-polyfills": "0.0.1", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.0" }, "devDependencies": { "@babel/cli": "^7.16.0", @@ -1681,11 +1690,12 @@ } }, "node_modules/@babel/runtime": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz", - "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -1782,6 +1792,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", + "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", @@ -1850,6 +1907,126 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@stripe/react-stripe-js": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.5.0.tgz", + "integrity": "sha512-oo5J2SNbuAUjE9XmQv/SOD7vgZCa1Y9OcZyRAfvQPkyrDrru35sg5c64ANdHEmOWUibism3+25rKdARSw3HOfA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": ">=1.44.1 <7.0.0", + "react": ">=16.8.0 <20.0.0", + "react-dom": ">=16.8.0 <20.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-6.1.0.tgz", + "integrity": "sha512-/5zxRol+MU4I7fjZXPxP2M6E1nuHOxAzoc0tOEC/TLnC31Gzc+5EE93mIjoAnu28O1Sqpl7/BkceDHwnGmn75A==", + "license": "MIT", + "engines": { + "node": ">=12.16" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -1976,6 +2153,12 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -1988,6 +2171,24 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", @@ -2022,6 +2223,12 @@ "@types/node": "*" } }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -2922,6 +3129,25 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3074,6 +3300,12 @@ "node": ">=6.0" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", @@ -3427,6 +3659,12 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -3501,6 +3739,15 @@ "node": ">= 0.6" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -3600,6 +3847,16 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", @@ -5331,14 +5588,6 @@ "he": "bin/he" } }, - "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -5699,7 +5948,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -6149,6 +6397,15 @@ "node": ">=4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -6283,6 +6540,15 @@ "node": ">=10" } }, + "node_modules/lucide-react": { + "version": "0.479.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", + "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -7214,13 +7480,27 @@ } }, "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "license": "MIT", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" } }, "node_modules/proxy-addr": { @@ -7360,6 +7640,37 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.9.tgz", + "integrity": "sha512-TJUCuHcxdgYpOqeWmRApM/Dy0+hVsxNRFvq2aRFQuxhNi/+ivOxC5OdWIeHS3agxvzJ4Ev4nDw2ZdBl9ymd/JQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -7620,38 +7931,77 @@ "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", "dev": true }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, "node_modules/react-polyfills": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/react-polyfills/-/react-polyfills-0.0.1.tgz", "integrity": "sha1-vxRjUVy/d1VuZSj6TelYRy3IJq0=" }, + "node_modules/react-router": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/react-router-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", - "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "license": "MIT", "dependencies": { - "history": "^5.2.0", - "react-router": "6.3.0" + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, - "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", - "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", "dependencies": { - "history": "^5.2.0" + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" }, "peerDependencies": { - "react": ">=16.8" + "react": ">=16.6.0", + "react-dom": ">=16.6.0" } }, "node_modules/readable-stream": { @@ -7723,9 +8073,10 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" }, "node_modules/regenerator-transform": { "version": "0.14.5", @@ -8672,10 +9023,10 @@ } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -8743,6 +9094,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -8881,6 +9247,15 @@ "node": ">= 0.8" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", @@ -10484,11 +10859,11 @@ } }, "@babel/runtime": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz", - "integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { @@ -10563,6 +10938,36 @@ } } }, + "@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "peer": true, + "requires": { + "@fortawesome/fontawesome-common-types": "6.7.2" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", + "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.7.2" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "requires": { + "prop-types": "^15.8.1" + } + }, "@humanwhocodes/config-array": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz", @@ -10619,6 +11024,85 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, + "@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "requires": { + "@swc/helpers": "^0.5.0" + } + }, + "@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==" + }, + "@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "requires": { + "dequal": "^2.0.3" + } + }, + "@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "requires": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "dependencies": { + "@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "requires": { + "dequal": "^2.0.3" + } + }, + "uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "requires": {} + } + } + }, + "@stripe/react-stripe-js": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-3.5.0.tgz", + "integrity": "sha512-oo5J2SNbuAUjE9XmQv/SOD7vgZCa1Y9OcZyRAfvQPkyrDrru35sg5c64ANdHEmOWUibism3+25rKdARSw3HOfA==", + "requires": { + "prop-types": "^15.7.2" + } + }, + "@stripe/stripe-js": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-6.1.0.tgz", + "integrity": "sha512-/5zxRol+MU4I7fjZXPxP2M6E1nuHOxAzoc0tOEC/TLnC31Gzc+5EE93mIjoAnu28O1Sqpl7/BkceDHwnGmn75A==" + }, + "@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "requires": { + "tslib": "^2.8.0" + } + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -10745,6 +11229,11 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -10757,6 +11246,20 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, + "@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "requires": { + "csstype": "^3.0.2" + } + }, + "@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "requires": {} + }, "@types/retry": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", @@ -10791,6 +11294,11 @@ "@types/node": "*" } }, + "@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -11550,6 +12058,12 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "requires": {} + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -11659,6 +12173,11 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "clean-css": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz", @@ -11929,6 +12448,11 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -11980,6 +12504,11 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -12061,6 +12590,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", @@ -13329,14 +13867,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "requires": { - "@babel/runtime": "^7.7.6" - } - }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -13605,7 +14135,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "requires": { "loose-envify": "^1.0.0" } @@ -13919,6 +14448,11 @@ "object.assign": "^4.1.2" } }, + "jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -14028,6 +14562,12 @@ "yallist": "^4.0.0" } }, + "lucide-react": { + "version": "0.479.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", + "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", + "requires": {} + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -14714,13 +15254,22 @@ } }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" + } + }, + "prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "requires": { + "react-is": "^16.3.2", + "warning": "^4.0.0" } }, "proxy-addr": { @@ -14819,6 +15368,26 @@ "prop-types": "^15.6.2" } }, + "react-bootstrap": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.9.tgz", + "integrity": "sha512-TJUCuHcxdgYpOqeWmRApM/Dy0+hVsxNRFvq2aRFQuxhNi/+ivOxC5OdWIeHS3agxvzJ4Ev4nDw2ZdBl9ymd/JQ==", + "requires": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + } + }, "react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -15006,33 +15575,53 @@ "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", "dev": true }, + "react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-polyfills": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/react-polyfills/-/react-polyfills-0.0.1.tgz", "integrity": "sha1-vxRjUVy/d1VuZSj6TelYRy3IJq0=" }, + "react-router": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "requires": { + "@remix-run/router": "1.23.0" + } + }, "react-router-dom": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", - "integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", "requires": { - "history": "^5.2.0", - "react-router": "6.3.0" - }, - "dependencies": { - "react-router": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz", - "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==", - "requires": { - "history": "^5.2.0" - } - } + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" + } + }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" } }, "readable-stream": { @@ -15089,9 +15678,9 @@ } }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regenerator-transform": { "version": "0.14.5", @@ -15815,10 +16404,9 @@ } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "type-check": { "version": "0.4.0", @@ -15864,6 +16452,17 @@ "which-boxed-primitive": "^1.0.2" } }, + "uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "requires": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -15977,6 +16576,14 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", diff --git a/package.json b/package.json index 3c8d47cba7..5fc5f2b08f 100755 --- a/package.json +++ b/package.json @@ -73,10 +73,19 @@ ] }, "dependencies": { + "@fortawesome/free-brands-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", + "@stripe/react-stripe-js": "^3.5.0", + "@stripe/stripe-js": "^6.1.0", + "bootstrap": "^5.3.3", + "jwt-decode": "^4.0.0", + "lucide-react": "^0.479.0", "prop-types": "^15.6.1", "react": "^16.8.4", + "react-bootstrap": "^2.10.9", "react-dom": "^16.8.4", + "react-icons": "^5.5.0", "react-polyfills": "0.0.1", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.30.0" } } diff --git a/render.yaml b/render.yaml index b243438abd..a6cca0f67d 100644 --- a/render.yaml +++ b/render.yaml @@ -19,6 +19,8 @@ services: value: 0 - key: FLASK_APP_KEY # Imported from Heroku app value: "any key works" + - key: DEBUG + value: TRUE - key: PYTHON_VERSION value: 3.10.6 - key: DATABASE_URL # Render PostgreSQL database diff --git a/render_build.sh b/render_build.sh index 404821a383..e7f5fd6f0e 100644 --- a/render_build.sh +++ b/render_build.sh @@ -2,9 +2,17 @@ # exit on error set -o errexit +cd src/front npm install npm run build +cd ../.. pipenv install - +pipenv run flask insert-test-users 5 +pipenv run flask insert_data_catfood +pipenv run flask insert_data_dogfood +pipenv run flask insert_data_exoticfood +pipenv run flask insert_data_accessories +pipenv run flask insert_data_pet +pipenv run migrate pipenv run upgrade diff --git a/requirements.txt b/requirements.txt index 4eac45f4f8..39d8350c51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,4 @@ sqlalchemy==1.3.23 urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' wtforms==2.3.3 + diff --git a/requirements.txt.save b/requirements.txt.save new file mode 100644 index 0000000000..5022733cbd --- /dev/null +++ b/requirements.txt.save @@ -0,0 +1,26 @@ +-i https://pypi.org/simple +alembic==1.5.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' +certifi==2020.12.5 +click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +cloudinary==1.24.0 +flask==1.1.2 +flask-admin==1.5.7 +flask-cors==3.0.10 +flask-migrate==2.6.0 +flask-sqlalchemy==2.4.4 +flask-swagger==0.2.14 +gunicorn==20.0.4 +itsdangerous==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +jinja2==2.11.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +mako==1.1.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +markupsafe==1.1.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +psycopg2-binary==2.8.6 +python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +python-dotenv==0.15.0 +python-editor==1.0.4 +pyyaml==5.4.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' +six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +sqlalchemy==1.3.23 +urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' +werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +wtforms==2. diff --git a/src/api/admin.py b/src/api/admin.py index 3eecb64140..6a36c9ff46 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, Food, Accessories, Pet, Order from flask_admin.contrib.sqla import ModelView def setup_admin(app): @@ -12,6 +12,9 @@ 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(Food, db.session)) + admin.add_view(ModelView(Accessories, db.session)) + admin.add_view(ModelView(Pet, db.session)) + admin.add_view(ModelView(Order, 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..0072ae37ad 100644 --- a/src/api/commands.py +++ b/src/api/commands.py @@ -1,6 +1,11 @@ + + import click -from api.models import db, User +from api.models import db, User, Food, Accessories, Pet +from flask_bcrypt import Bcrypt + +bcrypt = Bcrypt() """ In this file, you can add as many commands as you want using the @app.cli.command decorator @@ -20,8 +25,11 @@ def insert_test_users(count): print("Creating test users") for x in range(1, int(count) + 1): user = User() + user.name = "name"+ str(x) user.email = "test_user" + str(x) + "@test.com" - user.password = "123456" + user.password = bcrypt.generate_password_hash("123456").decode('utf-8') + user.address = "asd" + user.phone = 12324 user.is_active = True db.session.add(user) db.session.commit() @@ -29,6 +37,348 @@ def insert_test_users(count): print("All test users created") - @app.cli.command("insert-test-data") - def insert_test_data(): - pass \ No newline at end of file + @app.cli.command("insert_data_catfood") + def insert_data_catfood(): + catfood = Food() + catfood.name = "KA AYURVEDA Only Fish Gatos Sin Gluten" + catfood.brand="Feral" + catfood.description = "KA AYURVEDA Only Fish es el primer pienso SIN GLUTEN y elaborado sólo con pescado azul. Alimento REALMENTE HIPOALERGÉNICO y de gran digestibilidad. Agregamos a nuestra receta un ingrediente poco conocido, como es la harina de algarroba, cuyas propiedades nutricionales, suponen toda una revolución." + catfood.ingredients = "Salmón atlántico deshidratado e hidrolizado min. 21%, arenques deshidratados e hidrolizados 12%, anchoveta deshidratada e hidrolizada 9%, atún deshidratado e hidrolizado 6%, arroz integral, legumbre, levadura de cerveza (Saccharomyces cerevisae), aceite de salmón rico en Omega 3, harina de algarroba, Krill antártico (Euphausia Superba), complejo condroprotector (hidrocloruro de glucosamina, sulfato de condroitina), extracto de mejillón de labios verdes (Perna canaliculus), extracto de Yuca schidigera, manano-oligosacáridos, fructo-oligosacáridos, taurina (2000), caléndula, salvia, fenogreco, equinácea, eneldo, diente de león, té verde, tomillo, romero, cinc quelado, antioxidantes naturales, complejo vitamínico mineral, probióticos (E.faecium NCIMB 1041520×106 UFC), L-carnitina." + catfood.weight = 1.5 + catfood.price = 6.42 + catfood.animal_type = "gato" + catfood.age = "adulto" + catfood.pathologies = "hipoalergenico" + catfood.url = "https://era2vrmzk5n.exactdn.com/wp-content/uploads/2022/09/Pienso-15K-croqPEZ-Ayurveda-gato-kasaludintegral-1080x1080pix.jpg?strip=all&lossy=1&w=648&ssl=1" + db.session.add(catfood) + db.session.commit() + + catfood = Food() + catfood.name = "Farmina Vet Life Diabetic Gato" + catfood.brand="Farmina" + catfood.description = "Alimento para gatos adultos que facilita el control del aporte de glucosa en los casos de diabetes mellitus." + catfood.ingredients = "Proteína de pollo deshidratada, gluten de maíz, avena, espelta, proteína de pescado hidrolizada, grasa de pollo, fibra de guisante, proteína de pescado deshidratada, huevos desecados, aceite de pescado, pulpa de remolacha desecada, semillas de lino, cloruro de potasio, inulina, fructo-oligosacáridos, extracto de levadura (fuente de manno-oligosacáridos), cáscaras y semillas de psyllium, cloruro de sodio, citrato de potasio, sulfato de calcio dihidratado, condroitín sulfato, glucosamina. Fuentes de hidratos de carbono: avena, espelta." + catfood.weight = 0.4 + catfood.price = 6.99 + catfood.animal_type = "gato" + catfood.age = "adulto" + catfood.pathologies = "diabetes" + catfood.url = "https://bixoto.com/media/catalog/product/cache/2e45ba69d70625e0fc47dbc2d34862e1/M/Y/MYNKvuY-920-2042_webp.png" + db.session.add(catfood) + db.session.commit() + + catfood = Food() + catfood.name = "OWNAT GF PRIME KITTEN (CAT)" + catfood.brand="Ownat" + catfood.description = "Comida para gatos. Indicado para gatitos de 2 a 12 meses. Con un 50% de carne fresca de pollo y pavo, ofrece un aporte de energía elevado y muy rico en proteínas de alta calidad, que garantizan su óptimo crecimiento, así como el correcto desarrollo del sistema inmunitario del gatito." + catfood.ingredients = "Pollo fresco* (mín. 25% antes de la extrusión), pavo fresco* (mín. 25% antes de la extrusión), cerdo deshidratado (13%), guisantes enteros* (9%), grasa avícola (preservada con antioxidantes naturales), hidrolizado de salmón, pollo deshidratado (5%), raíces de mandioca* (5%), pescados deshidratados (anchoveta, jurel y caballa), patata deshidratada* (3%), huevo deshidratado* (2%), levadura de cerveza* (1%), proteína hidrolizada de pollo, manzana* (1%), aceite de pescado (preservado con antioxidantes naturales), sal gema*, fructo y manano oligosacáridos, algas marinas deshidratadas* (500 mg/kg), glucosamina (500 mg/kg), yucca schidigera, condroitín sulfato (100 mg/kg), arándanos* (100 mg/kg), tomillo* (80 mg/kg), flor de manzanilla* (80 mg/kg), hinojo* (80 mg/kg), equinácea* (80 mg/kg), té verde* (80 mg/kg).*INGREDIENTES NATURALES" + catfood.weight = 1. + catfood.price = 10.70 + catfood.animal_type = "gato" + catfood.age = "cachorro" + catfood.pathologies = "ninguna" + catfood.url = "https://www.tiendanimal.es/dw/image/v2/BDLQ_PRD/on/demandware.static/-/Sites-kiwoko-master-catalog/default/dw55de3cf2/images/ownat_prime_gf_kitten_OWN31463.jpg?sw=780&sh=780&sm=fit&q=85" + db.session.add(catfood) + db.session.commit() + + catfood = Food() + catfood.name = "Diet Vet Renal-Oxalate pienso para gatos" + catfood.brand="Natural Greatness" + catfood.description = "Si tu gato padece insuficiencia renal o cardíaca crónica, es esencial proporcionarle una dieta adaptada. Natural Greatness Diet Vet Renal-Oxalate ha sido especialmente desarrollado para ayudar a la función renal y cardíaca. Este pienso dietético para gatos adultos se caracteriza por una composición equilibrada con proteínas de alta calidad y un contenido reducido de sodio y fósforo." + catfood.ingredients = "Pollo fresco (25 %), patatas, aceite de pollo (13 %), guisantes, proteína de patata, pavo y pollo deshidratados (8 %), proteína de guisante, pulpa de remolacha, hidrolizado de hígado de pavo y pollo (2 %), pescado azul deshidratado (1,6 %), aceite de pescado azul (1,1 %), cloruro potásico, citrato de tripotasio [sustancia alcalinizante urinaria] (0,5 %), levadura de cerveza, mezcla de frutas [arándanos y pera] (0,3 %), mezcla de verduras [chirivía, laurel, perejil y diente de león] (0,3 %), huevo entero deshidratado, extracto de malta, achicoria, hidrolizado de la pared celular de la levadura (fuente de MOS), inulina (FOS), glucosamina, condroitín sulfato." + catfood.weight = 5. + catfood.price = 46.99 + catfood.animal_type = "gato" + catfood.age = "senior" + catfood.pathologies = "renal" + catfood.url = "https://media.zooplus.com/bilder/2/400/279797_pla_natural_greatness_diet_vet_renal_oxalate_5kg_hs_01_2.jpg" + db.session.add(catfood) + db.session.commit() + + catfood = Food() + catfood.name = "Adulto Obesity " + catfood.brand="Criadores Dietetic" + catfood.description = "El pienso Criadores Dietetic Obesity para gatos es un alimento dietético desarrollado para reducir el exceso de peso. Una receta completa y equilibrada con un bajo contenido de energía y grasa que favorece la pérdida de peso de forma sencilla y sin pasar hambre. Su fórmula especial mejora la calidad de vida de tu felino con ingredientes seleccionados y componentes que promueven una buena salud articular." + catfood.ingredients = "Pollo deshidratado y desgrasado (30%), Almidón de maíz, Gluten de maíz, Lignocelulosa, Proteína de patata, Arroz, Aceite de ave, Harina de ave hidrolizada, Maíz, Pulpa de remolacha, Proteína de arroz, Hidrolizado de hígado de ave, Aceite de pescado (2%), Levaduras, Sustancias minerales, Semillas de Psyllium (1%), Caléndula (0,1%), Inulina (FOS) (0,1%), Pared celular hidrolizada de levaduras (fuente de MOS) (0,1%), Glucosamina (300 mg/kg), Condroitina (200 mg/kg), Metilsulfonilmetano (MSM)." + catfood.weight = 2.5 + catfood.price = 19.99 + catfood.animal_type = "gato" + catfood.age = "adulto" + catfood.pathologies = "obesidad" + catfood.url = "https://www.tiendanimal.es/dw/image/v2/BDLQ_PRD/on/demandware.static/-/Sites-kiwoko-master-catalog/default/dwfc3b7ec1/images/pienso_gato_criadores_dietetic_obesity_CRD8967_M.jpg?sw=780&sh=780&sm=fit&q=85" + db.session.add(catfood) + db.session.commit() + + + + + @app.cli.command("insert_data_dogfood") + def insert_data_food(): + dogfood = Food() + dogfood.name = " Veterinary Diet Renal pienso para perros" + dogfood.brand="Concept for Life " + dogfood.description = "Pienso dietético para perros adultos con insuficiencia renal crónica. Bajo en fósforo y proteína para reducir la formación de cálculos de oxalato y de urato. Con propiedades alcalinizantes de la orina" + dogfood.ingredients = "Arroz, 31,5 % maíz, grasa de ave, 4,7 % proteína de ave (rica en pollo, en parte deshidratada e hidrolizada), proteína de maíz, pulpa de remolacha deshidratada (desazucarada), gelatina (hidrolizada, HDP*), linaza, aceite de pescado, 1,5 % hígado de pollo (en parte deshidratado e hidrolizado, HDP*), aceite de girasol, carbonato de calcio, cloruro sódico, cloruro potásico, huevo (deshidratado), lignocelulosa, 0,3 % paredes celulares de levadura (ricas en manano-oligosacáridos y en beta-glucanos), 0,2 % citrato de potasio, inulina de achicoria, harina de algas (rica en DHA**)." + dogfood.price = 7.99 + dogfood.pathologies = "renal" + dogfood.animal_type = "perro" + dogfood.age = "senior" + dogfood.size = "medio" + dogfood.weight = 1. + dogfood.url = "https://media.zooplus.com/bilder/9/400/cfl_dog_renal_1kg_1000x1000_9.jpg" + db.session.add(dogfood) + db.session.commit() + + dogfood = Food() + dogfood.name = "CARE DIGESTIVE (DOG)" + dogfood.brand="ownat" + dogfood.description = "Comida para perros. OWNAT CARE DIGESTIVE está indicado para perros con el tracto gastrointestinal sensible. Está elaborado de manera cuidadosa con ingredientes naturales, probióticos y prebióticos para favorecer la salud intestinal, promoviendo una flora intestinal equilibrada y una óptima digestión." + dogfood.ingredients = "Pollo fresco* (mín. 20%) antes de la cocción), pollo deshidratado, raíces de mandioca*, batata deshidratada* (10%), maíz integral*, arroz integral* (7%), grasa avícola (preservada con antioxidantes naturales), salmón hidrolizado (6%), pescado deshidratado, vaina de algarroba*, proteína hidrolizada de pollo, pulpa de remolacha, aceite de salmón (preservado con antioxidantes naturales), levaduras (nucleótidos)* (1,5%), manzana* (1%), semilla de lino* (1%), sal gema*, alfalfa deshidratada*, raíces de achicoria (FOS)* (2 g/kg), extracto de levadura (MOS) (2 g/kg), algas marinas deshidratadas (Ascophyllum nodosum)* (500 mg/kg), yucca schidigera, arándanos* (100 mg/kg), raíces de harpagofito* (100 mg/kg), flor de manzanilla* (100 mg/kg), equinácea* (100 mg/kg), té verde* (100 mg/kg). *INGREDIENTES NATURALES" + dogfood.price = 1. + dogfood.pathologies = "diabetes" + dogfood.animal_type = "perro" + dogfood.age = "cachorro" + dogfood.size = "grande" + dogfood.weight = 1. + dogfood.url = "https://www.tiendanimal.es/dw/image/v2/BDLQ_PRD/on/demandware.static/-/Sites-kiwoko-master-catalog/default/dw9b40881f/images/pienso_perros_ownat_care_digestive_OWN031569.jpg.jpg?sw=780&sh=780&sm=fit&q=85" + db.session.add(dogfood) + db.session.commit() + + + dogfood = Food() + dogfood.name = "Weight Loss & Diabetes" + dogfood.brand="Virbac W1 Veterinary HPM" + dogfood.description = "El alimento dietético para perros tiene un bajo contenido energético y de azúcares totales. Es bajo en hidratos de carbono y se caracteriza por un índice glucémico bajo. En cambio, Virbac Veterinary HPM Dog Weight Loss & Diabetes W1 proporciona a su mascota abundante fibra bruta, que favorece una agradable sensación de saciedad. La receta no contiene trigo, gluten, soja ni carne de vacuno, por lo que es bien tolerada por muchos perros." + dogfood.ingredients = "Proteínas deshidratadas de cerdo y de ave, fécula de patata, lignocelulosa, hidrolizado de proteínas de cerdo y de ave, cáscaras de judías, grasa animal, minerales, linaza, pulpa de remolacha deshidratada, fibra de psyllium" + dogfood.price = 12.99 + dogfood.pathologies = "diabetes" + dogfood.animal_type = "perro" + dogfood.age = "adulto" + dogfood.size = "medio" + dogfood.weight = 1.5 + dogfood.url = "https://www.tiendanimal.es/dw/image/v2/BDLQ_PRD/on/demandware.static/-/Sites-kiwoko-master-catalog/default/dw6af74272/images/virbac_pienso_perros_hpm_weight_loss_diabetes_VIRAD360106.jpg?sw=780&sh=780&sm=fit&q=85" + db.session.add(dogfood) + db.session.commit() + + dogfood = Food() + dogfood.name = "Adult Light Sterilised Pavo y patata - SIN CEREALES" + dogfood.brand="Briantos" + dogfood.description = "Briantos ofrece el alimento adecuado para todas las necesidades. Nuestras recetas sin cereales cuentan con una alta tolerancia entre los perros. El mayor contenido de humedad convence incluso a los perros más quisquillosos. La alta calidad de los ingredientes utilizados en la elaboración de este alimento y todos los controles de calidad durante la producción garantizan el mejor producto para tu perro." + dogfood.ingredients = "5 por ciento de proteínas de pavo (deshidratadas), (22 %) de patatas (deshidratadas), fécula de patata (deshidratada), celulosa, 5 % de boniatos (deshidratados), pulpa de remolacha deshidratada (desazucarada), grasa de ave, proteínas de ave (deshidratadas), hidrolizado de proteínas, guisantes (deshidratados), fosfato monocálcico, aceite de pescado, achicoria (deshidratada), levadura (deshidratada), psilio, cloruro sódico, apio (deshidratado), zanahorias (deshidratadas)." + dogfood.price = 33.99 + dogfood.pathologies = "ninguna" + dogfood.animal_type = "perro" + dogfood.age = "adulto" + dogfood.size = "grande" + dogfood.weight = 12. + dogfood.url = "https://media.zooplus.com/bilder/2/400/briantos_adult_gf_sterilised_turkey_12kg_1000x1000_2.jpg" + db.session.add(dogfood) + db.session.commit() + + dogfood = Food() + dogfood.name = "Concept for Life Mini Sterilised" + dogfood.brand="Concept for Life" + dogfood.description = "Un mayor contenido de proteínas combinadas con la L-carnitina, así como un ajustado contenido energético y en grasas (-11 % en comparación con Concept for Life Mini Adult) puede influir ayudar a mantener el peso ideal mediante una alimentación adecuada. De este modo, se puede evitar el aumento de peso causado por la alteración del metabolismo tras la castración. Además, las fibras dietéticas seleccionadas de psilio y lignocelulosa proporcionan una sensación de saciedad, lo que puede favorecer una ingesta menor de energía." + dogfood.ingredients = "proteína de ave (31 % parcialmente deshidratada e hidrolizada), carne de pollo fresca (20 %), copos de patata, guisantes, fécula de patata, lignocelulosa (5,4 %), pulpa de remolacha seca, grasa de ave, linaza, pulpa de manzana, aceite de salmón (0,25 %), cloruro de sodio, aceite de girasol (0,15 %), inulina de achicoria (0,2 %), psilio (0,1 %)." + dogfood.price = 5.49 + dogfood.pathologies = "ninguna" + dogfood.animal_type = "perro" + dogfood.age = "adulto" + dogfood.size = "pequeña" + dogfood.weight = 1. + dogfood.url = "https://media.zooplus.com/bilder/7/400/pla_163496_cfl_dog_sterilised_mini_1kg_7.jpg" + db.session.add(dogfood) + db.session.commit() + + + @app.cli.command("insert_data_exoticfood") + def insert_data_exoticfood(): + exoticfood = Food() + exoticfood.name = "Special Edition Harmony" + exoticfood.brand="KaninchenTraum" + exoticfood.description = "bunny KaninchenTraum Special Edition Harmony es el alimento ideal para tu conejo enano. Su mezcla única de 63 plantas y hierbas de praderas no tratadas ofrece una alimentación natural y variada. Los ingredientes cuidadosamente seleccionados, sobre todo las flores de aciano y siempreviva, garantizan una experiencia aromática y llena sabor que le encantará a tu amiguito." + exoticfood.ingredients = "Hierbas de pasto permanente (fleo de los prados, festuca de los prados, festuca roja, hierba de cola de zorra, pasto azul de Kentucky, dactilo, perifollo silvestre, falso diente de león, arveja de campo, milenrama común, alquimila, cerrillo, trébol amarillo, llantén menor, salvia de los prados, briza media, bromo erguido, campanilla silvestre, alcaravea, cártamo silvestre, cerastio común, cardo borriquero, bromo blando, diente de león común, hierba capilar, ulmaria, cerastio de campo, fresa silvestre, arveja silvestre, gallineta blanca, crepis de los prados, galio amarillo, avénula pelosa, lotus común, cola de caballo, galio blanco, geranio blanco de campo, margarita de campo, viuda silvestre, flor de cuclillo, arroyuela, verónica de los campos, hierba triguera, poa de los prados, llantén mediano, pimpinela mayor, alverjilla, nomeolvides silvestre, saxifraga blanca, pimpinela mayor, cincoenrama, primavera común, conejera cabizbaja, pimpinela menor, estelaria, barba de cabra, ortiga mayor, ontineta, trébol rojo, arveja de los setos, búgula de Ginebra, egopodio, margarita), cáscaras de avena, harina de extracción de semilla de girasol, harina de extracción de lino, pulpa de manzana, bagazo de zanahoria, salvado de trigo, harina de extracción de semillas de colza, flor de aciano rojo (2 %), flores de siempreviva (1 %), semillas de hinojo (0,5 %), papaya deshidratada, flores de camomila." + exoticfood.price = 14.69 + exoticfood.pathologies = "ninguna" + exoticfood.animal_type = "exotico" + exoticfood.age = "adulto" + exoticfood.weight = 1.5 + exoticfood.url = "https://media.zooplus.com/bilder/7/400/547507_pla_bunny_kaninchentraum_special_edition_harmony_1_5k_hs_01_7.jpg" + db.session.add(exoticfood) + db.session.commit() + + exoticfood = Food() + exoticfood.name = "Sbeaphar Care+ comida para conejos" + exoticfood.brand="beaphar Care+" + exoticfood.description = "Fórmula todo en uno: la comida beaphar Care+ está constituida por gránulos de composición uniforme. Esto permite evitar las carencias nutricionales que se pueden dar por la alimentación selectiva de tu conejo. Su fórmula equilibrada y sin azúcar ha sido elaborada por veterinarios y científicos expertos. Contiene un 25 % de extruido de fibra cruda y una cantidad elevada de nutrientes y otras sustancias vitales. La dureza de los gránulos favorece la abrasión dental y la higiene bucal." + exoticfood.price = 26.49 + exoticfood.ingredients = "Subproductos vegetales (Yucca Schidigera 0,1 %, Echinacea 0,03 %, extracto de té verde 0,03 %, FOS 0,01 %) , cereales, verduras, minerales, levadura (MOS 0,1 %), semillas, algas (espirulina 0,01 %)." + exoticfood.pathologies = "diabetes" + exoticfood.animal_type = "exotico" + exoticfood.age = "senior" + exoticfood.weight = 5. + exoticfood.url = "https://media.zooplus.com/bilder/0/400/73777_pla_beaphar_care_kaninchen_5kg_hs_1_2_0.jpg" + db.session.add(exoticfood) + db.session.commit() + + exoticfood = Food() + exoticfood.name = "Cuni Junior para conejos" + exoticfood.brand="beaphar Care+" + exoticfood.description = "Alimento variado y rico en fibra como base para el crecimiento óptimo de los conejos y conejos enanos hasta los 8 meses de edad. Nature Cuni Junior contiene una gran cantidad de hierbas, semillas y hierbas aromáticas sabrosas, combinadas con verduras y frutos que suministran altas dosis de vitaminas, minerales y oligoelementos. Sin cereales añadidos." + exoticfood.price = 9.99 + exoticfood.ingredients = "Subproductos vegetales (hierba timotea, 20 % hierbas y hierbas aromáticas), verdura (18,1 % guisantes, 5,4 % zanahorias, 4 % frijol chícharo, 4 % remolacha), frutos, semillas, extracto de proteína vegetal, minerales, aceites y grasas, FOS, caléndula, MOS, algas, yuca." + exoticfood.pathologies = "ninguna" + exoticfood.animal_type = "exotico" + exoticfood.age = "cachorro" + exoticfood.weight = 2.3 + exoticfood.url = "https://d7rh5s3nxmpy4.cloudfront.net/CMP9365/8/18407_347074-D.jpg" + db.session.add(exoticfood) + db.session.commit() + + exoticfood = Food() + exoticfood.name = "JR Farm Grainless Complete para conejos enanos" + exoticfood.brand="JR Farm Grainless" + exoticfood.description = "Mezcla natural y totalmente apropiada para esta especie. Hecha a base de hierbas aromáticas, deliciosas flores y verduras con vitaminas. No contiene cereales ni subproductos de cereales." + exoticfood.price = 8.69 + exoticfood.ingredients = "Fleo, dáctilo, hierbas, perejil, copos de guisantes, dados de zanahoria, habas, chirivía, hojas de menta, hinojo, dados de manzana, semilla de lino, hojas de diente de león, remolacha, hojas de ortiga, flor de manzanilla, comino y vitaminas." + exoticfood.pathologies = "escorbuto" + exoticfood.animal_type = "exotico" + exoticfood.age = "senior" + exoticfood.weight = 2.3 + exoticfood.url = "https://media.zooplus.com/bilder/5/400/28140_pla_jrfarm_grainless_complete_zwergkaninchen_5.jpg" + db.session.add(exoticfood) + db.session.commit() + + @app.cli.command("insert_data_accessories") + def insert_data_accessories(): + accessories= Accessories() + accessories.name = "Collarín de seguridad" + accessories.brand="TIAKI" + accessories.description = "El collar de seguridad TIAKI ofrece a tu perro una seguridad y un confort indispensables en todas sus aventuras. Está fabricado con una cincha ancha y resistente a los desgarros que resulta ideal para el uso diario, el entrenamiento o incluso las salidas de caza. Equipado con una práctica empuñadura , podrá intervenir con rapidez y seguridad en situaciones críticas para controlar a tu perro." + accessories.price = 7.99 + accessories.animal_type = "perro" + accessories.pathologies = "" + accessories.url = "https://media.zooplus.com/bilder/4/400/518156_pla_tiaki_security_collar_s_fg_5107_4.jpg" + db.session.add(accessories) + db.session.commit() + + accessories= Accessories() + accessories.name = "Rascadores de pared" + accessories.brand="Modern Living para gatos" + accessories.description = "Modern Living está diseñado para personas con mascotas que buscan sofisticación y durabilidad. Entendemos el esfuerzo que pones en la decoración de tu entorno y lo consciente que eres al ponerle más estilo y carácter a tu hogar. La armonía es nuestra pasión. Hacemos muebles y accesorios para mascotas con diseños únicos y llamativos que se integran de forma perfecta incluso en el hogar más elegante y merecen un lugar destacado. Eso siempre sin olvidarnos de las necesidades de tus mascotas." + accessories.price = 35.99 + accessories.animal_type = "gato" + accessories.pathologies = "" + accessories.url = "https://media.zooplus.com/bilder/3/400/modern_living_kratzm_bel_f_r_katzen_zur_wandmontage_hs_01_3.jpg" + db.session.add(accessories) + db.session.commit() + + accessories= Accessories() + accessories.name = "Caseta de plástico para perros" + accessories.brand="Ferplast Dogvilla" + accessories.description = "No sólo para tu amigo canino con alergia, la caseta para perros Dogvilla de Ferplast es un verdadero oasis de bienestar: las partículas de polvo y suciedad de superficies lisas se pueden limpiar sin dejar residuo alguno. ¡Los gérmenes no tendrán ninguna posibilidad! La espaciosa casita ahorra mucho espacio de manera única ya que la pared lateral es plegable gracias a unas bisagras. Las ranuras de ventilación del panel posterior pueden ajustarse manualmente mediante una palanquita para garantizar así un óptimo intercambio de aire. La abertura de la entrada está reforzada con aluminio para resistir los dientes de los perros y da a la caseta una estabilidad adicional." + accessories.price = 199.99 + accessories.animal_type = "perro" + accessories.pathologies = "" + accessories.url = "https://www.ferplast.es/cdn/shop/files/1-0180017573_x620.jpg?v=1728917798" + db.session.add(accessories) + db.session.commit() + + accessories= Accessories() + accessories.name = "Campo de zanahorias juguete de inteligencia" + accessories.brand="TIAKI" + accessories.description = "El juguete de inteligencia TIAKI para pequeños animales es un juguete maravilloso que estimula el comportamiento natural de búsqueda de comida y juego de conejos, hámsters y cobayas. Este juguete interactivo tiene una serie de zanahorias de madera que ofrecen a tus mascotas un divertido reto , por ejemplo, arrancarlas y roerlas. La zona de juegos no solo es entretenida, sino también segura para que tus animales la roan, ya que está hecha de papel, madera de pino y hojas de maíz." + accessories.price = 7.99 + accessories.animal_type = "exotico" + accessories.pathologies = "" + accessories.url = "https://media.zooplus.com/bilder/7/400/451140_pla_tiaki_intelligence_toy_carrot_patch_fg_9772_7.jpg" + db.session.add(accessories) + db.session.commit() + + + accessories= Accessories() + accessories.name = "Bayer Vetriderm loción tópica antialérgica" + accessories.brand="Bayer Vetriderm" + accessories.description = "Gracias a la loción tópica Vetriderm, de Bayer, es posible reducir de manera efectiva la causa de las reacciones alérgicas a los animales domésticos. Simplemente, aplica la solución sobre un paño y, con ayuda de este, extiende el producto sobre el pelo de tu perro o gato; primero en la dirección del crecimiento del pelo y después en el sentido opuesto. De este modo, eliminarás las partículas que quedan en el pelaje como los epitelios o los restos de saliva o de orina, que pueden producir reacciones alérgicas." + accessories.price = 21.99 + accessories.animal_type = "exotico" + accessories.pathologies = "" + accessories.url = "https://media.zooplus.com/bilder/7/400/73160_pla_vetriderm_350ml_hs_01_7.jpg" + db.session.add(accessories) + db.session.commit() + + @app.cli.command("insert_data_pet") + def insert_data_pet(): + pet= Pet() + pet.name = "" + pet.size="" + pet.breed= "asd" + pet.age= "cachorro" + pet.animal_type = "gato" + pet.pathologies="diabetes" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + + pet= Pet() + pet.name = "recoleto" + pet.size="medio" + pet.breed= "asd" + pet.age="senior" + pet.animal_type = "perro" + pet.pathologies="renal" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + + pet= Pet() + pet.name = "peluso" + pet.size="grande" + pet.breed= "asd" + pet.age="cachorro" + pet.animal_type = "perro" + pet.pathologies="diabetes" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + + pet= Pet() + pet.name = "asd" + pet.size="oxbow" + pet.breed= "asd" + pet.age= "2" + pet.animal_type = "cobaya" + pet.pathologies="escorbuto" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + + pet= Pet() + pet.name = "asd" + pet.size="oxbow" + pet.breed= "asd" + pet.age= "60" + pet.animal_type = "loro" + pet.pathologies="" + pet.user_id = 1 + db.session.add(pet) + db.session.commit() + + + + + + + + + + + + + + + + + + + diff --git a/src/api/models.py b/src/api/models.py index dccd8421ee..a5fe8bcc64 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,12 +1,21 @@ + from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import ForeignKey +from datetime import datetime + + + + db = SQLAlchemy() + class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + name= db.Column(db.String(80), nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password = db.Column(db.String(80), unique=False, nullable=False) - is_active = db.Column(db.Boolean(), unique=False, nullable=False) def __repr__(self): return f'' @@ -15,5 +24,187 @@ def serialize(self): return { "id": self.id, "email": self.email, + "name": self.name # do not serialize the password, its a security breach - } \ No newline at end of file + } + +class PasswordReset(db.Model): + __tablename__ = 'password_reset' + + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), nullable=False) + token = db.Column(db.String(255), nullable=False, unique=True) + expires_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + + def serialize(self): + return { + "id": self.id, + "email": self.email, + "token": self.token, + "expires_at": self.expires_at.strftime("%Y-%m-%d %H:%M:%S") + } + + def __repr__(self): + return f'' + + +class Food(db.Model): + __tablename__ = 'foods' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + brand = db.Column(db.String(100), nullable=False) + description = db.Column(db.Text, nullable=True) + ingredients = db.Column(db.Text, nullable=False) + weight = db.Column(db.Float, nullable=False) + price = db.Column(db.Float, nullable=False) + age = db.Column(db.String, nullable=False) + animal_type = db.Column(db.String(50), nullable=False) + size = db.Column(db.String(30), nullable=True) + pathologies = db.Column(db.Text, nullable=True) + url = db.Column(db.String(255), nullable=True) + + def __repr__(self): + return f'' + + def serialize(self): + return { + "id": self.id, + "name": self.name, + "brand": self.brand, + "age": self.age, + "description": self.description, + "ingredients": self.ingredients, + "weight": self.weight, #peso en kilos + "price": self.price, #precio en euros + "animal_type": self.animal_type, + "size": self.size, + "pathologies": self.pathologies, + "url": self.url + } + +class Accessories(db.Model): + __tablename__ = 'accessories' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + brand = db.Column(db.String(100), nullable=False) + description = db.Column(db.Text, nullable=True) + animal_type = db.Column(db.String(50), nullable=False) + pathologies = db.Column(db.Text, nullable=True) + price = db.Column(db.Float, nullable=False) + url = db.Column(db.String(255), nullable=True) + + def __repr__(self): + return f'' + def serialize(self): + return { + "id": self.id, + "name": self.name, + "brand": self.brand, + "description": self.description, + "animal_type": self.animal_type, + "price": self.price, #precio en euros + "pathologies": self.pathologies, + "url": self.url + } + +# class ExoticFood(db.Model): +# __tablename__ = '' + +# id = db.Column(db.Integer, primary_key=True) +# name = db.Column(db.String(100), nullable=False) +# brand = db.Column(db.String(100), nullable=False) +# description = db.Column(db.Text, nullable=True) +# ingredients = db.Column(db.Text, nullable=False) +# # is_hypoallergenic = db.Column(db.Boolean, default=False) +# # is_gluten_free = db.Column(db.Boolean, default=False) +# # protein_source = db.Column(db.String(100), nullable=False) +# # fat_content = db.Column(db.Float, nullable=True) +# # omega3_content = db.Column(db.Float, nullable=True) +# # taurine_content = db.Column(db.Float, nullable=True) +# # suitable_for_senior = db.Column(db.Boolean, default=False) +# # suitable_for_sterilized = db.Column(db.Boolean, default=False) +# # croquette_shape = db.Column(db.String(50), nullable=True) +# # weight_kg = db.Column(db.Float, nullable=False) +# # price_eur = db.Column(db.Float, nullable=False) +# url = db.Column(db.String(255), nullable=True) + +# def __repr__(self): +# return f'' +# def serialize(self): +# return { +# "id": self.id, +# "name": self.name, +# "brand": self.brand, +# "description": self.description, +# "ingredients": self.ingredients, +# "weight_kg": self.weight_kg, +# "price_eur": self.price_eur, +# "url": self.url +# } + +class Pet(db.Model): + __tablename__ = 'pet' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + size = db.Column(db.String(100), nullable=True) # cambiar raza por tamaño + breed = db.Column(db.String(100), nullable=True) # cambiar raza por tamaño + age = db.Column(db.String, nullable=False) + animal_type= db.Column(db.String, nullable=False) + pathologies = db.Column(db.Text, nullable=True) # patología contemple peso + user_id = db.Column(db.ForeignKey("user.id"), nullable=False) + + # is_hypoallergenic = db.Column(db.Boolean, default=False) + # is_gluten_free = db.Column(db.Boolean, default=False) + # protein_source = db.Column(db.String(100), nullable=False) + # fat_content = db.Column(db.Float, nullable=True) + # omega3_content = db.Column(db.Float, nullable=True) + # taurine_content = db.Column(db.Float, nullable=True) + # suitable_for_senior = db.Column(db.Boolean, default=False) + # suitable_for_sterilized = db.Column(db.Boolean, default=False) + # croquette_shape = db.Column(db.String(50), nullable=True) + # weight_kg = db.Column(db.Float, nullable=False) + # price_eur = db.Column(db.Float, nullable=False) + url = db.Column(db.String(255), nullable=True) + + def __repr__(self): + return f'' + def serialize(self): + return { + "id": self.id, + "name": self.name, + "size": self.size, + "age": self.age, + "breed": self.breed, + "animal_type": self.animal_type, + "pathologies": self.pathologies, + "user_id": self.user_id, + "url": self.url + } + + +#tabla para gestionar carrito/pedidos + +class Order(db.Model): + __tablename__ = 'order' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.ForeignKey("user.id"), nullable=False) + ordered_food = db.Column(db.String, nullable=True) + ordered_accessories = db.Column(db.String, nullable=True) + status = db.Column(db.String, nullable=False) + + def __repr__(self): + return f'' + + def serialize(self): + return { + "id": self.id, + "user_id": self.user_id, + "ordered_food": self.ordered_food, + "ordered_accessories": self.ordered_accessories, + "status": self.status + } + diff --git a/src/api/routes.py b/src/api/routes.py index 029589a3a1..3573d9cf74 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -1,22 +1,632 @@ """ 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, current_app +from api.models import db, User, Food, Pet, Accessories, Order from api.utils import generate_sitemap, APIException from flask_cors import CORS +from sqlalchemy import select, and_, or_ +import json +from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity +from flask_bcrypt import Bcrypt +import stripe +from flask_mail import Message +import random, string +from unidecode import unidecode + api = Blueprint('api', __name__) +bcrypt = Bcrypt() +# Configura tu clave secreta de Stripe +stripe.api_key = "sk_test_51R3GSLPowRpDmMbeD74L6GGIHz1FSWCfbrchq2LGRqIIJP1E0Rr11pu4nLqjSKqkO4ZtSrH23LcSkcCusMKELFT700pP7sFDoQ" # Reemplaza con tu clave secreta de Stripe + # Allow CORS requests to this API CORS(api) +@api.route('/create-payment', methods=['POST']) +def create_payment(): + response_body = {} + try: + data = request.json + amount = int(round(float(data['amount']) * 100)) # Convertir a céntimos y redondear + intent = stripe.PaymentIntent.create( + amount=amount, # Usar el amount convertido + currency=data['currency'], + automatic_payment_methods={'enabled': True} + ) + response_body['client_secret'] = intent['client_secret'] + return jsonify({'clientSecret': intent['client_secret']}), 200 + except Exception as e: + return jsonify({'success': False, 'error': str(e)}), 403 + + +@api.route("/forgotpassword", methods=["POST"]) +def forgotpassword(): + recover_email = request.json.get('email') + + user = User.query.filter_by(email=recover_email).first() + if not user: + return jsonify({"msg": "El correo ingresado no existe en nuestros registros"}), 400 + + # Genera una nueva contraseña temporal (aleatoria) + recover_password = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(12)) + + # Guardar la nueva contraseña cifrada (muy importante) + bcrypt = Bcrypt() + hashed_password = bcrypt.generate_password_hash(recover_password).decode('utf-8') + user.password = hashed_password + db.session.commit() + + # Enviar la contraseña temporal al correo del usuario + from flask_mail import Message + msg = Message( + subject="Recuperación de contraseña", + sender=current_app.config["MAIL_USERNAME"], + recipients=[recover_email], + body=f"Tu nueva contraseña temporal es: {recover_password}. Por favor inicia sesión y cámbiala inmediatamente." + ) + current_app.mail.send(msg) + + return jsonify({"msg": "Se ha enviado una nueva contraseña a tu correo electrónico"}), 200 + +@api.route('/') +def sitemap(): + return generate_sitemap(api) + +# Obtener todos los alimentos +@api.route('/foods', methods=['GET']) +def get_foods(): + foods = Food.query.all() + if not foods: + return "food not found", 404 + else: + return jsonify([food.serialize() for food in foods]), 200 + + +# Obtener un alimento por ID +@api.route('/foods/', methods=['GET']) +def get_food(food_id): + food = Food.query.get(food_id) + if not food: + return jsonify({"error": "Food not found"}), 404 + return jsonify(food.serialize()), 200 + +#obtener todos los usuarios +@api.route('/users', methods=['GET']) +def get_users(): + users = User.query.all() + if not users: + return "not users found", 404 + return jsonify([user.serialize() for user in users]), 200 + + +# Obtener un usuario por ID +@api.route('/users/', methods=['GET']) +def get_user(user_id): + user = User.query.get(user_id) + if not user: + return jsonify({"error": "user not found"}), 404 + return jsonify(user.serialize()), 200 + + +@api.route('/pets', methods=['GET']) +def get_pets(): + pets = Pet.query.all() + if not pets: + return "no pets found", 404 + return jsonify([pet.serialize() for pet in pets]), 200 + + +# Obtener una mascota por ID +@api.route('/pets/', methods=['GET']) +def get_pet(pet_id): + pet = Pet.query.get(pet_id) + if not pet: + return jsonify({"error": "pet not found"}), 404 + return jsonify(pet.serialize()), 200 + + + +#obtener sugerencias de comida según mascota +@api.route('/foods/suggestions/', methods=['GET']) +@jwt_required() +def get_pet_suggestions(pet_id): + pet = Pet.query.get(pet_id).serialize() + # Problema: Un animal puede tener varias patologias en su campo, habría que coger este campo y tratarlo, + # separar las patologias en una lista y hacer la query para cada patologia. + # Solucion simple: limitar a 1 patologia cada animal por ahora + #if para pet# anymal_type == perro, animal size #si no no hace falta size + if pet["animal_type"] == "perro": + food_suggestions = db.session.execute(select(Food).where(and_(Food.animal_type==pet["animal_type"]), + Food.size==pet["size"], + Food.age==pet["age"], + Food.pathologies==pet["pathologies"])).all() + else: + food_suggestions = db.session.execute(select(Food).where(and_(Food.animal_type==pet["animal_type"]), + Food.age==pet["age"], + Food.pathologies==pet["pathologies"])).all() + if not food_suggestions : + return "no suggestions found", 404 + return [food[0].serialize() for food in food_suggestions], 200 + + + + + + + +# #obtener sugerencias de comida según mascota +# @api.route('/foods/suggestions/', methods=['GET']) +# def get_pet_suggestions(pet_id): +# pet = Pet.query.get(pet_id) +# if not pet: +# return jsonify({"error": "pet not found"}), 404 + +# pet_data = pet.serialize() +# filters = [Food.animal_type == pet_data["animal_type"], Food.age == pet_data["age"]] + +# if pet_data["animal_type"] == "perro": +# filters.append(Food.size == pet_data["size"]) + +# # Si la mascota tiene patologías, agregarlas al filtro +# if pet_data["pathologies"]: +# pathologies_list = pet_data["pathologies"].split(",") +# filters.append(Food.pathologies.in_(pathologies_list)) +# else: +# filters.append(or_(Food.pathologies != None, Food.pathologies == None)) +# food_suggestions = db.session.execute(select(Food).where(and_(*filters))).all() + +# if not food_suggestions: +# return jsonify({"error": "no suggestions found"}), 404 + +# return jsonify([food[0].serialize() for food in food_suggestions]), 200 + + +#obtener todos los alimentos según tipo de animal +@api.route('/foods/cat', methods=['GET']) +def get_all_cat_food(): + food_cat = db.session.query(Food).filter(Food.animal_type.ilike("%gato%")).all() + + print("Datos obtenidos:", food_cat) + + if not food_cat: + return jsonify({"error": "No cat food found"}), 404 + + print("Datos obtenidos:", food_cat) + if not food_cat: + return jsonify({"error": "No cat food found"}), 404 + + return jsonify([food.serialize() for food in food_cat]), 200 + + + +@api.route('/foods/dog', methods=['GET']) +def get_all_dog_food(): + food_dog = db.session.query(Food).filter(Food.animal_type.ilike("%perro%")).all() + + + print("Datos obtenidos:", food_dog) + + if not food_dog: + return jsonify({"error": "No dog food found"}), 404 + + + print("Datos obtenidos:", food_dog) + if not food_dog: + return jsonify({"error": "No dog food found"}), 404 + + return jsonify([food.serialize() for food in food_dog]), 200 + +@api.route('/foods/exotic', methods=['GET']) +def get_all_exotic_food(): + food_exotic = db.session.query(Food).filter(Food.animal_type.ilike("%exotico%")).all() + + + print("Datos obtenidos:", food_exotic) + + if not food_exotic: + return jsonify({"error": "No exotic food found"}), 404 + + + print("Datos obtenidos:", food_exotic) + if not food_exotic: + return jsonify({"error": "No exotic food found"}), 404 + + return jsonify([food.serialize() for food in food_exotic]), 200 + +# Obtener todos los accesorios +@api.route('/accessories', methods=['GET']) +def get_accessories(): + accessories = Accessories.query.all() + if not accessories: + return "no accessories found", 404 + return jsonify([accessory.serialize() for accessory in accessories]), 200 + + +# Obtener una accesorio por ID +@api.route('/accessories/', methods=['GET']) +def get_accessory(accessories_id): + accessories = Accessories.query.get(accessories_id) + if not accessories: + return jsonify({"error": "accessories not found"}), 404 + return jsonify(accessories.serialize()) + +# Crear un nuevo alimento +@api.route('/foods', methods=['POST']) +def create_food(): + data = request.get_json() + new_food = Food( + name=data["name"], + brand=data["brand"], + description=data.get("description"), + ingredients=data["ingredients"], + animal_type=data["animal_type"], + pathologies=data["pathologies"], # Recibe lista JSON + size=data["size"], + weight=data["weight"], + price=data["price"], + url=data.get("url") + ) + db.session.add(new_food) + db.session.commit() + return jsonify(new_food.serialize()), 201 + + +#registrar nuevo usuario(signup) + +@api.route('/signup', methods=['POST']) +def create_user(): + data = request.get_json() + hashed_password = bcrypt.generate_password_hash(data["password"]).decode('utf-8') + + new_user = User( + name=data["name"], + email=data["email"], + password=hashed_password +) + if User.query.filter_by(email=data["email"]).first(): + return jsonify({"msg": "El usuario ya existe"}), 400 + + db.session.add(new_user) + db.session.commit() + return jsonify(new_user.serialize()), 201 + +# iniciar sesion(login) +# @api.route('/login', methods=['POST']) +# def logging_user(): +# data = request.get_json() +# user = User.query.filter_by(email=data["email"]).first() -@api.route('/hello', methods=['POST', 'GET']) -def handle_hello(): - 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" +# if User.query.filter_by(email=data["email"]).first() and User.query.filter_by(password=data["password"]).first(): +# access_token=create_access_token(identity=user.email) +# return jsonify(access_token=access_token), 200 + +# return jsonify ({"nsg":"credenciales invalidas"}), 400 + +@api.route('/login', methods=['POST']) +def login_user(): + + body = request.get_json() + + if not body or "email" not in body or "password" not in body: + return jsonify({"msg": "credenciales no validas"}), 400 + + email = body["email"] + password = body["password"] + user = User.query.filter_by(email=email).first() + print(user) + #if bcrypt.check_password_hash(user.password, body["password"]): + if user != None and bcrypt.check_password_hash(user.password, body["password"]): + token=create_access_token(identity=user.email) + user_data = { + "id": user.id, + "email": user.email, + "name": user.name + } + + return jsonify({"msg": "inicio de sesion exitoso", "token": token, "user": user_data}), 200 + return jsonify({"msg": "credenciales no validas"}), 400 + +#Vista privada del usuario CON el token +@api.route('/user', methods=['GET']) +@jwt_required() +def get_user_info(): + + current_user_email = get_jwt_identity() + + user = User().query.filter_by(email=current_user_email).first() + + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + + user_data = { + "id": user.id, + "email": user.email, + "name": user.name } - return jsonify(response_body), 200 + return jsonify(user_data), 200 + + +#Vista privada del usuario a sus mascotas +@api.route('/user_pets', methods=['GET']) +@jwt_required() +def get_user_pets_info(): + + current_user_email = get_jwt_identity() + + user = User().query.filter_by(email=current_user_email).first() + + pets = Pet().query.filter_by(user_id=user.id).all() + + if not pets: + return jsonify({"msg": "mascota no econtrada"}), 400 + + return jsonify([pet.serialize() for pet in pets]), 200 + + + +#crear un nuevo accesorio +@api.route('/accessories', methods=['POST']) +def create_accessory(): + data = request.get_json() + + new_accessory = Accessories( + name=data["name"], + brand=data["brand"], + description=data["description"], + animal_type=data["animal_type"], + pathologies=data["pathologies"], + price=data["price"], + url=data["url"] +) + db.session.add(new_accessory) + db.session.commit() + return jsonify(new_accessory.serialize()), 201 + +#crear una nueva mascota +@api.route('/pets', methods=['POST']) +@jwt_required() +def create_pet(): + data = request.get_json() + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + + if not user: + return jsonify({"msg": "usuario no encontrado"}), 400 + + new_pet = Pet( + name=data["name"], + size= data["size"], + breed= data["breed"], + age=data["age"], + animal_type=data["animal_type"], + pathologies= data["pathologies"], + url=data.get("url"), # Asegúrate de que se está obteniendo correctamente + user_id=user.id + ) + db.session.add(new_pet) + db.session.commit() + return jsonify(new_pet.serialize()), 201 + +@api.route('/foods/', methods=['PUT']) +def update_food(food_id): + food = Food.query.get(food_id) + if not food: + return jsonify({"message": "Food not found"}), 404 + + data = request.get_json() + + food.name = data.get("name", food.name) + food.brand = data.get("brand", food.brand) + food.description = data.get("description", food.description) + food.ingredients = data.get("ingredients", food.ingredients) + food.weight = data.get("weight", food.weight) + food.price = data.get("price", food.price) + food.animal_type = data.get("animal_type", food.animal_type) + food.size = data.get("size", food.size) + food.pathologies = data.get("pathologies", food.pathologies) + food.url = data.get("url", food.url) + + db.session.commit() + + return jsonify({ + "id": food.id, + "name": food.name, + "brand": food.brand, + "description": food.description, + "ingredients": food.ingredients, + "animal_type": food.animal_type, + "price": food.price, + "weight": food.weight, + "size" : food.size, + "pathologies": food.pathologies, + "url": food.url + }) + + + +@api.route('/users', methods=['PUT']) +@jwt_required() +def update_user(): + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + if not user: + return jsonify({"message": "user not found"}), 404 + + data = request.get_json() + + user.name = data.get("name", user.name) + user.password = bcrypt.generate_password_hash(data["password"]).decode('utf-8') + # user.password = data.get("password" , user.password) + + db.session.commit() + + return jsonify({ + "id": user.id, + "name": user.name, + "password":user.password + }) + + +@api.route('/pet/', methods=['PUT']) +@jwt_required() +def new_pet(pet_id): + data = request.get_json() + current_user_email = get_jwt_identity() + user = User().query.filter_by(email=current_user_email).first() + + if not user: + + return jsonify({"msg": "usuario no encontrado"}), 400 + + data = request.get_json() + + pet=Pet().query.filter_by(id=pet_id).first() + + pet.name = data.get("name", pet.name) + pet.size = data.get("size", pet.size) + pet.age = data.get("age", pet.age) + pet.animal_type = data.get("animal_type", pet.animal_type) + pet.pathologies = data.get("pathologies", pet.pathologies) + pet.url = data.get("url", pet.url) + + db.session.commit() + return jsonify(new_pet.serialize()), 201 + + +@api.route('/accessories/', methods=['PUT']) +def update_accessory(accessories_id): + accessories = Accessories.query.get(accessories_id) + if not accessories: + return jsonify({"message": "Accessory not found"}), 404 + + data = request.get_json() + + accessories.name = data.get("name", accessories.name) + accessories.brand = data.get("brand", accessories.brand) + accessories.description = data.get("description", accessories.description) + accessories.price = data.get("price", accessories.price) + accessories.animal_type = data.get("animal_type", accessories.animal_type) + accessories.pathologies = data.get("pathologies", accessories.pathologies) + accessories.url = data.get("url", accessories.url) + + db.session.commit() + + return jsonify({ + "id": accessories.id, + "name": accessories.name, + "brand": accessories.brand, + "description": accessories.description, + "animal_type": accessories.animal_type, + "price": accessories.price, + "pathologies": accessories.pathologies, + "url": accessories.url + }) + + + +@api.route('/user', methods=['DELETE']) +@jwt_required() +def delete_user(): + # Buscar el usuario por su ID + current_user_email = get_jwt_identity() + user = User.query.filter_by(email = current_user_email).first() + + # Eliminar el usuario de la base de datos + db.session.delete(user) + db.session.commit() + + # Devolver una respuesta JSON indicando que el usuario fue eliminado + return jsonify({ + 'message': f'User {user.name} with id {user.id} has been deleted successfully.' + }), 200 + + +@api.route('/pet/', methods=['DELETE']) +@jwt_required() +def delete_pet(pet_id): + + pet = Pet.query.get(pet_id) + + # Eliminar la mascota de la base de datos + db.session.delete(pet) + db.session.commit() + + # Devolver una respuesta JSON indicando que la mascota fue eliminada + return jsonify({ + 'message': f'Pet {pet.name} with id {pet.id} has been deleted successfully.' + }), 200 + +@api.route('/search', methods=['GET']) +def search_product(): + data = request.get_json() + # cada query (Food y Accessories) mira en cada uno de los campos de interes la busqueda introducida en el formulario + foods = Food.query.filter(or_(Food.name.like(f'%{data["search"]}%'),Food.animal_type.like(f'%{data["search"]}%'), + Food.pathologies.like(f'%{data["search"]}%'),Food.brand.like(f'%{data["search"]}%'))).all() + + accessories = Accessories.query.filter(or_(Accessories.name.like(f'%{data["search"]}%'), + Accessories.animal_type.like(f'%{data["search"]}%'), + Accessories.pathologies.like(f'%{data["search"]}%'), + Accessories.brand.like(f'%{data["search"]}%'))).all() + # Queries vacias devolvemos not found + if not foods and not accessories: + return jsonify({"message":"not results found"}), 404 + + # Si la query no esta vacia preparamos una lista con un diccionario y la serializacion de cada item + # para poderla jsonificar en el return + if len(foods) > 0: + dict_foods = [{ "foods":[food.serialize() for food in foods]}] + else: + dict_foods = [] + if len(accessories) > 0: + dict_accessories = [{"accessories":[accessory.serialize() for accessory in accessories]}] + else: + dict_accessories = [] + # concatenamos ambas listas obtenidas en las queries + return jsonify(dict_foods + dict_accessories), 200 + +# endpoint para carrito/pedido + +@api.route('/order/', methods=['POST']) +@jwt_required() +def order(user_id): + data = request.get_json() + + # queremos que frontend nos de una lista de Id's e.g. [0,1,4] para food y accessory por separado + selected_food = data["selected_food"] + selected_accessory = data["selected_accessory"] + + if len(selected_food) == 0 and len(selected_accessory) == 0: + return jsonify({"message":"not food and accessories selected"}), 404 + + # pasamos de lista de id a string separado por comas + string_selected_food = "" + string_selected_accessory = "" + # recorremos la lista y evitamos ponerle coma al lúltimo elemento + #idx para recorrer el array + # range (función de python que hace rango de un número a otro si sólo tiene un número va del 0 a ese número) + for idx in range(len(selected_food)): + if len(selected_food)-1 == idx: + string_selected_food = string_selected_food + str(selected_food[idx]) + else: + string_selected_food = string_selected_food + str(selected_food[idx])+"," + + for idx in range(len(selected_accessory)): + if len(selected_accessory)-1 == idx: + string_selected_accessory= string_selected_accessory + str(selected_accessory[idx]) + else: + string_selected_accessory= string_selected_accessory + str(selected_accessory[idx])+"," + + new_order= Order( + user_id= user_id, + ordered_food= string_selected_food, + ordered_accessories= string_selected_accessory, + status= data["status"] + ) + + db.session.add(new_order) + db.session.commit() + return jsonify(new_order.serialize()), 201 \ No newline at end of file diff --git a/src/app.py b/src/app.py index 0ea8351d5f..fc6b818cb4 100644 --- a/src/app.py +++ b/src/app.py @@ -4,22 +4,24 @@ import os from flask import Flask, request, jsonify, url_for, send_from_directory from flask_migrate import Migrate -from flask_swagger import swagger from api.utils import APIException, generate_sitemap from api.models import db from api.routes import api from api.admin import setup_admin from api.commands import setup_commands - -# from models import Person +from flask_jwt_extended import JWTManager +from flask_bcrypt import Bcrypt +from datetime import timedelta +from flask_mail import Mail ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production" static_file_dir = os.path.join(os.path.dirname( os.path.realpath(__file__)), '../public/') + app = Flask(__name__) app.url_map.strict_slashes = False -# database condiguration +# configuración base de datos db_url = os.getenv("DATABASE_URL") if db_url is not None: app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace( @@ -31,42 +33,58 @@ MIGRATE = Migrate(app, db, compare_type=True) db.init_app(app) +# JWT y Bcrypt +app.config["JWT_SECRET_KEY"] = "cualquiercosa" +app.config["JWT_ACCESS_TOKEN_EXPIRE"] = timedelta(hours=1) +jwt = JWTManager(app) +bcrypt = Bcrypt(app) + +# Configuración correcta y real de Flask-Mail 👇 (ajusta tus credenciales aquí) +mail_settings = { + "MAIL_SERVER": 'smtp.gmail.com', + "MAIL_PORT": 465, + "MAIL_USE_TLS": False, + "MAIL_USE_SSL": True, + "MAIL_USERNAME": 'Puppereatsapp@gmail.com', # el nuevo correo Gmail creado + "MAIL_PASSWORD": 'kxrv widw xqhr frgi' # la contraseña que generaste +} + +app.config.update(mail_settings) +mail = Mail(app) +app.mail = mail + + # add the admin setup_admin(app) -# add the admin +# add custom commands setup_commands(app) -# Add all endpoints form the API with a "api" prefix +# Registra todas tus rutas API app.register_blueprint(api, url_prefix='/api') -# Handle/serialize errors like a JSON object - - +# Manejo de errores @app.errorhandler(APIException) def handle_invalid_usage(error): return jsonify(error.to_dict()), error.status_code -# generate sitemap with all your endpoints - - +# Sitemap @app.route('/') def sitemap(): if ENV == "development": return generate_sitemap(app) return send_from_directory(static_file_dir, 'index.html') -# any other endpoint will try to serve it like a static file +# Cualquier otra ruta intenta servir un archivo estático @app.route('/', methods=['GET']) def serve_any_other_file(path): if not os.path.isfile(os.path.join(static_file_dir, path)): path = 'index.html' response = send_from_directory(static_file_dir, path) - response.cache_control.max_age = 0 # avoid cache memory + response.cache_control.max_age = 0 return response - -# this only runs if `$ python src/main.py` is executed +# Arranca el servidor Flask if __name__ == '__main__': PORT = int(os.environ.get('PORT', 3001)) app.run(host='0.0.0.0', port=PORT, debug=True) diff --git a/src/front/img/Icono puppereats.png b/src/front/img/Icono puppereats.png new file mode 100644 index 0000000000..d8b02f0ced Binary files /dev/null and b/src/front/img/Icono puppereats.png differ diff --git a/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg b/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg new file mode 100644 index 0000000000..7303dcf036 Binary files /dev/null and b/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1) (1).jpg differ diff --git a/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1).jpg b/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1).jpg new file mode 100644 index 0000000000..dcdfd10824 Binary files /dev/null and b/src/front/img/perros-gatos-asomandose-sobre-banner-web-aislado-fondo-blanco-al-generado_866663-5304 (1).jpg differ diff --git a/src/front/js/component/CheckoutForm.js b/src/front/js/component/CheckoutForm.js new file mode 100644 index 0000000000..36a9357d4e --- /dev/null +++ b/src/front/js/component/CheckoutForm.js @@ -0,0 +1,56 @@ +import { CardElement, useStripe, useElements, AddressElement, CardNumberElement, PaymentElement } from "@stripe/react-stripe-js" +import { useEffect, useState } from "react" +import React from "react" +import { useNavigate } from "react-router-dom" + + export const CheckoutForm = () => { + const stripe = useStripe() + const elements = useElements() + console.log(elements); + const navigate = useNavigate(); + + const [loading, setLoading] = useState(false) + const [paymentStatus, setPaymentStatus] = useState(null); + + + + const handleSubmit = async (event) => { + event.preventDefault(); + + const result = await stripe.confirmPayment({ + elements, + confirmParams: { + return_url: `${process.env.BACKEND_URL}/` + }, + redirect: 'if_required' + }); + + if (result.error) { + // Si hay un error, establece el estado con el mensaje de error + setPaymentStatus(result.error.message); + console.error(result.error.message); + } else { + // Aquí puedes manejar el resultado del pago + // Puedes verificar el estado de la confirmación de pago + if (result.paymentIntent && result.paymentIntent.status === 'succeeded') { + setPaymentStatus("Pago exitoso!"); + // alert("Pago realizado correctamente. Recibirá un correo de confirmación") + navigate ("/confirmacion-pedido"); + console.log("pago exitoso"); + + // Aquí puedes realizar otras acciones, como actualizar el estado de la aplicación + // o mostrar un modal de agradecimiento sin redirigir. + } else { + setPaymentStatus("El pago no se pudo completar."); + } + } + }; + + return ( +
+ + + + ); + } + diff --git a/src/front/js/component/card.js b/src/front/js/component/card.js new file mode 100644 index 0000000000..1a3f66dbc2 --- /dev/null +++ b/src/front/js/component/card.js @@ -0,0 +1,93 @@ +import React, { Component } from "react"; +import { useContext,useState} from "react"; +import { Context } from "../store/appContext"; +import { Link, useNavigate } from "react-router-dom"; + + + + +export const Card = ({name, id, description, price, url}) => { + +const navigate = useNavigate(); +const truncatedDescription = +description.length > 40 ? description.slice(0, 40) + "..." : description; +const truncatedName = name.length > 30 ? name.slice(0, 30) + "..." : name; + +const { store, actions } = useContext(Context); + +const handleAddToCart = () => { + + + if (store.user) { + actions.addToCart({ name, id, price, url }); + } else { + navigate("/loginSignup"); // Redirige al login si no está autenticado + } +}; + +return ( + +
+ + ... +
+
{truncatedName}
+ +
+

+ {truncatedDescription}

+
+
+ + + +
+
{price}€
+ + + + + +
+
+ +); +} \ No newline at end of file diff --git a/src/front/js/component/cardAccessories.js b/src/front/js/component/cardAccessories.js new file mode 100644 index 0000000000..1a3f66dbc2 --- /dev/null +++ b/src/front/js/component/cardAccessories.js @@ -0,0 +1,93 @@ +import React, { Component } from "react"; +import { useContext,useState} from "react"; +import { Context } from "../store/appContext"; +import { Link, useNavigate } from "react-router-dom"; + + + + +export const Card = ({name, id, description, price, url}) => { + +const navigate = useNavigate(); +const truncatedDescription = +description.length > 40 ? description.slice(0, 40) + "..." : description; +const truncatedName = name.length > 30 ? name.slice(0, 30) + "..." : name; + +const { store, actions } = useContext(Context); + +const handleAddToCart = () => { + + + if (store.user) { + actions.addToCart({ name, id, price, url }); + } else { + navigate("/loginSignup"); // Redirige al login si no está autenticado + } +}; + +return ( + +
+ + ... +
+
{truncatedName}
+ +
+

+ {truncatedDescription}

+
+
+ + + +
+
{price}€
+ + + + + +
+
+ +); +} \ No newline at end of file diff --git a/src/front/js/component/edicionPerfil.js b/src/front/js/component/edicionPerfil.js new file mode 100644 index 0000000000..3286bf0ee3 --- /dev/null +++ b/src/front/js/component/edicionPerfil.js @@ -0,0 +1,142 @@ +import React, { useState, useContext, useEffect } from "react"; +import { Modal, Button, Form, Alert } from "react-bootstrap"; +import { Context } from "../store/appContext"; +import { Eye, EyeOff } from "lucide-react"; + +export const EdicionPerfil = ({ isOpen, onClose }) => { + const { store, actions } = useContext(Context); + const user = store.user || {}; + + const [formData, setFormData] = useState({ + name: "", + email: "", + password: "", + confirmPassword: "" + }); + + const [error, setError] = useState(""); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + + // 🔹 Sincroniza el estado del formulario con los datos del usuario cuando se abra el modal + useEffect(() => { + setFormData({ + name: user.name || "", + email: user.email || "", + password: "", // No mostrar la contraseña actual por seguridad + }); + }, [user, isOpen]); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = async () => { + if (!formData.name || !formData.email) { + setError("El nombre y el correo son obligatorios"); + return; + } + + if (formData.password && formData.password !== formData.confirmPassword) { + setError("Las contraseñas no coinciden"); + return; + } + + setError(""); + + try { + await actions.updateUser({ + name: formData.name, + email: formData.email, + password: formData.password || undefined // Solo enviar la contraseña si se ingresó + }); + await actions.getUser(); // 🔹 Vuelve a traer los datos actualizados + onClose(); + } catch (error) { + setError("Hubo un error al actualizar el perfil"); + } + }; + + const handleDelete = () => { + setShowDeleteConfirm(true); + }; + + const confirmDelete = async () => { + try { + await actions.deleteUser(); + setShowDeleteConfirm(false); + } catch (error) { + setError("Hubo un error al eliminar la cuenta"); + } + }; + + return ( + <> + {/* Modal de Edición */} + + + Editar Perfil + + + {error && {error}} +
+ + Nombre + + + + Correo + + + + Nueva Contraseña (opcional) + + + + Confirmar Nueva Contraseña + + +
+
+ + + + + +
+ +{/* Modal de Confirmación de Eliminación */} + setShowDeleteConfirm(false)}> + + Confirmar Eliminación + + +

¿Estás seguro de que quieres eliminar tu cuenta? Esta acción no se puede deshacer.

+
+ + + + +
+ + ); +}; diff --git a/src/front/js/component/footer.js b/src/front/js/component/footer.js index 670323e091..7d8401f4cc 100755 --- a/src/front/js/component/footer.js +++ b/src/front/js/component/footer.js @@ -1,10 +1,37 @@ import React, { Component } from "react"; -export const Footer = () => ( - -); +export const Footer = () => { + return ( +
+
+ {/* Sección de Información */} +
+

Sobre Nosotros

+

En PupperEats queremos que tus peludos tengan la mejor calidad de vida posible. Somos defensores de la comida de calidad sin aditivos innecesarios que ofrezcan la mejor opción según las necesidades específicas de tu mascota.

+
+ + {/* Sección de Redes Sociales */} +
+

Síguenos

+ +
+
+
+ © 2025 Pupper Eats - Todos los derechos reservados +
+
+ ); +}; + + + diff --git a/src/front/js/component/formCarritoPago.js b/src/front/js/component/formCarritoPago.js new file mode 100644 index 0000000000..6e4e0c9853 --- /dev/null +++ b/src/front/js/component/formCarritoPago.js @@ -0,0 +1,237 @@ +// import React, { useState } from "react"; +// import { Link } from "react-router-dom"; +// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +// import { faPaypal } from '@fortawesome/free-brands-svg-icons'; + + +// export const formCarritoPago = () => { +// // const [newContact, setNewContact] = useState({ + +// // }); +// const [carrito, setCarrito] = useState([]); + +// const productos = [ +// { id: 1, name: 'Producto 1', price: 10 }, +// { id: 2, name: 'Producto 2', price: 20 }, +// { id: 3, name: 'Producto 3', price: 30 }, +// ]; + +// const handleChange = (e) => { +// const { name, value } = e.target; +// setNewContact({ +// ...newContact, +// [name]: value, +// }); +// }; + +// const handleSubmit = (e) => { +// e.preventDefault(); +// console.log('Datos enviados:', newContact); +// }; + +// const handleFormatoChange = (productoId, cantidad) => { +// const producto = productos.find(p => p.id === productoId); +// const existingProduct = carrito.find(item => item.id === productoId); + +// if (existingProduct) { +// setCarrito(carrito.map(item => +// item.id === productoId ? { ...item, cantidad } : item +// )); +// } else { +// setCarrito([...carrito, { ...producto, cantidad }]); +// } +// }; + +// const calcularTotal = () => { +// const subtotal = carrito.reduce((acc, item) => acc + (item.price * item.cantidad), 0); +// const total = subtotal + (subtotal * 0.21); // Incluyendo IVA +// const totalUnidades = carrito.reduce((acc, item) => acc + item.cantidad, 0); +// return { subtotal, total, totalUnidades }; +// }; + +// const { subtotal, total, totalUnidades } = calcularTotal(); + +// return ( +//
+// + +//
+//
+//
+//
+//

Productos en el carrito:

+// {productos.map(producto => ( +//
+//
+//
+//
+// Producto +//
+//
+//
+//
{producto.name}
+//
+ +//
+//
+//

Descripción breve del producto.

+//

{producto.price}€

+//
+ +//
+// +// +//
+//
+//
+//
+//
+//
+// ))} +//
+ +//
+//

Resumen del carrito

+//

Total de productos: {totalUnidades}

+//

Subtotal: {subtotal}€

+//

IVA (21%): {(subtotal * 0.21).toFixed(2)}€

+//

Total: {total.toFixed(2)}€

+//
+//
+ +//
    +// {carrito.map(item => ( +//
  • +// {item.name} - {item.cantidad} unidades +//
  • +// ))} +//
+//
+ +//
+//
+//
+//

Dirección y método de envío

+//
+//
+// +// +//
+//
+// +// +//
+//
+//
+//
+// +// +//
+//
+// +// +//
+//
+//
+//
+// +// +//
+//
+//
+//
+// +// +//
+//
+// +// +//
+//
+//
+//
+//
+// +// +//
+//
+//
+//
+// +// +//
+//
+//
+//
+// +// +//
+//
+ +//
+//

Método de pago

+//
+// +// +//
+//
+// +// +//
+//
+// +// +//
+//
+// +// +//
+//
+// +// +//
+//
+//
+ +//
+// +// +//
+//

Pago con mi cuenta de Paypal

+// +//
+//
+ +//
+// +// +//
+//
+//
+//
+//
+//
+// ); +// }; \ No newline at end of file diff --git a/src/front/js/component/navbar.js b/src/front/js/component/navbar.js index af4b01e334..eaa7a73f4c 100755 --- a/src/front/js/component/navbar.js +++ b/src/front/js/component/navbar.js @@ -1,19 +1,71 @@ -import React from "react"; +import React, { useContext, useEffect } from "react"; +import logo from "../../img/Icono puppereats.png"; import { Link } from "react-router-dom"; +import { Context } from "../store/appContext"; +import { FaUserCircle, FaShoppingCart, FaSignOutAlt } from "react-icons/fa"; -export const Navbar = () => { - return ( - - ); +export const Navbar = ({ setActiveCategory }) => { + const { store, actions } = useContext(Context); + const { user, cart } = store; + + useEffect(() => { + console.log("Usuario en navbar:", user); + }, [user, cart]); + + const totalUnidades = store.cart.reduce((total, producto) => total + (producto.cantidad || 1), 0); + + if (typeof setActiveCategory !== "function") { + console.error("⚠️ Error: setActiveCategory no es una función en Navbar.js."); + return null; + } + + return ( + + ); }; diff --git a/src/front/js/component/producto.js b/src/front/js/component/producto.js new file mode 100644 index 0000000000..d57cc8987c --- /dev/null +++ b/src/front/js/component/producto.js @@ -0,0 +1,121 @@ +import React, { useState, useContext } from "react"; +import { Context } from "../store/appContext"; +import { Link, useNavigate } from "react-router-dom"; + + +export const Producto = ({ id, data }) => { + const [precio, setPrecio] = useState(29.99); + const { actions, store } = useContext(Context); + const navigate = useNavigate(); + + const handleFormatoChange = (e) => { + const selectedOption = e.target.options[e.target.selectedIndex]; + const nuevoPrecio = selectedOption.dataset.precio; + if (nuevoPrecio) { + setPrecio(nuevoPrecio); + } else { + setPrecio(29.99); + } + }; + + const productAdd = () => { + // Verificar usuario registrado + if (store.user) { + // Añade el producto al carrito con todos los campos + actions.addToCart({ + id: data.id, + name: data.name, + price: data.price, + url: data.url, + description: data.description, + }); + } else { + navigate("/loginSignup"); // Redirige al login si no está autenticado + } + }; + + return ( +
+
+
+
+
+
+ {data.url && ( + {data.name} + )} +
+
+
+
+ {data.name} +
+
+

+ Descripción: +

+

{data.description}. +

+
+

+ Ingredientes: +

+

{data.ingredients}. +

+
+

+ Específico para: +

+

{data.pathologies}. +

+ +
+

{data.price}€

+
+ +
+ +
+
+
+
+
+
+
+
+ ); +}; diff --git "a/src/front/js/component/recuperacionContrase\303\261a.js" "b/src/front/js/component/recuperacionContrase\303\261a.js" new file mode 100644 index 0000000000..a6f5028b47 --- /dev/null +++ "b/src/front/js/component/recuperacionContrase\303\261a.js" @@ -0,0 +1,100 @@ +import React, { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; + +export const RecuperacionContraseña = () => { + const [email, setEmail] = useState(""); + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + const response = await fetch( + `${process.env.BACKEND_URL}/api/forgotpassword`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email }), + } + ); + + const data = await response.json(); + + if (response.ok) { + alert("Correo enviado correctamente. Revisa tu bandeja de entrada."); + navigate("/loginSignup"); + } else { + alert(`Error: ${data.msg}`); + } + } catch (error) { + alert("Error de conexión con el servidor."); + } + }; + + return ( +
+
+
+

Recuperar Contraseña

+

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

+
+
+ setEmail(e.target.value)} + required + onFocus={(e) => e.target.style.borderColor = "#007bff"} + onBlur={(e) => e.target.style.borderColor = "#ddd"} + /> +
+ +
+ + Volver a la página principal + +
+
+
+ ); +}; diff --git a/src/front/js/index.js b/src/front/js/index.js index e46232086b..c618cde39e 100755 --- a/src/front/js/index.js +++ b/src/front/js/index.js @@ -8,5 +8,9 @@ import "../styles/index.css"; //import your own components import Layout from "./layout"; +//importaciones Bootstrap +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'bootstrap/dist/js/bootstrap.bundle.min'; + //render your react application ReactDOM.render(, document.querySelector("#app")); diff --git a/src/front/js/layout.js b/src/front/js/layout.js index d42289f0ee..01ea293517 100755 --- a/src/front/js/layout.js +++ b/src/front/js/layout.js @@ -1,39 +1,100 @@ -import React from "react"; -import { BrowserRouter, Route, Routes } from "react-router-dom"; +import React, { useState, useEffect, useContext } from "react"; +import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom"; import ScrollToTop from "./component/scrollToTop"; import { BackendURL } from "./component/backendURL"; +import { Context } from "./store/appContext"; import { Home } from "./pages/home"; +import { PerfilUsuario} from "./pages/perfilUsuario"; import { Demo } from "./pages/demo"; import { Single } from "./pages/single"; import injectContext from "./store/appContext"; + import { Navbar } from "./component/navbar"; import { Footer } from "./component/footer"; +import { RecuperacionContraseña } from "./component/recuperacionContraseña"; +import { VistaMascota } from "./pages/vistaMascota"; +import { VistaProducto } from "./pages/VistaProducto"; +import { LoginSignup } from "./pages/loginSignup"; +import { RegistroMascota } from "./pages/RegistroMascota"; +import { CarritoPago } from "./pages/CarritoPago"; +import { StripePromise } from "./pages/PaymentPage"; +import { PaymentPage } from "./pages/PaymentPage"; +import AlertComponent from "./pages/AlertComponent"; + + //create your first component const Layout = () => { - //the basename is used when your project is published in a subdirectory and not in the root of the domain - // you can set the basename on the .env file located at the root of this project, E.g: BASENAME=/react-hello-webapp/ const basename = process.env.BASENAME || ""; + const [activeCategory, setActiveCategory] = useState(null); + const { actions } = useContext(Context); // Obtenemos acciones del contexto + + useEffect(() => { + actions.loadUserFromStorage(); + actions.loadCartFromStorage(); + + // Detectar actividad del usuario y reiniciar el temporizador + const resetInactivityTimer = () => { + clearTimeout(window.inactivityTimer); + window.inactivityTimer = setTimeout(() => { + console.log("Usuario inactivo, cerrando sesión..."); + actions.logout(); + }, 30 * 60 * 1000); // 🔹 Cerrar sesión tras 30 min de inactividad + }; + + document.addEventListener("mousemove", resetInactivityTimer); + document.addEventListener("keydown", resetInactivityTimer); + document.addEventListener("click", resetInactivityTimer); + + return () => { + document.removeEventListener("mousemove", resetInactivityTimer); + document.removeEventListener("keydown", resetInactivityTimer); + document.removeEventListener("click", resetInactivityTimer); + }; + }, []); + + + if (!process.env.BACKEND_URL || process.env.BACKEND_URL === "") return ; + + return ( + + + + {/* Colocamos useLocation dentro de Routes */} + } /> + +