diff --git a/.vscode/settings.json b/.vscode/settings.json
index 0fa4149..d7314f5 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,5 +4,6 @@
   ],
   "python.autoComplete.extraPaths": [
     "${workspaceFolder}/__pypackages__/3.11/lib"
-  ]
+  ],
+  "editor.defaultFormatter": "charliermarsh.ruff"
 }
\ No newline at end of file
diff --git a/pdm.lock b/pdm.lock
index b42b2c0..16c807c 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -5,7 +5,17 @@
 groups = ["default", "dev"]
 strategy = ["cross_platform"]
 lock_version = "4.4"
-content_hash = "sha256:3a80d01a4a37b3b1440a29f4cd06ba1d9fc3078191feaf3d3401511c9fcdf8c8"
+content_hash = "sha256:5f2270d2e84e1fc30449bbcb324864ff25807347a639eb8d6dd070c133fdbe13"
+
+[[package]]
+name = "annotated-types"
+version = "0.6.0"
+requires_python = ">=3.8"
+summary = "Reusable constraint types to use with typing.Annotated"
+files = [
+    {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
+    {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
+]
 
 [[package]]
 name = "anyio"
@@ -43,20 +53,41 @@ files = [
 ]
 
 [[package]]
-name = "asyncpg"
-version = "0.28.0"
-requires_python = ">=3.7.0"
-summary = "An asyncio PostgreSQL driver"
+name = "async-timeout"
+version = "4.0.3"
+requires_python = ">=3.7"
+summary = "Timeout context manager for asyncio programs"
 files = [
-    {file = "asyncpg-0.28.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a0e08fe2c9b3618459caaef35979d45f4e4f8d4f79490c9fa3367251366af207"},
-    {file = "asyncpg-0.28.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b24e521f6060ff5d35f761a623b0042c84b9c9b9fb82786aadca95a9cb4a893b"},
-    {file = "asyncpg-0.28.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99417210461a41891c4ff301490a8713d1ca99b694fef05dabd7139f9d64bd6c"},
-    {file = "asyncpg-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f029c5adf08c47b10bcdc857001bbef551ae51c57b3110964844a9d79ca0f267"},
-    {file = "asyncpg-0.28.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ad1d6abf6c2f5152f46fff06b0e74f25800ce8ec6c80967f0bc789974de3c652"},
-    {file = "asyncpg-0.28.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d7fa81ada2807bc50fea1dc741b26a4e99258825ba55913b0ddbf199a10d69d8"},
-    {file = "asyncpg-0.28.0-cp311-cp311-win32.whl", hash = "sha256:f33c5685e97821533df3ada9384e7784bd1e7865d2b22f153f2e4bd4a083e102"},
-    {file = "asyncpg-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:5e7337c98fb493079d686a4a6965e8bcb059b8e1b8ec42106322fc6c1c889bb0"},
-    {file = "asyncpg-0.28.0.tar.gz", hash = "sha256:7252cdc3acb2f52feaa3664280d3bcd78a46bd6c10bfd681acfffefa1120e278"},
+    {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
+    {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
+]
+
+[[package]]
+name = "asyncpg"
+version = "0.29.0"
+requires_python = ">=3.8.0"
+summary = "An asyncio PostgreSQL driver"
+dependencies = [
+    "async-timeout>=4.0.3; python_version < \"3.12.0\"",
+]
+files = [
+    {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"},
+    {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"},
+    {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"},
+    {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"},
+    {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"},
+    {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"},
+    {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"},
+    {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"},
+    {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"},
+    {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"},
+    {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"},
+    {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"},
+    {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"},
+    {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"},
+    {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"},
+    {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"},
+    {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"},
 ]
 
 [[package]]
@@ -78,6 +109,35 @@ files = [
     {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
 ]
 
+[[package]]
+name = "bcrypt"
+version = "4.0.1"
+requires_python = ">=3.6"
+summary = "Modern password hashing for your software and your servers"
+files = [
+    {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"},
+    {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"},
+    {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"},
+    {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"},
+    {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"},
+    {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"},
+    {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"},
+    {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"},
+    {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"},
+    {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"},
+    {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"},
+    {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"},
+    {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"},
+    {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"},
+    {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"},
+    {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"},
+    {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"},
+    {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"},
+    {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"},
+    {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"},
+    {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"},
+]
+
 [[package]]
 name = "certifi"
 version = "2023.7.22"
@@ -213,6 +273,16 @@ files = [
     {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
 ]
 
+[[package]]
+name = "dnspython"
+version = "2.4.2"
+requires_python = ">=3.8,<4.0"
+summary = "DNS toolkit"
+files = [
+    {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"},
+    {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"},
+]
+
 [[package]]
 name = "ecdsa"
 version = "0.18.0"
@@ -226,6 +296,20 @@ files = [
     {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
 ]
 
+[[package]]
+name = "email-validator"
+version = "2.1.0.post1"
+requires_python = ">=3.8"
+summary = "A robust email address syntax and deliverability validation library."
+dependencies = [
+    "dnspython>=2.0.0",
+    "idna>=2.0.0",
+]
+files = [
+    {file = "email_validator-2.1.0.post1-py3-none-any.whl", hash = "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"},
+    {file = "email_validator-2.1.0.post1.tar.gz", hash = "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44"},
+]
+
 [[package]]
 name = "executing"
 version = "2.0.0"
@@ -293,7 +377,7 @@ files = [
 
 [[package]]
 name = "geopandas"
-version = "0.14.0"
+version = "0.14.1"
 requires_python = ">=3.9"
 summary = "Geographic pandas extensions"
 dependencies = [
@@ -304,8 +388,8 @@ dependencies = [
     "shapely>=1.8.0",
 ]
 files = [
-    {file = "geopandas-0.14.0-py3-none-any.whl", hash = "sha256:a402a565e727642cb44a500c911f226eea26c1b1247c6586827031e3d7a9403a"},
-    {file = "geopandas-0.14.0.tar.gz", hash = "sha256:ea6c031889e1e1888aecaa6e182ca620d78f63551c49b3002a998bcbb280531f"},
+    {file = "geopandas-0.14.1-py3-none-any.whl", hash = "sha256:ed5a7cae7874bfc3238fb05e0501cc1760e1b7b11e5b76ecad29da644ca305da"},
+    {file = "geopandas-0.14.1.tar.gz", hash = "sha256:4853ff89ecb6d1cfc43e7b3671092c8160e8a46a3dd7368f25906283314e42bb"},
 ]
 
 [[package]]
@@ -394,6 +478,16 @@ files = [
     {file = "ipython-8.16.1.tar.gz", hash = "sha256:ad52f58fca8f9f848e256c629eff888efc0528c12fe0f8ec14f33205f23ef938"},
 ]
 
+[[package]]
+name = "itsdangerous"
+version = "2.1.2"
+requires_python = ">=3.7"
+summary = "Safely pass data to untrusted environments and back."
+files = [
+    {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+    {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+]
+
 [[package]]
 name = "jedi"
 version = "0.19.1"
@@ -458,7 +552,7 @@ files = [
 
 [[package]]
 name = "pandas"
-version = "2.1.2"
+version = "2.1.3"
 requires_python = ">=3.9"
 summary = "Powerful data structures for data analysis, time series, and statistics"
 dependencies = [
@@ -469,19 +563,19 @@ dependencies = [
     "tzdata>=2022.1",
 ]
 files = [
-    {file = "pandas-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08d287b68fd28906a94564f15118a7ca8c242e50ae7f8bd91130c362b2108a81"},
-    {file = "pandas-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bbd98dcdcd32f408947afdb3f7434fade6edd408c3077bbce7bd840d654d92c6"},
-    {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90c95abb3285d06f6e4feedafc134306a8eced93cb78e08cf50e224d5ce22e2"},
-    {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52867d69a54e71666cd184b04e839cff7dfc8ed0cd6b936995117fdae8790b69"},
-    {file = "pandas-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d0382645ede2fde352da2a885aac28ec37d38587864c0689b4b2361d17b1d4c"},
-    {file = "pandas-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:65177d1c519b55e5b7f094c660ed357bb7d86e799686bb71653b8a4803d8ff0d"},
-    {file = "pandas-2.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5aa6b86802e8cf7716bf4b4b5a3c99b12d34e9c6a9d06dad254447a620437931"},
-    {file = "pandas-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d594e2ce51b8e0b4074e6644758865dc2bb13fd654450c1eae51201260a539f1"},
-    {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3223f997b6d2ebf9c010260cf3d889848a93f5d22bb4d14cd32638b3d8bba7ad"},
-    {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4944dc004ca6cc701dfa19afb8bdb26ad36b9bed5bcec617d2a11e9cae6902"},
-    {file = "pandas-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3f76280ce8ec216dde336e55b2b82e883401cf466da0fe3be317c03fb8ee7c7d"},
-    {file = "pandas-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:7ad20d24acf3a0042512b7e8d8fdc2e827126ed519d6bd1ed8e6c14ec8a2c813"},
-    {file = "pandas-2.1.2.tar.gz", hash = "sha256:52897edc2774d2779fbeb6880d2cfb305daa0b1a29c16b91f531a18918a6e0f3"},
+    {file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"},
+    {file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"},
+    {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"},
+    {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"},
+    {file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"},
+    {file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"},
+    {file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"},
+    {file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"},
+    {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"},
+    {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"},
+    {file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"},
+    {file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"},
+    {file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"},
 ]
 
 [[package]]
@@ -494,6 +588,29 @@ files = [
     {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
 ]
 
+[[package]]
+name = "passlib"
+version = "1.7.4"
+summary = "comprehensive password hashing framework supporting over 30 schemes"
+files = [
+    {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
+    {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
+]
+
+[[package]]
+name = "passlib"
+version = "1.7.4"
+extras = ["bcrypt"]
+summary = "comprehensive password hashing framework supporting over 30 schemes"
+dependencies = [
+    "bcrypt>=3.1.0",
+    "passlib==1.7.4",
+]
+files = [
+    {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
+    {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
+]
+
 [[package]]
 name = "pexpect"
 version = "4.8.0"
@@ -548,6 +665,7 @@ files = [
     {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
     {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
     {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
+    {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
     {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
     {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
     {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
@@ -600,22 +718,115 @@ files = [
 
 [[package]]
 name = "pydantic"
-version = "1.10.13"
+version = "2.4.0"
 requires_python = ">=3.7"
-summary = "Data validation and settings management using python type hints"
+summary = "Data validation using Python type hints"
 dependencies = [
-    "typing-extensions>=4.2.0",
+    "annotated-types>=0.4.0",
+    "pydantic-core==2.10.0",
+    "typing-extensions>=4.6.1",
 ]
 files = [
-    {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"},
-    {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"},
-    {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"},
-    {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"},
-    {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"},
-    {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"},
-    {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"},
-    {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"},
-    {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"},
+    {file = "pydantic-2.4.0-py3-none-any.whl", hash = "sha256:909b2b7d7be775a890631218e8c4b6b5418c9b6c57074ae153e5c09b73bf06a3"},
+    {file = "pydantic-2.4.0.tar.gz", hash = "sha256:54216ccb537a606579f53d7f6ed912e98fffce35aff93b25cd80b1c2ca806fc3"},
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.10.0"
+requires_python = ">=3.7"
+summary = ""
+dependencies = [
+    "typing-extensions!=4.7.0,>=4.6.0",
+]
+files = [
+    {file = "pydantic_core-2.10.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:ab2d56dfa13244164f0ba8125d8315c799fa0150459b88fc42ed5c1e3c04d47a"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1e79893a20207ff671f13f5562c1f0aaece030e6e30252683f536286ba89864"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030ba2f59e78c8732445d8c9f093579674f2b5b93b3960945face14ec2e82682"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:705fad71297dfedc5c9e3c935702864aa0cc7812be11ac544f152677ba6ea430"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:394a8ce4a7495af8dbf33038daf57a6170be15f8d1d92a7b63c6f2211527d950"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19c7aa3c0ff08ddc91597d8af08f8c4de59b27fe752b3bd1db9a67f6f08c4020"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb204346d3eda4e0c63cbeeec6398a52682ac51f9cf7379a13505863e47d3186"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1fefe63baa04f1d9dd5b4564b1e73d133e1c745589933d7ef9718235915cc81"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fa4bd88165d860111e860e8b43efd97afd137a9165cf24eb3cfb2371f57452bf"},
+    {file = "pydantic_core-2.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e21ab9c49cc58282c228ff89fb4a5e4b447233ccd53acb7f333d1cde58df37b"},
+    {file = "pydantic_core-2.10.0-cp311-none-win32.whl", hash = "sha256:2a6f28e2b2a5cef3b52b5ac6c6d64fe810ca51ec57081554f447c818778eea09"},
+    {file = "pydantic_core-2.10.0-cp311-none-win_amd64.whl", hash = "sha256:f94539aa4265ab5528d8c3dc4505a19369083c29d0713b8ed536f93b9bc1e94f"},
+    {file = "pydantic_core-2.10.0-cp311-none-win_arm64.whl", hash = "sha256:2352f7cb8ef0cd21fbc582abe2a14105d7e8400f97a551ca2e3b05dee77525d2"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:c2a126c7271a9421005a0f57cf71294ad49c375e4d0a9198b93665796f49e7f7"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7440933341f655a64456065211cf7657c3cf3524d5b0b02f5d9b63ef5a7e0d49"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85d8225cd08aacb8a2843cf0a0a72f1c403c6ac6f18d4cfeecabe050f80c9ea3"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:573e89b3da5908f564ae54b6284e20b490158681e91e1776a59dfda17ec0a6a8"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0061965942489e6da23f0399b1136fd10eff0a4f0cefae13369eba1776e22a6"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:725f0276402773a6b61b6f67bf9562f37ba08a8bfebdfb9990eea786ed5711b2"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25cacd12689b1a357ae6212c7f5980ebf487720db5bbf1bb5d91085226b6a962"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e70c6c882ab101a72010c8f91e87db211fa2aaf6aa51acc7160fe5649630ed75"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e079540fd4c45c23de4465cafb20cddcd8befe3b5f46505a2eb28e49b9d13ee2"},
+    {file = "pydantic_core-2.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:98474284adb71c8738e5efb71ccb1382d8d66f042ad0875018c78bcb38ac0f47"},
+    {file = "pydantic_core-2.10.0-cp312-none-win32.whl", hash = "sha256:ab1fa046ef9058ceef941b576c5e7711bab3d99be00a304fb4726cf4b94e05ff"},
+    {file = "pydantic_core-2.10.0-cp312-none-win_amd64.whl", hash = "sha256:b4df023610af081d6da85328411fed7aacf19e939fe955bb31f29212f8dcf306"},
+    {file = "pydantic_core-2.10.0-cp312-none-win_arm64.whl", hash = "sha256:f1a70f99d1a7270d4f321a8824e87d5b88acd64c2af6049915b7fd8215437e04"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:b40221d1490f2c6e488d2576773a574d42436b5aba1faed91f59a9feb82c384b"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f3b25201efe20d182f3bd6fe8d99685f4ed01cac67b79c017c9cf688b747263"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a45943bb14275e9681fd4abafbe3acae1e7dac7248bebf38ac5bde492e00f7"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc5be7a29a6b25a186941e9e2b5f9281c05723628e1fdb244f429f4c1682ff49"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17460ffd8f8e49ca52711b4926fefe2b336d01b63dc27aee432a576c2147c8ce"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c1ab3701d660bd136a22e1ca95292bfed50245eb869adaee2e08f29d4dd5e360"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:09ac18617199704327d99c85893d697b8442c18b8c2db1ea636ba83313223541"},
+    {file = "pydantic_core-2.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e3f69d48191103587950981cf47c936064c808b6c18f57e745ed130a305c73a6"},
+    {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:792af9e4f78d6f1d0aabfb95162c5ed56b5369b25350eaa68b1495e8f675d4d9"},
+    {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ecd28fb4c98c97836046d092029017bcc35e060ea547484aa1234b8a592de17"},
+    {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a622a8abf656cc51960766fa4d194504e8a9f85ae48032f87fb42c79462c7b8"},
+    {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52eb5c61de017bfee422f6aa9a3e76de5aa5a9189ba808bba63b9de67e55c4ca"},
+    {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:69772dcdcf90b677d0d2ecedafe4c6a610572f1fad15912cde28a6f8eb5654fd"},
+    {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:12470a4de172aaa1bbadb45744de4a9b0298fa8f974eb508314c3b5da0cb4aed"},
+    {file = "pydantic_core-2.10.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f9f2c70257f03db712658d4138e2b892bdd7c71472783eaebc2813a47fd29ef3"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:8a5323d6778931ab1b3b22bac05fb7c961786d3b04a6c84f7c0ffcc331b4b998"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:5f00e83aa9aebbfd4382695a5ed94e6282ac01455fbb1a37d99d2effa29df30f"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c871820c60fc863c7b3f660612af6ce5bb8f5f69d6364f208e29d2ca7992d154"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1bcb1b9b33573eeef218ffb3a2910c57fedc8831caf3c942e68a2222481d2cc"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d122a46c360c8069f7ac39c6f2c29cf99436baa48ba1e28ea5443336e9bbb838"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ffb2a3462bb7905c4d849b95f536ac1f3948e92f5e0fc7e65bd3f3b0d132cf4"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b5d4eec8aba25b163a4d9dcc6be8354bc8f939040bc15a6400cbd62ba0511a5f"},
+    {file = "pydantic_core-2.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5cbfe4cd608cf6d032374961e4e07d0506acfaec7b1a69beade1d5f98dce00fd"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:02b3d546342e7f583bf58f4a4618c7e97f44426db2358789393537dd4e9a921d"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7820faf076216654ae54ad8a8443a296faaac9057a49ff404ce92ab85c9518a3"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f114130c44ae52b3bd2450dac8e1d3e1e92a92baecb24dbcdb6de2d2fc15bdb5"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f6f70680c15876c583a24bd476e49004327e87392be0282aedbc65773519ea8"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f230d70be54447e12fcd0f1c2319dac74341244fafd2350d5675aa194f6c3f4"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:96b3007451863b46e8138f8096ef31aea6f7721a9910843b0554ce4ae17024a2"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b196c4ace34be6c2953c6ec3906d1af88c418b93325d612d7f900ed30bf1e0ac"},
+    {file = "pydantic_core-2.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5958b1af7acd7b4a629e9758ce54a31c1910695e85e0ef847ba3daa4f25a0a08"},
+    {file = "pydantic_core-2.10.0.tar.gz", hash = "sha256:8fe66506700efdfc699c613ccc4974ac7d8fceed8c74983e55ec380504db2e05"},
+]
+
+[[package]]
+name = "pydantic-settings"
+version = "2.0.3"
+requires_python = ">=3.7"
+summary = "Settings management using Pydantic"
+dependencies = [
+    "pydantic>=2.0.1",
+    "python-dotenv>=0.21.0",
+]
+files = [
+    {file = "pydantic_settings-2.0.3-py3-none-any.whl", hash = "sha256:ddd907b066622bd67603b75e2ff791875540dc485b7307c4fffc015719da8625"},
+    {file = "pydantic_settings-2.0.3.tar.gz", hash = "sha256:962dc3672495aad6ae96a4390fac7e593591e144625e5112d359f8f67fb75945"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.4.0"
+extras = ["email"]
+requires_python = ">=3.7"
+summary = "Data validation using Python type hints"
+dependencies = [
+    "email-validator>=2.0.0",
+    "pydantic==2.4.0",
+]
+files = [
+    {file = "pydantic-2.4.0-py3-none-any.whl", hash = "sha256:909b2b7d7be775a890631218e8c4b6b5418c9b6c57074ae153e5c09b73bf06a3"},
+    {file = "pydantic-2.4.0.tar.gz", hash = "sha256:54216ccb537a606579f53d7f6ed912e98fffce35aff93b25cd80b1c2ca806fc3"},
 ]
 
 [[package]]
@@ -667,6 +878,16 @@ files = [
     {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
 ]
 
+[[package]]
+name = "python-dotenv"
+version = "1.0.0"
+requires_python = ">=3.8"
+summary = "Read key-value pairs from a .env file and set them as environment variables"
+files = [
+    {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
+    {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+]
+
 [[package]]
 name = "python-jose"
 version = "3.3.0"
@@ -695,6 +916,16 @@ files = [
     {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
 ]
 
+[[package]]
+name = "python-multipart"
+version = "0.0.6"
+requires_python = ">=3.7"
+summary = "A streaming multipart parser for Python"
+files = [
+    {file = "python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"},
+    {file = "python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"},
+]
+
 [[package]]
 name = "pytz"
 version = "2023.3.post1"
@@ -704,6 +935,29 @@ files = [
     {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
 ]
 
+[[package]]
+name = "pyyaml"
+version = "6.0.1"
+requires_python = ">=3.6"
+summary = "YAML parser and emitter for Python"
+files = [
+    {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
+    {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
+    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
+    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
+    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+    {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
+    {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
+    {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+    {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+    {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+    {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
+    {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
+]
+
 [[package]]
 name = "rsa"
 version = "4.9"
@@ -775,64 +1029,59 @@ files = [
 
 [[package]]
 name = "sqlalchemy"
-version = "1.4.50"
-requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+version = "2.0.11"
+requires_python = ">=3.7"
 summary = "Database Abstraction Library"
 dependencies = [
-    "greenlet!=0.4.17; 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\"))))))",
+    "greenlet!=0.4.17; 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\")))))",
+    "typing-extensions>=4.2.0",
 ]
 files = [
-    {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"},
-    {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"},
-    {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"},
-    {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"},
-    {file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"},
-]
-
-[[package]]
-name = "sqlalchemy2-stubs"
-version = "0.0.2a36"
-requires_python = ">=3.6"
-summary = "Typing Stubs for SQLAlchemy 1.4"
-dependencies = [
-    "typing-extensions>=3.7.4",
-]
-files = [
-    {file = "sqlalchemy2-stubs-0.0.2a36.tar.gz", hash = "sha256:1c820c176a50401b7b3fc1e25019703b2c0753fe99a79d7e19305146baf1f60f"},
-    {file = "sqlalchemy2_stubs-0.0.2a36-py3-none-any.whl", hash = "sha256:9b5b3eb263cdc649b6a5619d2c089b98290406027a01e1de171eeb98c38ce678"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa81761ff674d2e2d591fc88d31835d3ecf65bddb021a522f4eaaae831c584cf"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21f447403a1bfeb832a7384c4ac742b7baab04460632c0335e020e8e2c741d4b"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4d8d96c0a7265de8496250a2c2d02593da5e5e85ea24b5c54c2db028d74cf8c"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c4c5834789f718315cb25d1b95d18fde91b72a1a158cdc515d7f6380c1f02a3"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f57965a9d5882efdea0a2c87ae2f6c7dbc14591dcd0639209b50eec2b3ec947e"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0dd98b0be54503afc4c74e947720c3196f96fb2546bfa54d911d5de313c5463c"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-win32.whl", hash = "sha256:eec40c522781a58839df6a2a7a2d9fbaa473419a3ab94633d61e00a8c0c768b7"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-win_amd64.whl", hash = "sha256:62835d8cd6713458c032466c38a43e56503e19ea6e54b0e73295c6ab281fc0b1"},
+    {file = "SQLAlchemy-2.0.11-py3-none-any.whl", hash = "sha256:1d28e8278d943d9111d44720f92cc338282e956ed68849bfcee053c06bde4f39"},
+    {file = "SQLAlchemy-2.0.11.tar.gz", hash = "sha256:c3cbff7cced3c42dbe71448ce6bf4202b4a2d305e78dd77e3f280ba6cd245138"},
 ]
 
 [[package]]
 name = "sqlalchemy"
-version = "1.4.50"
+version = "2.0.11"
 extras = ["asyncio"]
-requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+requires_python = ">=3.7"
 summary = "Database Abstraction Library"
 dependencies = [
-    "greenlet!=0.4.17; python_version >= \"3\"",
-    "sqlalchemy==1.4.50",
+    "greenlet!=0.4.17",
+    "sqlalchemy==2.0.11",
 ]
 files = [
-    {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"},
-    {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"},
-    {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"},
-    {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"},
-    {file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa81761ff674d2e2d591fc88d31835d3ecf65bddb021a522f4eaaae831c584cf"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21f447403a1bfeb832a7384c4ac742b7baab04460632c0335e020e8e2c741d4b"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4d8d96c0a7265de8496250a2c2d02593da5e5e85ea24b5c54c2db028d74cf8c"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c4c5834789f718315cb25d1b95d18fde91b72a1a158cdc515d7f6380c1f02a3"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f57965a9d5882efdea0a2c87ae2f6c7dbc14591dcd0639209b50eec2b3ec947e"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0dd98b0be54503afc4c74e947720c3196f96fb2546bfa54d911d5de313c5463c"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-win32.whl", hash = "sha256:eec40c522781a58839df6a2a7a2d9fbaa473419a3ab94633d61e00a8c0c768b7"},
+    {file = "SQLAlchemy-2.0.11-cp311-cp311-win_amd64.whl", hash = "sha256:62835d8cd6713458c032466c38a43e56503e19ea6e54b0e73295c6ab281fc0b1"},
+    {file = "SQLAlchemy-2.0.11-py3-none-any.whl", hash = "sha256:1d28e8278d943d9111d44720f92cc338282e956ed68849bfcee053c06bde4f39"},
+    {file = "SQLAlchemy-2.0.11.tar.gz", hash = "sha256:c3cbff7cced3c42dbe71448ce6bf4202b4a2d305e78dd77e3f280ba6cd245138"},
 ]
 
 [[package]]
 name = "sqlmodel"
-version = "0.0.11"
+version = "0"
 requires_python = ">=3.7,<4.0"
+git = "https://github.com/honglei/sqlmodel.git"
+revision = "3005495a3ec6c8216b31cbd623f91c7bc8ba174f"
 summary = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
 dependencies = [
-    "SQLAlchemy<2.0.0,>=1.4.36",
-    "pydantic<2.0.0,>=1.9.0",
-    "sqlalchemy2-stubs",
-]
-files = [
-    {file = "sqlmodel-0.0.11-py3-none-any.whl", hash = "sha256:bc0d64c4b901d919d2f16bbd79aefb07cb268c29f7c1dd83a84758772ccc95c6"},
-    {file = "sqlmodel-0.0.11.tar.gz", hash = "sha256:fc33abbf7ec29caafabe3d0e1db61e33597857a289e5fd1ecdb91be702b26084"},
+    "SQLAlchemy<=2.0.11,>=2.0.0",
+    "pydantic[email]<=2.4,>=2.1.1",
 ]
 
 [[package]]
@@ -894,7 +1143,7 @@ files = [
 
 [[package]]
 name = "uvicorn"
-version = "0.23.2"
+version = "0.24.0.post1"
 requires_python = ">=3.8"
 summary = "The lightning-fast ASGI server."
 dependencies = [
@@ -902,8 +1151,8 @@ dependencies = [
     "h11>=0.8",
 ]
 files = [
-    {file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"},
-    {file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"},
+    {file = "uvicorn-0.24.0.post1-py3-none-any.whl", hash = "sha256:7c84fea70c619d4a710153482c0d230929af7bcf76c7bfa6de151f0a3a80121e"},
+    {file = "uvicorn-0.24.0.post1.tar.gz", hash = "sha256:09c8e5a79dc466bdf28dead50093957db184de356fcdc48697bad3bde4c2588e"},
 ]
 
 [[package]]
diff --git a/pyproject.toml b/pyproject.toml
index 23d976b..3f59377 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,20 +13,30 @@ dependencies = [
     "psycopg2-binary>=2.9.9",
     "sqlalchemy[asyncio]",
     "asyncpg>=0.28.0",
-    "sqlmodel>=0.0.11",
+    #"sqlmodel>=0.0.11",
     "python-jose[cryptography]>=3.3.0",
     "geoalchemy2>=0.14.2",
+    "pyyaml>=6.0.1",
+    "python-multipart>=0.0.6",
+    "pydantic-settings>=2.0.3",
+    "itsdangerous>=2.1.2",
+    "passlib[bcrypt]>=1.7.4",
 ]
 requires-python = ">=3.11"
 readme = "README.md"
 license = {text = "MIT"}
 
+[build-system]
+requires = ["pdm-backend"]
+build-backend = "pdm.backend"
+
 [project.optional-dependencies]
 dev = [
     "ipdb>=0.13.13",
+    "sqlmodel @ git+https://github.com/honglei/sqlmodel.git#egg=sqlmodel",
 ]
 
 [tool.pdm.version]
 source = "scm"
-write_to = "src/_version.py"
-write_template = "__version__ = '{}'"
\ No newline at end of file
+write_to = "_version.py"
+write_template = "__version__ = '{}'"
diff --git a/src/_version.py b/src/_version.py
new file mode 100644
index 0000000..a96fc95
--- /dev/null
+++ b/src/_version.py
@@ -0,0 +1 @@
+__version__ = '2023.3+d20231113'
\ No newline at end of file
diff --git a/src/api.py b/src/api.py
index a46994c..a172d91 100644
--- a/src/api.py
+++ b/src/api.py
@@ -1,7 +1,12 @@
+import logging
 from datetime import timedelta
+from time import time
+from uuid import uuid1
+from typing import Annotated
 
-from fastapi import Depends, FastAPI, HTTPException, status
+from fastapi import Depends, FastAPI, HTTPException, status, Request
 from fastapi.security import OAuth2PasswordRequestForm
+from starlette.middleware.sessions import SessionMiddleware
 
 from sqlmodel import select
 from sqlmodel.ext.asyncio.session import AsyncSession
@@ -17,22 +22,38 @@ from .models.category import (
   CategoryGroup, CategoryModelType,
   Category, CategoryRead
 )
+from .models.bootstrap import BootstrapData
 from .database import get_db_session, pandas_query
 from .security import (
-    User, Token,
-    authenticate_user, get_current_active_user, create_access_token,
+    User as UserAuth,
+    Token,
+    authenticate_user, get_current_user, create_access_token,
     )
 from .config import conf
 
-api = FastAPI()
+logger = logging.getLogger(__name__)
 
+api = FastAPI()
+api.add_middleware(SessionMiddleware, secret_key=conf.crypto.secret)
+
+db_session = Annotated[AsyncSession, Depends(get_db_session)]
 
 @api.get("/nothing")
 async def get_nothing() -> str:
     return ''
 
-@api.post("/token", response_model=Token)
-async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
+
+@api.get('/bootstrap')
+async def bootstrap(
+    user: Annotated[UserRead, Depends(get_current_user)]) -> BootstrapData:
+    return BootstrapData(user=user)
+
+
+@api.post("/token")
+async def login_for_access_token(
+    db_session: db_session,
+    form_data: OAuth2PasswordRequestForm = Depends()
+    ) -> Token:
     user = await authenticate_user(form_data.username, form_data.password)
     if not user:
         raise HTTPException(
@@ -40,16 +61,14 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(
             detail="Incorrect username or password",
             headers={"WWW-Authenticate": "Bearer"},
         )
-    access_token_expires = timedelta(
-        minutes=conf.security['access_token_expire_minutes'])
     access_token = create_access_token(
         data={"sub": user.username},
-        expires_delta=access_token_expires)
+        expires_delta=timedelta(seconds=conf.crypto.expire))
     return {"access_token": access_token, "token_type": "bearer"}
 
 @api.get("/users")
 async def get_users(
-    *, db_session: AsyncSession = Depends(get_db_session)
+    db_session: db_session,
     ) -> list[UserRead]:
     query = select(User).options(selectinload(User.roles))
     data = await db_session.exec(query)
@@ -57,7 +76,7 @@ async def get_users(
 
 @api.get("/roles")
 async def get_roles(
-    *, db_session: AsyncSession = Depends(get_db_session)
+    db_session: db_session,
     ) -> list[RoleRead]:
     query = select(Role).options(selectinload(Role.users))
     data = await db_session.exec(query)
@@ -65,7 +84,7 @@ async def get_roles(
 
 @api.get("/categories")
 async def get_categories(
-    *, db_session: AsyncSession = Depends(get_db_session)
+    db_session: db_session,
     ) -> list[CategoryRead]:
     query = select(Category)
     data = await db_session.exec(query)
@@ -74,7 +93,7 @@ async def get_categories(
 
 @api.get("/categories_p")
 async def get_categories_p(
-    *, db_session: AsyncSession = Depends(get_db_session)
+    db_session: db_session,
     ) -> list[CategoryRead]:
     query = select(Category)
     df = await db_session.run_sync(pandas_query, query)
diff --git a/src/application.py b/src/application.py
index 759ac3d..75e9b30 100644
--- a/src/application.py
+++ b/src/application.py
@@ -1,5 +1,14 @@
-from fastapi import Depends, FastAPI
-from .api import api
+from fastapi import FastAPI
+import logging
 
-app = FastAPI()
+from .api import api
+from .config import conf
+
+logging.basicConfig(level=conf.gisaf.debugLevel)
+
+app = FastAPI(
+    debug=True,
+    title=conf.gisaf.title,
+    version=conf.version,
+)
 app.mount('/v2', api)
\ No newline at end of file
diff --git a/src/config.py b/src/config.py
index 5be0631..f486a9b 100644
--- a/src/config.py
+++ b/src/config.py
@@ -1,55 +1,303 @@
 from os import environ
 import logging
 from pathlib import Path
-import yaml
+from typing import Any, Type, Tuple
 
-from sqlalchemy.ext.asyncio.engine import AsyncEngine
-from sqlalchemy.orm.session import sessionmaker
+from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
+from pydantic.v1.utils import deep_update
+from yaml import safe_load
+
+from ._version import __version__
+#from sqlalchemy.ext.asyncio.engine import AsyncEngine
+#from sqlalchemy.orm.session import sessionmaker
 
 logger = logging.getLogger(__name__)
 ENV = environ.get('env', 'prod')
 
+config_files = [
+    Path(Path.cwd().root) / 'etc' / 'gisaf' / ENV,
+    Path.home() / '.local' / 'gisaf' / ENV
+]
 
-class Config:
-    app: dict
-    postgres: dict
-    storage: dict
-    map: dict
-    security: dict
+class DashboardHome(BaseSettings):
+    title: str
+    content_file: str
+    footer_file: str
+
+class GisafConfig(BaseSettings):
+    title: str
+    windowTitle: str
+    debugLevel: str
+    dashboard_home: DashboardHome
+    redirect: str = ''
+
+class SpatialSysRef(BaseSettings):
+    author: str
+    ellps: str
+    k: int
+    lat_0: float
+    lon_0: float
+    no_defs: bool
+    proj: str
+    towgs84: str
+    units: str
+    x_0: float
+    y_0: float
+
+class RawSurvey(BaseSettings):
+    spatial_sys_ref: SpatialSysRef
+    srid: int
+
+class Geo(BaseSettings):
+    raw_survey: RawSurvey
+    simplify_geom_factor: int
+    srid: int
+    srid_for_proj: int
+
+class Flask(BaseSettings):
+    secret_key: str
+    debug: int
+
+class MQTT(BaseSettings):
+    broker: str
+
+class GisafLive(BaseSettings):
+    hostname: str
+    port: int
+    scheme: str
+    redis: str
+    mqtt: MQTT
+
+class DefaultSurvey(BaseSettings):
+    surveyor_id: int
+    equipment_id: int
+
+class Survey(BaseSettings):
+    schema_raw: str
+    schema: str
+    default: DefaultSurvey
+
+class Crypto(BaseSettings):
+    secret: str
+    algorithm: str
+    expire: float
+
+class DB(BaseSettings):
+    uri: str
+    host: str
+    user: str
+    db: str
+    password: str
+    debug: bool
+    info: bool
+    pool_size: int = 10
+    max_overflow: int = 10
+
+class Log(BaseSettings):
+    level: str
+
+class OGCAPILicense(BaseSettings):
+    name: str
+    url: str
+
+class OGCAPIProvider(BaseSettings):
+    name: str
+    url: str
+
+class OGCAPIServerContact(BaseSettings):
+    name: str
+    address: str
+    city: str
+    stateorprovince: str
+    postalcode: int
+    country: str
+    email: str
+
+class OGCAPIIdentification(BaseSettings):
+    title: str
+    description: str
+    keywords: list[str]
+    keywords_type: str
+    terms_of_service: str
+    url: str
+
+class OGCAPIMetadata(BaseSettings):
+    identification: OGCAPIIdentification
+    license: OGCAPILicense
+    provider: OGCAPIProvider
+    contact: OGCAPIServerContact
+
+class ServerBind(BaseSettings):
+    host: str
+    port: int
+
+class OGCAPIServerMap(BaseSettings):
+    url: str
+    attribution: str
+
+class OGCAPIServer(BaseSettings):
+    bind: ServerBind
+    url: str
+    mimetype: str
+    encoding: str
+    language: str
+    pretty_print: bool
+    limit: int
+    map: OGCAPIServerMap
+
+class OGCAPI(BaseSettings):
+    base_url: str
+    bbox: list[float]
+    log: Log
+    metadata: OGCAPIMetadata
+    server: OGCAPIServer
+
+class Map(BaseSettings):
+    tilesBaseDir: str
+    tilesUseRequestUrl: bool
+    tilesSpriteBaseDir: str
+    tilesSpriteUrl: str
+    tilesSpriteBaseUrl: str
+    openMapTilesKey: str
+    zoom: int
+    pitch: int
+    lat: float
+    lng: float
+    bearing: float
+    style: str
+    opacity: float
+    attribution: str
+    status: list[str]
+    defaultStatus: list[str] # FIXME: should be str
+    tagKeys: list[str]
+
+class Measures(BaseSettings):
+    defaultStore: str
+
+class BasketDefault(BaseSettings):
+    surveyor: str
+    equipment: str
+    project: str | None
+    status: str
+    store: str | None
+
+class BasketOldDef(BaseSettings):
+    base_dir: str
+
+class Basket(BaseSettings):
+    base_dir: str
+    default: BasketDefault
+
+class Plot(BaseSettings):
+    maxDataSize: int
+
+class Dashboard(BaseSettings):
+    base_source_url: str
+    base_storage_dir: str
+    base_storage_url: str
+
+class Widgets(BaseSettings):
+    footer: str
+
+class Admin(BaseSettings):
+    basket: Basket
+
+class Attachments(BaseSettings):
+    base_dir: str
+
+class Job(BaseSettings):
+    id: str
+    func: str
+    trigger: str
+    minutes: int | None = 0
+    seconds: int | None = 0
+
+class Config(BaseSettings):
+    @classmethod
+    def settings_customise_sources(
+        cls,
+        settings_cls: Type[BaseSettings],
+        init_settings: PydanticBaseSettingsSource,
+        env_settings: PydanticBaseSettingsSource,
+        dotenv_settings: PydanticBaseSettingsSource,
+        file_secret_settings: PydanticBaseSettingsSource,
+    ) -> Tuple[PydanticBaseSettingsSource, ...]:
+        return env_settings, init_settings, file_secret_settings, config_file_settings
+
+    admin: Admin
+    attachments: Attachments
+    basket: BasketOldDef
+    crypto: Crypto
+    dashboard: Dashboard
+    db: DB
+    flask: Flask
+    geo: Geo
+    gisaf: GisafConfig
+    gisaf_live: GisafLive
+    jobs: list[Job]
+    map: Map
+    measures: Measures
+    ogcapi: OGCAPI
+    plot: Plot
+    plugins: dict[str, dict[str, Any]]
+    survey: Survey
     version: str
-    engine: AsyncEngine
-    session_maker: sessionmaker
-
-    def __init__(self) -> None:
-        from ._version import __version__
-        self.version = __version__
+    weather_station: dict[str, dict[str, Any]]
+    widgets: Widgets
+    #engine: AsyncEngine
+    #session_maker: sessionmaker
 
 
-conf = Config()
+def config_file_settings() -> dict[str, Any]:
+    config: dict[str, Any] = {}
+    for p in config_files:
+        for suffix in {".yaml", ".yml"}:
+            path = p.with_suffix(suffix)
+            if not path.is_file():
+                logger.info(f"No file found at `{path.resolve()}`")
+                continue
+            logger.debug(f"Reading config file `{path.resolve()}`")
+            if path.suffix in {".yaml", ".yml"}:
+                config = deep_update(config, load_yaml(path))
+            else:
+                logger.info(f"Unknown config file extension `{path.suffix}`")
+    return config
 
 
-def set_app_config(app) -> None:
-    raw_configs = []
-    with open(Path(__file__).parent / 'defaults.yml') as cf:
-        raw_configs.append(cf.read())
-    for cf_path in (
-        Path(Path.cwd().root) / 'etc' / 'gisaf' / ENV,
-        Path.home() / '.local' / 'gisaf' / ENV
-    ):
-        try:
-            with open(cf_path.with_suffix('.yml')) as cf:
-                raw_configs.append(cf.read())
-        except FileNotFoundError:
-            pass
+def load_yaml(path: Path) -> dict[str, Any]:
+    with Path(path).open("r") as f:
+        config = safe_load(f)
+    if not isinstance(config, dict):
+        raise TypeError(
+            f"Config file has no top-level mapping: {path}"
+        )
+    return config
 
-    yaml_config = yaml.safe_load('\n'.join(raw_configs))
 
-    conf.app = yaml_config['app']
-    conf.postgres = yaml_config['postgres']
-    conf.storage = yaml_config['storage']
-    conf.map = yaml_config['map']
-    conf.security = yaml_config['security']
-    # create_dirs()
+conf = Config(version=__version__)
+
+# def set_app_config(app) -> None:
+#     raw_configs = []
+#     with open(Path(__file__).parent / 'defaults.yml') as cf:
+#         raw_configs.append(cf.read())
+#     for cf_path in (
+#         Path(Path.cwd().root) / 'etc' / 'gisaf' / ENV,
+#         Path.home() / '.local' / 'gisaf' / ENV
+#     ):
+#         try:
+#             with open(cf_path.with_suffix('.yml')) as cf:
+#                 raw_configs.append(cf.read())
+#         except FileNotFoundError:
+#             pass
+
+#     yaml_config = safe_load('\n'.join(raw_configs))
+
+#     conf.app = yaml_config['app']
+#     conf.postgres = yaml_config['postgres']
+#     conf.storage = yaml_config['storage']
+#     conf.map = yaml_config['map']
+#     conf.security = yaml_config['security']
+#     # create_dirs()
 
 
 # def create_dirs():
@@ -65,5 +313,5 @@ def set_app_config(app) -> None:
 #     get_cache_dir().mkdir(parents=True, exist_ok=True)
 
 
-def get_cache_dir() -> Path:
-    return Path(conf.storage['root_cache_path'])
\ No newline at end of file
+# def get_cache_dir() -> Path:
+#     return Path(conf.storage['root_cache_path'])
\ No newline at end of file
diff --git a/src/database.py b/src/database.py
index cb5bf0d..9433ea2 100644
--- a/src/database.py
+++ b/src/database.py
@@ -1,14 +1,28 @@
+from contextlib import asynccontextmanager
+
 from sqlalchemy.ext.asyncio import create_async_engine
 from sqlmodel.ext.asyncio.session import AsyncSession
 
 import pandas as pd
 
+from .config import conf
+
 echo = False
 pg_url = "postgresql+asyncpg://avgis@localhost/avgis"
 
-engine = create_async_engine(pg_url, echo=echo)
+engine = create_async_engine(
+    pg_url,
+    echo=echo,
+    pool_size=conf.db.pool_size,
+    max_overflow=conf.db.max_overflow,
+)
 
-async def get_db_session():
+async def get_db_session() -> AsyncSession:
+    async with AsyncSession(engine) as session:
+        yield session
+
+@asynccontextmanager
+async def db_session() -> AsyncSession:
     async with AsyncSession(engine) as session:
         yield session
 
diff --git a/src/models/authentication.py b/src/models/authentication.py
index 1eef017..39c7047 100644
--- a/src/models/authentication.py
+++ b/src/models/authentication.py
@@ -1,10 +1,9 @@
 from sqlmodel import Field, SQLModel, MetaData, Relationship
 
-schema = 'gisaf_admin'
-metadata = MetaData(schema=schema)
+from .metadata import gisaf_admin
 
 class UserRoleLink(SQLModel, table=True):
-    metadata = metadata
+    metadata = gisaf_admin
     __tablename__: str = 'roles_users'
     user_id: int | None = Field(
         default=None, foreign_key="user.id", primary_key=True
@@ -20,7 +19,7 @@ class UserBase(SQLModel):
 
 
 class User(UserBase, table=True):
-    metadata = metadata
+    metadata = gisaf_admin
     id: int | None = Field(default=None, primary_key=True)
     roles: list["Role"] = Relationship(back_populates="users",
                                        link_model=UserRoleLink)
@@ -34,7 +33,7 @@ class RoleWithDescription(RoleBase):
     description: str | None
 
 class Role(RoleWithDescription, table=True):
-    metadata = metadata
+    metadata = gisaf_admin
     id: int | None = Field(default=None, primary_key=True)
     users: list[User] = Relationship(back_populates="roles",
                                      link_model=UserRoleLink)
diff --git a/src/models/bootstrap.py b/src/models/bootstrap.py
new file mode 100644
index 0000000..d2dff89
--- /dev/null
+++ b/src/models/bootstrap.py
@@ -0,0 +1,17 @@
+from sqlmodel import Field, SQLModel, MetaData, JSON, TEXT, Relationship, Column
+from ..config import conf, Map, Measures, Geo
+from .authentication import UserRead
+
+class Proj(SQLModel):
+    srid: str
+    srid_for_proj: str
+
+class BootstrapData(SQLModel):
+    version: str = conf.version
+    title: str = conf.gisaf.title
+    windowTitle: str = conf.gisaf.windowTitle
+    map: Map = conf.map
+    geo: Geo = conf.geo
+    measures: Measures = conf.measures
+    redirect: str = conf.gisaf.redirect
+    user: UserRead | None = None
\ No newline at end of file
diff --git a/src/models/category.py b/src/models/category.py
index d3511f7..74e24fe 100644
--- a/src/models/category.py
+++ b/src/models/category.py
@@ -1,8 +1,8 @@
 from typing import Any
 from sqlmodel import Field, SQLModel, MetaData, JSON, TEXT, Relationship, Column
+from pydantic import computed_field
 
-schema = 'gisaf_survey'
-metadata = MetaData(schema=schema)
+from .metadata import gisaf_survey
 
 mapbox_type_mapping = {
     'Point': 'symbol',
@@ -11,7 +11,7 @@ mapbox_type_mapping = {
 }
 
 class CategoryGroup(SQLModel, table=True):
-    metadata = metadata
+    metadata = gisaf_survey
     name: str = Field(min_length=4, max_length=4,
                       default=None, primary_key=True)
     major: str
@@ -23,7 +23,7 @@ class CategoryGroup(SQLModel, table=True):
 
 
 class CategoryModelType(SQLModel, table=True):
-    metadata = metadata
+    metadata = gisaf_survey
     name: str = Field(default=None, primary_key=True)
 
     class Admin:
@@ -32,8 +32,6 @@ class CategoryModelType(SQLModel, table=True):
 
 
 class CategoryBase(SQLModel):
-    metadata = metadata
-
     class Admin:
         menu = 'Other'
         flask_admin_model_view = 'CategoryModelView'
@@ -49,35 +47,39 @@ class CategoryBase(SQLModel):
     custom: bool | None
     auto_import: bool = True
     model_type: str = Field(max_length=50,
-                            foreign_key='CategoryModelType.name', default='Point')
+                            foreign_key='CategoryModelType.name',
+                            default='Point')
     long_name: str | None = Field(max_length=50)
     style: str | None = Field(sa_column=Column(TEXT))
     symbol: str | None = Field(max_length=1)
     mapbox_type_custom: str | None = Field(max_length=32)
-    mapbox_paint: dict[str, Any] | None = Field(sa_column=Column(JSON, none_as_null=True))
-    mapbox_layout: dict[str, Any] | None = Field(sa_column=Column(JSON, none_as_null=True))
+    mapbox_paint: dict[str, Any] | None = Field(sa_column=Column(JSON(none_as_null=True)))
+    mapbox_layout: dict[str, Any] | None = Field(sa_column=Column(JSON(none_as_null=True)))
     viewable_role: str | None
-    extra: dict[str, Any] | None = Field(sa_column=Column(JSON, none_as_null=True))
+    extra: dict[str, Any] | None = Field(sa_column=Column(JSON(none_as_null=True)))
 
 
 class Category(CategoryBase, table=True):
+    metadata = gisaf_survey
     name: str = Field(default=None, primary_key=True)
 
 
 class CategoryRead(CategoryBase):
     name: str
-    domain = 'V'  # Survey
+    domain: str = 'V'  # Survey
 
+    @computed_field
     @property
-    def layer_name(self):
+    def layer_name(self) -> str:
         """
         ISO compliant layer name (see ISO 13567)
         :return: str
         """
         return '{self.domain}-{self.group:4s}-{self.minor_group_1:4s}-{self.minor_group_2:4s}-{self.status:1s}'.format(self=self)
 
+    @computed_field
     @property
-    def table_name(self):
+    def table_name(self) -> str:
         """
         Table name
         :return:
@@ -87,8 +89,9 @@ class CategoryRead(CategoryBase):
         else:
             return '{self.domain}_{self.group:4s}_{self.minor_group_1:4s}_{self.minor_group_2:4s}'.format(self=self)
 
+    @computed_field
     @property
-    def raw_survey_table_name(self):
+    def raw_survey_table_name(self) -> str:
         """
         Table name
         :return:
@@ -98,6 +101,7 @@ class CategoryRead(CategoryBase):
         else:
             return 'RAW_{self.domain}_{self.group:4s}_{self.minor_group_1:4s}_{self.minor_group_2:4s}'.format(self=self)
 
+    @computed_field
     @property
-    def mapbox_type(self):
+    def mapbox_type(self) -> str:
         return self.mapbox_type_custom or mapbox_type_mapping[self.model_type]
diff --git a/src/models/metadata.py b/src/models/metadata.py
new file mode 100644
index 0000000..38474ab
--- /dev/null
+++ b/src/models/metadata.py
@@ -0,0 +1,5 @@
+from sqlmodel import MetaData
+
+gisaf = MetaData(schema='gisaf')
+gisaf_survey = MetaData(schema='gisaf_survey')
+gisaf_admin= MetaData(schema='gisaf_admin')
\ No newline at end of file
diff --git a/src/models/tags.py b/src/models/tags.py
new file mode 100644
index 0000000..f051f0b
--- /dev/null
+++ b/src/models/tags.py
@@ -0,0 +1,9 @@
+from typing import Any
+from sqlmodel import Field, SQLModel, MetaData, JSON, TEXT, Relationship, Column
+from pydantic import computed_field
+
+from .metadata import gisaf
+from .models_base import GeoPointModel
+
+class Tags(GeoPointModel, table=True):
+    metadata = gisaf
\ No newline at end of file
diff --git a/src/security.py b/src/security.py
index 4ecf08a..95cd929 100644
--- a/src/security.py
+++ b/src/security.py
@@ -1,21 +1,26 @@
 from datetime import datetime, timedelta
-from passlib.context import CryptContext
+import logging
+from typing import Annotated
+#from passlib.context import CryptContext
 
 from fastapi import Depends, HTTPException, status
 from fastapi.security import OAuth2PasswordBearer
+from passlib.context import CryptContext
+from passlib.exc import UnknownHashError
 from pydantic import BaseModel
-from jose import JWTError, jwt
+from sqlmodel.ext.asyncio.session import AsyncSession
+from jose import JWTError, jwt, ExpiredSignatureError
 
-from sqlalchemy.future import select
+from sqlalchemy import select
+from sqlalchemy.orm import selectinload
 
 from .config import conf
-from .models.authentication import User as UserInDB
+from .database import db_session
+from .models.authentication import User, UserRead
 
+logger = logging.getLogger(__name__)
 
-# openssl rand -hex 32
-# import secrets
-# SECRET_KEY = secrets.token_hex(32)
-ALGORITHM: str = "HS256"
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
 
 
 class Token(BaseModel):
@@ -27,105 +32,118 @@ class TokenData(BaseModel):
     username: str | None = None
 
 
-class User(BaseModel):
-    username: str
-    email: str | None = None
-    full_name: str | None = None
-    disabled: bool | None = None
+# class User(BaseModel):
+#     username: str
+#     email: str | None = None
+#     full_name: str | None = None
+#     disabled: bool | None = None
 
 
-oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
-
-pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
 
 
 def get_password_hash(password: str):
     return pwd_context.hash(password)
 
 
-async def delete_user(username):
-    async with conf.session_maker.begin() as session:
-        user_in_db: UserInDB | None = await get_user(username)
-        if user_in_db is None:
-            raise SystemExit(f'User {username} does not exist in the database')
-        await session.delete(user_in_db)
+async def delete_user(session: AsyncSession, username: str) -> None:
+    user_in_db: User | None = await get_user(session, username)
+    if user_in_db is None:
+        raise SystemExit(f'User {username} does not exist in the database')
+    await session.delete(user_in_db)
 
 
-async def enable_user(username, enable=True):
-    async with conf.session_maker.begin() as session:
-        user_in_db: UserInDB | None = await get_user(username)
-        if user_in_db is None:
-            raise SystemExit(f'User {username} does not exist in the database')
-        user_in_db.disabled = not enable # type: ignore
-        session.add(user_in_db)
-        await session.commit()
+async def enable_user(session: AsyncSession, username: str, enable=True):
+    user_in_db: UserRead | None = await get_user(session, username)
+    if user_in_db is None:
+        raise SystemExit(f'User {username} does not exist in the database')
+    user_in_db.disabled = not enable # type: ignore
+    session.add(user_in_db)
+    await session.commit()
 
 
-async def create_user(username: str, password: str, full_name: str,
+async def create_user(session: AsyncSession, username: str, password: str, full_name: str,
                       email: str, **kwargs):
-    async with conf.session_maker.begin() as session:
-        user_in_db: UserInDB | None = await get_user(username)
-        if user_in_db is None:
-            user = UserInDB(
-                username=username,
-                password=get_password_hash(password),
-                full_name=full_name,
-                email=email,
-                disabled=False
-            )
-            session.add(user)
-        else:
-            user_in_db.full_name = full_name # type: ignore
-            user_in_db.email = email # type: ignore
-            user_in_db.password = get_password_hash(password) # type: ignore
-        await session.commit()
+    user_in_db: User | None = await get_user(session, username)
+    if user_in_db is None:
+        user = User(
+            username=username,
+            password=get_password_hash(password),
+            full_name=full_name,
+            email=email,
+            disabled=False
+        )
+        session.add(user)
+    else:
+        user_in_db.full_name = full_name # type: ignore
+        user_in_db.email = email # type: ignore
+        user_in_db.password = get_password_hash(password) # type: ignore
+    await session.commit()
 
 
-async def get_user(username: str) -> (UserInDB | None):
-    async with conf.session_maker.begin() as session:
-        req = await session.execute(select(UserInDB).where(UserInDB.username==username))
-        return req.scalar()
+async def get_user(
+        session: AsyncSession,
+        username: str) -> (User | None):
+    query = select(User).where(User.username==username).options(selectinload(User.roles))
+    data = await session.exec(query)
+    return data.scalar()
 
 
-def verify_password(plain_password, hashed_password):
-    return pwd_context.verify(plain_password, hashed_password)
+def verify_password(user: User, plain_password):
+    try:
+        return pwd_context.verify(plain_password, user.password)
+    except UnknownHashError:
+        logger.warning(f'Password not encrypted in DB for {user.username}, assuming it is stored in plain text')
+        return plain_password == user.password
 
 
-async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
+async def get_current_user(
+        token: str = Depends(oauth2_scheme)) -> UserRead | None:
     credentials_exception = HTTPException(
         status_code=status.HTTP_401_UNAUTHORIZED,
         detail="Could not validate credentials",
         headers={"WWW-Authenticate": "Bearer"},
     )
+    expired_exception = HTTPException(
+        status_code=status.HTTP_401_UNAUTHORIZED,
+        detail="Token has expired",
+        headers={"WWW-Authenticate": "Bearer"},
+    )
+    if token is None:
+        return None
     try:
-        payload = jwt.decode(token, conf.security['secret_key'], algorithms=[ALGORITHM])
+        payload = jwt.decode(token, conf.crypto.secret,
+                             algorithms=[conf.crypto.algorithm])
         username: str = payload.get("sub", '')
         if username == '':
             raise credentials_exception
         token_data = TokenData(username=username)
+    except ExpiredSignatureError:
+        raise expired_exception
     except JWTError:
         raise credentials_exception
-    user = await get_user(username=token_data.username) # type: ignore
-    if user is None:
-        raise credentials_exception
-    return User(username=user.username, # type: ignore
-                email=user.email, # type: ignore
-                full_name=user.full_name) # type: ignore
+    async with db_session() as session:
+        user = await get_user(session, username=token_data.username)
+        if user is None:
+            raise credentials_exception
+        return user
 
 
 async def authenticate_user(username: str, password: str):
-    user = await get_user(username)
-    if not user:
-        return False
-    if not verify_password(password, user.password):
-        return False
-    return user
+    async with db_session() as session:
+        user = await get_user(session, username)
+        if not user:
+            return False
+        if not verify_password(user, password):
+            return False
+        return user
 
 
-async def get_current_active_user(current_user: User = Depends(get_current_user)):
-    if current_user.disabled:
-        raise HTTPException(status_code=400, detail="Inactive user")
-    return current_user
+# async def get_current_active_user(
+#         current_user: Annotated[UserRead, Depends(get_current_user)]):
+#     if current_user.disabled:
+#         raise HTTPException(status_code=400, detail="Inactive user")
+#     return current_user
 
 
 def create_access_token(data: dict, expires_delta: timedelta):
@@ -133,6 +151,6 @@ def create_access_token(data: dict, expires_delta: timedelta):
     expire = datetime.utcnow() + expires_delta
     to_encode.update({"exp": expire})
     encoded_jwt = jwt.encode(to_encode,
-                             conf.security['secret_key'],
-                             algorithm=ALGORITHM)
+                             conf.crypto.secret,
+                             algorithm=conf.crypto.algorithm)
     return encoded_jwt