diff --git a/.env b/.env index 25cdf21..bfe595c 100644 --- a/.env +++ b/.env @@ -9,4 +9,7 @@ FLASK_RUN_PORT=5555 FLASK_ENV=development # Secret key for session management -SECRET_KEY=51645fd45d5g5d154g5561f1gkkku65 +SECRET_KEY=51645fd45d5g5d154g5561f1gkkku65j + +# Firebase credentials +FIREBASE_CREDENTIALS_BASE64=ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiZmFybWZvbGlvLTJhMTFhIiwKICAicHJpdmF0ZV9rZXlfaWQiOiAiNWRhMzBlOTUxZjdhYWMyZTNiNWFmYWVkZTQ3ZTVkM2NkZjA1ZDRjYyIsCiAgInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuTUlJRXZRSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2N3Z2dTakFnRUFBb0lCQVFDakZyU0tCSmxBZm9pd1xudUk1dVhBWGNyb2JSSVVFdS8xblhLaGVVYUx3RER0MWVZOCtSQm1HU0IrUlRvZ25VS1g0eFRkQnJObFN1QzZlWFxuUzVZVGVRd0VyTkF6YzEzbjlGTVFJTmZrN0JUcjlzKzBhRnhmZkdEcXlESklRQVRMeVVFU1VEU3NCTjJzdFN4Vlxub2JUYnFTMnozdm5ULzJUMUErdGJjS2J4TDFkdkdjOThZSnloWi9YVXI0SkhzY1UvM2tnQnRqU0RmYUdwU2loN1xub3hVM1lTSHdNVnJzWjg2bldOUytkWlNnYzBKZGx6b2lIV3ZtcFB4Y0o2V2d5dFM1eEh6dkYwQk1qMVFwNU1Rc1xuYnpmUzVxdVhhUW9wZytGcXFwaXIwSWU4L2ZvemYyT2MzMFdJcDRrZnp3WlVJc3NGamZNWjM5enhJM0JwTHUzMlxuNmJFdFZORWJBZ01CQUFFQ2dnRUFBZHNIOGl0bWp2eE1Qb1BWdUYyN0FnTEpBcElpb0EvdkRCVlhoVmNYdWQvWlxuRnZJdHgybWsxempZYm95QkdNcU1FVnJnVElmOERnQTdSNVpvUG1Va2hOMFpLcXl5WEg3dTFGZlFwN01qOHlJR1xuR2syM0FnTWVteTFCOG9rcW1pTDZadVNiWk5CU3NWeXlXaHNTa3JrL0RZcUk3eWVRYWlxVzZwM0NneXRWdWF3elxuTFp2Q0xpdG5QK05tMmc1ZFI5WDQyV0R1NnVudzJHQTJJMmk4VXB0a0pMdXBWdk9XWGtxbXlLc3YvTFh3ZVR0RlxuWjhnc0hXOXJrbDh3b2pLN3NBZ0M2V1NjUnlhOGM1Mk8yTGF2Q0pmeUFSRUx6SWVDVmwyQzFpdWhlMmo0alhZWVxuMGRKYTVLQnZLdWY0cnVOSmNLejg4bWxDb2hRdzFoM3JzVCt5YVhGL0NRS0JnUURiN0RFR3Q5b1h0U0xpU0NxSVxuam5nRjNlL3JBYmhVR3lmSk9RVGUydVd5MFQ1RzFsZkRqaGVvUHg4U2J2Nld2K1JoSlJsc3NzbkZ4S3dEM1BvSVxuQ0cyZUZiWnNQSlVSY2cwWnZCcm1BL0VWN0p2RWsxeW9ha2hSY0RKaTZ1WENTT3RrQXNQN1BWaVlvbktRaWpZbFxuemR4QXZYQkI1QXU2dkYyakNhMk9jc1p4M3dLQmdRQzkxN3RqV3VDVDNkM1orZGkzYXRGN1BTUHBNNmhSYzYzc1xuODVWK0hINmNEWUhmazczaGZBSTAyeUM1MkhLTUZpMFVJcFVHeHB5NkowelRFYnl5a1N1L0lBbGxRTGxIK2Q3VVxueldwRG95THRHREF4ZWdLSWxHZlJBeWMvSUt4UXR6TnVFRU1aTk1RVXc3R0R6SFBNdGpzbkxqNzBNblhPdnZ0NFxud2kvSFBtdmdSUUtCZ0NWYjZUUW45VCtEd01wSjBoZmlnOWNUVFpkUmNudXZKVUlVa3BROUFUb3dPM0VUSmxPTlxuQnMzSTgwRlJZNlErTXRDaDJjRFNXbDVqTzRsb2QzejMyWTg2UndQNkFxUzFDQzZ2K3EzS1M2RWEwWmRDSlVKaVxuMDZncDlCVkRyQVltRnY2bFRaK1hXejZLa2dXbHhPOEZHbjVROEFueGNHQ25jUXlxTHpaOG51Tm5Bb0dCQUlvTFxuSC9pY01XZDBVb2paTTZ3VDRXV1N6TlViT2c2WVptVkJybmU0Y3B3NCtqSkFOVGVNTy9LQlVLZXk2NEJQODlHTFxuWUFCQVZlRExDUU1HQjhkMDVuR2c4eFFNWDI0bUZORjYyUVY3ejc3Rjh1MVhRaFpjaTYwaGROMWpmY2xubmQ4dlxudGdyVElxM1NxdmNpcVNXM2NRWUppNzk0SGRBTGNjS2F1YkZSQTVKZEFvR0FBTlQxTkxzQm40YzRGTzd6U0tYTlxuL1lvZnV5cWpwcnA3NVA3S3pNanFRMFh3NFNHQzJ4SU1yc0UxTDFIdzJPSTczUHlEdE9nM2pLMVF5UFdCMDJVcFxuMGNMUEZuWTVjWXk5VEtOQ0lGc0kzUnV4RWQzSWNGM1l6OFQ0MWRrTGRRbmFkL0VZUitwZVoyTm5KQUVxZ05waVxucWJkZUJOM0prN1N0VnIrZEQxckhxQ1k9XG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXG4iLAogICJjbGllbnRfZW1haWwiOiAiZmlyZWJhc2UtYWRtaW5zZGstOXR4a2FAZmFybWZvbGlvLTJhMTFhLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjEwNTcxODU5MjgyMjg3NDIzODY5NyIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vb2F1dGgyLmdvb2dsZWFwaXMuY29tL3Rva2VuIiwKICAiYXV0aF9wcm92aWRlcl94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsCiAgImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkvZmlyZWJhc2UtYWRtaW5zZGstOXR4a2ElNDBmYXJtZm9saW8tMmExMWEuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLAogICJ1bml2ZXJzZV9kb21haW4iOiAiZ29vZ2xlYXBpcy5jb20iCn0K diff --git a/.gitignore b/.gitignore index 2038c24..a7a9529 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ app.db # Environment variables .env +firebase-adminsdk.json +firebase_credentials_base64.txt \ No newline at end of file diff --git a/Pipfile b/Pipfile index 2c01670..3a8fe49 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,8 @@ flask = "*" gunicorn = "*" flask-restful = "*" python-dotenv = "*" +firebase-admin = "*" +flask-cors = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 4436e34..50efbea 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "2f1af22ee787bea9a98f7dd2b2abf079e87e9c4b01808813ea75a311bc98d203" + "sha256": "99ff29046bfff6c6a7a1a1881271a104f9df00359da9c8c87c74c37fa915faf2" }, "pipfile-spec": 6, "requires": { @@ -39,6 +39,199 @@ "markers": "python_version >= '3.8'", "version": "==1.8.2" }, + "cachecontrol": { + "hashes": [ + "sha256:7db1195b41c81f8274a7bbd97c956f44e8348265a1bc7641c37dfebc39f0c938", + "sha256:f5bf3f0620c38db2e5122c0726bdebb0d16869de966ea6a2befe92470b740ea0" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "cachetools": { + "hashes": [ + "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474", + "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827" + ], + "markers": "python_version >= '3.7'", + "version": "==5.4.0" + }, + "certifi": { + "hashes": [ + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.7.4" + }, + "cffi": { + "hashes": [ + "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", + "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab", + "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499", + "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058", + "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693", + "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb", + "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", + "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", + "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2", + "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401", + "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4", + "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b", + "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59", + "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f", + "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c", + "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", + "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa", + "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424", + "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb", + "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2", + "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8", + "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e", + "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9", + "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82", + "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828", + "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759", + "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc", + "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", + "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf", + "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932", + "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", + "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29", + "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206", + "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2", + "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c", + "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c", + "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", + "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a", + "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195", + "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6", + "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9", + "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc", + "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", + "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0", + "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", + "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb", + "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a", + "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", + "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720", + "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42", + "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", + "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d", + "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d", + "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb", + "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4", + "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", + "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b", + "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8", + "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e", + "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", + "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3", + "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150", + "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", + "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76", + "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e", + "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb", + "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.17.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, "click": { "hashes": [ "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", @@ -47,14 +240,55 @@ "markers": "python_version >= '3.7'", "version": "==8.1.7" }, + "cryptography": { + "hashes": [ + "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709", + "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069", + "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2", + "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b", + "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e", + "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70", + "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778", + "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22", + "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895", + "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf", + "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431", + "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f", + "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947", + "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74", + "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc", + "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66", + "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66", + "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf", + "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f", + "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5", + "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e", + "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f", + "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55", + "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1", + "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47", + "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5", + "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0" + ], + "version": "==43.0.0" + }, "faker": { "hashes": [ - "sha256:0f60978314973de02c00474c2ae899785a42b2cf4f41b7987e93c132a2b8a4a9", - "sha256:886ee28219be96949cd21ecc96c4c742ee1680e77f687b095202c8def1a08f06" + "sha256:7c10ebdf74aaa0cc4fe6ec6db5a71e8598ec33503524bd4b5f4494785a5670dd", + "sha256:97fe1e7e953dd640ca2cd4dfac4db7c4d2432dd1b7a244a3313517707f3b54e9" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==26.0.0" + "version": "==26.3.0" + }, + "firebase-admin": { + "hashes": [ + "sha256:e716dde1447f0a1cd1523be76ff872df33c4e1a3c079564ace033b2ad60bcc4f", + "sha256:fe34ee3ca0e625c5156b3931ca4b4b69b5fc344dbe51bba9706ff674ce277898" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==6.5.0" }, "flask": { "hashes": [ @@ -65,6 +299,14 @@ "markers": "python_version >= '3.8'", "version": "==3.0.3" }, + "flask-cors": { + "hashes": [ + "sha256:eeb69b342142fdbf4766ad99357a7f3876a2ceb77689dc10ff912aac06c389e4", + "sha256:f2a704e4458665580c074b714c4627dd5a306b333deb9074d0b1794dfa2fb677" + ], + "index": "pypi", + "version": "==4.0.1" + }, "flask-migrate": { "hashes": [ "sha256:5c532be17e7b43a223b7500d620edae33795df27c75811ddf32560f7d48ec617", @@ -91,6 +333,154 @@ "markers": "python_version >= '3.8'", "version": "==3.1.1" }, + "google-api-core": { + "extras": [ + "grpc" + ], + "hashes": [ + "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125", + "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==2.19.1" + }, + "google-api-python-client": { + "hashes": [ + "sha256:0bb973adccbe66a3d0a70abe4e49b3f2f004d849416bfec38d22b75649d389d8", + "sha256:aeb4bb99e9fdd241473da5ff35464a0658fea0db76fe89c0f8c77ecfc3813404" + ], + "markers": "python_version >= '3.7'", + "version": "==2.140.0" + }, + "google-auth": { + "hashes": [ + "sha256:8eff47d0d4a34ab6265c50a106a3362de6a9975bb08998700e389f857e4d39df", + "sha256:d6a52342160d7290e334b4d47ba390767e4438ad0d45b7630774533e82655b95" + ], + "markers": "python_version >= '3.7'", + "version": "==2.33.0" + }, + "google-auth-httplib2": { + "hashes": [ + "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", + "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d" + ], + "version": "==0.2.0" + }, + "google-cloud-core": { + "hashes": [ + "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073", + "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61" + ], + "markers": "python_version >= '3.7'", + "version": "==2.4.1" + }, + "google-cloud-firestore": { + "hashes": [ + "sha256:3e81b71d963b7e3bcc87fb813236f3864bc7b0a3f207ac4d87bc6595efe2b8a3", + "sha256:a4211c9f4ff9a701103c2217f87855ffb04ec595447753956b2ab97d8492039e" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==2.17.0" + }, + "google-cloud-storage": { + "hashes": [ + "sha256:6707a6f30a05aee36faca81296419ca2907ac750af1c0457f278bc9a6fb219ad", + "sha256:9d8db6bde3a979cca7150511cd0e4cb363e5f69d31259d890ba1124fa109418c" + ], + "markers": "python_version >= '3.7'", + "version": "==2.18.1" + }, + "google-crc32c": { + "hashes": [ + "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a", + "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876", + "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c", + "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289", + "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298", + "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02", + "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f", + "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2", + "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a", + "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb", + "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210", + "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5", + "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee", + "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c", + "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a", + "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314", + "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd", + "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65", + "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37", + "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4", + "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13", + "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894", + "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31", + "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e", + "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709", + "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740", + "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc", + "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d", + "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c", + "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c", + "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d", + "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906", + "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61", + "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57", + "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c", + "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a", + "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438", + "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946", + "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7", + "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96", + "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091", + "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae", + "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d", + "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88", + "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2", + "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd", + "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541", + "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728", + "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178", + "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968", + "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346", + "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8", + "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93", + "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7", + "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273", + "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462", + "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94", + "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd", + "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e", + "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57", + "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b", + "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9", + "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a", + "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100", + "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325", + "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183", + "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556", + "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4" + ], + "markers": "python_version >= '3.7'", + "version": "==1.5.0" + }, + "google-resumable-media": { + "hashes": [ + "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", + "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0" + ], + "markers": "python_version >= '3.7'", + "version": "==2.7.2" + }, + "googleapis-common-protos": { + "hashes": [ + "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945", + "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87" + ], + "markers": "python_version >= '3.7'", + "version": "==1.63.2" + }, "greenlet": { "hashes": [ "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", @@ -155,6 +545,64 @@ "markers": "python_version < '3.13' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", "version": "==3.0.3" }, + "grpcio": { + "hashes": [ + "sha256:075f3903bc1749ace93f2b0664f72964ee5f2da5c15d4b47e0ab68e4f442c257", + "sha256:0a0720299bdb2cc7306737295d56e41ce8827d5669d4a3cd870af832e3b17c4d", + "sha256:0cef8c919a3359847c357cb4314e50ed1f0cca070f828ee8f878d362fd744d52", + "sha256:0e85c8766cf7f004ab01aff6a0393935a30d84388fa3c58d77849fcf27f3e98c", + "sha256:17de4fda50967679677712eec0a5c13e8904b76ec90ac845d83386b65da0ae1e", + "sha256:18c10f0d054d2dce34dd15855fcca7cc44ec3b811139437543226776730c0f28", + "sha256:24a2246e80a059b9eb981e4c2a6d8111b1b5e03a44421adbf2736cc1d4988a8a", + "sha256:280e93356fba6058cbbfc6f91a18e958062ef1bdaf5b1caf46c615ba1ae71b5b", + "sha256:2a1d4c84d9e657f72bfbab8bedf31bdfc6bfc4a1efb10b8f2d28241efabfaaf2", + "sha256:2a4f476209acffec056360d3e647ae0e14ae13dcf3dfb130c227ae1c594cbe39", + "sha256:2bd672e005afab8bf0d6aad5ad659e72a06dd713020554182a66d7c0c8f47e18", + "sha256:3d1bbf7e1dd1096378bd83c83f554d3b93819b91161deaf63e03b7022a85224a", + "sha256:3dee50c1b69754a4228e933696408ea87f7e896e8d9797a3ed2aeed8dbd04b74", + "sha256:4482a44ce7cf577a1f8082e807a5b909236bce35b3e3897f839f2fbd9ae6982d", + "sha256:4934077b33aa6fe0b451de8b71dabde96bf2d9b4cb2b3187be86e5adebcba021", + "sha256:5764237d751d3031a36fafd57eb7d36fd2c10c658d2b4057c516ccf114849a3e", + "sha256:626319a156b1f19513156a3b0dbfe977f5f93db63ca673a0703238ebd40670d7", + "sha256:644a783ce604a7d7c91412bd51cf9418b942cf71896344b6dc8d55713c71ce82", + "sha256:66bb051881c84aa82e4f22d8ebc9d1704b2e35d7867757f0740c6ef7b902f9b1", + "sha256:74c34fc7562bdd169b77966068434a93040bfca990e235f7a67cdf26e1bd5c63", + "sha256:7656376821fed8c89e68206a522522317787a3d9ed66fb5110b1dff736a5e416", + "sha256:85e9c69378af02e483bc626fc19a218451b24a402bdf44c7531e4c9253fb49ef", + "sha256:870370524eff3144304da4d1bbe901d39bdd24f858ce849b7197e530c8c8f2ec", + "sha256:874acd010e60a2ec1e30d5e505b0651ab12eb968157cd244f852b27c6dbed733", + "sha256:886b45b29f3793b0c2576201947258782d7e54a218fe15d4a0468d9a6e00ce17", + "sha256:88fcabc332a4aef8bcefadc34a02e9ab9407ab975d2c7d981a8e12c1aed92aa1", + "sha256:8dc9ddc4603ec43f6238a5c95400c9a901b6d079feb824e890623da7194ff11e", + "sha256:8eb485801957a486bf5de15f2c792d9f9c897a86f2f18db8f3f6795a094b4bb2", + "sha256:926a0750a5e6fb002542e80f7fa6cab8b1a2ce5513a1c24641da33e088ca4c56", + "sha256:a146bc40fa78769f22e1e9ff4f110ef36ad271b79707577bf2a31e3e931141b9", + "sha256:a925446e6aa12ca37114840d8550f308e29026cdc423a73da3043fd1603a6385", + "sha256:a99e6dffefd3027b438116f33ed1261c8d360f0dd4f943cb44541a2782eba72f", + "sha256:abccc5d73f5988e8f512eb29341ed9ced923b586bb72e785f265131c160231d8", + "sha256:ade1256c98cba5a333ef54636095f2c09e6882c35f76acb04412f3b1aa3c29a5", + "sha256:b07f36faf01fca5427d4aa23645e2d492157d56c91fab7e06fe5697d7e171ad4", + "sha256:b81711bf4ec08a3710b534e8054c7dcf90f2edc22bebe11c1775a23f145595fe", + "sha256:be952436571dacc93ccc7796db06b7daf37b3b56bb97e3420e6503dccfe2f1b4", + "sha256:c9ba3e63108a8749994f02c7c0e156afb39ba5bdf755337de8e75eb685be244b", + "sha256:cdb34278e4ceb224c89704cd23db0d902e5e3c1c9687ec9d7c5bb4c150f86816", + "sha256:cf53e6247f1e2af93657e62e240e4f12e11ee0b9cef4ddcb37eab03d501ca864", + "sha256:d2b819f9ee27ed4e3e737a4f3920e337e00bc53f9e254377dd26fc7027c4d558", + "sha256:d72962788b6c22ddbcdb70b10c11fbb37d60ae598c51eb47ec019db66ccfdff0", + "sha256:d7b984a8dd975d949c2042b9b5ebcf297d6d5af57dcd47f946849ee15d3c2fb8", + "sha256:e4a795c02405c7dfa8affd98c14d980f4acea16ea3b539e7404c645329460e5a", + "sha256:e6cbdd107e56bde55c565da5fd16f08e1b4e9b0674851d7749e7f32d8645f524", + "sha256:ee40d058cf20e1dd4cacec9c39e9bce13fedd38ce32f9ba00f639464fcb757de" + ], + "version": "==1.65.4" + }, + "grpcio-status": { + "hashes": [ + "sha256:289bdd7b2459794a12cf95dc0cb727bd4a1742c37bd823f760236c937e53a485", + "sha256:f9049b762ba8de6b1086789d8315846e094edac2c50beaf462338b301a8fd4b8" + ], + "version": "==1.62.3" + }, "gunicorn": { "hashes": [ "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9", @@ -164,6 +612,22 @@ "markers": "python_version >= '3.7'", "version": "==22.0.0" }, + "httplib2": { + "hashes": [ + "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", + "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.22.0" + }, + "idna": { + "hashes": [ + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + ], + "markers": "python_version >= '3.5'", + "version": "==3.7" + }, "importlib-metadata": { "hashes": [ "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369", @@ -270,6 +734,68 @@ "markers": "python_version >= '3.7'", "version": "==2.1.5" }, + "msgpack": { + "hashes": [ + "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982", + "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3", + "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40", + "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee", + "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693", + "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950", + "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151", + "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24", + "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305", + "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b", + "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c", + "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659", + "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d", + "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18", + "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746", + "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868", + "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2", + "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba", + "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228", + "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2", + "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273", + "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c", + "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653", + "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a", + "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596", + "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd", + "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8", + "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa", + "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85", + "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc", + "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836", + "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3", + "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58", + "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128", + "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db", + "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f", + "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77", + "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad", + "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13", + "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8", + "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b", + "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a", + "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543", + "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b", + "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce", + "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d", + "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a", + "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c", + "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f", + "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e", + "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011", + "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04", + "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480", + "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a", + "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d", + "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.8" + }, "packaging": { "hashes": [ "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", @@ -278,6 +804,31 @@ "markers": "python_version >= '3.8'", "version": "==24.1" }, + "proto-plus": { + "hashes": [ + "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445", + "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12" + ], + "markers": "python_version >= '3.7'", + "version": "==1.24.0" + }, + "protobuf": { + "hashes": [ + "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1", + "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d", + "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040", + "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835", + "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1", + "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca", + "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f", + "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d", + "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978", + "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4", + "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b" + ], + "markers": "python_version >= '3.8'", + "version": "==4.25.4" + }, "psycopg2-binary": { "hashes": [ "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9", @@ -357,6 +908,49 @@ "markers": "python_version >= '3.7'", "version": "==2.9.9" }, + "pyasn1": { + "hashes": [ + "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", + "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", + "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.0" + }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + ], + "markers": "python_version >= '3.8'", + "version": "==2.22" + }, + "pyjwt": { + "extras": [ + "crypto" + ], + "hashes": [ + "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", + "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c" + ], + "markers": "python_version >= '3.8'", + "version": "==2.9.0" + }, + "pyparsing": { + "hashes": [ + "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", + "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + ], + "markers": "python_version >= '3.1'", + "version": "==3.1.2" + }, "python-dateutil": { "hashes": [ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", @@ -381,6 +975,22 @@ ], "version": "==2024.1" }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "rsa": { + "hashes": [ + "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", + "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==4.9" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -391,59 +1001,59 @@ }, "sqlalchemy": { "hashes": [ - "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96", - "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396", - "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb", - "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac", - "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704", - "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca", - "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe", - "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808", - "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4", - "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42", - "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc", - "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c", - "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238", - "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f", - "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740", - "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d", - "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327", - "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4", - "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e", - "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911", - "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac", - "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c", - "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e", - "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58", - "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4", - "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88", - "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be", - "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005", - "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc", - "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443", - "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1", - "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484", - "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1", - "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67", - "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203", - "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05", - "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227", - "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3", - "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449", - "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109", - "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b", - "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3", - "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9", - "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c", - "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa", - "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0", - "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298", - "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a", - "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9" + "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da", + "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5", + "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619", + "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78", + "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f", + "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389", + "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6", + "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533", + "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9", + "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f", + "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d", + "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0", + "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c", + "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d", + "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d", + "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632", + "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1", + "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8", + "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5", + "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb", + "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525", + "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2", + "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c", + "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1", + "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b", + "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da", + "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92", + "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a", + "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d", + "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16", + "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec", + "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84", + "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3", + "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd", + "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924", + "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb", + "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28", + "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22", + "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4", + "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961", + "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be", + "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5", + "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0", + "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e", + "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8", + "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8", + "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65", + "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad", + "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.0.31" + "version": "==2.0.32" }, "sqlalchemy-serializer": { "hashes": [ @@ -460,6 +1070,22 @@ "markers": "python_version >= '3.8'", "version": "==4.12.2" }, + "uritemplate": { + "hashes": [ + "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0", + "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e" + ], + "markers": "python_version >= '3.6'", + "version": "==4.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", + "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.2" + }, "werkzeug": { "hashes": [ "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18", diff --git a/README.md b/README.md index e69de29..a40542b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,88 @@ +# SendIT - Backend Server + +Welcome to the backend server repository for SendIT. This server handles database operations and provides endpoints for managing parcel orders, allocation and user management + +## Setup and contribution Instructions + +### 1. Clone the repo + +``` +git clone git@github.com:NB-Kamoni/global-learn-backend.git + +cd global-learn-backend + +``` + + +### 2. Add upstream remote + +``` +git remote add upstream git@github.com:NB-Kamoni/global-learn-backend.git + +``` + +### 3. Create new branch + + +``` +git checkout -b + +``` + +### 4. Install Dependencies and Activate Virtual Env + +Make sure you have Python and `pipenv` installed. Use `pipenv` to install dependencies. + +``` +pipenv install +pipenv shell + +``` + +### 5. Set Set Flask Environment Variables + +``` +cd server +export FLASK_APP=app.py +export FLASK_RUN_PORT=5555 + +``` + + +### 6. Database Setup and seeding + +``` +flask db init +flask db migrate -m "Initial migrate" +flask db upgrade head +python seed.py + +``` + +### 7. Run server + + +``` +flask run + +``` +### 8. stage and commit changes + + +``` +git add . +git commit -m "" + +``` + +### 9. push changes to your branch + + +``` +git push --set-upstream origin + +``` + +### 10. After you update the code, create a pull request + +Go to the original repository on GitHub. Switch to the branch you just pushed (your branch). Create a pull request with your changes. \ No newline at end of file diff --git a/app.py b/app.py index a8df279..66e3b23 100644 --- a/app.py +++ b/app.py @@ -1,16 +1,15 @@ -import os -from dotenv import load_dotenv -from flask import Flask, request -from flask_sqlalchemy import SQLAlchemy +from flask import Flask, jsonify, request, abort +from flask_cors import CORS from flask_restful import Api, Resource -from models import db, User, Parcel, DeliveryStatus - -# Load environment variables -load_dotenv() +from flask_migrate import Migrate +import os +from models import db, User, Parcel -# Initialize Flask app app = Flask(__name__) +CORS(app) + +# --------------------------configuration-------------------------------------- # Load appropriate configuration based on FLASK_ENV if os.getenv('FLASK_ENV') == 'production': app.config.from_object('config.ProductionConfig') @@ -31,269 +30,238 @@ # Set the Flask app secret key from environment variable app.config["SECRET_KEY"] = os.getenv("SECRET_KEY") + + +# db = SQLAlchemy(app) + # Initialize SQLAlchemy with the Flask app db.init_app(app) # Create the app context -# with app.app_context(): -# # Create the database tables if they don't exist -# # db.create_all() +with app.app_context(): + # Create the database tables if they don't exist + db.create_all() -# Set up Flask-Restful API +migrate = Migrate(app, db) api = Api(app) -# ---------------------Define resource endpoints--------------------------------------------- -# -------------------------USER RESOURCES------------------------------------------------- -class UserResource(Resource): - def get(self): - """ - Fetches either all users or a specific user by user_id, email, or name. - """ - user_id = request.args.get('user_id') - email = request.args.get('email') - name = request.args.get('name') - - if user_id: - user = User.query.filter_by(user_id=user_id).first() - if user: - return {'user': user.to_dict()} - else: - return {'message': 'User not found'}, 404 - - elif email: - user = User.query.filter_by(email=email).first() - if user: - return {'user': user.to_dict()} - else: - return {'message': 'User not found'}, 404 - - elif name: - users = User.query.filter(User.name.ilike(f'%{name}%')).all() - if users: - return {'users': [user.to_dict() for user in users]} - else: - return {'message': 'User not found'}, 404 - - else: - users = User.query.all() - return {'users': [user.to_dict() for user in users]} +#-------------------------------------------- +# Admin Endpoints +class CreateUserResource(Resource): def post(self): - """ - Registers a new user. - """ data = request.get_json() - new_user = User( - name=data.get('name'), - email=data.get('email'), - password=data.get('password'), - # Add other fields if necessary - ) - db.session.add(new_user) - db.session.commit() - return new_user.to_dict(), 201 + email = data.get('email') + role = data.get('role') + firebase_uid = data.get('firebase_uid') + user_status = data.get('status', 'active') + + if not email or not role or not firebase_uid: + return jsonify({'error': 'Email, role, and firebase_uid are required'}), 400 + + # Check if the user already exists + if User.query.filter_by(firebase_uid=firebase_uid).first(): + return jsonify({'error': 'User with this Firebase UID already exists'}), 400 + + try: + user = User( + email=email, + role=role, + firebase_uid=firebase_uid, + user_status=user_status + ) + db.session.add(user) + db.session.commit() + return jsonify({'message': 'User created successfully', 'user': user.to_dict()}), 201 + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 + + +class SearchUserByIdResource(Resource): + def get(self, user_id): + """Search user by user ID""" + user = User.query.get(user_id) + if not user: + abort(404, description="User not found") + return jsonify(user.to_dict()) +class SearchUserByEmailResource(Resource): + def get(self, email): + """Search user by email""" + user = User.query.filter_by(email=email).first() + if not user: + abort(404, description="User not found") + return jsonify(user.to_dict()) + +class UpdateUserProfileResource(Resource): def put(self, user_id): - """ - Updates information for a specific user identified by user_id. - """ data = request.get_json() + + # Fetch the user by ID user = User.query.get(user_id) if not user: - return {'message': 'User not found'}, 404 - - # Update user fields based on data provided - if 'name' in data: - user.name = data['name'] - if 'email' in data: - user.email = data['email'] - if 'password' in data: - user.password = data['password'] - - db.session.commit() - return user.to_dict() - - def delete(self, user_id): - """ - Deletes a specific user identified by user_id. - """ - user = User.query.get(user_id) - if not user: - return {'message': 'User not found'}, 404 - - db.session.delete(user) - db.session.commit() - return {'message': 'User deleted'}, 204 - -# Add resource endpoints to API -api.add_resource(UserResource, '/users', '/users/') - -# View all users: GET /users -# Add new user: POST /users -# Change user data: PUT /users/ -# Delete users: DELETE /users/ -# Search user by id: GET /users?user_id= -# Search user by email: GET /users?email= -# Search user by name: GET /users?name= - -# -------------------------PARCEL RESOURCES------------------------------------------------- - -class ParcelResource(Resource): + return jsonify({'error': 'User not found'}), 404 + + # Update user attributes if they are provided in the request data + user.first_name = data.get('first_name', user.first_name) + user.last_name = data.get('last_name', user.last_name) + user.company_name = data.get('company_name', user.company_name) + user.phone_number = data.get('phone_number', user.phone_number) + user.address = data.get('address', user.address) + user.profile_photo_url = data.get('profile_photo_url', user.profile_photo_url) + user.account_balance = data.get('account_balance', user.account_balance) + user.gps_location = data.get('gps_location', user.gps_location) + user.country = data.get('country', user.country) + user.mode_of_transport = data.get('mode_of_transport', user.mode_of_transport) + + try: + db.session.commit() + return jsonify({'message': 'User profile updated successfully', 'user': user.to_dict()}), 200 + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 + +class UserListResource(Resource): def get(self): - """ - Fetches either all parcels or a specific parcel by parcel_id or user_id. - """ - parcel_id = request.args.get('parcel_id') - user_id = request.args.get('user_id') - - if parcel_id: - parcel = Parcel.query.filter_by(parcel_id=parcel_id).first() - if parcel: - return {'parcel': parcel.to_dict()} - else: - return {'message': 'Parcel not found'}, 404 - - elif user_id: - parcels = Parcel.query.filter_by(user_id=user_id).all() - if parcels: - return {'parcels': [parcel.to_dict() for parcel in parcels]} - else: - return {'message': 'No parcels found for this user'}, 404 + """Admin: Get a list of all users""" + users = User.get_all_users() + return jsonify([user.to_dict() for user in users]) +class ParcelListResource(Resource): + def get(self): + """Admin: Get a list of all parcels with details""" + parcels = Parcel.get_parcels_with_details() + return jsonify([parcel.to_dict() for parcel in parcels]) + +class CourierListResource(Resource): + def get(self, status=None): + """Admin: Get a list of couriers filtered by status""" + if status: + couriers = User.get_couriers_by_status(status) else: - parcels = Parcel.query.all() - return {'parcels': [parcel.to_dict() for parcel in parcels]} + couriers = User.get_users_by_role('individual_courier') + User.get_users_by_role('corporate_courier') + return jsonify([courier.to_dict() for courier in couriers]) +class ParcelSearchByTrackingResource(Resource): + def get(self, tracking_number): + """Admin: Search parcels by tracking number""" + parcel = Parcel.get_parcel_by_tracking_number(tracking_number) + if not parcel: + abort(404, description="Parcel not found") + return jsonify(parcel.to_dict()) + +class UserSearchByStatusResource(Resource): + def get(self, status): + """Admin: Get a list of users filtered by status""" + users = User.get_users_by_status(status) + return jsonify([user.to_dict() for user in users]) + +#-------------------------------------------- +# Client Endpoints +class ClientParcelListResource(Resource): + def get(self, client_id): + """Client: Get all parcels where the client is the sender or recipient""" + parcels = Parcel.get_parcels_by_client(client_id) + return jsonify([parcel.to_dict() for parcel in parcels]) + +class CreateParcelResource(Resource): def post(self): - """ - Creates a new parcel. - """ data = request.get_json() + + # Extract required fields from the request data + tracking_number = data.get('tracking_number') + weight = data.get('weight') + length = data.get('length') + width = data.get('width') + height = data.get('height') + value = data.get('value') + pickup_location = data.get('pickup_location') + drop_off_location = data.get('drop_off_location') + sender_id = data.get('sender_id') + recipient_id = data.get('recipient_id') + courier_id = data.get('courier_id') + shipping_cost = data.get('shipping_cost') + distance = data.get('distance') + + # Check for required fields, including tracking_number + if not all([tracking_number, weight, length, width, height, value, pickup_location, drop_off_location, sender_id, recipient_id, courier_id, shipping_cost, distance]): + return jsonify({"error": "Missing required fields"}), 400 + + # Validate that sender, recipient, and courier exist + sender = User.query.get(sender_id) + recipient = User.query.get(recipient_id) + courier = User.query.get(courier_id) + + if not sender or not recipient or not courier: + return jsonify({"error": "Invalid sender, recipient, or courier ID"}), 404 + + # Create a new Parcel instance new_parcel = Parcel( - user_id=data.get('user_id'), - origin=data.get('origin'), - destination=data.get('destination'), - weight=data.get('weight'), - status=data.get('status'), - # Add other fields if necessary + tracking_number=tracking_number, + weight=weight, + length=length, + width=width, + height=height, + value=value, + pickup_location=pickup_location, + drop_off_location=drop_off_location, + sender_id=sender_id, + recipient_id=recipient_id, + courier_id=courier_id, + shipping_cost=shipping_cost, + distance=distance, ) - db.session.add(new_parcel) - db.session.commit() - return new_parcel.to_dict(), 201 - - def put(self, parcel_id): - """ - Updates information for a specific parcel identified by parcel_id. - """ - data = request.get_json() - parcel = Parcel.query.get(parcel_id) - if not parcel: - return {'message': 'Parcel not found'}, 404 - - # Update parcel fields based on data provided - if 'origin' in data: - parcel.origin = data['origin'] - if 'destination' in data: - parcel.destination = data['destination'] - if 'weight' in data: - parcel.weight = data['weight'] - if 'status' in data: - parcel.status = data['status'] - - db.session.commit() - return parcel.to_dict() - - def delete(self, parcel_id): - """ - Deletes a specific parcel identified by parcel_id. - """ - parcel = Parcel.query.get(parcel_id) + + # Add the new parcel to the session and commit to the database + try: + db.session.add(new_parcel) + db.session.commit() + return jsonify({"message": "Parcel created successfully", "parcel": new_parcel.to_dict()}), 201 + except Exception as e: + db.session.rollback() + return jsonify({"error": str(e)}), 500 + + +class ParcelTrackingResource(Resource): + def get(self, tracking_number): + """Client: Get parcel details by tracking number""" + parcel = Parcel.get_parcel_by_tracking_number(tracking_number) if not parcel: - return {'message': 'Parcel not found'}, 404 - - db.session.delete(parcel) - db.session.commit() - return {'message': 'Parcel deleted'}, 204 - -# Add resource endpoints to API -api.add_resource(ParcelResource, '/parcels', '/parcels/') - -# View all parcels: GET /parcels -# Add new parcel: POST /parcels -# Change parcel data: PUT /parcels/ -# Delete parcels: DELETE /parcels/ -# Search parcel by id: GET /parcels?parcel_id= -# Search parcels by user: GET /parcels?user_id= - -# -------------------------DELIVERY STATUS RESOURCES------------------------------------------------- - -class DeliveryStatusResource(Resource): - def get(self, parcel_id): - """ - Fetches the delivery status of a specific parcel identified by parcel_id. - """ - delivery_status = DeliveryStatus.query.filter_by(parcel_id=parcel_id).first() - if delivery_status: - return delivery_status.to_dict() - else: - return {'message': 'Delivery status not found'}, 404 + abort(404, description="Parcel not found") + return jsonify(parcel.to_dict()) - def post(self, parcel_id): - """ - Creates a new delivery status for the parcel identified by parcel_id. - """ - data = request.get_json() - new_status = DeliveryStatus( - parcel_id=parcel_id, - status=data.get('status'), - location=data.get('location'), - timestamp=data.get('timestamp') - ) - db.session.add(new_status) - db.session.commit() - return new_status.to_dict(), 201 - - def put(self, parcel_id): - """ - Updates the delivery status of the parcel identified by parcel_id. - """ - data = request.get_json() - delivery_status = DeliveryStatus.query.filter_by(parcel_id=parcel_id).first() - if not delivery_status: - return {'message': 'Delivery status not found'}, 404 - - # Update status fields based on data provided - if 'status' in data: - delivery_status.status = data['status'] - if 'location' in data: - delivery_status.location = data['location'] - if 'timestamp' in data: - delivery_status.timestamp = data['timestamp'] - - db.session.commit() - return delivery_status.to_dict() - - def delete(self, parcel_id): - """ - Deletes the delivery status of the parcel identified by parcel_id. - """ - delivery_status = DeliveryStatus.query.filter_by(parcel_id=parcel_id).first() - if not delivery_status: - return {'message': 'Delivery status not found'}, 404 - - db.session.delete(delivery_status) - db.session.commit() - return {'message': 'Delivery status deleted'}, 204 - -# Add resource endpoints to API -api.add_resource(DeliveryStatusResource, '/parcels//status') - -# Fetch delivery status: GET /parcels//status -# Create new status: POST /parcels//status -# Update status: PUT /parcels//status -# Delete status: DELETE /parcels//status - -# Main entry point for the application +#-------------------------------------------- +# API Routes +# Admin routes +api.add_resource(UserListResource, '/admin/users') +api.add_resource(ParcelListResource, '/admin/parcels') +api.add_resource(CourierListResource, '/admin/couriers', '/admin/couriers/') +api.add_resource(ParcelSearchByTrackingResource, '/admin/parcels/track/') +api.add_resource(UserSearchByStatusResource, '/admin/users/status/') + +# Client routes +api.add_resource(ClientParcelListResource, '/client//parcels') +api.add_resource(CreateParcelResource, '/client/parcels') +api.add_resource(ParcelTrackingResource, '/client/parcels/track/') + +# updating user profile +api.add_resource(UpdateUserProfileResource, '/user/profiles') +# creating user +api.add_resource(CreateUserResource, '/users') + +#Search user by id +api.add_resource(SearchUserByIdResource, '/users/') + +#Search user by email +api.add_resource(SearchUserByEmailResource, '/users/email/') + + +#-------------------------------------------- +# Run the app if __name__ == '__main__': - app.run(port=os.getenv('FLASK_RUN_PORT', 5555), debug=app.config['DEBUG']) + with app.app_context(): + db.create_all() + app.run(debug=True) + diff --git a/models.py b/models.py index b9191e4..f2ceb1b 100644 --- a/models.py +++ b/models.py @@ -1,160 +1,188 @@ from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import MetaData +from sqlalchemy import MetaData, func from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy_serializer import SerializerMixin -from datetime import datetime - -metadata = MetaData( - naming_convention={ - "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", - } -) - +from sqlalchemy.orm import aliased + +# Define a naming convention for the metadata +convention = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" +} + +metadata = MetaData(naming_convention=convention) db = SQLAlchemy(metadata=metadata) -class Client(db.Model, SerializerMixin): - __tablename__ = 'client' - - ClientID = db.Column(db.Integer, primary_key=True) - Email = db.Column(db.String, index=True, unique=True) - State = db.Column(db.String) - City = db.Column(db.String) - Address = db.Column(db.String) - PhoneNumber = db.Column(db.String) - FirstName = db.Column(db.String) - LastName = db.Column(db.String) - ProfilePicture = db.Column(db.String) - - # Relationships - sent_parcels = db.relationship('Parcel', foreign_keys='Parcel.SenderID', backref='sender', lazy=True) - received_parcels = db.relationship('Parcel', foreign_keys='Parcel.RecipientID', backref='recipient', lazy=True) - - serialize_rules = ('-sent_parcels', '-received_parcels') - - def __repr__(self): - return (f'') - -class Admin(db.Model, SerializerMixin): - __tablename__ = 'admin' - - AdminID = db.Column(db.Integer, primary_key=True) - FirstName = db.Column(db.String) - SecondName = db.Column(db.String) - City = db.Column(db.String) - State = db.Column(db.String) - BranchCode = db.Column(db.String) - ProfilePicture = db.Column(db.String) - - # Relationships - allocations = db.relationship('AdminDeliveryGuyAllocation', backref='admin', lazy=True) - - serialize_rules = ('-allocations',) +#-------------------------------------------- +# Define the User model +class User(db.Model, SerializerMixin): + __tablename__ = 'users' + + serialize_rules = ('-password', '-sent_parcels_list.sender', '-received_parcels_list.recipient', '-assigned_parcels_list.courier') + + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False) + firebase_uid = db.Column(db.String(120), unique=True, nullable=True) + first_name = db.Column(db.String(120)) + last_name = db.Column(db.String(120)) + company_name = db.Column(db.String(120)) + phone_number = db.Column(db.String(20)) + address = db.Column(db.String(200)) + role = db.Column(db.String(50), default='client') + profile_photo_url = db.Column(db.String(255)) + account_balance = db.Column(db.Float, default=0.0) + gps_location = db.Column(db.String(255)) + country = db.Column(db.String(100)) + user_status = db.Column(db.String(50), default='active') + mode_of_transport = db.Column(db.String(250)) + + # Relationships with explicit back_populates + sent_parcels_list = db.relationship('Parcel', foreign_keys='Parcel.sender_id', back_populates='sender_user', lazy='dynamic') + received_parcels_list = db.relationship('Parcel', foreign_keys='Parcel.recipient_id', back_populates='recipient_user', lazy='dynamic') + assigned_parcels_list = db.relationship('Parcel', foreign_keys='Parcel.courier_id', back_populates='courier_user', lazy='dynamic') + + # Association proxy for easy access to all parcels related to the user + parcels = association_proxy('sent_parcels_list', 'id') def __repr__(self): - return (f'') - -class ParcelStatus(db.Model, SerializerMixin): - __tablename__ = 'parcel_status' + return f'' - StatusID = db.Column(db.Integer, primary_key=True) - Name = db.Column(db.String, unique=True) - cancelled = db.Column(db.Boolean, default=False) - delivered = db.Column(db.Boolean, default=False) - posted = db.Column(db.Boolean, default=False) - en_route = db.Column(db.Boolean, default=False) - - # Relationships - parcels = db.relationship('Parcel', backref='status', lazy=True) - - serialize_rules = ('-parcels',) - - def __repr__(self): - return f'' - -class DeliveryGuy(db.Model, SerializerMixin): - __tablename__ = 'delivery_guy' + @staticmethod + def create_user(firebase_uid, email, role='client', status='active'): + """Create a new user with the provided Firebase UID, email, role, and status.""" + new_user = User( + firebase_uid=firebase_uid, + email=email, + role=role, + user_status=status + ) + db.session.add(new_user) + db.session.commit() + return new_user - DeliveryGuyID = db.Column(db.Integer, primary_key=True) - FirstName = db.Column(db.String) - SecondName = db.Column(db.String) - Address = db.Column(db.String) - City = db.Column(db.String) - State = db.Column(db.String) - PhoneNumber = db.Column(db.String) - Mode = db.Column(db.String) - LiveLocation = db.Column(db.String) - ProfilePictureURL = db.Column(db.String) - - # Relationships - parcels = db.relationship('Parcel', backref='delivery_guy', lazy=True) - allocations = db.relationship('AdminDeliveryGuyAllocation', backref='delivery_guy', lazy=True) - - serialize_rules = ('-parcels', '-allocations') - - def __repr__(self): - return (f'') - + @staticmethod + def update_profile(user_id, data): + """ + Update the user's profile with the provided data. + Only updates fields that are present in the data. + """ + user = User.query.get(user_id) + if not user: + return False, "User not found." + + # Update user attributes if they are provided in the request data + user.first_name = data.get('first_name', user.first_name) + user.last_name = data.get('last_name', user.last_name) + user.company_name = data.get('company_name', user.company_name) + user.phone_number = data.get('phone_number', user.phone_number) + user.address = data.get('address', user.address) + user.profile_photo_url = data.get('profile_photo_url', user.profile_photo_url) + user.account_balance = data.get('account_balance', user.account_balance) + user.gps_location = data.get('gps_location', user.gps_location) + user.country = data.get('country', user.country) + user.mode_of_transport = data.get('mode_of_transport', user.mode_of_transport) + + # Commit the changes to the database + try: + db.session.commit() + return True, "User profile updated successfully." + except Exception as e: + db.session.rollback() + return False, str(e) + + @staticmethod + def get_couriers_by_status(status): + """Return a list of couriers based on their user_status""" + return User.query.filter(User.role.in_(['individual_courier', 'corporate_courier']), User.user_status == status).all() + + @staticmethod + def get_all_parcels(): + """Return a list of all parcels along with their associated sender, recipient, and courier""" + return Parcel.query.all() + + @staticmethod + def get_users_by_role(role): + """Return a list of users based on their role""" + return User.query.filter_by(role=role).all() + + + @staticmethod + def get_all_users(): + """Return a list of all users""" + return User.query.all() + + @staticmethod + def get_users_by_status(status): + """Return a list of users based on their status""" + return User.query.filter_by(user_status=status).all() + +#-------------------------------------------- +# Define the Parcel model class Parcel(db.Model, SerializerMixin): - __tablename__ = 'parcel' - - ParcelID = db.Column(db.Integer, primary_key=True) - Weight = db.Column(db.Float) - Origin = db.Column(db.String) - Destination = db.Column(db.String) - SenderID = db.Column(db.Integer, db.ForeignKey('client.ClientID')) - RecipientID = db.Column(db.Integer, db.ForeignKey('client.ClientID')) - StatusID = db.Column(db.Integer, db.ForeignKey('parcel_status.StatusID')) - DeliveryGuyID = db.Column(db.Integer, db.ForeignKey('delivery_guy.DeliveryGuyID')) - DeliveryStatusID = db.Column(db.Integer, db.ForeignKey('delivery_status.DeliveryStatusID')) # New foreign key - - serialize_rules = ('-sender', '-recipient', '-status', '-delivery_guy', '-delivery_status') - - def __repr__(self): - return (f'') - -class AdminDeliveryGuyAllocation(db.Model, SerializerMixin): - __tablename__ = 'admin_delivery_guy_allocation' - - AllocationID = db.Column(db.Integer, primary_key=True) - AdminID = db.Column(db.Integer, db.ForeignKey('admin.AdminID')) - DeliveryGuyID = db.Column(db.Integer, db.ForeignKey('delivery_guy.DeliveryGuyID')) - ParcelID = db.Column(db.Integer, db.ForeignKey('parcel.ParcelID')) - AllocatedAt = db.Column(db.DateTime, default=datetime.utcnow) - PickUpLocation = db.Column(db.String) - DropOffLocation = db.Column(db.String) - - serialize_rules = ('-admin', '-delivery_guy', '-parcel') - def __repr__(self): - return (f'') - -class User(db.Model, SerializerMixin): - __tablename__ = 'user' - - UserID = db.Column(db.Integer, primary_key=True) - Username = db.Column(db.String, index=True, unique=True) - PasswordHash = db.Column(db.String) - Email = db.Column(db.String, index=True, unique=True) - CreatedAt = db.Column(db.DateTime, default=datetime.utcnow) + __tablename__ = 'parcels' + + serialize_rules = ('-sender_user', '-recipient_user', '-courier_user') + + id = db.Column(db.Integer, primary_key=True) + tracking_number = db.Column(db.String(50), unique=True, nullable=False) + weight = db.Column(db.Float, nullable=False) + length = db.Column(db.Float, nullable=False) + width = db.Column(db.Float, nullable=False) + height = db.Column(db.Float, nullable=False) + value = db.Column(db.Float, nullable=False) + pickup_location = db.Column(db.String(255), nullable=False) + drop_off_location = db.Column(db.String(255), nullable=False) + sender_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + recipient_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + courier_id = db.Column(db.Integer, db.ForeignKey('users.id')) + delivery_status = db.Column(db.String(50), default='pending') + shipping_cost = db.Column(db.Float, nullable=False) + distance = db.Column(db.Float, nullable=False) + created_at = db.Column(db.DateTime, server_default=func.now()) + updated_at = db.Column(db.DateTime, onupdate=func.now()) + + # Define relationships with back_populates + sender_user = db.relationship('User', foreign_keys=[sender_id], back_populates='sent_parcels_list') + recipient_user = db.relationship('User', foreign_keys=[recipient_id], back_populates='received_parcels_list') + courier_user = db.relationship('User', foreign_keys=[courier_id], back_populates='assigned_parcels_list') def __repr__(self): - return f'' - -class DeliveryStatus(db.Model, SerializerMixin): - __tablename__ = 'delivery_status' - - DeliveryStatusID = db.Column(db.Integer, primary_key=True) - Name = db.Column(db.String, unique=True) - Description = db.Column(db.String) - - # Relationships - parcels = db.relationship('Parcel', backref='delivery_status', lazy=True) + return f'' + + @staticmethod + def get_parcels_by_client(client_id): + """Return all parcels where the client is either the sender or the recipient""" + return Parcel.query.filter((Parcel.sender_id == client_id) | (Parcel.recipient_id == client_id)).all() + + @staticmethod + def get_parcel_by_tracking_number(tracking_number): + """Return the parcel details by tracking number""" + return Parcel.query.filter_by(tracking_number=tracking_number).first() + + @staticmethod + def get_parcels_by_status(status): + """Return all parcels by delivery status""" + return Parcel.query.filter_by(delivery_status=status).all() + + @staticmethod + def get_parcels_with_details(): + """Return all parcels along with their sender, recipient, and courier details.""" + # Aliases for the User table + sender_alias = aliased(User, name='sender') + recipient_alias = aliased(User, name='recipient') + courier_alias = aliased(User, name='courier') + + query = db.session.query( + Parcel, + sender_alias, + recipient_alias, + courier_alias + ).join(sender_alias, Parcel.sender_id == sender_alias.id) \ + .join(recipient_alias, Parcel.recipient_id == recipient_alias.id) \ + .join(courier_alias, Parcel.courier_id == courier_alias.id) + + return query.all() - serialize_rules = ('-parcels',) - - def __repr__(self): - return f'' diff --git a/requirements.txt b/requirements.txt index df50169..b80f6be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,28 +2,62 @@ alembic==1.13.2; python_version >= '3.8' aniso8601==9.0.1 blinker==1.8.2; python_version >= '3.8' +cachecontrol==0.14.0; python_version >= '3.7' +cachetools==5.4.0; python_version >= '3.7' +certifi==2024.7.4; python_version >= '3.6' +cffi==1.17.0; platform_python_implementation != 'PyPy' +charset-normalizer==3.3.2; python_full_version >= '3.7.0' click==8.1.7; python_version >= '3.7' -faker==26.0.0; python_version >= '3.8' +cryptography==43.0.0 +faker==26.3.0; python_version >= '3.8' +firebase-admin==6.5.0; python_version >= '3.7' flask==3.0.3; python_version >= '3.8' +flask-cors==4.0.1 flask-migrate==4.0.7; python_version >= '3.6' flask-restful==0.3.10 flask-sqlalchemy==3.1.1; python_version >= '3.8' +google-api-core[grpc]==2.19.1; platform_python_implementation != 'PyPy' +google-api-python-client==2.140.0; python_version >= '3.7' +google-auth==2.33.0; python_version >= '3.7' +google-auth-httplib2==0.2.0 +google-cloud-core==2.4.1; python_version >= '3.7' +google-cloud-firestore==2.17.0; platform_python_implementation != 'PyPy' +google-cloud-storage==2.18.1; python_version >= '3.7' +google-crc32c==1.5.0; python_version >= '3.7' +google-resumable-media==2.7.2; python_version >= '3.7' +googleapis-common-protos==1.63.2; python_version >= '3.7' greenlet==3.0.3; python_version < '3.13' 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'))))) +grpcio==1.65.4 +grpcio-status==1.62.3 gunicorn==22.0.0; python_version >= '3.7' +httplib2==0.22.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +idna==3.7; python_version >= '3.5' importlib-metadata==8.2.0; python_version < '3.10' importlib-resources==6.4.0; python_version < '3.9' itsdangerous==2.2.0; python_version >= '3.8' jinja2==3.1.4; python_version >= '3.7' mako==1.3.5; python_version >= '3.8' markupsafe==2.1.5; python_version >= '3.7' +msgpack==1.0.8; python_version >= '3.8' packaging==24.1; python_version >= '3.8' +proto-plus==1.24.0; python_version >= '3.7' +protobuf==4.25.4; python_version >= '3.8' psycopg2-binary==2.9.9; python_version >= '3.7' +pyasn1==0.6.0; python_version >= '3.8' +pyasn1-modules==0.4.0; python_version >= '3.8' +pycparser==2.22; python_version >= '3.8' +pyjwt[crypto]==2.9.0; python_version >= '3.8' +pyparsing==3.1.2; python_version >= '3.1' python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' python-dotenv==1.0.1; python_version >= '3.8' pytz==2024.1 +requests==2.32.3; python_version >= '3.8' +rsa==4.9; python_version >= '3.6' and python_version < '4' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -sqlalchemy==2.0.31; python_version >= '3.7' +sqlalchemy==2.0.32; python_version >= '3.7' sqlalchemy-serializer==1.4.12 typing-extensions==4.12.2; python_version >= '3.8' +uritemplate==4.1.1; python_version >= '3.6' +urllib3==2.2.2; python_version >= '3.8' werkzeug==3.0.3; python_version >= '3.8' zipp==3.19.2; python_version >= '3.8' diff --git a/seed.py b/seed.py index e69de29..d693562 100644 --- a/seed.py +++ b/seed.py @@ -0,0 +1,129 @@ +from app import app, db +from models import User, Parcel +from datetime import datetime, timedelta +import random +from sqlalchemy.exc import IntegrityError +from faker import Faker + +fake = Faker() + +def seed_users(): + """Seed the database with sample user data.""" + try: + # Clear existing users + User.query.delete() + + users = [ + {'email': 'client1@example.com', 'firebase_uid': 'firebase_uid_client1', 'role': 'client', 'first_name': 'Alice', 'last_name': 'Smith'}, + {'email': 'client2@example.com', 'firebase_uid': 'firebase_uid_client2', 'role': 'client', 'first_name': 'Bob', 'last_name': 'Johnson'}, + {'email': 'courier1@example.com', 'firebase_uid': 'firebase_uid_courier1', 'role': 'individual_courier', 'first_name': 'Charlie', 'last_name': 'Brown'}, + {'email': 'courier2@example.com', 'firebase_uid': 'firebase_uid_courier2', 'role': 'corporate_courier', 'first_name': 'Diana', 'last_name': 'Wilson'}, + {'email': 'admin@example.com', 'firebase_uid': 'firebase_uid_admin', 'role': 'admin', 'first_name': 'Eve', 'last_name': 'Davis'} + ] + + for user_data in users: + user = User( + email=user_data['email'], + firebase_uid=user_data['firebase_uid'], + role=user_data['role'], + first_name=user_data['first_name'], + last_name=user_data['last_name'] + ) + db.session.add(user) + + db.session.commit() + print("Users seeded successfully.") + except IntegrityError as e: + db.session.rollback() + print(f"IntegrityError: {e}") + +def seed_parcels(): + """Seed the database with sample parcel data.""" + try: + users = User.query.all() + if len(users) < 3: + print("Not enough users to create parcels.") + return + + sender = users[0] + recipient = users[1] + courier = users[2] if len(users) > 2 else None + + now = datetime.utcnow() + one_day = timedelta(days=1) + + parcels = [ + { + 'weight': random.uniform(1, 10), + 'length': random.uniform(10, 20), + 'width': random.uniform(5, 15), + 'height': random.uniform(5, 15), + 'value': random.uniform(100, 500), + 'pickup_location': 'Location A', + 'drop_off_location': 'Location B', + 'shipping_cost': random.uniform(10, 50), + 'distance': random.uniform(5, 20), + 'delivery_status': 'delivered', + 'created_at': now, + 'updated_at': now + }, + { + 'weight': random.uniform(1, 10), + 'length': random.uniform(10, 20), + 'width': random.uniform(5, 15), + 'height': random.uniform(5, 15), + 'value': random.uniform(100, 500), + 'pickup_location': 'Location C', + 'drop_off_location': 'Location D', + 'shipping_cost': random.uniform(10, 50), + 'distance': random.uniform(5, 20), + 'delivery_status': 'in_progress', + 'created_at': now - one_day, + 'updated_at': now, + + } + ] + + for parcel_data in parcels: + parcel = Parcel( + weight=parcel_data['weight'], + length=parcel_data['length'], + width=parcel_data['width'], + height=parcel_data['height'], + value=parcel_data['value'], + pickup_location=parcel_data['pickup_location'], + drop_off_location=parcel_data['drop_off_location'], + sender_id=sender.id, + recipient_id=recipient.id, + courier_id=courier.id if courier else None, + shipping_cost=parcel_data['shipping_cost'], + distance=parcel_data['distance'], + delivery_status=parcel_data['delivery_status'], + created_at=parcel_data['created_at'], + updated_at=parcel_data['updated_at'] + ) + try: + db.session.add(parcel) + db.session.commit() + except IntegrityError as e: + db.session.rollback() + print(f"Failed to add parcel with tracking number {parcel_data.get('tracking_number')}: {e}") + + print("Parcels seeded successfully.") + except Exception as e: + print(f"Error seeding parcels: {e}") + +def seed_database(): + """Initialize the database and seed it with sample data.""" + with app.app_context(): + try: + db.drop_all() + db.create_all() + seed_users() + seed_parcels() + print("Database seeded successfully.") + except Exception as e: + print(f"Error during database seeding: {e}") + +if __name__ == '__main__': + seed_database()