Repository: dscc-admin-ch/lomas (language: python)

Issue:
Streamlit local deployment
Added streamlit container for local deployment. Added mounts for two config types for the streamlit server. Changed user to non-root in server/streamlit container images.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index a79d8afe..f6a67b4a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
-poc_server/*
-server/src/tests/test_data/local_db_file_*
+server/lomas_server/tests/test_data/local_db_file_*
+server/lomas_server/.streamlit/*
 client/notebooks/personal_notebooks/*
 
 # Byte-compiled / optimized / DLL files
diff --git a/client/lomas_client/client.py b/client/lomas_client/client.py
index dba85727..41859d5f 100644
--- a/client/lomas_client/client.py
+++ b/client/lomas_client/client.py
@@ -60,9 +60,7 @@ def get_dataset_metadata(
             Optional[Dict[str, Union[int, bool, Dict[str, Union[str, int]]]]]:
                 _description_
         """
-        res = self._exec(
-            "get_dataset_metadata", {"dataset_name": self.dataset_name}
-        )
+        res = self._exec("get_dataset_metadata", {"dataset_name": self.dataset_name})
         if res.status_code == 200:
             data = res.content.decode("utf8")
             metadata = json.loads(data)
@@ -235,9 +233,7 @@ def opendp_query(
             # we handle the latter here.
             # This is a hack for now, maybe use parquet to send results over.
             if isinstance(response_dict["query_response"], str):
-                raise Exception(
-                    "Not implemented: server should not return dataframes"
-                )
+                raise Exception("Not implemented: server should not return dataframes")
                 # Note: leaving this here. Support for opendp_polars
                 # response_dict["query_response"] = polars.read_json(
                 #    StringIO(response_dict["query_response"])
@@ -344,9 +340,7 @@ def get_previous_queries(self) -> Optional[List[dict]]:
         res = self._exec("get_previous_queries", body_json)
 
         if res.status_code == 200:
-            queries = json.loads(res.content.decode("utf8"))[
-                "previous_queries"
-            ]
+            queries = json.loads(res.content.decode("utf8"))["previous_queries"]
 
             if not queries:
                 return queries
diff --git a/client/notebooks/Demo_Client_Notebook.ipynb b/client/notebooks/Demo_Client_Notebook.ipynb
index 74c99c82..027c5c5f 100644
--- a/client/notebooks/Demo_Client_Notebook.ipynb
+++ b/client/notebooks/Demo_Client_Notebook.ipynb
@@ -432,7 +432,7 @@
      "output_type": "stream",
      "text": [
       "Average bill length in remote dummy: 47.52mm.\n",
-      "Number of rows in remote dummy: 200.\n"
+      "Number of rows in remote dummy: 199.\n"
      ]
     }
    ],
@@ -469,7 +469,7 @@
     {
      "data": {
       "text/plain": [
-       "{'initial_epsilon': 10, 'initial_delta': 0.005}"
+       "{'initial_epsilon': 10.0, 'initial_delta': 0.005}"
       ]
      },
      "execution_count": 11,
@@ -500,7 +500,7 @@
     {
      "data": {
       "text/plain": [
-       "{'total_spent_epsilon': 0, 'total_spent_delta': 0}"
+       "{'total_spent_epsilon': 0.0, 'total_spent_delta': 0.0}"
       ]
      },
      "execution_count": 12,
@@ -529,7 +529,7 @@
     {
      "data": {
       "text/plain": [
-       "{'remaining_epsilon': 10, 'remaining_delta': 0.005}"
+       "{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}"
       ]
      },
      "execution_count": 13,
@@ -626,7 +626,7 @@
     {
      "data": {
       "text/plain": [
-       "{'remaining_epsilon': 10, 'remaining_delta': 0.005}"
+       "{'remaining_epsilon': 10.0, 'remaining_delta': 0.005}"
       ]
      },
      "execution_count": 16,
@@ -663,8 +663,8 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Number of penguins in real data: 342.\n",
-      "Average bill length of penguins in real data: 43.13mm.\n"
+      "Number of penguins in real data: 343.\n",
+      "Average bill length of penguins in real data: 44.12mm.\n"
      ]
     }
    ],
diff --git a/server/CONTRIBUTING.md b/server/CONTRIBUTING.md
index 072520f8..dadb8d15 100644
--- a/server/CONTRIBUTING.md
+++ b/server/CONTRIBUTING.md
@@ -1,5 +1,7 @@
 # Tips for developers
 
+It is recommended to run the project with python 3.11.
+
 ## Start service
 
 The different notebooks in `./notebooks` provide more complete documentation regarding the set up and administration of the server.
@@ -8,6 +10,11 @@ To start the container on a local machine, first create a mondodb volume
 with `docker volume create mongodata`, then go to `lomas/server/` and run `docker compose up`. 
 If you encounter any issue, you might want to run `docker compose down` first.
 
+#### Additional services
+Running `docker compose up` will also start two additional services automatically:
+- a jupyter notebook environment that will be available at the address http://127.0.0.1:8888/ to interact as a user with the server
+- a streamlit application that will be available at the address http://localhost:8501/ to interact with the server and the administration database as an administrator.
+
 ### On a kubernetes cluster
 To start the server on a kubernetes cluster, first add the repo with:
 `helm repo add lomas https://dscc-admin-ch.github.io/helm-charts`
@@ -20,10 +27,11 @@ To start the server on Onyxia, select the `lomas `service, (optionnally adapt th
 ## Tests
 
 It is possible to test the server with standard tests or integration tests (for the mongodb).
-The `run_integration_tests.sh` script runs the integration tests, make sure to have an activated python venv
-with the server requirements installed for it to work.
+The `run_integration_tests.sh` script runs the integration tests, make sure to have an activated python venv in a linux environment with the server requirements installed for it to work.
+
+Local tests (except for those using the mongodb_admin) can be run with a simple `python -m unittest discover -s . ` from the `lomas_server` directory. The tests will be based on the config in `lomas/server/lomas_server/tests/test_configs/test_config.yaml` and be executed with the AdminYamlDatabase. 
+For local tests on the mongodb_admin, first get in the container with `docker exec -it lomas_server_dev bash` and then run the tests.
 
-Local tests can be run with a simple `python -m unittest -s discover . ` from the `lomas_server` directory. The tests will be based on the config in `lomas/server/lomas_server/tests/test_configs/test_config.yaml` and be executed with the AdminYamlDatabase. 
 
 A github workflow is configured to run the integration tests script for pull requests on the develop and master branches.
 
@@ -33,7 +41,7 @@ Here is a list of the checks performed:
     - Use black to automatically format the code: `black .`
     - Use flake to verify formating and performing a static code analysis: `flake8 .`
     - Use mypy for static type checking: `mypy .`
-    - Use pylint for further static analysis: `pylint . --disable=E0401 --disable=C0114 --disable=C0115 --disable=C0116 --disable=R0903 --disable=C0301`
+    - Use pylint for further static analysis: `pylint . --disable=E0401 --disable=C0114 --disable=R0903 --disable=C0301`
 
 We rely on a github workflow to automatically run the checks on pull requests.
 
@@ -46,4 +54,3 @@ The process goes in two steps
 
 We do not check the documentation files into the repo, but rather rely on a github workflow to generate and publish it 
 on a dedicated repo's github pages for easy access from the web.
-
diff --git a/server/Dockerfile b/server/Dockerfile
index c525ed69..0922f37a 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -4,15 +4,37 @@ FROM python:3.11 as lomas_server_base
 # To install missing packages
 COPY ./requirements.txt /requirements.txt
 RUN pip install --no-cache-dir --upgrade -r requirements.txt 
-#ENV IN_CONTAINER=1
 
 WORKDIR /code
+
 FROM lomas_server_base AS lomas_server
+# Switch to non-root user
+USER 1000
 COPY ./lomas_server/ /code/
 COPY ./LICENSE /code/LICENSE
 COPY ./data/ /data/
 CMD ["python", "uvicorn_serve.py"]
 
 FROM lomas_server_base AS lomas_server_dev
+# Switch to non-root user
+USER 1000
+ENV PYTHONDONTWRITEBYTECODE 1
+CMD ["python", "uvicorn_serve.py"]
+
+FROM lomas_server_base AS lomas_streamlit_base
+COPY ./requirements_streamlit.txt /requirements_streamlit.txt
+RUN pip install --no-cache-dir --upgrade -r /requirements_streamlit.txt
+
+FROM lomas_streamlit_base AS lomas_streamlit
+# Switch to non-root user
+USER 1000
+COPY ./lomas_server/ /code/
+COPY ./LICENSE /code/LICENSE
+COPY ./data/ /data/
+CMD ["streamlit", "run", "administration/dashboard/about.py"]
+
+FROM lomas_streamlit_base AS lomas_streamlit_dev
+# Switch to non-root user
+USER 1000
 ENV PYTHONDONTWRITEBYTECODE 1
-CMD ["python", "uvicorn_serve.py"]
\ No newline at end of file
+CMD ["streamlit", "run", "administration/dashboard/about.py"]
diff --git a/server/README.md b/server/README.md
index c561c8eb..76ac08dd 100644
--- a/server/README.md
+++ b/server/README.md
@@ -1,3 +1,1 @@
-# Administration Database
-
-TODO: describe 4 collections (and in readme or in sphinx, describe CLI commands)
\ No newline at end of file
+# Server
diff --git a/server/admin_requirements.txt b/server/admin_requirements.txt
index 0b8a08d6..31611654 100644
--- a/server/admin_requirements.txt
+++ b/server/admin_requirements.txt
@@ -2,7 +2,7 @@ annotated-types==0.5.0
 anyio==3.7.1
 asttokens==2.4.0
 backcall==0.2.0
-boto3==1.34.39
+boto3==1.34.115
 comm==0.1.4
 debugpy==1.7.0
 decorator==5.1.1
@@ -18,7 +18,11 @@ jupyter_client==8.3.1
 jupyter_core==5.3.1
 matplotlib-inline==0.1.6
 nest-asyncio==1.5.7
+numpy==1.26.4
+opendp==0.8.0
+opendp-logger==0.3.0
 packaging==23.1
+pandas==2.2.2
 parso==0.8.3
 pexpect==4.8.0
 pickleshare==0.7.5
@@ -36,10 +40,13 @@ python-dateutil==2.8.2
 PyYAML==6.0.1
 pyzmq==25.1.1
 six==1.16.0
+smartnoise-sql==1.0.3
 sniffio==1.3.0
 stack-data==0.6.2
 starlette==0.27.0
 tornado==6.3.3
 traitlets==5.9.0
 typing_extensions==4.7.1
+urllib3==1.26.15
+uvicorn==0.29.0
 wcwidth==0.2.6
diff --git a/server/configs/example_streamlit_config.yaml b/server/configs/example_streamlit_config.yaml
new file mode 100644
index 00000000..ceac5b3a
--- /dev/null
+++ b/server/configs/example_streamlit_config.yaml
@@ -0,0 +1,1 @@
+# TODO Config for the streamlit python code would belong here.
\ No newline at end of file
diff --git a/server/configs/example_streamlit_server_config.toml b/server/configs/example_streamlit_server_config.toml
new file mode 100644
index 00000000..2d63868a
--- /dev/null
+++ b/server/configs/example_streamlit_server_config.toml
@@ -0,0 +1,5 @@
+[server]
+address = "0.0.0.0"
+port = "8501"
+# Set this when there is a base path before the streamlit root address.
+# baseUrlPath = "..."
diff --git a/server/data/collections/user_collection.yaml b/server/data/collections/user_collection.yaml
index 798c7da0..4cab0517 100644
--- a/server/data/collections/user_collection.yaml
+++ b/server/data/collections/user_collection.yaml
@@ -3,12 +3,12 @@ users:
   may_query: True
   datasets_list:
   - dataset_name: "IRIS"
-    initial_epsilon: 10
+    initial_epsilon: 10.0
     initial_delta: 0.0001
-    total_spent_epsilon: 1
+    total_spent_epsilon: 1.0
     total_spent_delta: 0.000001
   - dataset_name: "PENGUIN"
-    initial_epsilon: 5
+    initial_epsilon: 5.0
     initial_delta: 0.0005
     total_spent_epsilon: 0.2
     total_spent_delta: 0.0000001
@@ -16,31 +16,31 @@ users:
   may_query: True
   datasets_list:
   - dataset_name: "PENGUIN"
-    initial_epsilon: 10
+    initial_epsilon: 10.0
     initial_delta: 0.005
-    total_spent_epsilon: 0
-    total_spent_delta: 0
+    total_spent_epsilon: 0.0
+    total_spent_delta: 0.0
 - user_name: Dr. FSO
   may_query: True
   datasets_list:
   - dataset_name: "FSO_INCOME_SYNTHETIC"
-    initial_epsilon: 45
+    initial_epsilon: 45.0
     initial_delta: 0.005
-    total_spent_epsilon: 0
-    total_spent_delta: 0
+    total_spent_epsilon: 0.0
+    total_spent_delta: 0.0
 - user_name: Bob
   may_query: True
   datasets_list:
   - dataset_name: "IRIS"
-    initial_epsilon: 10
+    initial_epsilon: 10.0
     initial_delta: 0.0001
-    total_spent_epsilon: 0
-    total_spent_delta: 0
+    total_spent_epsilon: 0.0
+    total_spent_delta: 0.0
 - user_name: Jack
   may_query: True
   datasets_list:
   - dataset_name: "TITANIC"
-    initial_epsilon: 45
+    initial_epsilon: 45.0
     initial_delta: 0.2
-    total_spent_epsilon: 0
-    total_spent_delta: 0
\ No newline at end of file
+    total_spent_epsilon: 0.0
+    total_spent_delta: 0.0
\ No newline at end of file
diff --git a/server/docker-compose.yml b/server/docker-compose.yml
index 6b21d593..1c12ea58 100644
--- a/server/docker-compose.yml
+++ b/server/docker-compose.yml
@@ -40,6 +40,26 @@ services:
       - MONGODB_INITDB_ROOT_PASSWORD=root_pwd
       - MONGODB_INITDB_DATABASE=defaultdb
       # add user https://stackoverflow.com/questions/42912755/how-to-create-a-db-for-mongodb-container-on-start-up/54064268#54064268
+  lomas_streamlit:
+    image: dsccadminch/lomas_streamlit_dev
+    build:
+      dockerfile: ./Dockerfile
+      target: lomas_streamlit_dev
+    container_name: lomas_streamlit_dev
+    ports:
+      - 8501:8501
+    restart: always
+    volumes:
+      - ./lomas_server:/code/
+      - ./configs/example_config.yaml:/usr/lomas_server/runtime.yaml
+      - ./configs/example_secrets.yaml:/usr/lomas_server/secrets.yaml
+      - ./configs/example_secrets.yaml:/usr/lomas_server/streamlit.yaml
+      - ./configs/example_streamlit_server_config.toml:/code/.streamlit/config.toml
+      - ./data/:/data/
+    depends_on:
+      - lomas_server
+    networks:
+      - lomas-network
   client:
     image: dsccadminch/lomas_client_dev
     build:
diff --git a/server/lomas_server/admin_database/utils.py b/server/lomas_server/admin_database/utils.py
index 410499d5..37631b97 100644
--- a/server/lomas_server/admin_database/utils.py
+++ b/server/lomas_server/admin_database/utils.py
@@ -1,8 +1,13 @@
+from types import SimpleNamespace
+
+from pymongo import MongoClient
+from pymongo.database import Database
+
 from admin_database.admin_database import AdminDatabase
 from admin_database.mongodb_database import AdminMongoDatabase
 from admin_database.yaml_database import AdminYamlDatabase
 from constants import AdminDBType
-from utils.config import DBConfig
+from utils.config import DBConfig, get_config
 from utils.error_handler import InternalServerException
 
 
@@ -60,3 +65,18 @@ def get_mongodb_url(config: DBConfig) -> str:
     )
 
     return db_url
+
+
+def get_mongodb() -> Database:
+    """Get URL of the administration MongoDB.
+
+    Args:
+        config (DBConfig): An instance of DBConfig.
+
+    Returns:
+        str: A correctly formatted url for connecting to the
+            MongoDB database.
+    """
+    db_args = SimpleNamespace(**vars(get_config().admin_database))
+    db_url = get_mongodb_url(db_args)
+    return MongoClient(db_url)[db_args.db_name]
diff --git a/server/lomas_server/administration/README.md b/server/lomas_server/administration/README.md
new file mode 100644
index 00000000..cba4b0ac
--- /dev/null
+++ b/server/lomas_server/administration/README.md
@@ -0,0 +1,159 @@
+# MongoDB CLI Documentation
+
+## Introduction
+This is a command-line interface (CLI) tool designed to administer a MongoDB database. It provides functionalities for managing users, datasets, metatada and queries_archive collections within the MongoDB environment.
+
+NOTE: it is possible to use a streamlit app to interact with the database instead.
+
+## Usage
+The CLI provides several subcommands for different operations. Below are the available commands and their descriptions:
+
+### MongoDB Connection
+
+The CLI requires connection parameters to establish a connection with MongoDB. These parameters include:
+
+- `-db_u, --username`: MongoDB username (default: "user")
+- `-db_pwd, --password`: MongoDB password (default: "user_pwd")
+- `-db_a, --address`: MongoDB server address (default: "mongodb")
+- `-db_p, --port`: MongoDB server port (default: 27017)
+- `-db_n, --db_name`: MongoDB database name (default: "defaultdb")
+
+### MongoDB Administration
+
+#### Users
+
+- `add_user`: Add a user to the users collection.
+  - `-u, --user`: Username of the user to be added (required).
+- `add_user_with_budget`: Add a user with a budget to the users collection.
+  - `-u, --user`: Username of the user to be added (required).
+  - `-d, --dataset`: Name of the dataset for the user (required).
+  - `-e, --epsilon`: Epsilon value for the dataset (required).
+  - `-del, --delta`: Delta value for the dataset (required).
+- `del_user`: Delete a user from the users collection.
+  - `-u, --user`: Username of the user to be deleted (required).
+- `add_dataset_to_user`: Add a dataset with initialized budget values for a user.
+  - `-u, --user`: Username of the user (required).
+  - `-d, --dataset`: Name of the dataset to be added (required).
+  - `-e, --epsilon`: Epsilon value for the dataset (required).
+  - `-del, --delta`: Delta value for the dataset (required).
+- `del_dataset_to_user`: Delete a dataset for a user from the users collection.
+  - `-u, --user`: Username of the user (required).
+  - `-d, --dataset`: Name of the dataset to be deleted (required).
+- `set_budget_field`: Set a budget field to a given value for a specified user and dataset.
+  - `-u, --user`: Username of the user (required).
+  - `-d, --dataset`: Name of the dataset (required).
+  - `-f, --field`: Field to be set ("initial_epsilon" or "initial_delta") (required).
+  - `-v, --value`: Value to set for the field (required).
+- `set_may_query`: Set the "may query" field to a given value for a specified user.
+  - `-u, --user`: Username of the user (required).
+  - `-v, --value`: Value to set for "may query" (choices: "False" or "True") (required).
+- `show_user`: Show all information about a user in the users collection.
+  - `-u, --user`: Username of the user to be shown (required).
+- `add_users_via_yaml`: Create users collection from a YAML file.
+  - `-yf, --yaml_file`: Path to the YAML file (required).
+  - `-c, --clean`: Clean the existing users collection (optional, default: False).
+  - `-o, --overwrite`: Overwrite the existing users collection (optional, default: False).
+- `show_archives`: Show all previous queries from a user.
+  - `-u, --user`: Username of the user to show archives (required).
+- `get_users`: Get the list of all users in the 'users' collection.
+- `get_user_datasets`: Get the list of all datasets from a user.
+  - `-u, --user`: Username of the user to show datasets (required).
+
+
+#### Datasets
+
+- `add_dataset`: Add a dataset to the datasets collection.
+  - `-d, --dataset_name`: Name of the dataset (required).
+  - `-db, --database_type`: Type of the database where the dataset is stored (required).
+  - `-d_path, --dataset_path`: Path to the dataset (required if database_type is 'PATH_DB').
+  - `-s3b, --s3_bucket`: S3 bucket name for the dataset file (required if database_type is 'S3_DB').
+  - `-s3k, --s3_key`: S3 key for the dataset file  (required if database_type is 'S3_DB').
+  - `-s3_url, --endpoint_url`: S3 endpoint URL for the dataset file  (required if database_type is 'S3_DB').
+  - `-s3_ak, --aws_access_key_id`: AWS access key ID for S3 for the dataset file (required if database_type is 'S3_DB').
+  - `-s3_sak, --aws_secret_access_key`: AWS secret access key for S3 for the dataset file  (required if database_type is 'S3_DB').
+  - `-m_db, --metadata_database_type`: Type of the database where metadata is stored (required).
+  - `-mp, --metadata_path`: Path to the metadata (required if metadata_database_type is 'PATH_DB').
+  - `-m_s3b, --metadata_s3_bucket`: S3 bucket name for metadata (required if metadata_database_type is 'S3_DB').
+  - `-m_s3k, --metadata_s3_key`: S3 key for metadata (required if metadata_database_type is 'S3_DB').
+  - `-m_s3_url, --metadata_endpoint_url`: S3 endpoint URL for metadata (required if metadata_database_type is 'S3_DB').
+  - `-m_s3_ak, --metadata_aws_access_key_id`: AWS access key ID for metadata (required if metadata_database_type is 'S3_DB').
+  - `-m_s3_sak, --metadata_aws_secret_access_key`: AWS secret access key for metadata (required if metadata_database_type is 'S3_DB').
+- `add_datasets_via_yaml`: Create datasets to database type collection based on a yaml file.
+  - `-yf, --yaml_file`: Path to the YAML file (required).
+  - `-c, --clean`: Clean the existing datasets collection (optional, default: False).
+  - `-od, --overwrite_datasets`: Overwrite the existing datasets collection (optional, default: False).
+  - `-om, --overwrite_metadata`: Overwrite the existing metadata collection (optional, default: False).
+- `del_dataset`: Delete dataset and metadata from datasets and metadata collection.
+  - `-d, --dataset`: Name of the dataset to be deleted (required).
+- `show_dataset`: Show a dataset from the dataset collection.
+  - `-d, --dataset`: Name of the dataset to show (required).
+- `show_metadata`: Show metadata from the metadata collection.
+  - `-d, --dataset`: Name of the dataset of the metadata to show (required).
+- `get_datasets`: Get the list of all datasets in the 'datasets' collection.
+
+#### Collections
+
+- `drop_collection`: Delete a collection from the database.
+  - `-c, --collection`: Name of the collection to be deleted. Choices: "users", "datasets", "metadata", "queries_archives" (required).
+- `show_collection`: Print a collection.
+  - `-c, --collection`: Name of the collection to be shown. Choices: "users", "datasets", "metadata", "queries_archives" (required).
+
+### Examples
+```
+# Add a user
+python mongodb_admin.py add_user -u username
+
+# Add a user with budget
+python mongodb_admin.py add_user_with_budget -u username -d dataset_name -e 0.5 -del 0.1
+
+# Delete a user
+python mongodb_admin.py del_user -u username
+
+# Add a dataset to a user
+python mongodb_admin.py add_dataset_to_user -u username -d dataset_name -e 0.5 -del 0.1
+
+# Delete a dataset from a user
+python mongodb_admin.py del_dataset_to_user -u username -d dataset_name
+
+# Set budget field for a user and dataset
+python mongodb_admin.py set_budget_field -u username -d dataset_name -f initial_epsilon -v 0.5
+
+# Set may query field for a user
+python mongodb_admin.py set_may_query -u username -v True
+
+# Show user metadata
+python mongodb_admin.py show_user -u username
+
+# Create users collection from a YAML file
+python mongodb_admin.py add_users_via_yaml -yf users.yaml -c
+
+# Show all previous queries from user "username"
+python mongodb_admin.py show_archives -u username
+
+# Get the list of all users
+python mongodb_admin.py get_users
+
+# Get the list of all datasets from user "username"
+python mongodb_admin.py get_user_datasets -u username
+
+# Add a dataset
+python mongodb_admin.py add_dataset -d dataset_name -db database_type -d_path dataset_path -m_db metadata_database_type
+
+# Create datasets from a YAML file
+python mongodb_admin.py add_datasets_via_yaml -yf datasets.yaml -c -od -om
+
+# Delete a dataset
+python mongodb_admin.py del_dataset -d dataset_name
+
+# Show dataset "dataset_name"
+python mongodb_admin.py show_dataset -d dataset_name
+
+# Show metadata for dataset "dataset_name"
+python mongodb_admin.py show_metadata -d dataset_name
+
+# Drop a collection
+python mongodb_admin.py drop_collection -c users
+
+# Show a collection
+python mongodb_admin.py show_collection -c datasets
+```
\ No newline at end of file
diff --git a/server/lomas_server/administration/dashboard/about.py b/server/lomas_server/administration/dashboard/about.py
new file mode 100644
index 00000000..b4a260de
--- /dev/null
+++ b/server/lomas_server/administration/dashboard/about.py
@@ -0,0 +1,85 @@
+import os
+import sys
+import streamlit as st
+from st_pages import Page, show_pages
+
+if __name__ == "__main__":
+    # We add the src directory to the python search path
+    # Required if the code is not installed as a package via pip/setuptools
+    admin_dir = os.path.abspath(
+        os.path.join(os.path.dirname(os.path.abspath(__file__)), "../")
+    )
+    sys.path.append(admin_dir)
+    src_dir = os.path.abspath(
+        os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")
+    )
+    sys.path.append(src_dir)
+
+    st.set_page_config(page_title="Lomas Dashboard")
+    FOLDER = "./administration/dashboard"  # TODO move
+    show_pages(
+        [
+            Page(f"{FOLDER}/about.py", "Home Page", "🏠"),
+            Page(
+                f"{FOLDER}/pages/01_server_overview.py",
+                "Lomas server overview",
+                ":computer:",
+            ),
+            Page(
+                f"{FOLDER}/pages/02_database_administration.py",
+                "Admin database management",
+                ":file_folder:",
+            ),
+        ]
+    )
+
+    st.title("Welcome!")
+
+    st.header("Lomas Administation Dashboard")
+    st.write(
+        """
+    The Lomas Administration Dashboard provides a centralized interface for managing various aspects of your server and database.
+    Whether you need to monitor server status, manage user accounts, or administer datasets, this dashboard offers a convenient way to do so.
+    """  # noqa: E501
+    )
+
+    st.header("Key Features")
+
+    st.write(
+        """
+        - **Server Overview**: Quickly check the status of your server, including live status and configuration details.
+        - **Admin Database Management**: Effortlessly manage users and datasets through intuitive interfaces.
+        - **User Management**: Add, modify, or delete user accounts, set budget parameters, and control user permissions.
+        - **Dataset Management**: Add, remove, or modify datasets and associated metadata with ease.
+        - **View Database Content**: Dive deep into the database to view detailed information about users, datasets, metadata, and archives.
+        - **Delete Content (DANGEROUS)**: Safely delete users, datasets, metadata, or entire collections when necessary.
+        """  # noqa: E501
+    )
+
+    st.header("Quick Start")
+
+    st.write(
+        """
+        1. Navigate through the tabs to access different functionalities:
+            - **Server Overview**: Check server status and configuration.
+            - **Admin Database Management**: Manage users, datasets, and database content.
+
+        2. Use the intuitive interfaces to perform actions such as adding users, modifying datasets, or viewing database content.
+
+        3. Exercise caution when using deletion functionalities, as they can permanently remove data.
+
+        4. Refer to the documentation or tooltips for additional guidance on specific features.
+    """  # noqa: E501
+    )
+
+    # Additional resources
+    st.header("Resources")
+
+    st.write(
+        "**Documentation**: [server documentation](%s)"
+        % "https://dscc-admin-ch.github.io/lomas-docs/lomas_server.admin_database.html"  # noqa: E501
+    )
+    st.write(
+        "**Support**: If you encounter any issues or have questions, reach out on [Github issues](%s)"  # noqa: E501
+        % "https://github.com/dscc-admin-ch/lomas/issues"
+    )
diff --git a/server/lomas_server/administration/dashboard/pages/01_server_overview.py b/server/lomas_server/administration/dashboard/pages/01_server_overview.py
new file mode 100644
index 00000000..a3d38391
--- /dev/null
+++ b/server/lomas_server/administration/dashboard/pages/01_server_overview.py
@@ -0,0 +1,27 @@
+# import requests
+import streamlit as st
+
+
+###############################################################################
+# BACKEND
+###############################################################################
+url = "http://lomas_server_dev:80"  # TODO get from config
+
+###############################################################################
+# GUI and user interactions
+###############################################################################
+
+# Initialization
+st.set_page_config(layout="wide")
+
+st.title("Lomas server overview")
+
+st.subheader("Server state")
+# state = requests.post(f"{url}/state", timeout=50)
+# st.write(f"Server live : {state}")
+
+st.subheader("Server config")
+# show config
+
+st.subheader("Server data")
+# show memory used ? and other stuff if we want
diff --git a/server/lomas_server/administration/dashboard/pages/02_database_administration.py b/server/lomas_server/administration/dashboard/pages/02_database_administration.py
new file mode 100644
index 00000000..0682a66b
--- /dev/null
+++ b/server/lomas_server/administration/dashboard/pages/02_database_administration.py
@@ -0,0 +1,647 @@
+# type: ignore
+import streamlit as st
+import yaml
+
+from admin_database.utils import get_mongodb
+from administration.mongodb_admin import (
+    add_user,
+    add_user_with_budget,
+    del_user,
+    add_dataset_to_user,
+    del_dataset_to_user,
+    set_budget_field,
+    set_may_query,
+    show_user,
+    add_users_via_yaml,
+    add_dataset,
+    add_datasets_via_yaml,
+    del_dataset,
+    drop_collection,
+    get_list_of_users,
+    get_list_of_datasets,
+    get_list_of_datasets_from_user,
+    show_archives_of_user,
+    show_collection,
+    show_dataset,
+    show_metadata_of_dataset,
+)
+from constants import PrivateDatabaseType
+
+###############################################################################
+# BACKEND
+###############################################################################
+if "admin_db" not in st.session_state:
+    st.session_state["admin_db"] = get_mongodb()
+
+if "list_users" not in st.session_state:
+    st.session_state["list_users"] = get_list_of_users(
+        st.session_state.admin_db
+    )
+
+if "list_datasets" not in st.session_state:
+    st.session_state["list_datasets"] = get_list_of_datasets(
+        st.session_state.admin_db
+    )
+
+###############################################################################
+# GUI and user interactions
+###############################################################################
+
+# Initialization
+st.set_page_config(layout="wide")
+
+st.title("Admin Database Management")
+
+user_tab, dataset_tab, content_tab, deletion_tab = st.tabs(
+    [
+        ":technologist: User Management",
+        ":file_cabinet: Dataset Management",
+        ":eyes: View Database Content",
+        ":wastebasket: Delete Content (:red[DANGEROUS])",
+    ]
+)
+
+with user_tab:
+    st.subheader("Add user")
+    au_username = st.text_input("Username (add user)", value="", key=None)
+    if au_username:
+        st.write("Click to add user", au_username, "to the list of users.")
+        if st.button(
+            "Add user",
+            on_click=add_user,
+            args=(
+                st.session_state.admin_db,
+                au_username,
+            ),
+        ):
+            st.session_state["list_users"] = get_list_of_users(
+                st.session_state.admin_db
+            )
+            st.write(f"User {au_username} was added.")
+
+    st.subheader("Add user with budget")
+    auwb_1, auwb_2, auwb_3, auwb_4 = st.columns(4)
+    with auwb_1:
+        auwb_username = st.text_input("Username (add user with budget)", None)
+    with auwb_2:
+        auwb_dataset = st.selectbox(
+            "Dataset (add user with budget)", st.session_state.list_datasets
+        )
+    with auwb_3:
+        auwb_epsilon = st.number_input("Epsilon (add user with budget)", None)
+    with auwb_4:
+        auwb_delta = st.number_input("Delta (add user with budget)", None)
+    if auwb_username and auwb_dataset and auwb_epsilon and auwb_delta:
+        st.write("Click to add user", auwb_username, "to the list of users.")
+        if st.button(
+            "Add user with dataset",
+            on_click=add_user_with_budget,
+            args=(
+                st.session_state.admin_db,
+                auwb_username,
+                auwb_dataset,
+                auwb_epsilon,
+                auwb_delta,
+            ),
+        ):
+            st.session_state["list_users"] = get_list_of_users(
+                st.session_state.admin_db
+            )
+            st.write(
+                f"User {auwb_username} was added with dataset {auwb_dataset}."
+            )
+
+    st.subheader("Add dataset to user")
+    adtu_1, adtu_2, adtu_3, adtu_4 = st.columns(4)
+    with adtu_1:
+        adtu_username = st.selectbox(
+            "Username (add dataset to user)", st.session_state.list_users
+        )
+    with adtu_2:
+        if adtu_username:
+            adtu_datasets_from_user = get_list_of_datasets_from_user(
+                st.session_state.admin_db, adtu_username
+            )
+            adtu_dataset_available = [
+                dataset
+                for dataset in st.session_state.list_datasets
+                if dataset not in adtu_datasets_from_user
+            ]
+        else:
+            adtu_dataset_available = st.session_state.list_datasets
+        adtu_dataset = st.selectbox(
+            "Dataset (add dataset to user)", adtu_dataset_available
+        )
+    with adtu_3:
+        adtu_epsilon = st.number_input("Epsilon (add dataset to user)", None)
+    with adtu_4:
+        adtu_delta = st.number_input("Delta (add dataset to user)", None)
+
+    if adtu_username and adtu_dataset and adtu_epsilon and adtu_delta:
+        st.write("Click to add dataset", adtu_dataset, "to", adtu_username)
+        if st.button(
+            "Add dataset to user",
+            on_click=add_dataset_to_user,
+            args=(
+                st.session_state.admin_db,
+                adtu_username,
+                adtu_dataset,
+                adtu_epsilon,
+                adtu_delta,
+            ),
+        ):
+            st.write(
+                f"Dataset {adtu_dataset} was added to user {adtu_username}."
+            )
+
+    st.subheader("Modify user epsilon")
+    sue_1, sue_2, sue_3 = st.columns(3)
+    with sue_1:
+        sue_username = st.selectbox(
+            "Username (modify user epsilon)", st.session_state.list_users
+        )
+    with sue_2:
+        if sue_username:
+            sue_datasets_from_user = get_list_of_datasets_from_user(
+                st.session_state.admin_db, sue_username
+            )
+        else:
+            sue_datasets_from_user = st.session_state.list_datasets
+        sue_dataset = st.selectbox(
+            "Dataset (modify user epsilon)", sue_datasets_from_user
+        )
+    with sue_3:
+        sue_epsilon = st.number_input(
+            "Epsilon value (modify user epsilon)", None
+        )
+    if sue_username and sue_dataset and sue_epsilon:
+        st.write(
+            "Click to modify initial epsilon value from user",
+            sue_username,
+            "on dataset",
+            sue_dataset,
+            "to",
+            sue_epsilon,
+        )
+        if st.button(
+            "Modify user epsilon",
+            on_click=set_budget_field,
+            args=(
+                st.session_state.admin_db,
+                sue_username,
+                sue_dataset,
+                "initial_epsilon",
+                sue_epsilon,
+            ),
+        ):
+            st.write(f"User {sue_username} epsilon value was modified.")
+
+    st.subheader("Modify user delta")
+    sud_1, sud_2, sud_3 = st.columns(3)
+    with sud_1:
+        sud_username = st.selectbox(
+            "Username (modify user delta)", st.session_state.list_users
+        )
+    with sud_2:
+        if sud_username:
+            sud_datasets_from_user = get_list_of_datasets_from_user(
+                st.session_state.admin_db, sud_username
+            )
+        else:
+            sud_datasets_from_user = st.session_state.list_datasets
+        sud_dataset = st.selectbox(
+            "Dataset (modify user delta)", sud_datasets_from_user
+        )
+    with sud_3:
+        sud_delta = st.number_input("Delta value (modify user delta)", None)
+    if sud_username and sud_dataset and sud_delta:
+        st.write(
+            "Click to modify initial delta value from user",
+            sud_username,
+            "on dataset",
+            sud_dataset,
+            "to",
+            sud_delta,
+        )
+        if st.button(
+            "Modify user delta",
+            on_click=set_budget_field,
+            args=(
+                st.session_state.admin_db,
+                sud_username,
+                sud_dataset,
+                "initial_delta",
+                sud_delta,
+            ),
+        ):
+            st.write(f"User {sud_username} delta value was modified.")
+
+    st.subheader("Modify user may query")
+    umq_1, umq_2 = st.columns(2)
+    with umq_1:
+        umq_username = st.selectbox(
+            "Username (user may query)", st.session_state.list_users
+        )
+    with umq_2:
+        umq_may_query = st.selectbox("May query", (True, False))
+    if umq_username:
+        st.write(
+            f"Change user {umq_username} may query to", umq_may_query, "."
+        )
+        if st.button(
+            "Modify user may query",
+            on_click=set_may_query,
+            args=(st.session_state.admin_db, umq_username, umq_may_query),
+        ):
+            st.write("User", umq_username, "may_query is now:", umq_may_query)
+
+    st.subheader("Add many users via a yaml file")
+    amu_1, amu_2 = st.columns(2)
+    with amu_1:
+        u_clean = st.toggle(
+            "Clean: recreate collection from scratch "
+            + "(will delete all previous users)"
+        )
+    with amu_2:
+        u_overwrite = st.toggle(
+            "Overwrite: if user already exists, overwrites values"
+        )
+    u_uploaded_file = st.file_uploader(
+        "Choose a YAML file for the user collection",
+        accept_multiple_files=False,
+    )
+    if u_uploaded_file:
+        st.write("Click to add users")
+        if st.button("Add users"):
+            user_collection = yaml.safe_load(u_uploaded_file)
+            add_users_via_yaml(
+                st.session_state.admin_db,
+                user_collection,
+                u_clean,
+                u_overwrite,
+            )
+            st.session_state["list_users"] = get_list_of_users(
+                st.session_state.admin_db
+            )
+            st.write("Users were added.")
+
+with dataset_tab:
+    st.subheader("Add one dataset")
+    ad_1, ad_2, ad_3 = st.columns(3)
+    with ad_1:
+        ad_dataset = st.text_input("Dataset name (add dataset)", None)
+    with ad_2:
+        ad_type = st.selectbox(
+            "Dataset type (add dataset)",
+            (PrivateDatabaseType.PATH, PrivateDatabaseType.S3),
+        )
+    with ad_3:
+        ad_meta_type = st.selectbox(
+            "Metadata type (add dataset)",
+            (PrivateDatabaseType.PATH, PrivateDatabaseType.S3),
+        )
+    match ad_type:
+        case PrivateDatabaseType.PATH:
+            ad_path = st.text_input("Dataset path (add dataset)", None)
+        case PrivateDatabaseType.S3:
+            ad_s3_1, ad_s3_2, ad_s3_3, ad_s3_4, ad_s3_5 = st.columns(5)
+            with ad_s3_1:
+                ad_s3_bucket = st.text_input("s3_bucket (add dataset)", None)
+            with ad_s3_2:
+                ad_s3_key = st.text_input("s3_key (add dataset)", None)
+            with ad_s3_3:
+                ad_s3_url = st.text_input("endpoint_url (add dataset)", None)
+            with ad_s3_4:
+                ad_s3_kid = st.text_input(
+                    "aws_access_key_id (add dataset)", None
+                )
+            with ad_s3_5:
+                ad_s3_sk = st.text_input("aws_secret_key (add dataset)", None)
+
+    match ad_meta_type:
+        case PrivateDatabaseType.PATH:
+            ad_meta_path = st.text_input("Metadata path (add dataset)", None)
+        case PrivateDatabaseType.S3:
+            (
+                ad_meta_s3_1,
+                ad_meta_s3_2,
+                ad_meta_s3_3,
+                ad_meta_s3_4,
+                ad_meta_s3_5,
+            ) = st.columns(5)
+            with ad_meta_s3_1:
+                ad_meta_s3_bucket = st.text_input(
+                    "Metadata s3_bucket (add dataset)", None
+                )
+            with ad_meta_s3_2:
+                ad_meta_s3_key = st.text_input(
+                    "Metadata s3_key (add dataset)", None
+                )
+            with ad_meta_s3_3:
+                ad_meta_s3_url = st.text_input(
+                    "Metadata endpoint_url (add dataset)", None
+                )
+            with ad_meta_s3_4:
+                ad_meta_s3_kid = st.text_input(
+                    "Metadata aws_access_key_id (add dataset)", None
+                )
+            with ad_meta_s3_5:
+                ad_meta_s3_sk = st.text_input(
+                    "Metadata aws_secret_key (add dataset)", None
+                )
+
+    if ad_dataset and ad_type and ad_meta_type:
+        keyword_args = {}
+        dataset_ready = False
+        metadata_ready = False
+        button_text = ""
+        if ad_type == PrivateDatabaseType.PATH and ad_path:
+            keyword_args["dataset_path"] = ad_path
+            button_text += f"Add local dataset {ad_dataset} at path {ad_path}"
+            dataset_ready = True
+        elif (
+            ad_type == PrivateDatabaseType.S3
+            and ad_s3_bucket
+            and ad_s3_key
+            and ad_s3_url
+            and ad_s3_kid
+            and ad_s3_sk
+        ):
+            keyword_args["s3_bucket"] = ad_s3_bucket
+            keyword_args["s3_key"] = ad_s3_key
+            keyword_args["endpoint_url"] = ad_s3_url
+            keyword_args["aws_access_key_id"] = ad_s3_kid
+            keyword_args["aws_secret_access_key"] = ad_s3_sk
+            button_text += f"Add S3 dataset {ad_dataset}"
+            dataset_ready = True
+        else:
+            st.write("Please, fill all empty fields for dataset.")
+
+        if ad_meta_type == PrivateDatabaseType.PATH and ad_meta_path:
+            keyword_args["metadata_path"] = ad_meta_path
+            button_text += f" with local metadata at path {ad_meta_path}"
+            metadata_ready = True
+        elif (
+            ad_meta_type == PrivateDatabaseType.S3
+            and ad_meta_s3_bucket
+            and ad_meta_s3_key
+            and ad_meta_s3_url
+            and ad_meta_s3_kid
+            and ad_meta_s3_sk
+        ):
+            keyword_args["metadata_s3_bucket"] = ad_meta_s3_bucket
+            keyword_args["metadata_s3_key"] = ad_meta_s3_key
+            keyword_args["metadata_endpoint_url"] = ad_meta_s3_url
+            keyword_args["metadata_aws_access_key_id"] = ad_meta_s3_kid
+            keyword_args["metadata_aws_secret_access_key"] = ad_meta_s3_sk
+            button_text += " with S3 metadata"
+            metadata_ready = True
+        else:
+            st.write("Please, fill all empty fields for the metadata.")
+
+        if dataset_ready and metadata_ready:
+            if st.button(
+                button_text,
+                on_click=add_dataset,
+                args=(
+                    st.session_state.admin_db,
+                    ad_dataset,
+                    ad_type,
+                    ad_meta_type,
+                ),
+                kwargs=keyword_args,
+            ):
+                dataset_ready = False
+                metadata_ready = False
+                st.session_state["list_datasets"] = get_list_of_datasets(
+                    st.session_state.admin_db
+                )
+                st.write("Dataset", ad_dataset, "added.")
+
+    st.subheader("Add many datasets via a yaml file")
+    amd_1, amd_2, amd_3 = st.columns(3)
+    with amd_1:
+        d_clean = st.toggle("Clean: will delete all previous datasets")
+    with amd_2:
+        d_overwrite_datasets = st.toggle(
+            "Overwrite: if dataset already exists, overwrites values"
+        )
+    with amd_3:
+        d_overwrite_metadata = st.toggle(
+            "Overwrite: if metadata already exists, overwrites values"
+        )
+    dataset_collection = st.file_uploader(
+        "Select a YAML file for the dataset collection",
+        type="yaml",
+        accept_multiple_files=False,
+    )
+    if dataset_collection:
+        st.write("Click to add datasets")
+        if st.button("Add datasets"):
+            dataset_collection = yaml.safe_load(dataset_collection)
+
+            add_datasets_via_yaml(
+                st.session_state.admin_db,
+                dataset_collection,
+                d_clean,
+                d_overwrite_datasets,
+                d_overwrite_metadata,
+            )
+            st.session_state["list_datasets"] = get_list_of_datasets(
+                st.session_state.admin_db
+            )
+            st.write("Datasets were added.")
+
+with content_tab:
+    st.subheader("Show one element")
+    elem_users, elem_archives = st.columns(2)
+    with elem_users:
+        user_selected = st.selectbox(
+            "User to show", st.session_state.list_users
+        )
+        if st.button(f"Displaying information of: {user_selected}"):
+            user_to_show = show_user(st.session_state.admin_db, user_selected)
+            st.write(user_to_show)
+    with elem_archives:
+        user_archives_selected = st.selectbox(
+            "Archives from user", st.session_state.list_users
+        )
+        st.write("Displaying information of:", user_archives_selected)
+        if st.button(
+            f"Displaying previous queries of: {user_archives_selected}"
+        ):
+            user_archives_to_show = show_archives_of_user(
+                st.session_state.admin_db, user_archives_selected
+            )
+            st.write(user_archives_to_show)
+
+    elem_datasets, elem_metadata = st.columns(2)
+    with elem_datasets:
+        dataset_selected = st.selectbox(
+            "Dataset to show", st.session_state.list_datasets
+        )
+        st.write("Displaying information of:", dataset_selected)
+        if st.button(f"Displaying dataset: {dataset_selected}"):
+            dataset_to_show = show_dataset(
+                st.session_state.admin_db, dataset_selected
+            )
+            st.write(dataset_to_show)
+    with elem_metadata:
+        metadata_selected = st.selectbox(
+            "Metadata to show from dataset", st.session_state.list_datasets
+        )
+        st.write("Displaying metadata of:", metadata_selected)
+        if st.button(f"Displaying metadata of: {metadata_selected}"):
+            metadata_to_show = show_metadata_of_dataset(
+                st.session_state.admin_db, metadata_selected
+            )
+            st.write(metadata_to_show)
+
+    st.subheader("Show full collection")
+    col_users, col_datasets, col_metadata, col_archives = st.columns(4)
+    with col_users:
+        if st.button("Show all users"):
+            users = show_collection(st.session_state.admin_db, "users")
+            st.write(users)
+    with col_datasets:
+        if st.button("Show all datasets"):
+            datasets = show_collection(st.session_state.admin_db, "datasets")
+            st.write(datasets)
+    with col_metadata:
+        if st.button("Show all metadata"):
+            metadatas = show_collection(st.session_state.admin_db, "metadata")
+            st.write(metadatas)
+    with col_archives:
+        if st.button(
+            "Show archives",
+        ):
+            archives = show_collection(
+                st.session_state.admin_db, "queries_archives"
+            )
+            st.write(archives)
+
+
+with deletion_tab:
+    _, center, _ = st.columns(3)
+    with center:
+        st.markdown(
+            ":warning: :red[**Danger Zone: deleting is final**] :warning:"
+        )
+
+    st.subheader("Delete one element")
+    st.markdown("**Delete one user**")
+    du_username = st.selectbox(
+        "Username (delete user)", st.session_state.list_users
+    )
+    if du_username:
+        st.write(
+            "Click to delete user", du_username, "from the list of users."
+        )
+        if st.button(
+            "Delete user",
+            on_click=del_user,
+            args=(
+                st.session_state.admin_db,
+                du_username,
+            ),
+        ):
+            st.session_state["list_users"] = get_list_of_users(
+                st.session_state.admin_db
+            )
+            st.write(f"User {du_username} was deleted.")
+
+    st.markdown("**Remove dataset from user**")
+    rdtu_1, rdtu_2 = st.columns(2)
+    with rdtu_1:
+        rdtu_user = st.selectbox(
+            "Username (remove dataset from user)", st.session_state.list_users
+        )
+    with rdtu_2:
+        if rdtu_user:
+            rdtu_datasets_from_user = get_list_of_datasets_from_user(
+                st.session_state.admin_db, rdtu_user
+            )
+        else:
+            rdtu_datasets_from_user = st.session_state.list_datasets
+        rdtu_dataset = st.selectbox(
+            "Dataset (remove dataset from user)", rdtu_datasets_from_user
+        )
+    if rdtu_user and rdtu_dataset:
+        st.write(
+            "Click to remove dataset", rdtu_dataset, "from user", rdtu_user
+        )
+        if st.button(
+            "Remove dataset from user",
+            on_click=del_dataset_to_user,
+            args=(st.session_state.admin_db, rdtu_user, rdtu_dataset),
+        ):
+            st.session_state["list_datasets"] = get_list_of_datasets(
+                st.session_state.admin_db
+            )
+            st.write(
+                f"Dataset {rdtu_dataset} was removed from user {rdtu_user}."
+            )
+
+    st.markdown("**Remove dataset and it's associated metadata**")
+    rd_dataset = st.selectbox(
+        "Dataset (remove dataset)", st.session_state.list_datasets
+    )
+    if rd_dataset:
+        st.write(
+            "Click to delete dataset",
+            rd_dataset,
+            "from the list of datasets.",
+        )
+        if st.button(
+            "Delete dataset",
+            on_click=del_dataset,
+            args=(
+                st.session_state.admin_db,
+                rd_dataset,
+            ),
+        ):
+            st.session_state["list_datasets"] = get_list_of_datasets(
+                st.session_state.admin_db
+            )
+            st.write(f"Dataset {rd_dataset} was deleted.")
+
+    st.subheader("Delete full collection")
+    d_col_users, d_col_datasets, d_col_metadata, d_col_archives = st.columns(4)
+
+    with d_col_users:
+        if st.button(
+            "Delete all users",
+            on_click=drop_collection,
+            args=(st.session_state.admin_db, "users"),
+        ):
+            st.session_state["list_users"] = get_list_of_users(
+                st.session_state.admin_db
+            )
+            st.write("Users were all deleted.")
+
+    with d_col_datasets:
+        if st.button(
+            "Delete all datasets",
+            on_click=drop_collection,
+            args=(st.session_state.admin_db, "datasets"),
+        ):
+            st.session_state["list_datasets"] = get_list_of_datasets(
+                st.session_state.admin_db
+            )
+            st.write("Datasets were all deleted.")
+
+    with d_col_metadata:
+        if st.button(
+            "Delete all metadata",
+            on_click=drop_collection,
+            args=(st.session_state.admin_db, "metadata"),
+        ):
+            st.write("Metadata were all deleted.")
+
+    with d_col_archives:
+        if st.button(
+            "Delete all archives",
+            on_click=drop_collection,
+            args=(st.session_state.admin_db, "archives"),
+        ):
+            st.write("Archives were all deleted.")
diff --git a/server/lomas_server/administration/mongodb_admin.py b/server/lomas_server/administration/mongodb_admin.py
new file mode 100644
index 00000000..c381abd9
--- /dev/null
+++ b/server/lomas_server/administration/mongodb_admin.py
@@ -0,0 +1,1301 @@
+import argparse
+import functools
+from typing import Callable, Dict, List, Optional, Union
+from warnings import warn
+
+import boto3
+from pymongo import MongoClient
+from pymongo.database import Database
+from pymongo.results import _WriteResult
+import yaml
+
+from admin_database.utils import get_mongodb_url
+from admin_database.mongodb_database import check_result_acknowledged
+from constants import PrivateDatabaseType
+from utils.collections_models import DatasetsCollection, UserCollection
+from utils.error_handler import InternalServerException
+from utils.loggr import LOG
+
+
+def check_user_exists(enforce_true: bool) -> Callable:
+    """Creates a wrapper function that raises a ValueError if the supplied
+    user does (not) exist in the user collection depending on the
+    enforce_true parameter.
+
+    Args:
+        enforce_true (bool): If set to True, the wrapper will enforce
+            the user is already in the database. If set to False, it
+            will enforce the user is NOT in the database.
+
+    Returns:
+        Callable: The wrapper function that enforces user presence
+            (or absence) before calling the suplied function.
+    """
+
+    def inner_func(
+        function: Callable[[Database, argparse.Namespace], None]
+    ) -> Callable:
+        @functools.wraps(function)
+        def wrapper_decorator(
+            *arguments: argparse.Namespace, **kwargs: Dict
+        ) -> None:
+            db = arguments[0]
+            user = arguments[1]
+
+            user_count = db.users.count_documents({"user_name": user})
+
+            if enforce_true and user_count == 0:
+                raise ValueError(
+                    f"User {user} does not exist in user collecion"
+                )
+            if not enforce_true and user_count > 0:
+                raise ValueError(
+                    f"User {user} already exists in user collection"
+                )
+
+            return function(*arguments, **kwargs)  # type: ignore
+
+        return wrapper_decorator
+
+    return inner_func
+
+
+def check_user_has_dataset(enforce_true: bool) -> Callable:
+    """Creates a wrapper function that raises a ValueError if the supplied
+    user has access (or not) to the supplied dataset depending on the
+    enforce_true parameter.
+
+    Args:
+        enforce_true (bool): If set to true, the wrapper function will
+            enforce the user has access to the dataset. If set to False,
+            the wrapper function will enforce the user has NOT access
+            to the specified dataset.
+
+    Returns:
+        Callable: The wrapper function that asserts user access (or not)
+            to the provided dataset.
+    """
+
+    def inner_func(
+        function: Callable[[Database, argparse.Namespace], None]
+    ) -> Callable:
+        @functools.wraps(function)
+        def wrapper_decorator(
+            *arguments: argparse.Namespace, **kwargs: Dict
+        ) -> None:
+            db = arguments[0]
+            user = arguments[1]
+            dataset = arguments[2]
+
+            user_and_ds_count = db.users.count_documents(
+                {
+                    "user_name": user,
+                    "datasets_list": {"$elemMatch": {"dataset_name": dataset}},
+                }
+            )
+
+            if enforce_true and user_and_ds_count == 0:
+                raise ValueError(
+                    f"User {user} does not have dataset {dataset}"
+                )
+            if not enforce_true and user_and_ds_count > 0:
+                raise ValueError(f"User {user} already has dataset {dataset}")
+
+            return function(*arguments, **kwargs)  # type: ignore
+
+        return wrapper_decorator
+
+    return inner_func
+
+
+def check_dataset_and_metadata_exist(enforce_true: bool) -> Callable:
+    """Creates a wrapper function that raises a ValueError
+    if the supplied user does not already exist in the user collection.
+    """
+
+    def inner_func(
+        function: Callable[[Database, argparse.Namespace], None]
+    ) -> Callable:
+        @functools.wraps(function)
+        def wrapper_decorator(
+            *arguments: argparse.Namespace, **kwargs: Dict
+        ) -> None:
+            db = arguments[0]
+            dataset = arguments[1]
+
+            dataset_count = db.datasets.count_documents(
+                {"dataset_name": dataset}
+            )
+
+            if enforce_true and dataset_count == 0:
+                raise ValueError(
+                    f"Dataset {dataset} does not exist in dataset collecion"
+                )
+            if not enforce_true and dataset_count > 0:
+                raise ValueError(
+                    f"Dataset {dataset} already exists in dataset collection"
+                )
+
+            metadata_count = db.metadata.count_documents(
+                {dataset: {"$exists": True}}
+            )
+
+            if enforce_true and metadata_count == 0:
+                raise ValueError(
+                    f"Metadata for dataset {dataset} does"
+                    "not exist in metadata collecion"
+                )
+            if not enforce_true and metadata_count > 0:
+                raise ValueError(
+                    f"Metadata for dataset {dataset} already"
+                    "exists in metadata collection"
+                )
+
+            return function(*arguments, **kwargs)  # type: ignore
+
+        return wrapper_decorator
+
+    return inner_func
+
+
+##########################  USERS  ########################## # noqa: E266
+@check_user_exists(False)
+def add_user(db: Database, user: str) -> None:
+    """Add new user in users collection with initial values for all fields
+    set by default.
+
+    Args:
+        db (Database): mongo database object
+        user (str): username to be added
+
+    Raises:
+        ValueError: If the user already exists.
+        WriteConcernError: If the result is not acknowledged.
+
+    Returns:
+        None
+    """
+
+    res = db.users.insert_one(
+        {
+            "user_name": user,
+            "may_query": True,
+            "datasets_list": [],
+        }
+    )
+
+    check_result_acknowledged(res)
+
+    LOG.info(f"Added user {user}.")
+
+
+@check_user_exists(False)
+def add_user_with_budget(
+    db: Database, user: str, dataset: str, epsilon: float, delta: float
+) -> None:
+    """Add new user in users collection with initial values
+    for all fields set by default.
+
+    Args:
+        db (Database): mongo database object
+        user (str): username to be added
+        dataset (str): name of the dataset to add to user
+        epsilon (float): epsilon value for initial budget of user
+        delta (float): delta value for initial budget of user
+
+    Raises:
+        ValueError: _description_
+
+    Returns:
+        None
+    """
+    res = db.users.insert_one(
+        {
+            "user_name": user,
+            "may_query": True,
+            "datasets_list": [
+                {
+                    "dataset_name": dataset,
+                    "initial_epsilon": epsilon,
+                    "initial_delta": delta,
+                    "total_spent_epsilon": 0.0,
+                    "total_spent_delta": 0.0,
+                }
+            ],
+        }
+    )
+
+    check_result_acknowledged(res)
+
+    LOG.info(
+        f"Added access to user {user} with dataset {dataset}, "
+        + f"budget epsilon {epsilon} and delta {delta}."
+    )
+
+
+@check_user_exists(True)
+def del_user(db: Database, user: str) -> None:
+    """Delete all related information for user from the users collection.
+
+    Args:
+        db (Database): mongo database object
+        user (str): username to be deleted
+
+    Returns:
+        None
+    """
+    res = db.users.delete_many({"user_name": user})
+    check_result_acknowledged(res)
+
+    LOG.info(f"Deleted user {user}.")
+
+
+@check_user_exists(True)
+@check_user_has_dataset(False)
+def add_dataset_to_user(
+    db: Database, user: str, dataset: str, epsilon: float, delta: float
+) -> None:
+    """Add dataset with initialized budget values to list of datasets
+    that the user has access to.
+    Will not add if already added (no error will be raised in that case).
+
+    Args:
+        db (Database): mongo database object
+        user (str): username of the user to check
+        dataset (str): name of the dataset to add to user
+        epsilon (float): epsilon value for initial budget of user
+        delta (float): delta value for initial budget of user
+
+    Raises:
+        ValueError: _description_
+
+    Returns:
+        None
+    """
+    res = db.users.update_one(
+        {
+            "user_name": user,
+            "datasets_list.dataset_name": {"$ne": dataset},
+        },
+        {
+            "$push": {
+                "datasets_list": {
+                    "dataset_name": dataset,
+                    "initial_epsilon": epsilon,
+                    "initial_delta": delta,
+                    "total_spent_epsilon": 0.0,
+                    "total_spent_delta": 0.0,
+                }
+            }
+        },
+    )
+
+    check_result_acknowledged(res)
+
+    LOG.info(
+        f"Added access to dataset {dataset}"
+        f"to user {user}"
+        f" with budget epsilon {epsilon}"
+        f" and delta {delta}."
+    )
+
+
+@check_user_exists(True)
+@check_user_has_dataset(True)
+def del_dataset_to_user(db: Database, user: str, dataset: str) -> None:
+    """Remove if exists the dataset (and all related budget info)
+    from list of datasets that user has access to.
+
+    Args:
+        db (Database): mongo database object
+        user (str): username of the user to which to delete a dataset
+        dataset (str): name of the dataset to remove from user
+
+    Returns:
+        None
+    """
+    res = db.users.update_one(
+        {"user_name": user},
+        {"$pull": {"datasets_list": {"dataset_name": {"$eq": dataset}}}},
+    )
+
+    check_result_acknowledged(res)
+
+    LOG.info(f"Remove access to dataset {dataset}" + f" from user {user}.")
+
+
+@check_user_exists(True)
+@check_user_has_dataset(True)
+def set_budget_field(
+    db: Database, user: str, dataset: str, field: str, value: float
+) -> None:
+    """Set (for some reason) a budget field to a given value
+    if given user exists and has access to given dataset.
+
+    Args:
+        db (Database): mongo database object
+        user (str): username of the user to set budget to
+        dataset (str): name of the dataset to set budget to
+        field (str): one of 'epsilon' or 'delta'
+        value (float): value to set as epsilon or delta
+
+    Returns:
+        None
+    """
+    res = db.users.update_one(
+        {
+            "user_name": user,
+            "datasets_list.dataset_name": dataset,
+        },
+        {"$set": {f"datasets_list.$.{field}": value}},
+    )
+
+    check_result_acknowledged(res)
+
+    LOG.info(
+        f"Set budget of {user} for dataset {dataset}"
+        f" of {field} to {value}."
+    )
+
+
+@check_user_exists(True)
+def set_may_query(db: Database, user: str, value: bool) -> None:
+    """Set (for some reason) the 'may query' field to a given value
+    if given user exists.
+
+    Args:
+        db (Database): mongo database object
+        user (str): username of the user to enable/disable
+        value (bool): may query value (True or False)
+
+    Returns:
+        None
+    """
+    res = db.users.update_one(
+        {"user_name": user},
+        {"$set": {"may_query": (value == "True")}},
+    )
+
+    check_result_acknowledged(res)
+
+    LOG.info(f"Set user {user} may query to True.")
+
+
+@check_user_exists(True)
+def show_user(db: Database, user: str) -> dict:
+    """Show a user
+
+    Args:
+        db (Database): mongo database object
+        user (str): username of the user to show
+
+    Returns:
+        user (dict): all information of user from 'users' collection
+    """
+    user_info = list(db.users.find({"user_name": user}))[0]
+    user_info.pop("_id", None)
+    LOG.info(user_info)
+    return user_info
+
+
+def add_users_via_yaml(
+    db: Database,
+    yaml_file: Union[str, Dict],
+    clean: bool,
+    overwrite: bool,
+) -> None:
+    """Add all users from yaml file to the user collection
+
+    Args:
+        db (Database): mongo database object
+        yaml_file (Union[str, Dict]):
+            if str: a path to the YAML file location
+            if Dict: a dictionnary containing the collection data
+        clean (bool): boolean flag
+            True if drop current user collection
+            False if keep current user collection
+        overwrite (bool): boolean flag
+            True if overwrite already existing users
+            False errors if new values for already existing users
+
+    Returns:
+        None
+    """
+    if clean:
+        # Collection created from scratch
+        db.users.drop()
+        LOG.info("Cleaning done. \n")
+
+    # Load yaml data and insert it
+    if isinstance(yaml_file, str):
+        with open(yaml_file, encoding="utf-8") as f:
+            yaml_file = yaml.safe_load(f)
+    user_dict = UserCollection(**yaml_file)
+
+    # Filter out duplicates
+    new_users = []
+    existing_users = []
+    for user in user_dict.users:
+        if not db.users.find_one({"user_name": user.user_name}):
+            new_users.append(user)
+        else:
+            existing_users.append(user)
+
+    # Overwrite values for existing user with values from yaml
+    if existing_users:
+        if overwrite:
+            for user in existing_users:
+                user_filter = {"user_name": user.user_name}
+                update_operation = {"$set": user.dict()}
+                res: _WriteResult = db.users.update_many(
+                    user_filter, update_operation
+                )
+                check_result_acknowledged(res)
+            LOG.info("Existing users updated. ")
+        else:
+            warn(
+                "Some users already present in database."
+                "Overwrite is set to False."
+            )
+
+    if new_users:
+        # Insert new users
+        new_users_dicts = [user.dict() for user in new_users]
+        res = db.users.insert_many(new_users_dicts)
+        check_result_acknowledged(res)
+        LOG.info("Added user data from yaml.")
+    else:
+        LOG.info("No new users added, they already exist in the server")
+
+
+def show_archives_of_user(db: Database, user: str) -> List[dict]:  # TODO  test
+    """Show all previous queries from a user
+
+    Args:
+        db (Database): mongo database object
+        user (str): username of the user to show archives
+
+    Returns:
+        archives (List): list of previous queries from the user
+    """
+    archives_infos: List[dict] = list(
+        db.queries_archives.find({"user_name": user})
+    )
+    LOG.info(archives_infos)
+    return archives_infos
+
+
+def get_list_of_users(db: Database) -> list:  # TODO  test
+    """Get the list of all users is 'users' collection
+
+    Args:
+        db (Database): mongo database object
+
+    Returns:
+        user_names (list): list of names of all users
+    """
+    user_names = []
+    for elem in db.users.find():
+        user_names.append(elem["user_name"])
+    return user_names
+
+
+def get_list_of_datasets_from_user(
+    db: Database, user: str
+) -> list:  # TODO  test
+    """Get the list of all datasets from the user
+
+    Args:
+        db (Database): mongo database object
+        user (str): username of the user to show archives
+
+    Returns:
+        user_datasets (list): list of names of all users
+    """
+    user_data = db.users.find_one({"user_name": user})
+    if user_data:
+        return [
+            dataset["dataset_name"] for dataset in user_data["datasets_list"]
+        ]
+    else:
+        return []
+
+
+###################  DATASET TO DATABASE  ################### # noqa: E266
+@check_dataset_and_metadata_exist(False)
+def add_dataset(
+    db: Database,
+    dataset_name: str,
+    database_type: str,
+    metadata_database_type: str,
+    dataset_path: Optional[str] = "",
+    metadata_path: Optional[str] = "",
+    s3_bucket: Optional[str] = "",
+    s3_key: Optional[str] = "",
+    endpoint_url: Optional[str] = "",
+    aws_access_key_id: Optional[str] = "",
+    aws_secret_access_key: Optional[str] = "",
+    metadata_s3_bucket: Optional[str] = "",
+    metadata_s3_key: Optional[str] = "",
+    metadata_endpoint_url: Optional[str] = "",
+    metadata_aws_access_key_id: Optional[str] = "",
+    metadata_aws_secret_access_key: Optional[str] = "",
+) -> None:
+    """Set a database type to a dataset in dataset collection.
+
+    Args:
+        db (Database): mongo database object
+        dataset_name (str): Dataset name
+        database_type (str): Type of the database
+        metadata_database_type (str): Metadata database type
+
+        dataset_path (str): Path to the dataset (for local db type)
+        metadata_path (str): Path to metadata (for local db type)
+
+        s3_bucket (str): S3 bucket name
+        s3_key (str): S3 key
+        endpoint_url (str): S3 endpoint URL
+        aws_access_key_id (str): AWS access key ID
+        aws_secret_access_key (str): AWS secret access key
+        metadata_s3_bucket (str): Metadata S3 bucket name
+        metadata_s3_key (str): Metadata S3 key
+        metadata_endpoint_url (str): Metadata S3 endpoint URL
+        metadata_aws_access_key_id (str): Metadata AWS access key ID
+        metadata_aws_secret_access_key (str): Metadata AWS secret access key
+
+    Raises:
+        ValueError: If the dataset already exists
+                    or if the database type is unknown.
+
+    Returns:
+        None
+    """
+    if db.datasets.count_documents({"dataset_name": dataset_name}) > 0:
+        raise ValueError("Cannot add database because already set. ")
+
+    # Step 1: Build dataset
+    dataset: Dict = {
+        "dataset_name": dataset_name,
+        "database_type": database_type,
+    }
+
+    if database_type == PrivateDatabaseType.PATH:
+        dataset["dataset_path"] = dataset_path
+    elif database_type == PrivateDatabaseType.S3:
+        dataset["s3_bucket"] = s3_bucket
+        dataset["s3_key"] = s3_key
+        dataset["endpoint_url"] = endpoint_url
+        dataset["aws_access_key_id"] = aws_access_key_id
+        dataset["aws_secret_access_key"] = aws_secret_access_key
+    else:
+        raise ValueError(f"Unknown database type {database_type}")
+
+    # Step 2: Build metadata
+    dataset["metadata"] = {"database_type": metadata_database_type}
+    if metadata_database_type == PrivateDatabaseType.PATH:
+        # Store metadata from yaml to metadata collection
+        with open(metadata_path, encoding="utf-8") as f:  # type: ignore
+            metadata_dict = yaml.safe_load(f)
+
+        dataset["metadata"]["metadata_path"] = metadata_path
+
+    elif metadata_database_type == PrivateDatabaseType.S3:
+        client = boto3.client(
+            "s3",
+            endpoint_url=metadata_endpoint_url,
+            aws_access_key_id=metadata_aws_access_key_id,
+            aws_secret_access_key=metadata_aws_secret_access_key,
+        )
+        response = client.get_object(
+            Bucket=metadata_s3_bucket, Key=metadata_s3_key
+        )
+        try:
+            metadata_dict = yaml.safe_load(response["Body"])
+        except yaml.YAMLError as e:
+            raise e
+
+        dataset["metadata"]["s3_bucket"] = metadata_s3_bucket
+        dataset["metadata"]["s3_key"] = metadata_s3_key
+        dataset["metadata"]["endpoint_url"] = metadata_endpoint_url
+        dataset["metadata"]["aws_access_key_id"] = metadata_aws_access_key_id
+        dataset["metadata"][
+            "aws_secret_access_key"
+        ] = metadata_aws_secret_access_key
+
+    else:
+        raise ValueError(f"Unknown database type {metadata_database_type}")
+
+    # Step 3: Insert into db
+    res = db.datasets.insert_one(dataset)
+    check_result_acknowledged(res)
+    res = db.metadata.insert_one({dataset_name: metadata_dict})
+    check_result_acknowledged(res)
+
+    LOG.info(
+        f"Added dataset {dataset_name} with database "
+        f"{database_type} and associated metadata."
+    )
+
+
+def add_datasets_via_yaml(
+    db: Database,
+    yaml_file: Union[str, Dict],
+    clean: bool,
+    overwrite_datasets: bool,
+    overwrite_metadata: bool,
+) -> None:
+    """Set all database types to datasets in dataset collection based
+    on yaml file.
+
+    Args:
+        db (Database): mongo database object
+        yaml_file (Union[str, Dict]):
+            if str: a path to the YAML file location
+            if Dict: a dictionnary containing the collection data
+        clean (bool): Whether to clean the collection before adding.
+        overwrite_datasets (bool): Whether to overwrite existing datasets.
+        overwrite_metadata (bool): Whether to overwrite existing metadata.
+
+    Raises:
+        ValueError: If there are errors in the YAML file format.
+
+    Returns:
+        None
+    """
+    if clean:
+        # Collection created from scratch
+        db.datasets.drop()
+        db.metadata.drop()
+        LOG.info("Cleaning done. \n")
+
+    if isinstance(yaml_file, str):
+        with open(yaml_file, encoding="utf-8") as f:
+            yaml_file = yaml.safe_load(f)
+    dataset_dict = DatasetsCollection(**yaml_file)
+
+    # Step 1: add datasets
+    new_datasets = []
+    existing_datasets = []
+    for d in dataset_dict.datasets:
+        # Fill datasets_list
+        if not db.datasets.find_one({"dataset_name": d.dataset_name}):
+            new_datasets.append(d)
+        else:
+            existing_datasets.append(d)
+
+    # Overwrite values for existing dataset with values from yaml
+    if existing_datasets:
+        if overwrite_datasets:
+            for d in existing_datasets:
+                dataset_filter = {"dataset_name": d.dataset_name}
+                update_operation = {"$set": d.dict()}
+                res: _WriteResult = db.datasets.update_many(
+                    dataset_filter, update_operation
+                )
+                check_result_acknowledged(res)
+            LOG.info("Existing datasets updated with new collection")
+        else:
+            warn(
+                "Some datasets already present in database."
+                "Overwrite is set to False."
+            )
+
+    # Add dataset collection
+    if new_datasets:
+        new_datasets_dicts = [d.dict() for d in new_datasets]
+        res = db.datasets.insert_many(new_datasets_dicts)
+        check_result_acknowledged(res)
+        LOG.info("Added datasets collection from yaml.")
+
+    # Step 2: add metadata collections (one metadata per dataset)
+    for d in dataset_dict.datasets:
+        dataset_name = d.dataset_name
+        metadata_db_type = d.metadata.database_type
+
+        match metadata_db_type:
+            case PrivateDatabaseType.PATH:
+                with open(d.metadata.metadata_path, encoding="utf-8") as f:
+                    metadata_dict = yaml.safe_load(f)
+
+            case PrivateDatabaseType.S3:
+                client = boto3.client(
+                    "s3",
+                    endpoint_url=d.metadata.endpoint_url,
+                    aws_access_key_id=d.metadata.aws_access_key_id,
+                    aws_secret_access_key=d.metadata.aws_secret_access_key,
+                )
+                response = client.get_object(
+                    Bucket=d.metadata.s3_bucket,
+                    Key=d.metadata.s3_key,
+                )
+                try:
+                    metadata_dict = yaml.safe_load(response["Body"])
+                except yaml.YAMLError as e:
+                    return e
+
+            case _:
+                raise InternalServerException(
+                    "Unknown metadata_db_type PrivateDatabaseType:"
+                    + f"{metadata_db_type}"
+                )
+
+        # Overwrite or not depending on config if metadata already exists
+        metadata_filter = {dataset_name: metadata_dict}
+        metadata = db.metadata.find_one(metadata_filter)
+
+        if metadata and overwrite_metadata:
+            LOG.info(f"Metadata updated for dataset : {dataset_name}.")
+            res = db.metadata.update_one(
+                metadata_filter, {"$set": {dataset_name: metadata_dict}}
+            )
+            check_result_acknowledged(res)
+        elif metadata:
+            LOG.info(
+                "Metadata already exist. "
+                "Use the command -om to overwrite with new values."
+            )
+        else:
+            res = db.metadata.insert_one({dataset_name: metadata_dict})
+            check_result_acknowledged(res)
+            LOG.info(f"Added metadata of {dataset_name} dataset. ")
+
+
+@check_dataset_and_metadata_exist(True)
+def del_dataset(db: Database, dataset: str) -> None:
+    """Delete dataset from dataset collection.
+
+    Args:
+        db (Database): mongo database object
+        dataset (str): Dataset name to be deleted
+
+    Returns:
+        None
+    """
+    res = db.datasets.delete_many({"dataset_name": dataset})
+    check_result_acknowledged(res)
+    res = db.metadata.delete_many({dataset: {"$exists": True}})
+    check_result_acknowledged(res)
+    LOG.info(f"Deleted dataset and metadata for {dataset}.")
+
+
+def show_dataset(db: Database, dataset: str) -> dict:  # TODO test
+    """Show a dataset from dataset collection.
+
+    Args:
+        db (Database): mongo database object
+        dataset (str): name of the dataset to show
+
+    Returns:
+        dataset_info (dict): informations about the dataset
+    """
+    dataset_info = list(db.datasets.find({"dataset_name": dataset}))[0]
+    dataset_info.pop("_id", None)
+    LOG.info(dataset_info)
+    return dataset_info
+
+
+def show_metadata_of_dataset(db: Database, dataset: str) -> dict:  # TODO test
+    """Show a metadata from metadata collection.
+
+    Args:
+        db (Database): mongo database object
+        dataset (str): name of the dataset of the metadata to show
+
+    Returns:
+        metadata (dict): informations about the metadata
+    """
+    # Retrieve the document containing metadata for the specified dataset
+    metadata_document = db.metadata.find_one({dataset: {"$exists": True}})
+
+    if metadata_document:
+        # Extract metadata for the specified dataset
+        metadata_info = metadata_document[dataset]
+        return metadata_info
+    else:
+        raise ValueError(f"No metadata found for dataset: {dataset}")
+
+
+def get_list_of_datasets(db: Database) -> list:  # TODO  test
+    """Get the list of all dataset is 'datasets' collection
+
+    Args:
+        db (Database): mongo database object
+
+    Returns:
+        dataset_names (list): list of names of all datasets
+    """
+    dataset_names = []
+    for elem in db.datasets.find():
+        dataset_names.append(elem["dataset_name"])
+    return dataset_names
+
+
+#######################  COLLECTIONS  ####################### # noqa: E266
+def drop_collection(db: Database, collection: str) -> None:
+    """Delete collection.
+
+    Args:
+        db (Database): mongo database object
+        collection (str): Collection name to be deleted.
+
+    Returns:
+        None
+    """
+    db.drop_collection(collection)
+    LOG.info(f"Deleted collection {collection}.")
+
+
+def show_collection(db: Database, collection: str) -> list:
+    """Show a collection
+
+    Args:
+        db (Database): mongo database object
+        collection (str): Collection name to be shown.
+
+    Returns:
+        None
+    """
+    collection_query = db[collection].find({})
+    if not collection_query:
+        return []
+
+    collections = []
+    for document in collection_query:
+        document.pop("_id", None)
+        collections.append(document)
+    LOG.info(collections)
+    return collections
+
+
+if __name__ == "__main__":
+    ########################################################################
+    ########################## MongoDB Connection ############# # noqa: E266
+    ########################################################################
+    connection_parser = argparse.ArgumentParser(add_help=False)
+    connection_parser.add_argument("-db_u", "--username", default="user")
+    connection_parser.add_argument("-db_pwd", "--password", default="user_pwd")
+    connection_parser.add_argument("-db_a", "--address", default="mongodb")
+    connection_parser.add_argument("-db_p", "--port", default=27017)
+    connection_parser.add_argument("-db_n", "--db_name", default="defaultdb")
+
+    ########################################################################
+    ######################## MongoDB Administration ############ # noqa: E266
+    ########################################################################
+    parser = argparse.ArgumentParser(
+        description="MongoDB administration script for the database"
+    )
+    subparsers = parser.add_subparsers(
+        title="subcommands", help="user database administration operations"
+    )
+
+    ##########################  USERS  ########################## # noqa: E266
+    # Create the parser for the "add_user" command
+    add_user_parser = subparsers.add_parser(
+        "add_user",
+        help="add user to users collection",
+        parents=[connection_parser],
+    )
+    add_user_parser.add_argument("-u", "--user", required=True, type=str)
+    add_user_parser.set_defaults(func=add_user)
+
+    # Create the parser for the "add_user_with_budget" command
+    add_user_wb_parser = subparsers.add_parser(
+        "add_user_with_budget",
+        help="add user with budget to users collection",
+        parents=[connection_parser],
+    )
+    add_user_wb_parser.add_argument("-u", "--user", required=True, type=str)
+    add_user_wb_parser.add_argument("-d", "--dataset", required=True, type=str)
+    add_user_wb_parser.add_argument(
+        "-e", "--epsilon", required=True, type=float
+    )
+    add_user_wb_parser.add_argument(
+        "-del", "--delta", required=True, type=float
+    )
+    add_user_wb_parser.set_defaults(func=add_user_with_budget)
+
+    # Create the parser for the "del_user" command
+    del_user_parser = subparsers.add_parser(
+        "del_user",
+        help="delete user from users collection",
+        parents=[connection_parser],
+    )
+    del_user_parser.add_argument("-u", "--user", required=True, type=str)
+    del_user_parser.set_defaults(func=del_user)
+
+    # Create the parser for the "add_dataset" command
+    add_dataset_to_user_parser = subparsers.add_parser(
+        "add_dataset_to_user",
+        help="add dataset with initialized budget values for a user",
+        parents=[connection_parser],
+    )
+    add_dataset_to_user_parser.add_argument(
+        "-u", "--user", required=True, type=str
+    )
+    add_dataset_to_user_parser.add_argument(
+        "-d", "--dataset", required=True, type=str
+    )
+    add_dataset_to_user_parser.add_argument(
+        "-e", "--epsilon", required=True, type=float
+    )
+    add_dataset_to_user_parser.add_argument(
+        "-del", "--delta", required=True, type=float
+    )
+    add_dataset_to_user_parser.set_defaults(func=add_dataset_to_user)
+
+    # Create the parser for the "del_dataset" command
+    del_dataset_to_user_parser = subparsers.add_parser(
+        "del_dataset_to_user",
+        help="delete dataset for user in users collection",
+        parents=[connection_parser],
+    )
+    del_dataset_to_user_parser.add_argument(
+        "-u", "--user", required=True, type=str
+    )
+    del_dataset_to_user_parser.add_argument(
+        "-d", "--dataset", required=True, type=str
+    )
+    del_dataset_to_user_parser.set_defaults(func=del_dataset_to_user)
+
+    # Create the parser for the "set_budget_field" command
+    set_budget_field_parser = subparsers.add_parser(
+        "set_budget_field",
+        help="set budget field to given value for given user and dataset",
+        parents=[connection_parser],
+    )
+    set_budget_field_parser.add_argument(
+        "-u", "--user", required=True, type=str
+    )
+    set_budget_field_parser.add_argument(
+        "-d", "--dataset", required=True, type=str
+    )
+    set_budget_field_parser.add_argument(
+        "-f",
+        "--field",
+        required=True,
+        choices=["initial_epsilon", "initial_delta"],
+    )
+    set_budget_field_parser.add_argument(
+        "-v", "--value", required=True, type=float
+    )
+    set_budget_field_parser.set_defaults(func=set_budget_field)
+
+    # Create the parser for the "set_may_query" command
+    set_may_query_parser = subparsers.add_parser(
+        "set_may_query",
+        help="set may query field to given value for given user",
+        parents=[connection_parser],
+    )
+    set_may_query_parser.add_argument("-u", "--user", required=True, type=str)
+    set_may_query_parser.add_argument(
+        "-v", "--value", required=True, choices=["False", "True"]
+    )
+    set_may_query_parser.set_defaults(func=set_may_query)
+
+    # Show the user
+    show_user_parser = subparsers.add_parser(
+        "show_user",
+        help="show all metadata of user",
+        parents=[connection_parser],
+    )
+    show_user_parser.add_argument("-u", "--user", required=True, type=str)
+    show_user_parser.set_defaults(func=show_user)
+
+    # Create the parser for the "create_example_users" command
+    users_collection_from_yaml_parser = subparsers.add_parser(
+        "add_users_via_yaml",
+        help="create users collection from yaml file",
+        parents=[connection_parser],
+    )
+    users_collection_from_yaml_parser.add_argument(
+        "-c",
+        "--clean",
+        required=False,
+        action="store_const",
+        const=True,
+        default=False,
+    )
+    users_collection_from_yaml_parser.add_argument(
+        "-o",
+        "--overwrite",
+        required=False,
+        action="store_const",
+        const=True,
+        default=False,
+    )
+    users_collection_from_yaml_parser.add_argument(
+        "-yf", "--yaml_file", required=True, type=str
+    )
+    users_collection_from_yaml_parser.set_defaults(func=add_users_via_yaml)
+
+    # Function: Show Archives of User
+    show_archives_parser = subparsers.add_parser(
+        "show_archives",
+        help="show all previous queries from a user",
+        parents=[connection_parser],
+    )
+    show_archives_parser.add_argument(
+        "-u",
+        "--user",
+        required=True,
+        type=str,
+        help="username of the user to show archives",
+    )
+    show_archives_parser.set_defaults(func=show_archives_of_user)
+
+    # Function: Get List of Users
+    get_users_parser = subparsers.add_parser(
+        "get_users",
+        help="get the list of all users in 'users' collection",
+        parents=[connection_parser],
+    )
+    get_users_parser.set_defaults(func=get_list_of_users)
+
+    # Function: Get List of Datasets from User
+    get_user_datasets_parser = subparsers.add_parser(
+        "get_user_datasets",
+        help="get the list of all datasets from a user",
+        parents=[connection_parser],
+    )
+    get_user_datasets_parser.add_argument(
+        "-u",
+        "--user",
+        required=True,
+        type=str,
+        help="username of the user to show datasets",
+    )
+    get_user_datasets_parser.set_defaults(func=get_list_of_datasets_from_user)
+
+    #######################  DATASETS  ####################### # noqa: E266
+    # Create parser for dataset private database
+    add_dataset_parser = subparsers.add_parser(
+        "add_dataset",
+        help="set in which database the dataset is stored",
+        parents=[connection_parser],
+    )
+    # Dataset location
+    add_dataset_parser.add_argument("-d", "--dataset_name", required=True)
+    add_dataset_parser.add_argument("-db", "--database_type", required=True)
+    add_dataset_parser.add_argument(
+        "-d_path", "--dataset_path", required=False
+    )
+    add_dataset_parser.add_argument("-s3b", "--s3_bucket", required=False)
+    add_dataset_parser.add_argument("-s3k", "--s3_key", required=False)
+    add_dataset_parser.add_argument(
+        "-s3_url", "--endpoint_url", required=False
+    )
+    add_dataset_parser.add_argument(
+        "-s3_ak", "--aws_access_key_id", required=False
+    )
+    add_dataset_parser.add_argument(
+        "-s3_sak", "--aws_secret_access_key", required=False
+    )
+    # Metadata location
+    add_dataset_parser.add_argument(
+        "-m_db", "--metadata_database_type", required=True
+    )
+    add_dataset_parser.add_argument("-mp", "--metadata_path", required=False)
+    add_dataset_parser.add_argument(
+        "-m_s3b", "--metadata_s3_bucket", required=False
+    )
+    add_dataset_parser.add_argument(
+        "-m_s3k", "--metadata_s3_key", required=False
+    )
+    add_dataset_parser.add_argument(
+        "-m_s3_url", "--metadata_endpoint_url", required=False
+    )
+    add_dataset_parser.add_argument(
+        "-m_s3_ak", "--metadata_aws_access_key_id", required=False
+    )
+    add_dataset_parser.add_argument(
+        "-m_s3_sak", "--metadata_aws_secret_access_key", required=False
+    )
+    add_dataset_parser.set_defaults(func=add_dataset)
+
+    # Create the parser for the "add_datasets_via_yaml" command
+    add_datasets_via_yaml_parser = subparsers.add_parser(
+        "add_datasets_via_yaml",
+        help="create dataset to database type collection",
+        parents=[connection_parser],
+    )
+    add_datasets_via_yaml_parser.add_argument(
+        "-yf", "--yaml_file", required=True, type=str
+    )
+    add_datasets_via_yaml_parser.add_argument(
+        "-c",
+        "--clean",
+        required=False,
+        action="store_const",
+        const=True,
+        default=False,
+    )
+    add_datasets_via_yaml_parser.add_argument(
+        "-od",
+        "--overwrite_datasets",
+        required=False,
+        action="store_const",
+        const=True,
+        default=False,
+    )
+    add_datasets_via_yaml_parser.add_argument(
+        "-om",
+        "--overwrite_metadata",
+        required=False,
+        action="store_const",
+        const=True,
+        default=False,
+    )
+    add_datasets_via_yaml_parser.set_defaults(func=add_datasets_via_yaml)
+
+    # Create the parser for the "del_dataset" command
+    del_dataset_parser = subparsers.add_parser(
+        "del_dataset",
+        help="delete dataset and metadata from "
+        "datasets and metadata collection",
+        parents=[connection_parser],
+    )
+    del_dataset_parser.add_argument("-d", "--dataset", required=True, type=str)
+    del_dataset_parser.set_defaults(func=del_dataset)
+
+    # Function: Show Dataset
+    show_dataset_parser = subparsers.add_parser(
+        "show_dataset",
+        help="show a dataset from the dataset collection",
+        parents=[connection_parser],
+    )
+    show_dataset_parser.add_argument(
+        "-d",
+        "--dataset",
+        required=True,
+        type=str,
+        help="name of the dataset to show",
+    )
+    show_dataset_parser.set_defaults(func=show_dataset)
+
+    # Function: Show Metadata of Dataset
+    show_metadata_parser = subparsers.add_parser(
+        "show_metadata",
+        help="show metadata from the metadata collection",
+        parents=[connection_parser],
+    )
+    show_metadata_parser.add_argument(
+        "-d",
+        "--dataset",
+        required=True,
+        type=str,
+        help="name of the dataset of the metadata to show",
+    )
+    show_metadata_parser.set_defaults(func=show_metadata_of_dataset)
+
+    # Function: Get List of Datasets
+    get_datasets_parser = subparsers.add_parser(
+        "get_datasets",
+        help="get the list of all datasets in 'datasets' collection",
+        parents=[connection_parser],
+    )
+    get_datasets_parser.set_defaults(func=get_list_of_datasets)
+
+    #######################  COLLECTIONS  ####################### # noqa: E266
+    # Create the parser for the "drop_collection" command
+    drop_collection_parser = subparsers.add_parser(
+        "drop_collection",
+        help="delete collection from database",
+        parents=[connection_parser],
+    )
+    drop_collection_parser.add_argument(
+        "-c",
+        "--collection",
+        required=True,
+        choices=["users", "datasets", "metadata", "queries_archives"],
+    )
+    drop_collection_parser.set_defaults(func=drop_collection)
+
+    # Create the parser for the "show_users_collection" command
+    show_collection_parser = subparsers.add_parser(
+        "show_collection",
+        help="print a collection",
+        parents=[connection_parser],
+    )
+    show_collection_parser.add_argument(
+        "-c",
+        "--collection",
+        required=True,
+        choices=["users", "datasets", "metadata", "queries_archives"],
+    )
+    show_collection_parser.set_defaults(func=show_collection)
+
+    args = parser.parse_args()
+
+    #######################  FUNCTION CALL  ###################### # noqa: E266
+    # Get MongoDB
+    db_url = get_mongodb_url(args)
+    mongo_db: Database = MongoClient(db_url)[args.db_name]
+
+    function_map = {
+        "add_user": lambda args: add_user(mongo_db, args.user),
+        "add_user_with_budget": lambda args: add_user_with_budget(
+            mongo_db, args.user, args.dataset, args.epsilon, args.delta
+        ),
+        "del_user": lambda args: del_user(mongo_db, args.user),
+        "add_dataset_to_user": lambda args: add_dataset_to_user(
+            mongo_db, args.user, args.dataset, args.epsilon, args.delta
+        ),
+        "del_dataset_to_user": lambda args: del_dataset_to_user(
+            mongo_db, args.user, args.dataset
+        ),
+        "set_budget_field": lambda args: set_budget_field(
+            mongo_db, args.user, args.dataset, args.field, args.value
+        ),
+        "set_may_query": lambda args: set_may_query(
+            mongo_db, args.user, args.value
+        ),
+        "show_user": lambda args: show_user(mongo_db, args.user),
+        "add_users_via_yaml": lambda args: add_users_via_yaml(
+            mongo_db, args.yaml_file, args.clean, args.overwrite
+        ),
+        "show_archives": lambda args: show_archives_of_user(
+            mongo_db, args.user
+        ),
+        "get_users": lambda args: get_list_of_users(mongo_db),
+        "get_user_datasets": lambda args: get_list_of_datasets_from_user(
+            mongo_db, args.user
+        ),
+        "add_dataset": lambda args: add_dataset(
+            mongo_db,
+            args.dataset_name,
+            args.database_type,
+            args.metadata_database_type,
+            args.dataset_path,
+            args.metadata_path,
+            args.s3_bucket,
+            args.s3_key,
+            args.endpoint_url,
+            args.aws_access_key_id,
+            args.aws_secret_access_key,
+            args.metadata_s3_bucket,
+            args.metadata_s3_key,
+            args.metadata_endpoint_url,
+            args.metadata_aws_access_key_id,
+            args.metadata_aws_secret_access_key,
+        ),
+        "add_datasets_via_yaml": lambda args: add_datasets_via_yaml(
+            mongo_db,
+            args.yaml_file,
+            args.clean,
+            args.overwrite_datasets,
+            args.overwrite_metadata,
+        ),
+        "del_dataset": lambda args: del_dataset(mongo_db, args.dataset),
+        "show_dataset": lambda args: show_dataset(mongo_db, args.dataset),
+        "show_metadata": lambda args: show_metadata_of_dataset(
+            mongo_db, args.dataset
+        ),
+        "get_datasets": lambda args: get_list_of_datasets(mongo_db),
+        "drop_collection": lambda args: drop_collection(
+            mongo_db, args.collection
+        ),
+        "show_collection": lambda args: show_collection(
+            mongo_db, args.collection
+        ),
+    }
+    function_map[args.func](args)
diff --git a/server/lomas_server/app.py b/server/lomas_server/app.py
index 24ea3c2f..4dbfce77 100644
--- a/server/lomas_server/app.py
+++ b/server/lomas_server/app.py
@@ -167,6 +167,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator:
 async def middleware(
     request: Request, call_next: Callable[[Request], Response]
 ) -> Response:
+    """Adds delays to requests response to protect against timing attack"""
     return await anti_timing_att(request, call_next, get_config())
 
 
@@ -672,10 +673,11 @@ def get_initial_budget(
             - initial_delta (float): initial delta budget.
     """
     try:
-        initial_epsilon, initial_delta = (
-            app.state.admin_database.get_initial_budget(
-                user_name, query_json.dataset_name
-            )
+        (
+            initial_epsilon,
+            initial_delta,
+        ) = app.state.admin_database.get_initial_budget(
+            user_name, query_json.dataset_name
         )
     except CUSTOM_EXCEPTIONS as e:
         raise e
diff --git a/server/lomas_server/constants.py b/server/lomas_server/constants.py
index 5612fbb3..77be723e 100644
--- a/server/lomas_server/constants.py
+++ b/server/lomas_server/constants.py
@@ -33,11 +33,15 @@
 
 
 class AdminDBType(StrEnum):
+    """Types of administration databases"""
+
     YAML_TYPE: str = "yaml"
     MONGODB_TYPE: str = "mongodb"
 
 
 class ConfDatasetStore(StrEnum):
+    """Types of classes to handle datasets in memory"""
+
     BASIC: str = "basic"
     LRU: str = "LRU_cache"
 
@@ -60,18 +64,24 @@ class ConfDatasetStore(StrEnum):
 
 # Supported DP libraries
 class DPLibraries(StrEnum):
+    """Name of DP Library used in the query"""
+
     SMARTNOISE_SQL = "smartnoise_sql"
     OPENDP = "opendp"
 
 
 # Private Databases
 class PrivateDatabaseType(StrEnum):
+    """Type of Private Database for the private data"""
+
     PATH = "PATH_DB"
     S3 = "S3_DB"
 
 
 # OpenDP Measurement Divergence Type
 class OpenDPMeasurement(StrEnum):
+    """Type of divergence for opendp measurement"""
+
     FIXED_SMOOTHED_MAX_DIVERGENCE = "fixed_smoothed_max_divergence"
     MAX_DIVERGENCE = "max_divergence"
     SMOOTHED_MAX_DIVERGENCE = "smoothed_max_divergence"
diff --git a/server/lomas_server/dataset_store/private_dataset_observer.py b/server/lomas_server/dataset_store/private_dataset_observer.py
index 1e7f9984..b7fdc5cf 100644
--- a/server/lomas_server/dataset_store/private_dataset_observer.py
+++ b/server/lomas_server/dataset_store/private_dataset_observer.py
@@ -11,4 +11,4 @@ class PrivateDatasetObserver(ABC):
 
     @abstractmethod
     def update_memory_usage(self) -> None:
-        pass
+        """Abstract method to update total memory used by datasets"""
diff --git a/server/lomas_server/mongodb_admin.py b/server/lomas_server/mongodb_admin.py
deleted file mode 100644
index 86681cbd..00000000
--- a/server/lomas_server/mongodb_admin.py
+++ /dev/null
@@ -1,1007 +0,0 @@
-import argparse
-import functools
-from typing import Callable
-from warnings import warn
-
-import boto3
-from pymongo import MongoClient
-from pymongo.database import Database
-from pymongo.results import _WriteResult
-import yaml
-
-from admin_database.utils import get_mongodb_url
-from admin_database.mongodb_database import check_result_acknowledged
-from constants import PrivateDatabaseType
-from utils.error_handler import InternalServerException
-from utils.loggr import LOG
-
-
-def connect(
-    function: Callable[[Database, argparse.Namespace], None]
-) -> Callable:
-    """Returns a wrapping function to connect to the database
-
-    Args:
-        function (Callable[[Database, argparse.Namespace], None]):
-            Any function taking a Database instance as argument,
-            as well as some argparse.Namespace object.
-
-    Returns:
-        Callable: The wrapping function which sets up the database
-        connection before calling the wrapped function.
-    """
-
-    @functools.wraps(function)
-    def wrap_function(*arguments: argparse.Namespace) -> None:
-        db_url: str = get_mongodb_url(arguments[0])
-        db: Database = MongoClient(db_url)[arguments[0].db_name]
-        return function(db, *arguments)
-
-    return wrap_function
-
-
-def check_user_exists(enforce_true: bool) -> Callable:
-    """Creates a wrapper function that raises a ValueError if the supplied
-    user does (not) exist in the user collection depending on the
-    enforce_true parameter.
-
-    Args:
-        enforce_true (bool): If set to True, the wrapper will enforce
-            the user is already in the database. If set to False, it
-            will enforce the user is NOT in the database.
-
-    Returns:
-        Callable: The wrapper function that enforces user presence
-            (or absence) before calling the suplied function.
-    """
-
-    def inner_func(
-        function: Callable[[Database, argparse.Namespace], None]
-    ) -> Callable:
-        @functools.wraps(function)
-        def wrapper_decorator(*arguments: argparse.Namespace) -> None:
-            db = arguments[0]
-            user = arguments[1].user
-
-            user_count = db.users.count_documents({"user_name": user})
-
-            if enforce_true and user_count == 0:
-                raise ValueError(
-                    f"User {user} does not exist in user collecion"
-                )
-            if not enforce_true and user_count > 0:
-                raise ValueError(
-                    f"User {user} already exists in user collection"
-                )
-
-            return function(*arguments)  # type: ignore
-
-        return wrapper_decorator
-
-    return inner_func
-
-
-def check_user_has_dataset(enforce_true: bool) -> Callable:
-    """Creates a wrapper function that raises a ValueError if the supplied
-    user has access (or not) to the supplied dataset depending on the
-    enforce_true parameter.
-
-    Args:
-        enforce_true (bool): If set to true, the wrapper function will
-            enforce the user has access to the dataset. If set to False,
-            the wrapper function will enforce the user has NOT access
-            to the specified dataset.
-
-    Returns:
-        Callable: The wrapper function that asserts user access (or not)
-            to the provided dataset.
-    """
-
-    def inner_func(
-        function: Callable[[Database, argparse.Namespace], None]
-    ) -> Callable:
-        @functools.wraps(function)
-        def wrapper_decorator(*arguments: argparse.Namespace) -> None:
-            db = arguments[0]
-            user = arguments[1].user
-            dataset = arguments[1].dataset
-
-            user_and_ds_count = db.users.count_documents(
-                {
-                    "user_name": user,
-                    "datasets_list": {"$elemMatch": {"dataset_name": dataset}},
-                }
-            )
-
-            if enforce_true and user_and_ds_count == 0:
-                raise ValueError(
-                    f"User {user} does not have dataset {dataset}"
-                )
-            if not enforce_true and user_and_ds_count > 0:
-                raise ValueError(f"User {user} already has dataset {dataset}")
-
-            return function(*arguments)  # type: ignore
-
-        return wrapper_decorator
-
-    return inner_func
-
-
-def check_dataset_and_metadata_exist(enforce_true: bool) -> Callable:
-    """Creates a wrapper function that raises a ValueError
-    if the supplied user does not already exist in the user collection.
-    """
-
-    def inner_func(
-        function: Callable[[Database, argparse.Namespace], None]
-    ) -> Callable:
-        @functools.wraps(function)
-        def wrapper_decorator(*arguments: argparse.Namespace) -> None:
-            db = arguments[0]
-            dataset = arguments[1].dataset
-
-            dataset_count = db.datasets.count_documents(
-                {"dataset_name": dataset}
-            )
-
-            if enforce_true and dataset_count == 0:
-                raise ValueError(
-                    f"Dataset {dataset} does not exist in dataset collecion"
-                )
-            if not enforce_true and dataset_count > 0:
-                raise ValueError(
-                    f"Dataset {dataset} already exists in dataset collection"
-                )
-
-            metadata_count = db.metadata.count_documents(
-                {dataset: {"$exists": True}}
-            )
-
-            if enforce_true and metadata_count == 0:
-                raise ValueError(
-                    f"Metadata for dataset {dataset} does"
-                    "not exist in metadata collecion"
-                )
-            if not enforce_true and metadata_count > 0:
-                raise ValueError(
-                    f"Metadata for dataset {dataset} already"
-                    "exists in metadata collection"
-                )
-
-            return function(*arguments)  # type: ignore
-
-        return wrapper_decorator
-
-    return inner_func
-
-
-##########################  USERS  ########################## # noqa: E266
-@connect
-@check_user_exists(False)
-def add_user(db: Database, arguments: argparse.Namespace) -> None:
-    """Add new user in users collection with initial values for all fields
-    set by default.
-
-    Args:
-        db (Database): The database connection.
-        arguments (argparse.Namespace):
-
-    Raises:
-        ValueError: If the user already exists.
-        WriteConcernError: If the result is not acknowledged.
-    """
-
-    res = db.users.insert_one(
-        {
-            "user_name": arguments.user,
-            "may_query": True,
-            "datasets_list": [],
-        }
-    )
-
-    check_result_acknowledged(res)
-
-    LOG.info(f"Added user {arguments.user}.")
-
-
-@connect
-@check_user_exists(False)
-def add_user_with_budget(db: Database, arguments: argparse.Namespace) -> None:
-    """Add new user in users collection with initial values
-    for all fields set by default.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-
-    Raises:
-        ValueError: _description_
-    """
-    res = db.users.insert_one(
-        {
-            "user_name": arguments.user,
-            "may_query": True,
-            "datasets_list": [
-                {
-                    "dataset_name": arguments.dataset,
-                    "initial_epsilon": arguments.epsilon,
-                    "initial_delta": arguments.delta,
-                    "total_spent_epsilon": 0.0,
-                    "total_spent_delta": 0.0,
-                }
-            ],
-        }
-    )
-
-    check_result_acknowledged(res)
-
-    LOG.info(
-        f"Added access to user {arguments.user} "
-        + f"with dataset {arguments.dataset}, "
-        + f"budget epsilon {arguments.epsilon} and "
-        + f"delta {arguments.delta}."
-    )
-
-
-@connect
-@check_user_exists(True)
-def del_user(db: Database, arguments: argparse.Namespace) -> None:
-    """Delete all related information for user from the users collection.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    res = db.users.delete_many({"user_name": arguments.user})
-    check_result_acknowledged(res)
-
-    LOG.info(f"Deleted user {arguments.user}.")
-
-
-@connect
-@check_user_exists(True)
-@check_user_has_dataset(False)
-def add_dataset_to_user(db: Database, arguments: argparse.Namespace) -> None:
-    """Add dataset with initialized budget values to list of datasets
-    that the user has access to.
-    Will not add if already added (no error will be raised in that case).
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-
-    Raises:
-        ValueError: _description_
-    """
-    res = db.users.update_one(
-        {
-            "user_name": arguments.user,
-            "datasets_list.dataset_name": {"$ne": arguments.dataset},
-        },
-        {
-            "$push": {
-                "datasets_list": {
-                    "dataset_name": arguments.dataset,
-                    "initial_epsilon": arguments.epsilon,
-                    "initial_delta": arguments.delta,
-                    "total_spent_epsilon": 0.0,
-                    "total_spent_delta": 0.0,
-                }
-            }
-        },
-    )
-
-    check_result_acknowledged(res)
-
-    LOG.info(
-        f"Added access to dataset {arguments.dataset}"
-        f"to user {arguments.user}"
-        f" with budget epsilon {arguments.epsilon}"
-        f" and delta {arguments.delta}."
-    )
-
-
-@connect
-@check_user_exists(True)
-@check_user_has_dataset(True)
-def del_dataset_to_user(db: Database, arguments: argparse.Namespace) -> None:
-    """Remove if exists the dataset (and all related budget info)
-    from list of datasets that user has access to.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    res = db.users.update_one(
-        {"user_name": arguments.user},
-        {
-            "$pull": {
-                "datasets_list": {"dataset_name": {"$eq": arguments.dataset}}
-            }
-        },
-    )
-
-    check_result_acknowledged(res)
-
-    LOG.info(
-        f"Remove access to dataset {arguments.dataset}"
-        + f" from user {arguments.user}."
-    )
-
-
-@connect
-@check_user_exists(True)
-@check_user_has_dataset(True)
-def set_budget_field(db: Database, arguments: argparse.Namespace) -> None:
-    """Set (for some reason) a budget field to a given value
-    if given user exists and has access to given dataset.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    res = db.users.update_one(
-        {
-            "user_name": arguments.user,
-            "datasets_list.dataset_name": arguments.dataset,
-        },
-        {"$set": {f"datasets_list.$.{arguments.field}": arguments.value}},
-    )
-
-    check_result_acknowledged(res)
-
-    LOG.info(
-        f"Set budget of {arguments.user} for dataset {arguments.dataset}"
-        f" of {arguments.field} to {arguments.value}."
-    )
-
-
-@connect
-@check_user_exists(True)
-def set_may_query(db: Database, arguments: argparse.Namespace) -> None:
-    """Set (for some reason) the 'may query' field to a given value
-    if given user exists.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    res = db.users.update_one(
-        {"user_name": arguments.user},
-        {"$set": {"may_query": (arguments.value == "True")}},
-    )
-
-    check_result_acknowledged(res)
-
-    LOG.info(f"Set user {arguments.user} may query to True.")
-
-
-@connect
-@check_user_exists(True)
-def show_user(db: Database, arguments: argparse.Namespace) -> None:
-    """Show a user
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    user = list(db.users.find({"user_name": arguments.user}))[0]
-    user.pop("_id", None)
-    LOG.info(user)
-
-
-@connect
-def create_users_collection(
-    db: Database, arguments: argparse.Namespace
-) -> None:
-    """Add all users from yaml file to the user collection
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    if arguments.clean:
-        # Collection created from scratch
-        db.users.drop()
-        LOG.info("Cleaning done. \n")
-
-    # Load yaml data and insert it
-    with open(arguments.path, encoding="utf-8") as f:
-        user_dict = yaml.safe_load(f)
-        # Filter out duplicates
-        new_users = []
-        existing_users = []
-        for user in user_dict["users"]:
-            if not db.users.find_one({"user_name": user["user_name"]}):
-                new_users.append(user)
-            else:
-                existing_users.append(user)
-
-        # Overwrite values for existing user with values from yaml
-        if existing_users:
-            if arguments.overwrite:
-                for user in existing_users:
-                    user_filter = {"user_name": user["user_name"]}
-                    update_operation = {"$set": user}
-                    res: _WriteResult = db.users.update_many(
-                        user_filter, update_operation
-                    )
-                    check_result_acknowledged(res)
-                LOG.info("Existing users updated. ")
-            else:
-                warn(
-                    "Some users already present in database."
-                    "Overwrite is set to False."
-                )
-
-        if new_users:
-            # Insert new users
-            res = db.users.insert_many(new_users)
-            check_result_acknowledged(res)
-            LOG.info(f"Added user data from yaml at {arguments.path}.")
-        else:
-            LOG.info("No new users added, they already exist in the server")
-
-
-###################  DATASET TO DATABASE  ################### # noqa: E266
-@connect
-@check_dataset_and_metadata_exist(False)
-def add_dataset(db: Database, arguments: argparse.Namespace) -> None:
-    """Set a database type to a dataset in dataset collection.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-
-    Raises:
-        ValueError: _description_
-        ValueError: _description_
-        ValueError: _description_
-
-    Returns:
-        _type_: _description_
-    """
-    if db.datasets.count_documents({"dataset_name": arguments.dataset}) > 0:
-        raise ValueError("Cannot add database because already set. ")
-
-    # Step 1: Build dataset
-    dataset = {
-        "dataset_name": arguments.dataset,
-        "database_type": arguments.database_type,
-    }
-
-    if arguments.database_type == PrivateDatabaseType.PATH:
-        dataset["dataset_path"] = arguments.dataset_path
-    elif arguments.database_type == PrivateDatabaseType.S3:
-        dataset["s3_bucket"] = arguments.s3_bucket
-        dataset["s3_key"] = arguments.s3_key
-        dataset["endpoint_url"] = arguments.endpoint_url
-        dataset["aws_access_key_id"] = arguments.aws_access_key_id
-        dataset["aws_secret_access_key"] = arguments.aws_secret_access_key
-    else:
-        raise ValueError(f"Unknown database type {arguments.database_type}")
-
-    # Step 2: Build metadata
-    dataset["metadata"] = {"database_type": arguments.metadata_database_type}
-    if arguments.metadata_database_type == PrivateDatabaseType.PATH:
-        # Store metadata from yaml to metadata collection
-        with open(arguments.metadata_path, encoding="utf-8") as f:
-            metadata_dict = yaml.safe_load(f)
-
-        dataset["metadata"]["metadata_path"] = arguments.metadata_path
-
-    elif arguments.metadata_database_type == PrivateDatabaseType.S3:
-        client = boto3.client(
-            "s3",
-            endpoint_url=arguments.metadata_endpoint_url,
-            aws_access_key_id=arguments.metadata_aws_access_key_id,
-            aws_secret_access_key=arguments.metadata_aws_secret_access_key,
-        )
-        response = client.get_object(
-            Bucket=arguments.metadata_s3_bucket, Key=arguments.metadata_s3_key
-        )
-        try:
-            metadata_dict = yaml.safe_load(response["Body"])
-        except yaml.YAMLError as e:
-            raise e
-
-        dataset["metadata"]["s3_bucket"] = arguments.metadata_s3_bucket
-        dataset["metadata"]["s3_key"] = arguments.metadata_s3_key
-        dataset["metadata"]["endpoint_url"] = arguments.metadata_endpoint_url
-        dataset["metadata"][
-            "aws_access_key_id"
-        ] = arguments.metadata_aws_access_key_id
-        dataset["metadata"][
-            "aws_secret_access_key"
-        ] = arguments.metadata_aws_secret_access_key
-
-    else:
-        raise ValueError(
-            f"Unknown database type {arguments.metadata_database_type}"
-        )
-
-    # Step 3: Insert into db
-    res = db.datasets.insert_one(dataset)
-    check_result_acknowledged(res)
-    res = db.metadata.insert_one({arguments.dataset: metadata_dict})
-    check_result_acknowledged(res)
-
-    LOG.info(
-        f"Added dataset {arguments.dataset} with database "
-        f"{arguments.database_type} and associated metadata."
-    )
-
-
-@connect
-def add_datasets(db: Database, arguments: argparse.Namespace) -> None:
-    """Set all database types to datasets in dataset collection based
-    on yaml file.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-
-    Raises:
-        InternalServerException: _description_
-        InternalServerException: _description_
-
-    Returns:
-        _type_: _description_
-    """
-    if arguments.clean:
-        # Collection created from scratch
-        db.datasets.drop()
-        db.metadata.drop()
-        LOG.info("Cleaning done. \n")
-
-    with open(arguments.path, encoding="utf-8") as f:
-        dataset_dict = yaml.safe_load(f)
-
-    def verify_keys(d: dict, field: str, metadata: bool = False) -> None:
-        """_summary_
-
-        Args:
-            d (dict): _description_
-            field (str): _description_
-            metadata (bool, optional): _description_. Defaults to False.
-        """
-        if metadata:
-            assert (
-                field in d["metadata"].keys()
-            ), f"Metadata of {d['dataset_name']} requires '{field}' key."
-        else:
-            assert (
-                field in d.keys()
-            ), f"Dataset {d['dataset_name']} requires '{field}' key."
-
-    # Step 1: add datasets
-    new_datasets = []
-    existing_datasets = []
-    for d in dataset_dict["datasets"]:
-        verify_keys(d, "dataset_name")
-        verify_keys(d, "database_type")
-        verify_keys(d, "metadata")
-
-        match d["database_type"]:
-            case PrivateDatabaseType.PATH:
-                verify_keys(d, "dataset_path")
-            case PrivateDatabaseType.S3:
-                verify_keys(d, "s3_bucket")
-                verify_keys(d, "s3_key")
-            case _:
-                raise InternalServerException(
-                    f"Unknown PrivateDatabaseType: {d['database_type']}"
-                )
-
-        # Fill datasets_list
-        if not db.datasets.find_one({"dataset_name": d["dataset_name"]}):
-            new_datasets.append(d)
-        else:
-            existing_datasets.append(d)
-
-    # Overwrite values for existing dataset with values from yaml
-    if existing_datasets:
-        if arguments.overwrite_datasets:
-            for d in existing_datasets:
-                dataset_filter = {"dataset_name": d["dataset_name"]}
-                update_operation = {"$set": d}
-                res: _WriteResult = db.datasets.update_many(
-                    dataset_filter, update_operation
-                )
-                check_result_acknowledged(res)
-            LOG.info(
-                f"Existing datasets updated with values"
-                f"from yaml at {arguments.path}. "
-            )
-        else:
-            warn(
-                "Some datasets already present in database."
-                "Overwrite is set to False."
-            )
-
-    # Add dataset collection
-    if new_datasets:
-        res = db.datasets.insert_many(new_datasets)
-        check_result_acknowledged(res)
-        LOG.info(f"Added datasets collection from yaml at {arguments.path}. ")
-
-    # Step 2: add metadata collections (one metadata per dataset)
-    for d in dataset_dict["datasets"]:
-        dataset_name = d["dataset_name"]
-        metadata_db_type = d["metadata"]["database_type"]
-
-        verify_keys(d, "database_type", metadata=True)
-        match metadata_db_type:
-            case PrivateDatabaseType.PATH:
-                verify_keys(d, "metadata_path", metadata=True)
-
-                with open(
-                    d["metadata"]["metadata_path"], encoding="utf-8"
-                ) as f:
-                    metadata_dict = yaml.safe_load(f)
-
-            case PrivateDatabaseType.S3:
-                verify_keys(d, "s3_bucket", metadata=True)
-                verify_keys(d, "s3_key", metadata=True)
-                verify_keys(d, "endpoint_url", metadata=True)
-                verify_keys(d, "aws_access_key_id", metadata=True)
-                verify_keys(d, "aws_secret_access_key", metadata=True)
-                client = boto3.client(
-                    "s3",
-                    endpoint_url=d["metadata"]["endpoint_url"],
-                    aws_access_key_id=d["metadata"]["aws_access_key_id"],
-                    aws_secret_access_key=d["metadata"][
-                        "aws_secret_access_key"
-                    ],
-                )
-                response = client.get_object(
-                    Bucket=d["metadata"]["s3_bucket"],
-                    Key=d["metadata"]["s3_key"],
-                )
-                try:
-                    metadata_dict = yaml.safe_load(response["Body"])
-                except yaml.YAMLError as e:
-                    return e
-
-            case _:
-                raise InternalServerException(
-                    "Unknown metadata_db_type PrivateDatabaseType:"
-                    + f"{metadata_db_type}"
-                )
-
-        # Overwrite or not depending on config if metadata already exists
-        metadata_filter = {dataset_name: metadata_dict}
-        metadata = db.metadata.find_one(metadata_filter)
-
-        if metadata and arguments.overwrite_metadata:
-            LOG.info(f"Metadata updated for dataset : {dataset_name}.")
-            res = db.metadata.update_one(
-                metadata_filter, {"$set": {dataset_name: metadata_dict}}
-            )
-            check_result_acknowledged(res)
-        elif metadata:
-            LOG.info(
-                "Metadata already exist. "
-                "Use the command -om to overwrite with new values."
-            )
-        else:
-            res = db.metadata.insert_one({dataset_name: metadata_dict})
-            check_result_acknowledged(res)
-            LOG.info(f"Added metadata of {dataset_name} dataset. ")
-
-
-@connect
-@check_dataset_and_metadata_exist(True)
-def del_dataset(db: Database, arguments: argparse.Namespace) -> None:
-    """Delete dataset from dataset collection.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    res = db.datasets.delete_many({"dataset_name": arguments.dataset})
-    check_result_acknowledged(res)
-    res = db.metadata.delete_many({arguments.dataset: {"$exists": True}})
-    check_result_acknowledged(res)
-    LOG.info(f"Deleted dataset and metadata for {arguments.dataset}.")
-
-
-#######################  COLLECTIONS  ####################### # noqa: E266
-@connect
-def drop_collection(db: Database, arguments: argparse.Namespace) -> None:
-    """Delete collection.
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    db.drop_collection(arguments.collection)
-    LOG.info(f"Deleted collection {arguments.collection}.")
-
-
-@connect
-def show_collection(db: Database, arguments: argparse.Namespace) -> None:
-    """Show a collection
-
-    Args:
-        db (Database): _description_
-        arguments (argparse.Namespace): _description_
-    """
-    collection_query = db[arguments.collection].find({})
-    collections = []
-    for document in collection_query:
-        document.pop("_id", None)
-        collections.append(document)
-    LOG.info(collections)
-
-
-if __name__ == "__main__":
-    ########################################################################
-    ########################## MongoDB Connection ############# # noqa: E266
-    ########################################################################
-    connection_parser = argparse.ArgumentParser(add_help=False)
-    connection_parser.add_argument("-db_u", "--username", default="user")
-    connection_parser.add_argument("-db_pwd", "--password", default="user_pwd")
-    connection_parser.add_argument("-db_a", "--address", default="mongodb")
-    connection_parser.add_argument("-db_p", "--port", default=27017)
-    connection_parser.add_argument("-db_n", "--db_name", default="defaultdb")
-
-    ########################################################################
-    ######################## MongoDB Administration ############ # noqa: E266
-    ########################################################################
-    parser = argparse.ArgumentParser(
-        description="MongoDB administration script for the database"
-    )
-    subparsers = parser.add_subparsers(
-        title="subcommands", help="user database administration operations"
-    )
-
-    ##########################  USERS  ########################## # noqa: E266
-    # Create the parser for the "add_user" command
-    add_user_parser = subparsers.add_parser(
-        "add_user",
-        help="add user to users collection",
-        parents=[connection_parser],
-    )
-    add_user_parser.add_argument("-u", "--user", required=True, type=str)
-    add_user_parser.set_defaults(func=add_user)
-
-    # Create the parser for the "add_user_with_budget" command
-    add_user_wb_parser = subparsers.add_parser(
-        "add_user_with_budget",
-        help="add user with budget to users collection",
-        parents=[connection_parser],
-    )
-    add_user_wb_parser.add_argument("-u", "--user", required=True, type=str)
-    add_user_wb_parser.add_argument("-d", "--dataset", required=True, type=str)
-    add_user_wb_parser.add_argument(
-        "-e", "--epsilon", required=True, type=float
-    )
-    add_user_wb_parser.add_argument(
-        "-del", "--delta", required=True, type=float
-    )
-    add_user_wb_parser.set_defaults(func=add_user_with_budget)
-
-    # Create the parser for the "del_user" command
-    del_user_parser = subparsers.add_parser(
-        "del_user",
-        help="delete user from users collection",
-        parents=[connection_parser],
-    )
-    del_user_parser.add_argument("-u", "--user", required=True, type=str)
-    del_user_parser.set_defaults(func=del_user)
-
-    # Create the parser for the "add_dataset" command
-    add_dataset_to_user_parser = subparsers.add_parser(
-        "add_dataset_to_user",
-        help="add dataset with initialized budget values for a user",
-        parents=[connection_parser],
-    )
-    add_dataset_to_user_parser.add_argument(
-        "-u", "--user", required=True, type=str
-    )
-    add_dataset_to_user_parser.add_argument(
-        "-d", "--dataset", required=True, type=str
-    )
-    add_dataset_to_user_parser.add_argument(
-        "-e", "--epsilon", required=True, type=float
-    )
-    add_dataset_to_user_parser.add_argument(
-        "-del", "--delta", required=True, type=float
-    )
-    add_dataset_to_user_parser.set_defaults(func=add_dataset_to_user)
-
-    # Create the parser for the "del_dataset" command
-    del_dataset_to_user_parser = subparsers.add_parser(
-        "del_dataset_to_user",
-        help="delete dataset for user in users collection",
-        parents=[connection_parser],
-    )
-    del_dataset_to_user_parser.add_argument(
-        "-u", "--user", required=True, type=str
-    )
-    del_dataset_to_user_parser.add_argument(
-        "-d", "--dataset", required=True, type=str
-    )
-    del_dataset_to_user_parser.set_defaults(func=del_dataset_to_user)
-
-    # Create the parser for the "set_budget_field" command
-    set_budget_field_parser = subparsers.add_parser(
-        "set_budget_field",
-        help="set budget field to given value for given user and dataset",
-        parents=[connection_parser],
-    )
-    set_budget_field_parser.add_argument(
-        "-u", "--user", required=True, type=str
-    )
-    set_budget_field_parser.add_argument(
-        "-d", "--dataset", required=True, type=str
-    )
-    set_budget_field_parser.add_argument(
-        "-f",
-        "--field",
-        required=True,
-        choices=["initial_epsilon", "initial_delta"],
-    )
-    set_budget_field_parser.add_argument(
-        "-v", "--value", required=True, type=float
-    )
-    set_budget_field_parser.set_defaults(func=set_budget_field)
-
-    # Create the parser for the "set_may_query" command
-    set_may_query_parser = subparsers.add_parser(
-        "set_may_query",
-        help="set may query field to given value for given user",
-        parents=[connection_parser],
-    )
-    set_may_query_parser.add_argument("-u", "--user", required=True, type=str)
-    set_may_query_parser.add_argument(
-        "-v", "--value", required=True, choices=["False", "True"]
-    )
-    set_may_query_parser.set_defaults(func=set_may_query)
-
-    # Show the user
-    show_user_parser = subparsers.add_parser(
-        "show_user",
-        help="show all metadata of user",
-        parents=[connection_parser],
-    )
-    show_user_parser.add_argument("-u", "--user", required=True, type=str)
-    show_user_parser.set_defaults(func=show_user)
-
-    # Create the parser for the "create_example_users" command
-    users_collection_from_yaml_parser = subparsers.add_parser(
-        "create_users_collection",
-        help="create users collection from yaml file",
-        parents=[connection_parser],
-    )
-    users_collection_from_yaml_parser.add_argument(
-        "-c",
-        "--clean",
-        required=False,
-        action="store_const",
-        const=True,
-        default=False,
-    )
-    users_collection_from_yaml_parser.add_argument(
-        "-o",
-        "--overwrite",
-        required=False,
-        action="store_const",
-        const=True,
-        default=False,
-    )
-    users_collection_from_yaml_parser.add_argument(
-        "-p", "--path", required=True, type=str
-    )
-    users_collection_from_yaml_parser.set_defaults(
-        func=create_users_collection
-    )
-
-    #######################  DATASETS  ####################### # noqa: E266
-    # Create parser for dataset private database
-    add_dataset_parser = subparsers.add_parser(
-        "add_dataset",
-        help="set in which database the dataset is stored",
-        parents=[connection_parser],
-    )
-    # Dataset location
-    add_dataset_parser.add_argument("-d", "--dataset", required=True)
-    add_dataset_parser.add_argument("-db", "--database_type", required=True)
-    add_dataset_parser.add_argument("-s3b", "--s3_bucket", required=False)
-    add_dataset_parser.add_argument("-s3k", "--s3_key", required=False)
-    add_dataset_parser.add_argument(
-        "-s3_url", "--endpoint_url", required=False
-    )
-    add_dataset_parser.add_argument(
-        "-s3_ak", "--aws_access_key_id", required=False
-    )
-    add_dataset_parser.add_argument(
-        "-s3_sak", "--aws_secret_access_key", required=False
-    )
-    # Metadata location
-    add_dataset_parser.add_argument(
-        "-m_db", "--metadata_database_type", required=True
-    )
-    add_dataset_parser.add_argument("-mp", "--metadata_path", required=False)
-    add_dataset_parser.add_argument(
-        "-m_s3b", "--metadata_s3_bucket", required=False
-    )
-    add_dataset_parser.add_argument(
-        "-m_s3k", "--metadata_s3_key", required=False
-    )
-    add_dataset_parser.add_argument(
-        "-m_s3_url", "--metadata_endpoint_url", required=False
-    )
-    add_dataset_parser.add_argument(
-        "-m_s3_ak", "--metadata_aws_access_key_id", required=False
-    )
-    add_dataset_parser.add_argument(
-        "-m_s3_sak", "--metadata_aws_secret_access_key", required=False
-    )
-    add_dataset_parser.set_defaults(func=add_dataset)
-
-    # Create the parser for the "add_datasets" command
-    add_datasets_parser = subparsers.add_parser(
-        "add_datasets",
-        help="create dataset to database type collection",
-        parents=[connection_parser],
-    )
-    add_datasets_parser.add_argument("-p", "--path", required=True, type=str)
-    add_datasets_parser.add_argument(
-        "-c",
-        "--clean",
-        required=False,
-        action="store_const",
-        const=True,
-        default=False,
-    )
-    add_datasets_parser.add_argument(
-        "-od",
-        "--overwrite_datasets",
-        required=False,
-        action="store_const",
-        const=True,
-        default=False,
-    )
-    add_datasets_parser.add_argument(
-        "-om",
-        "--overwrite_metadata",
-        required=False,
-        action="store_const",
-        const=True,
-        default=False,
-    )
-    add_datasets_parser.set_defaults(func=add_datasets)
-
-    # Create the parser for the "del_dataset" command
-    del_dataset_parser = subparsers.add_parser(
-        "del_dataset",
-        help="delete dataset and metadata from "
-        "datasets and metadata collection",
-        parents=[connection_parser],
-    )
-    del_dataset_parser.add_argument("-d", "--dataset", required=True, type=str)
-    del_dataset_parser.set_defaults(func=del_dataset)
-
-    #######################  COLLECTIONS  ####################### # noqa: E266
-    # Create the parser for the "drop_collection" command
-    drop_collection_parser = subparsers.add_parser(
-        "drop_collection",
-        help="delete collection from database",
-        parents=[connection_parser],
-    )
-    drop_collection_parser.add_argument("-c", "--collection", required=True)
-    drop_collection_parser.set_defaults(func=drop_collection)
-
-    # Create the parser for the "show_users_collection" command
-    show_collection_parser = subparsers.add_parser(
-        "show_collection",
-        help="print a collection",
-        parents=[connection_parser],
-    )
-    show_collection_parser.add_argument("-c", "--collection", required=True)
-    show_collection_parser.set_defaults(func=show_collection)
-
-    args = parser.parse_args()
-    args.func(args)
diff --git a/server/lomas_server/private_dataset/utils.py b/server/lomas_server/private_dataset/utils.py
index 47a9393f..3a95acf6 100644
--- a/server/lomas_server/private_dataset/utils.py
+++ b/server/lomas_server/private_dataset/utils.py
@@ -46,15 +46,15 @@ def private_dataset_factory(
             s3_parameters["s3_endpoint"] = admin_database.get_dataset_field(
                 dataset_name, "endpoint_url"
             )
-            s3_parameters["s3_aws_access_key_id"] = (
-                admin_database.get_dataset_field(
-                    dataset_name, "aws_access_key_id"
-                )
+            s3_parameters[
+                "s3_aws_access_key_id"
+            ] = admin_database.get_dataset_field(
+                dataset_name, "aws_access_key_id"
             )
-            s3_parameters["aws_secret_access_key"] = (
-                admin_database.get_dataset_field(
-                    dataset_name, "aws_secret_access_key"
-                )
+            s3_parameters[
+                "aws_secret_access_key"
+            ] = admin_database.get_dataset_field(
+                dataset_name, "aws_secret_access_key"
             )
             private_db = S3Dataset(ds_metadata, s3_parameters)
         case _:
diff --git a/server/lomas_server/utils/anti_timing_att.py b/server/lomas_server/utils/anti_timing_att.py
index 2e9a3490..1a95f8e4 100644
--- a/server/lomas_server/utils/anti_timing_att.py
+++ b/server/lomas_server/utils/anti_timing_att.py
@@ -5,6 +5,7 @@
 from fastapi import Request, Response
 
 from utils.config import Config
+from utils.error_handler import InternalServerException
 
 
 async def anti_timing_att(
@@ -27,17 +28,24 @@ async def anti_timing_att(
     start_time = time.time()
     response = await call_next(request)
     process_time = time.time() - start_time
-    if config.server.time_attack:
-        if config.server.time_attack.method == "stall":
-            # if stall is used slow fast callbacks
-            # to a minimum response time defined by magnitude
-            if process_time < config.server.time_attack.magnitude:
-                time.sleep(config.server.time_attack.magnitude - process_time)
-        elif config.server.time_attack.method == "jitter":
-            # if jitter is used it just adds some time
-            # between 0 and magnitude secs
-            time.sleep(
-                config.server.time_attack.magnitude * random.uniform(0, 1)
-            )
 
+    if config.server.time_attack:
+        match config.server.time_attack.method:
+            case "stall":
+                # if stall is used slow fast callbacks
+                # to a minimum response time defined by magnitude
+                if process_time < config.server.time_attack.magnitude:
+                    time.sleep(
+                        config.server.time_attack.magnitude - process_time
+                    )
+            case "jitter":
+                # if jitter is used it just adds some time
+                # between 0 and magnitude secs
+                time.sleep(
+                    config.server.time_attack.magnitude * random.uniform(0, 1)
+                )
+            case _:
+                raise InternalServerException(
+                    "Time attack method not supported."
+                )
     return response
diff --git a/server/lomas_server/utils/collections_models.py b/server/lomas_server/utils/collections_models.py
new file mode 100644
index 00000000..06a8e57d
--- /dev/null
+++ b/server/lomas_server/utils/collections_models.py
@@ -0,0 +1,88 @@
+from typing import Dict, List, Union
+from pydantic import BaseModel
+
+from constants import PrivateDatabaseType
+
+
+class DatasetOfUser(BaseModel):
+    """BaseModel for informations of a user on a dataset"""
+
+    dataset_name: str
+    initial_epsilon: float
+    initial_delta: float
+    total_spent_epsilon: float
+    total_spent_delta: float
+
+
+class User(BaseModel):
+    """BaseModel for a user in a user collection"""
+
+    user_name: str
+    may_query: bool
+    datasets_list: List[DatasetOfUser]
+
+
+class UserCollection(BaseModel):
+    """BaseModel for users collection"""
+
+    users: List[User]
+
+
+class MetadataOfDataset(BaseModel):
+    """BaseModel for metadata of a dataset"""
+
+    database_type: PrivateDatabaseType
+
+
+class MetadataOfPathDB(MetadataOfDataset):
+    """BaseModel for metadata of a dataset with PATH_DB"""
+
+    metadata_path: str
+
+
+class MetadataOfS3DB(MetadataOfDataset):
+    """BaseModel for metadata of a dataset with S3_DB"""
+
+    s3_bucket: str
+    s3_key: str
+    endpoint_url: str
+    aws_access_key_id: str
+    aws_secret_access_key: str
+
+
+class Dataset(BaseModel):
+    """BaseModel for a dataset"""
+
+    dataset_name: str
+    database_type: PrivateDatabaseType
+    metadata: Union[MetadataOfPathDB, MetadataOfS3DB]
+
+
+class DatasetOfPathDB(Dataset):
+    """BaseModel for a local dataset"""
+
+    dataset_path: str
+
+
+class DatasetOfS3DB(Dataset):
+    """BaseModel for a dataset on S3"""
+
+    s3_bucket: str
+    s3_key: str
+    endpoint_url: str
+    aws_access_key_id: str
+    aws_secret_access_key: str
+
+
+class DatasetsCollection(BaseModel):
+    """BaseModel for datasets collection"""
+
+    datasets: List[Union[DatasetOfPathDB, DatasetOfS3DB]]
+
+
+class Metadata(BaseModel):
+    """BaseModel for a metadata format"""
+
+    max_ids: int
+    row_privacy: bool
+    columns: Dict[str, Dict[str, Union[int, float, str, List[str]]]]
diff --git a/server/lomas_server/utils/config.py b/server/lomas_server/utils/config.py
index 9007d37b..11974464 100644
--- a/server/lomas_server/utils/config.py
+++ b/server/lomas_server/utils/config.py
@@ -25,11 +25,15 @@
 
 
 class TimeAttack(BaseModel):
+    """BaseModel for configs to prevent timing attacks"""
+
     method: Literal["jitter", "stall"]
     magnitude: float
 
 
 class Server(BaseModel):
+    """BaseModel for uvicorn server configs"""
+
     time_attack: TimeAttack
     host_ip: str
     host_port: int
@@ -39,22 +43,32 @@ class Server(BaseModel):
 
 
 class DatasetStoreConfig(BaseModel):
+    """BaseModel for dataset store configs"""
+
     ds_store_type: ConfDatasetStore
 
 
 class LRUDatasetStoreConfig(DatasetStoreConfig):
+    """BaseModel for dataset store configs in case of a LRU dataset store"""
+
     max_memory_usage: int
 
 
 class DBConfig(BaseModel):
+    """BaseModel for database type config"""
+
     db_type: str = AdminDBType
 
 
 class YamlDBConfig(DBConfig):
+    """BaseModel for dataset store configs  in case of a Yaml database"""
+
     db_file: str
 
 
 class MongoDBConfig(DBConfig):
+    """BaseModel for dataset store configs  in case of a  MongoDB database"""
+
     address: str
     port: int
     username: str
@@ -141,7 +155,9 @@ def update(
 
                 update(config_data, secret_data)
 
-            server_config: Server = Server.parse_obj(config_data[CONF_SERVER])
+            server_config: Server = Server.model_validate(
+                config_data[CONF_SERVER]
+            )
 
             db_type = config_data[CONF_DB][CONF_DB_TYPE]
             match db_type:
diff --git a/server/lomas_server/utils/input_models.py b/server/lomas_server/utils/input_models.py
index b9df4445..4e5db0b5 100644
--- a/server/lomas_server/utils/input_models.py
+++ b/server/lomas_server/utils/input_models.py
@@ -6,16 +6,22 @@
 
 
 class GetDbData(BaseModel):
+    """Model input to get information about a dataset"""
+
     dataset_name: str
 
 
 class GetDummyDataset(BaseModel):
+    """Model input to get a dummy dataset"""
+
     dataset_name: str
     dummy_nb_rows: int
     dummy_seed: int
 
 
 class SNSQLInp(BaseModel):
+    """Model input for a smarnoise-sql query"""
+
     query_str: str
     dataset_name: str
     epsilon: float = Field(
@@ -33,6 +39,8 @@ class SNSQLInp(BaseModel):
 
 
 class DummySNSQLInp(BaseModel):
+    """Model input for a smarnoise-sql dummy query"""
+
     query_str: str
     dataset_name: str
     dummy_nb_rows: int
@@ -44,6 +52,8 @@ class DummySNSQLInp(BaseModel):
 
 
 class SNSQLInpCost(BaseModel):
+    """Model input for a smarnoise-sql cost query"""
+
     query_str: str
     dataset_name: str
     epsilon: float
@@ -52,12 +62,16 @@ class SNSQLInpCost(BaseModel):
 
 
 class OpenDPInp(BaseModel):
+    """Model input for an opendp query"""
+
     dataset_name: str
     opendp_json: str
     fixed_delta: Optional[float] = None
 
 
 class DummyOpenDPInp(BaseModel):
+    """Model input for a dummy opendp query"""
+
     dataset_name: str
     opendp_json: str
     dummy_nb_rows: int
diff --git a/server/lomas_server/utils/utils.py b/server/lomas_server/utils/utils.py
index d59344b0..f8170726 100644
--- a/server/lomas_server/utils/utils.py
+++ b/server/lomas_server/utils/utils.py
@@ -1,18 +1,17 @@
 from collections.abc import AsyncGenerator
 import io
-from types import SimpleNamespace
 
 import pandas as pd
 from fastapi import Request
 from fastapi.responses import StreamingResponse
+from pymongo.database import Database
 
-
-from mongodb_admin import (
-    add_datasets,
-    create_users_collection,
+from admin_database.utils import get_mongodb
+from administration.mongodb_admin import (
+    add_datasets_via_yaml,
+    add_users_via_yaml,
     drop_collection,
 )
-from utils.config import get_config
 from utils.error_handler import InternalServerException
 from utils.loggr import LOG
 
@@ -56,9 +55,9 @@ def stream_dataframe(df: pd.DataFrame) -> StreamingResponse:
     response = StreamingResponse(
         iter([stream.getvalue()]), media_type="text/csv"
     )
-    response.headers["Content-Disposition"] = (
-        "attachment; filename=synthetic_data.csv"
-    )
+    response.headers[
+        "Content-Disposition"
+    ] = "attachment; filename=synthetic_data.csv"
     return response
 
 
@@ -70,22 +69,24 @@ def add_demo_data_to_admindb() -> None:
     Meant to be used in the develop mode of the service.
     """
     LOG.info("Creating example user collection")
-
-    config = get_config()
-    args = SimpleNamespace(**vars(config.admin_database))
+    mongo_db: Database = get_mongodb()
 
     LOG.info("Creating user collection")
-    args.clean = True
-    args.overwrite = True
-    args.path = "/data/collections/user_collection.yaml"
-    create_users_collection(args)
+    add_users_via_yaml(
+        mongo_db,
+        clean=True,
+        overwrite=True,
+        yaml_file="/data/collections/user_collection.yaml",
+    )
 
     LOG.info("Creating datasets and metadata collection")
-    args.path = "/data/collections/dataset_collection.yaml"
-    args.overwrite_datasets = True
-    args.overwrite_metadata = True
-    add_datasets(args)
+    add_datasets_via_yaml(
+        mongo_db,
+        clean=True,
+        overwrite_datasets=True,
+        overwrite_metadata=True,
+        yaml_file="/data/collections/dataset_collection.yaml",
+    )
 
     LOG.info("Empty archives")
-    args.collection = "queries_archives"
-    drop_collection(args)
+    drop_collection(mongo_db, collection="queries_archives")
diff --git a/server/requirements_streamlit.txt b/server/requirements_streamlit.txt
new file mode 100644
index 00000000..2f2950b4
--- /dev/null
+++ b/server/requirements_streamlit.txt
@@ -0,0 +1,3 @@
+requests==2.31.0
+streamlit==1.34.0
+st-pages==0.4.5


Test patch:
diff --git a/server/lomas_server/tests/test_api.py b/server/lomas_server/tests/test_api.py
index 8742d0f7..e7df91fc 100644
--- a/server/lomas_server/tests/test_api.py
+++ b/server/lomas_server/tests/test_api.py
@@ -1,13 +1,19 @@
 from io import StringIO
 import json
 import os
-from types import SimpleNamespace
 import unittest
 
 import pandas as pd
+from pymongo.database import Database
 from fastapi import status
 from fastapi.testclient import TestClient
 
+from admin_database.utils import get_mongodb
+from administration.mongodb_admin import (
+    add_users_via_yaml,
+    add_datasets_via_yaml,
+    drop_collection,
+)
 from app import app
 from constants import DPLibraries, EPSILON_LIMIT
 from tests.constants import ENV_MONGO_INTEGRATION
@@ -24,12 +30,7 @@
     example_smartnoise_sql,
     example_smartnoise_sql_cost,
 )
-from utils.config import get_config, CONFIG_LOADER
-from mongodb_admin import (
-    create_users_collection,
-    add_datasets,
-    drop_collection,
-)
+from utils.config import CONFIG_LOADER
 
 INITAL_EPSILON = 10
 INITIAL_DELTA = 0.005
@@ -75,38 +76,29 @@ def setUp(self) -> None:
 
         # Fill up database if needed
         if os.getenv(ENV_MONGO_INTEGRATION, "0").lower() in ("true", "1", "t"):
+            self.db: Database = get_mongodb()
 
-            args = SimpleNamespace(**vars(get_config().admin_database))
-
-            args.clean = True
-            args.overwrite = True
-            args.path = "tests/test_data/test_user_collection.yaml"
-            create_users_collection(args)
-
-            args.path = "tests/test_data/test_datasets.yaml"
-            args.dataset = "PENGUIN_DATASET"
-            args.dataset_type = "LOCAL_DB"
-            args.overwrite_datasets = True
-            args.overwrite_metadata = True
-            add_datasets(args)
+            add_users_via_yaml(
+                self.db,
+                yaml_file="tests/test_data/test_user_collection.yaml",
+                clean=True,
+                overwrite=True,
+            )
+            add_datasets_via_yaml(
+                self.db,
+                yaml_file="tests/test_data/test_datasets.yaml",
+                clean=True,
+                overwrite_datasets=True,
+                overwrite_metadata=True,
+            )
 
     def tearDown(self) -> None:
         # Clean up database if needed
         if os.getenv(ENV_MONGO_INTEGRATION, "0").lower() in ("true", "1", "t"):
-
-            args = SimpleNamespace(**vars(get_config().admin_database))
-
-            args.collection = "metadata"
-            drop_collection(args)
-
-            args.collection = "datasets"
-            drop_collection(args)
-
-            args.collection = "users"
-            drop_collection(args)
-
-            args.collection = "queries_archives"
-            drop_collection(args)
+            drop_collection(self.db, "metadata")
+            drop_collection(self.db, "datasets")
+            drop_collection(self.db, "users")
+            drop_collection(self.db, "queries_archives")
 
     def test_state(self) -> None:
         """_summary_"""
@@ -234,9 +226,9 @@ def test_smartnoise_query(self) -> None:
 
             # Expect to fail: query does not make sense
             input_smartnoise = dict(example_smartnoise_sql)
-            input_smartnoise["query_str"] = (
-                "SELECT AVG(bill) FROM df"  # no 'bill' column
-            )
+            input_smartnoise[
+                "query_str"
+            ] = "SELECT AVG(bill) FROM df"  # no 'bill' column
             response = client.post(
                 "/smartnoise_query",
                 json=input_smartnoise,
diff --git a/server/lomas_server/tests/test_mongodb_admin.py b/server/lomas_server/tests/test_mongodb_admin.py
index e63d4cde..7a3732b4 100644
--- a/server/lomas_server/tests/test_mongodb_admin.py
+++ b/server/lomas_server/tests/test_mongodb_admin.py
@@ -6,8 +6,7 @@
 from pymongo import MongoClient
 
 from admin_database.utils import get_mongodb_url
-from constants import PrivateDatabaseType
-from mongodb_admin import (
+from administration.mongodb_admin import (
     add_user,
     add_user_with_budget,
     del_user,
@@ -15,12 +14,13 @@
     del_dataset_to_user,
     set_budget_field,
     set_may_query,
-    create_users_collection,
+    add_users_via_yaml,
     add_dataset,
-    add_datasets,
+    add_datasets_via_yaml,
     del_dataset,
     drop_collection,
 )
+from constants import PrivateDatabaseType
 from utils.config import get_config, CONFIG_LOADER
 from tests.constants import ENV_MONGO_INTEGRATION
 
@@ -44,43 +44,32 @@ class TestMongoDBAdmin(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls) -> None:
+        """Connection to database"""
         CONFIG_LOADER.load_config(
             config_path="tests/test_configs/test_config_mongo.yaml",
             secrets_path="tests/test_configs/test_secrets.yaml",
         )
 
-        cls.db_config = get_config().admin_database
-        db_url = get_mongodb_url(cls.db_config)
-        cls.db = MongoClient(db_url)[cls.db_config.db_name]
-
-    def setUp(self) -> None:
-        self.args = SimpleNamespace(**vars(get_config().admin_database))
+        db_args = SimpleNamespace(**vars(get_config().admin_database))
+        db_url = get_mongodb_url(db_args)
+        cls.db = MongoClient(db_url)[db_args.db_name]
 
     def tearDown(self) -> None:
-        self.args = SimpleNamespace(**vars(get_config().admin_database))
-
-        self.args.collection = "metadata"
-        drop_collection(self.args)
-
-        self.args.collection = "datasets"
-        drop_collection(self.args)
-
-        self.args.collection = "users"
-        drop_collection(self.args)
-
-        self.args.collection = "queries_archives"
-        drop_collection(self.args)
-
-        self.args = None  # type: ignore
+        """Drop all data from database"""
+        drop_collection(self.db, "metadata")
+        drop_collection(self.db, "datasets")
+        drop_collection(self.db, "users")
+        drop_collection(self.db, "queries_archives")
 
     def test_add_user(self) -> None:
-        self.args.user = "Tintin"
+        """Test adding a user"""
+        user = "Tintin"
 
         # Add user
-        add_user(self.args)
+        add_user(self.db, user)
 
         expected_user = {
-            "user_name": self.args.user,
+            "user_name": user,
             "may_query": True,
             "datasets_list": [],
         }
@@ -92,216 +81,218 @@ def test_add_user(self) -> None:
 
         # Adding existing user raises error
         with self.assertRaises(ValueError):
-            add_user(self.args)
+            add_user(self.db, user)
 
     def test_add_user_wb(self) -> None:
-        self.args.user = "Tintin"
-        self.args.dataset = "Bijoux de la Castafiore"
-        self.args.epsilon = 10
-        self.args.delta = 0.02
+        """Test adding a user with a dataset"""
+        user = "Tintin"
+        dataset = "Bijoux de la Castafiore"
+        epsilon = 10
+        delta = 0.02
 
-        add_user_with_budget(self.args)
+        add_user_with_budget(self.db, user, dataset, epsilon, delta)
         expected_user = {
-            "user_name": self.args.user,
+            "user_name": user,
             "may_query": True,
             "datasets_list": [
                 {
-                    "dataset_name": self.args.dataset,
-                    "initial_epsilon": self.args.epsilon,
-                    "initial_delta": self.args.delta,
+                    "dataset_name": dataset,
+                    "initial_epsilon": epsilon,
+                    "initial_delta": delta,
                     "total_spent_epsilon": 0.0,
                     "total_spent_delta": 0.0,
                 }
             ],
         }
 
-        user_found = self.db.users.find_one({"user_name": "Tintin"})
+        user_found = self.db.users.find_one({"user_name": user})
         del user_found["_id"]
 
         self.assertEqual(user_found, expected_user)
 
         # Adding budget to existing user should raise error
         with self.assertRaises(ValueError):
-            add_user_with_budget(self.args)
+            add_user_with_budget(self.db, user, dataset, epsilon, delta)
 
     def test_del_user(self) -> None:
+        """Test deleting a user"""
         # Setup: add a user
-        self.args.user = "Tintin"
-        add_user(self.args)
+        user = "Tintin"
+        add_user(self.db, user)
 
         # Deleting user
-        del_user(self.args)
+        del_user(self.db, user)
 
         expected_user = None
-        user_found = self.db.users.find_one({"user_name": "Tintin"})
+        user_found = self.db.users.find_one({"user_name": user})
         self.assertEqual(user_found, expected_user)
 
         # Removing non-existing should raise error
         with self.assertRaises(ValueError):
-            del_user(self.args)
+            del_user(self.db, user)
 
     def test_add_dataset_to_user(self) -> None:
-        self.args.user = "Tintin"
-        self.args.dataset = "Bijoux de la Castafiore"
-        self.args.epsilon = 10
-        self.args.delta = 0.02
-
-        add_user(self.args)
-        add_dataset_to_user(self.args)
+        """Test add dataset to a user"""
+        user = "Tintin"
+        dataset = "Bijoux de la Castafiore"
+        epsilon = 10
+        delta = 0.02
+
+        add_user(self.db, user)
+        add_dataset_to_user(self.db, user, dataset, epsilon, delta)
         expected_user = {
-            "user_name": self.args.user,
+            "user_name": user,
             "may_query": True,
             "datasets_list": [
                 {
-                    "dataset_name": self.args.dataset,
-                    "initial_epsilon": self.args.epsilon,
-                    "initial_delta": self.args.delta,
+                    "dataset_name": dataset,
+                    "initial_epsilon": epsilon,
+                    "initial_delta": delta,
                     "total_spent_epsilon": 0.0,
                     "total_spent_delta": 0.0,
                 }
             ],
         }
 
-        user_found = self.db.users.find_one({"user_name": "Tintin"})
+        user_found = self.db.users.find_one({"user_name": user})
         del user_found["_id"]
 
         assert user_found == expected_user
 
         # Adding dataset to existing user with existing dataset should
         # raise and error
-        self.args.epsilon = 20
+        epsilon = 20
         with self.assertRaises(ValueError):
-            add_dataset_to_user(self.args)
+            add_dataset_to_user(self.db, user, dataset, epsilon, delta)
 
         # Adding dataset to non-existing user should raise an error
-        self.args.user = "Milou"
+        user = "Milou"
         with self.assertRaises(ValueError):
-            add_dataset_to_user(self.args)
+            add_dataset_to_user(self.db, user, dataset, epsilon, delta)
 
     def test_del_dataset_to_user(self) -> None:
+        """Test delete dataset from user"""
         # Setup: add user with dataset
-        self.args.user = "Tintin"
-        self.args.dataset = "Bijoux de la Castafiore"
-        self.args.epsilon = 10
-        self.args.delta = 0.02
+        user = "Tintin"
+        dataset = "Bijoux de la Castafiore"
+        epsilon = 10
+        delta = 0.02
 
-        add_user_with_budget(self.args)
+        add_user_with_budget(self.db, user, dataset, epsilon, delta)
 
         # Test dataset deletion
-        del_dataset_to_user(self.args)
+        del_dataset_to_user(self.db, user, dataset)
         expected_user = {
-            "user_name": self.args.user,
+            "user_name": user,
             "may_query": True,
             "datasets_list": [],
         }
-        user_found = self.db.users.find_one({"user_name": "Tintin"})
+        user_found = self.db.users.find_one({"user_name": user})
         del user_found["_id"]
 
         self.assertEqual(user_found, expected_user)
 
         # Remove dataset from non-existant user should raise error
-        self.args.user = "Milou"
-
+        user = "Milou"
         with self.assertRaises(ValueError):
-            del_dataset_to_user(self.args)
+            del_dataset_to_user(self.db, user, dataset)
 
         # Remove dataset not present in user should raise error
-        self.args.user = "Tintin"
-        self.args.dataset = "Bijoux de la Castafiore"
-
+        user = "Tintin"
+        dataset = "Bijoux de la Castafiore"
         with self.assertRaises(Exception):
-            del_dataset_to_user(self.args)
+            del_dataset_to_user(self.db, user, dataset)
 
     def test_set_budget_field(self) -> None:
+        """Test setting a budget field"""
         # Setup: add user with budget
-        self.args.user = "Tintin"
-        self.args.dataset = "Bijoux de la Castafiore"
-        self.args.epsilon = 10
-        self.args.delta = 0.02
+        user = "Tintin"
+        dataset = "Bijoux de la Castafiore"
+        epsilon = 10
+        delta = 0.02
 
-        add_user_with_budget(self.args)
+        add_user_with_budget(self.db, user, dataset, epsilon, delta)
 
         # Updating budget should work
-        self.args.field = "initial_epsilon"
-        self.args.value = 15
-        set_budget_field(self.args)
+        field = "initial_epsilon"
+        value = 15
+        set_budget_field(self.db, user, dataset, field, value)
 
         expected_user = {
-            "user_name": self.args.user,
+            "user_name": user,
             "may_query": True,
             "datasets_list": [
                 {
-                    "dataset_name": self.args.dataset,
-                    "initial_epsilon": self.args.value,
-                    "initial_delta": self.args.delta,
+                    "dataset_name": dataset,
+                    "initial_epsilon": value,
+                    "initial_delta": delta,
                     "total_spent_epsilon": 0.0,
                     "total_spent_delta": 0.0,
                 }
             ],
         }
 
-        user_found = self.db.users.find_one({"user_name": "Tintin"})
+        user_found = self.db.users.find_one({"user_name": user})
         del user_found["_id"]
 
         self.assertEqual(user_found, expected_user)
 
         # Setting budget for non-existing user should fail
-        self.args.user = "Milou"
-
+        user = "Milou"
         with self.assertRaises(ValueError):
-            set_budget_field(self.args)
+            set_budget_field(self.db, user, dataset, field, value)
 
         # Setting budget for non-existing dataset should fail
-        self.args.user = "Tintin"
-        self.args.dataset = "os de Milou"
-
+        user = "Tintin"
+        dataset = "os de Milou"
         with self.assertRaises(ValueError):
-            set_budget_field(self.args)
+            set_budget_field(self.db, user, dataset, field, value)
 
     def test_set_may_query(self) -> None:
+        """Test set may query"""
         # Setup: add user with budget
-        self.args.user = "Tintin"
-        self.args.dataset = "PENGUIN"
-        self.args.epsilon = 10
-        self.args.delta = 0.02
+        user = "Tintin"
+        dataset = "PENGUIN"
+        epsilon = 10
+        delta = 0.02
 
-        add_user_with_budget(self.args)
+        add_user_with_budget(self.db, user, dataset, epsilon, delta)
 
         # Set may query
-        self.args.value = False
-        set_may_query(self.args)
+        value = False
+        set_may_query(self.db, user, value)
 
         expected_user = {
-            "user_name": self.args.user,
-            "may_query": False,
+            "user_name": user,
+            "may_query": value,
             "datasets_list": [
                 {
-                    "dataset_name": self.args.dataset,
-                    "initial_epsilon": self.args.epsilon,
-                    "initial_delta": self.args.delta,
+                    "dataset_name": dataset,
+                    "initial_epsilon": epsilon,
+                    "initial_delta": delta,
                     "total_spent_epsilon": 0.0,
                     "total_spent_delta": 0.0,
                 }
             ],
         }
 
-        user_found = self.db.users.find_one({"user_name": "Tintin"})
+        user_found = self.db.users.find_one({"user_name": user})
         del user_found["_id"]
 
         self.assertEqual(user_found, expected_user)
 
         # Raises error when user does not exist
-        self.args.user = "Milou"
-
+        user = "Milou"
         with self.assertRaises(ValueError):
-            set_may_query(self.args)
+            set_may_query(self.db, user, value)
 
-    def test_create_users_collection(self) -> None:
+    def test_add_users_via_yaml(self) -> None:
+        """Test create user collection via YAML file"""
         # Adding two users
-        self.args.path = "./tests/test_data/test_user_collection.yaml"
-        self.args.clean = False
-        self.args.overwrite = False
-        create_users_collection(self.args)
+        path = "./tests/test_data/test_user_collection.yaml"
+        clean = False
+        overwrite = False
+        add_users_via_yaml(self.db, path, clean, overwrite)
 
         tintin = {
             "user_name": "Tintin",
@@ -342,18 +333,14 @@ def test_create_users_collection(self) -> None:
         self.assertEqual(user_found, milou)
 
         # Check cleaning
-        self.args.user = "Tintin"
-        self.args.field = "initial_epsilon"
-        self.args.value = 25.0
-        self.args.dataset = "Bijoux de la Castafiore"
-        set_budget_field(self.args)
-        del self.args.user
-        del self.args.field
-        del self.args.value
-        del self.args.dataset
-
-        self.args.clean = True
-        create_users_collection(self.args)
+        user = "Tintin"
+        field = "initial_epsilon"
+        value = 25.0
+        dataset = "Bijoux de la Castafiore"
+        set_budget_field(self.db, user, dataset, field, value)
+
+        clean = True
+        add_users_via_yaml(self.db, path, clean, overwrite)
 
         user_found = self.db.users.find_one({"user_name": "Tintin"})
         del user_found["_id"]
@@ -364,24 +351,15 @@ def test_create_users_collection(self) -> None:
         self.assertEqual(user_found, milou)
 
         # Check overwriting (with new user)
-        self.args.user = "Tintin"
-        self.args.field = "initial_epsilon"
-        self.args.value = False
-        self.args.dataset = "Bijoux de la Castafiore"
-        set_budget_field(self.args)
-        del self.args.user
-        del self.args.field
-        del self.args.value
-        del self.args.dataset
-
-        self.args.user = "Milou"
-        del_user(self.args)
-
-        self.args.user = None
-        self.args.value = None
-        self.args.clean = False
-        self.args.overwrite = True
-        create_users_collection(self.args)
+        user = "Tintin"
+        field = "initial_epsilon"
+        value = False
+        dataset = "Bijoux de la Castafiore"
+        set_budget_field(self.db, user, dataset, field, value)
+
+        user = "Milou"
+        del_user(self.db, user)
+        add_users_via_yaml(self.db, path, clean=False, overwrite=True)
 
         user_found = self.db.users.find_one({"user_name": "Tintin"})
         del user_found["_id"]
@@ -392,29 +370,33 @@ def test_create_users_collection(self) -> None:
         self.assertEqual(user_found, milou)
 
         # Overwrite to false and existing users should warn
-        self.args.overwrite = False
-
         with self.assertWarns(UserWarning):
-            create_users_collection(self.args)
-
-    def test_add_dataset(self) -> None:
-        self.args.dataset = "PENGUIN"
-        self.args.database_type = PrivateDatabaseType.PATH
-        self.args.dataset_path = "some_path"
-        self.args.metadata_database_type = PrivateDatabaseType.PATH
-        self.args.metadata_path = (
-            "./tests/test_data/metadata/penguin_metadata.yaml"
+            add_users_via_yaml(self.db, path, clean=False, overwrite=False)
+
+    def test_add_local_dataset(self) -> None:
+        """Test adding a local dataset"""
+        dataset = "PENGUIN"
+        database_type = PrivateDatabaseType.PATH
+        dataset_path = "some_path"
+        metadata_database_type = PrivateDatabaseType.PATH
+        metadata_path = "./tests/test_data/metadata/penguin_metadata.yaml"
+
+        add_dataset(
+            self.db,
+            dataset,
+            database_type,
+            metadata_database_type,
+            dataset_path=dataset_path,
+            metadata_path=metadata_path,
         )
 
-        add_dataset(self.args)
-
         expected_dataset = {
-            "dataset_name": self.args.dataset,
-            "database_type": self.args.database_type,
-            "dataset_path": self.args.dataset_path,
+            "dataset_name": dataset,
+            "database_type": database_type,
+            "dataset_path": dataset_path,
             "metadata": {
-                "database_type": self.args.metadata_database_type,
-                "metadata_path": self.args.metadata_path,
+                "database_type": metadata_database_type,
+                "metadata_path": metadata_path,
             },
         }
         with open(
@@ -428,11 +410,12 @@ def test_add_dataset(self) -> None:
         self.assertEqual(dataset_found, expected_dataset)
 
         metadata_found = self.db.metadata.find_one(
-            {self.args.dataset: {"$exists": True}}
-        )[self.args.dataset]
+            {dataset: {"$exists": True}}
+        )[dataset]
         self.assertEqual(metadata_found, expected_metadata)
 
-    def test_add_datasets(self) -> None:
+    def test_add_datasets_via_yaml(self) -> None:
+        """Test add datasets via a YAML file"""
         # Load reference data
         with open(
             "./tests/test_data/test_datasets.yaml",
@@ -470,57 +453,68 @@ def verify_datasets():
             )["IRIS"]
             self.assertEqual(metadata_found, penguin_metadata)
 
-        self.args.clean = False
-        self.args.overwrite_datasets = False
-        self.args.overwrite_metadata = False
-        self.args.path = "./tests/test_data/test_datasets.yaml"
+        path = "./tests/test_data/test_datasets.yaml"
+        clean = False
+        overwrite_datasets = False
+        overwrite_metadata = False
 
-        add_datasets(self.args)
+        add_datasets_via_yaml(
+            self.db, path, clean, overwrite_datasets, overwrite_metadata
+        )
 
         verify_datasets()
 
         # Check clean works
 
-        # Add new dataset and then add_datasets with clean option
+        # Add new dataset and then add_datasets_via_yaml with clean option
         self.db.datasets.insert_one(
             {"dataset_name": "Les aventures de Tintin"}
         )
 
-        self.args.clean = True
-        add_datasets(self.args)
+        clean = True
+        add_datasets_via_yaml(
+            self.db, path, clean, overwrite_datasets, overwrite_metadata
+        )
         verify_datasets()
 
         # Check no overwrite triggers warning
-        self.args.clean = False
-
+        clean = False
         with self.assertWarns(UserWarning):
-            add_datasets(self.args)
+            add_datasets_via_yaml(
+                self.db, path, clean, overwrite_datasets, overwrite_metadata
+            )
 
         # Check overwrite works
-
         self.db.datasets.update_one(
             {"dataset_name": "IRIS"}, {"$set": {"dataset_name": "IRIS"}}
         )
 
-        self.args.overwrite = True
-        add_datasets(self.args)
-
+        overwrite_datasets = True
+        add_datasets_via_yaml(
+            self.db, path, clean, overwrite_datasets, overwrite_metadata
+        )
         verify_datasets()
 
     def test_del_dataset(self) -> None:
+        """Test dataset deletion"""
         # Setup: add one dataset
-        self.args.dataset = "PENGUIN"
-        self.args.database_type = PrivateDatabaseType.PATH
-        self.args.dataset_path = "some_path"
-        self.args.metadata_database_type = PrivateDatabaseType.PATH
-        self.args.metadata_path = (
-            "./tests/test_data/metadata/penguin_metadata.yaml"
+        dataset = "PENGUIN"
+        database_type = PrivateDatabaseType.PATH
+        dataset_path = "some_path"
+        metadata_database_type = PrivateDatabaseType.PATH
+        metadata_path = "./tests/test_data/metadata/penguin_metadata.yaml"
+
+        add_dataset(
+            self.db,
+            dataset,
+            database_type,
+            metadata_database_type,
+            dataset_path=dataset_path,
+            metadata_path=metadata_path,
         )
 
-        add_dataset(self.args)
-
         # Verify delete works
-        del_dataset(self.args)
+        del_dataset(self.db, dataset)
 
         dataset_found = self.db.datasets.find_one({"dataset_name": "PENGUIN"})
         self.assertEqual(dataset_found, None)
@@ -530,23 +524,29 @@ def test_del_dataset(self) -> None:
 
         # Delete non-existing dataset should trigger error
         with self.assertRaises(ValueError):
-            del_dataset(self.args)
+            del_dataset(self.db, dataset)
 
     def test_drop_collection(self) -> None:
+        """Test drop collection from db"""
         # Setup: add one dataset
-        self.args.dataset = "PENGUIN"
-        self.args.database_type = PrivateDatabaseType.PATH
-        self.args.dataset_path = "some_path"
-        self.args.metadata_database_type = PrivateDatabaseType.PATH
-        self.args.metadata_path = (
-            "./tests/test_data/metadata/penguin_metadata.yaml"
+        dataset = "PENGUIN"
+        database_type = PrivateDatabaseType.PATH
+        dataset_path = "some_path"
+        metadata_database_type = PrivateDatabaseType.PATH
+        metadata_path = "./tests/test_data/metadata/penguin_metadata.yaml"
+
+        add_dataset(
+            self.db,
+            dataset,
+            database_type,
+            metadata_database_type,
+            dataset_path=dataset_path,
+            metadata_path=metadata_path,
         )
 
-        add_dataset(self.args)
-
         # Test
-        self.args.collection = "datasets"
-        drop_collection(self.args)
+        collection = "datasets"
+        drop_collection(self.db, collection)
 
         nb_datasets = self.db.datasets.count_documents({})
         self.assertEqual(nb_datasets, 0)