diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..91ccc4e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,22 @@
+# Python files
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+
+# Dependencies
+venv/
+env/
+env/
+*.env
+*.venv
+*.env.bak
+pip-log.txt
+pip-delete-this-directory.txt
+dist/
+build/
+*.egg-info/
+.idea/
+.vscode/
+*.egg
+*.egg-info/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..91ccc4e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+# Python files
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+
+# Dependencies
+venv/
+env/
+env/
+*.env
+*.venv
+*.env.bak
+pip-log.txt
+pip-delete-this-directory.txt
+dist/
+build/
+*.egg-info/
+.idea/
+.vscode/
+*.egg
+*.egg-info/
\ No newline at end of file
diff --git a/README.md b/README.md
index e549315..9546150 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,6 @@
# myFitApp
-This is a microservices application for tracking gym workouts, built with [FastAPI](https://fastapi.tiangolo.com/) and [MongoDB](https://www.mongodb.com/). The application consists of two microservices:
-
-- *workouts*: A FastAPI microservice for creating, retrieving, updating, and deleting workouts.
-- *mongo*: A MongoDB microservice for storing and managing workout data.
-
-## Prerequisites
-
-Before you can run the application, you need to have the following tools installed on your system:
-
-- [Docker](https://www.docker.com/)
-- [Docker Compose](https://docs.docker.com/compose/)
+This is a cloud native application for tracking gym workouts, built in FastAPI.
## Usage
@@ -29,5 +19,3 @@ To retrieve a workout, send a GET request to the /workouts/{workout_id} endpoint
### Updating a Workout
To update a workout, send a PATCH request to the /workouts/{workout_id} endpoint of the workouts microservice, where workout_id is the ID of the workout you want to update, with a JSON payload containing the updated 'Scheda' data.
-
-
diff --git a/app/api/__init__.py b/app/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/api/models/__init__.py b/app/api/models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/api/models/workout.py b/app/api/models/workout.py
new file mode 100644
index 0000000..b5d26b5
--- /dev/null
+++ b/app/api/models/workout.py
@@ -0,0 +1,22 @@
+from typing import List
+from pydantic import BaseModel, validator
+
+
+class Serie(BaseModel):
+ reps: str
+ carico: str
+
+
+class Esercizio(BaseModel):
+ name: str
+ serie: List[Serie]
+
+
+class Scheda(BaseModel):
+ name: str
+ esercizi: List[Esercizio]
+
+
+class Workout(BaseModel):
+ name: str
+ schede: List[Scheda]
diff --git a/app/db/workout_db.py b/app/db/workout_db.py
new file mode 100644
index 0000000..2dcc9b3
--- /dev/null
+++ b/app/db/workout_db.py
@@ -0,0 +1,79 @@
+from pymongo import MongoClient
+from ..api.models.workout import Workout, Scheda
+import logging
+import json
+
+client = MongoClient(
+ host="ferretdb.internal",
+ port=27017,
+ username="username",
+ password="password",
+ authMechanism="PLAIN",
+)
+db = client["myfitappdb"]
+collection = db["workouts"]
+
+logging.basicConfig(filename="app.log", level=logging.ERROR)
+
+
+def getWorkoutDB(name: str):
+ try:
+ return collection.find_one({"name": name})
+
+ except Exception as e:
+ logging.error(f"Failed to get item {name}: {e}")
+ return None
+
+
+def getWorkoutList():
+ try:
+ return [doc["name"] for doc in collection.find({}, {"name": 1})]
+
+ except Exception as e:
+ logging.error(f"Failed to get workout list: {e}")
+ return None
+
+
+def loadWorkoutDB(workout: Workout):
+ workout_data = json.dumps(workout.dict())
+ try:
+ if getWorkoutDB(workout.name) is None:
+ collection.insert_one({"name": workout.name, "data": workout_data})
+
+ else:
+ collection.update_one(
+ {"name": workout.name}, {"$set": {"data": workout_data}}
+ )
+
+ return True
+ except Exception as e:
+ logging.error(f"Failed to write item {workout.name}: {e}")
+ return False
+
+
+def updateWorkout(name: str, scheda: Scheda):
+ try:
+ workout_dump = getWorkoutDB(name)
+
+ if workout_dump is not None:
+ workout_dict = json.loads(workout_dump.get("data"))
+ workout_data = Workout(**workout_dict)
+
+ for idx, s in enumerate(workout_data.schede):
+ if s.name == scheda.name:
+ workout_data.schede[idx] = scheda
+
+ if loadWorkoutDB(workout_data):
+ return True
+ else:
+ logging.error(
+ f"Failed to update workout {name}: Cannot write new one on DB!"
+ )
+ return False
+ else:
+ logging.error(f"Failed to update workout {name}: It does not exist!")
+ return False
+
+ except Exception as e:
+ logging.error(f"Failed to update workout {name}: {e}")
+ return False
diff --git a/app/main.py b/app/main.py
new file mode 100644
index 0000000..e5cec62
--- /dev/null
+++ b/app/main.py
@@ -0,0 +1,88 @@
+from fastapi import FastAPI, HTTPException
+from fastapi.exceptions import RequestValidationError
+from fastapi.responses import JSONResponse
+from fastapi.staticfiles import StaticFiles
+from pydantic import ValidationError
+
+from .api.models.workout import Workout, Scheda
+from .db.workout_db import getWorkoutDB, getWorkoutList, loadWorkoutDB, updateWorkout
+
+import uvicorn
+
+import json
+
+app = FastAPI()
+
+
+@app.exception_handler(RequestValidationError)
+async def validation_exception_handler(request, exc):
+ return JSONResponse(
+ status_code=400,
+ content={"message": "Data validation failed", "detail": exc.errors()},
+ )
+
+
+@app.get("/workout/{name}")
+async def get_workout(name: str):
+ if not name:
+ raise HTTPException(status_code=400, detail="Name parameter cannot be empty")
+
+ try:
+ workout_db = getWorkoutDB(name)
+
+ if workout_db is not None:
+ workout_dict = json.loads(workout_db.get("data"))
+
+ workout_data = Workout(**workout_dict)
+
+ return workout_data
+ else:
+ raise HTTPException(status_code=400, detail="No workout to load")
+ except ValidationError as e:
+ raise RequestValidationError(errors=e.errors())
+
+
+@app.get("/workout")
+async def get_workout_list():
+ try:
+ workout_list_data = getWorkoutList()
+
+ return workout_list_data
+ except ValidationError as e:
+ raise RequestValidationError(errors=e.errors())
+
+
+@app.post("/workout")
+async def load_workout(workout: Workout):
+ try:
+ if loadWorkoutDB(workout):
+ return JSONResponse(
+ status_code=200, content={"message": "Workout upload successful"}
+ )
+ else:
+ return JSONResponse(
+ status_code=500, content={"message": "Cannot upload workout"}
+ )
+ except ValidationError as e:
+ raise RequestValidationError(errors=e.errors())
+
+
+@app.patch("/workout/{name}")
+async def update_workout(name: str, scheda: Scheda):
+ try:
+ if updateWorkout(name, scheda):
+ return JSONResponse(
+ status_code=200, content={"message": "Workout upload successful"}
+ )
+ else:
+ return JSONResponse(
+ status_code=500, content={"message": "Cannot upload workout"}
+ )
+ except ValidationError as e:
+ raise RequestValidationError(errors=e.errors())
+
+
+app.mount("/", StaticFiles(directory="app/public", html=True))
+
+if __name__ == "__main__":
+ uvicorn.run(app, host="0.0.0.0", port=8000)
diff --git a/app/public/assets/CSS/font.css b/app/public/assets/CSS/font.css
new file mode 100644
index 0000000..c8e7738
--- /dev/null
+++ b/app/public/assets/CSS/font.css
@@ -0,0 +1,3 @@
+body {
+ font-family: 'Nunito', sans-serif;
+}
\ No newline at end of file
diff --git a/app/public/assets/CSS/got_top.css b/app/public/assets/CSS/got_top.css
new file mode 100644
index 0000000..b3c95a3
--- /dev/null
+++ b/app/public/assets/CSS/got_top.css
@@ -0,0 +1,32 @@
+.top-link {
+ transition: all .25s ease-in-out;
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ display: inline-flex;
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+ margin: 0 1em 1em 0;
+ border-radius: 50%;
+ padding: .25em;
+ width: 40px;
+ height: 40px;
+ background-color: #F8F8F8;
+}
+
+.show {
+ visibility: visible;
+ opacity: 1;
+}
+
+.hide {
+ visibility: hidden;
+ opacity: 0;
+}
+
+svg {
+ fill: #000;
+ width: 30px;
+ height: 30px;
+}
\ No newline at end of file
diff --git a/app/public/assets/JS/changeWorkout.js b/app/public/assets/JS/changeWorkout.js
new file mode 100644
index 0000000..6c02183
--- /dev/null
+++ b/app/public/assets/JS/changeWorkout.js
@@ -0,0 +1,11 @@
+function changeWorkout() {
+ var list_workouts = document.querySelectorAll(".listWorkouts");
+
+ list_workouts.forEach((workout_button) => {
+ workout_button.onclick = (event) => {
+ // from FillPage.js
+ fillPage(event.target.textContent);
+ $("#modalAddWorkout").modal("hide");
+ };
+ });
+}
diff --git a/app/public/assets/JS/fillPage.js b/app/public/assets/JS/fillPage.js
new file mode 100644
index 0000000..0768480
--- /dev/null
+++ b/app/public/assets/JS/fillPage.js
@@ -0,0 +1,127 @@
+var list = document.querySelector(".listExercises");
+var schedaButtons = document.querySelector("#schedeButtons");
+list.innerHTML = "";
+schedaButtons.innerHTML = "";
+fillListWorkouts();
+
+function fillPage(workout_id) {
+ list.innerHTML = "";
+ schedaButtons.innerHTML = "";
+ setTimeout(() => {
+ fetch("../workout/" + workout_id, {
+ method: "GET",
+ })
+ .then(function (response) {
+ return response.json();
+ // return response
+ })
+ .then(function (json) {
+ fillButtons(json);
+
+ let arraySchede = document.querySelectorAll(".schedaButton");
+
+ arraySchede.forEach((el) => {
+ el.onclick = (event) => {
+ list.innerHTML = "";
+ let schedaName = event.target.textContent;
+ fillScheda(json, schedaName);
+ // from modifySerie.js
+ modifySerie();
+ updateScheda(workout_id);
+ sendUpdate.parentElement.removeAttribute("hidden");
+ };
+ });
+ });
+ }, 1000);
+}
+
+function fillScheda(workout, name) {
+ list.setAttribute("scheda", name);
+ for (let i = 0; i < workout.schede.length; i++) {
+ if (workout.schede[i].name === name) {
+ workout.schede[i].esercizi.forEach((el) => {
+ let elJSON = fillExercise(el.name, el.serie);
+ list.appendChild(elJSON);
+ });
+ }
+ }
+}
+
+function fillExercise(name, serie) {
+ let li = document.createElement("li");
+ let h5 = document.createElement("h5");
+ h5.textContent = name;
+
+ li.setAttribute("class", "list-group-item esercizi");
+ li.appendChild(h5);
+ for (let i = 0; i < serie.length; i++) {
+ li.innerHTML =
+ li.innerHTML +
+ completeElement(name, i + 1, serie[i].carico, serie[i].reps);
+ }
+
+ return li;
+}
+
+function completeElement(name, num, carico, reps) {
+ let nameToPlace = name.replace(/[^a-zA-Z0-9]+/g, "");
+ let exElement =
+ '
' +
+ '
' +
+ '' +
+ '
" +
+ '
';
+
+ return exElement;
+}
+
+function fillButtons(json) {
+ json.schede.forEach((scheda) => {
+ let name = scheda.name;
+ let button =
+ '";
+
+ schedaButtons.innerHTML += button;
+ });
+}
+
+function fillListWorkouts() {
+ let list = document.querySelector(".listWorkouts");
+ list.innerHTML = "";
+
+ fetch("../workout")
+ .then(function (response) {
+ return response.json();
+ // return response
+ })
+ .then(function (array) {
+ array.forEach((el) => {
+ let button =
+ '";
+
+ list.innerHTML += button;
+ });
+ // from changeWorkout.js
+ changeWorkout();
+ });
+}
diff --git a/app/public/assets/JS/go_top.js b/app/public/assets/JS/go_top.js
new file mode 100644
index 0000000..3164d7b
--- /dev/null
+++ b/app/public/assets/JS/go_top.js
@@ -0,0 +1,38 @@
+// Set a variable for our button element.
+const scrollToTopButton = document.getElementById("js-top");
+
+// Let's set up a function that shows our scroll-to-top button if we scroll beyond the height of the initial window.
+const scrollFunc = () => {
+ // Get the current scroll value
+ let y = window.scrollY;
+
+ // If the scroll value is greater than the window height, let's add a class to the scroll-to-top button to show it!
+ if (y > 0) {
+ scrollToTopButton.className = "top-link show";
+ } else {
+ scrollToTopButton.className = "top-link hide";
+ }
+};
+
+window.addEventListener("scroll", scrollFunc);
+
+const scrollToTop = () => {
+ // Let's set a variable for the number of pixels we are from the top of the document.
+ const c = document.documentElement.scrollTop || document.body.scrollTop;
+
+ // If that number is greater than 0, we'll scroll back to 0, or the top of the document.
+ // We'll also animate that scroll with requestAnimationFrame:
+ // https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
+ if (c > 0) {
+ window.requestAnimationFrame(scrollToTop);
+ // ScrollTo takes an x and a y coordinate.
+ // Increase the '10' value to get a smoother/slower scroll!
+ window.scrollTo(0, c - c / 10);
+ }
+};
+
+// When the button is clicked, run our ScrolltoTop function above!
+scrollToTopButton.onclick = function (e) {
+ e.preventDefault();
+ scrollToTop();
+};
diff --git a/app/public/assets/JS/modifySerie.js b/app/public/assets/JS/modifySerie.js
new file mode 100644
index 0000000..e4be1f0
--- /dev/null
+++ b/app/public/assets/JS/modifySerie.js
@@ -0,0 +1,46 @@
+function modifySerie() {
+ var elements = document.querySelectorAll(".modalMOD");
+ var save_changes = document.querySelector(".saveChanges");
+ var weightMOD = null;
+ var repsMOD = null;
+
+ let weight = document.querySelector(".inputWeight");
+ let reps = document.querySelector(".inputReps");
+
+ elements.forEach((el) => {
+ el.onclick = (event) => {
+ weightMOD =
+ event.target.parentElement.firstElementChild.firstElementChild
+ .nextElementSibling.firstElementChild;
+
+ repsMOD = weightMOD.nextElementSibling;
+
+ weight.placeholder = weightMOD.textContent;
+ reps.placeholder = repsMOD.textContent;
+ };
+ });
+
+ // -------------------------------------------------------------
+
+ save_changes.onclick = function () {
+ if (weight.value.length === 0 && reps.value.length > 0) {
+ weightMOD.textContent = weight.placeholder;
+ repsMOD.textContent = reps.value;
+ } else if (reps.value.length === 0 && weight.value.length > 0) {
+ weightMOD.textContent = weight.value;
+ repsMOD.textContent = reps.placeholder;
+ } else if (reps.value.length > 0 && weight.value.length > 0) {
+ weightMOD.textContent = weight.value;
+ repsMOD.textContent = reps.value;
+ } else {
+ weightMOD.textContent = weight.placeholder;
+ repsMOD.textContent = reps.placeholder;
+ }
+
+ weight.value = "";
+ reps.value = "";
+
+ // close the modal window
+ $("#modalModifySerie").modal("hide");
+ };
+}
diff --git a/app/public/assets/JS/sendWorkout.js b/app/public/assets/JS/sendWorkout.js
new file mode 100644
index 0000000..3b89d06
--- /dev/null
+++ b/app/public/assets/JS/sendWorkout.js
@@ -0,0 +1,144 @@
+var sendWorkout = document.querySelector(".sendWorkout");
+var buttonExercise = document.querySelector(".addExerciseButton");
+var buttonScheda = document.querySelector(".addSchedaButton");
+
+var input_group =
+ '';
+
+buttonExercise.onclick = function () {
+ buttonExercise.insertAdjacentHTML("beforebegin", input_group);
+ buttonScheda.removeAttribute("hidden");
+};
+
+buttonScheda.onclick = function () {
+ buttonExercise.insertAdjacentHTML("beforebegin", "
");
+ buttonExercise.insertAdjacentHTML(
+ "beforebegin",
+ ''
+ );
+};
+
+document.addEventListener("click", function (event) {
+ if (event.target.classList.contains("delete-row")) {
+ event.target.parentNode.remove();
+ }
+});
+
+document.addEventListener("click", function (event) {
+ if (event.target.classList.contains("move-up")) {
+ let row = event.target.parentNode;
+ let prevRow = row.previousElementSibling;
+ if (prevRow && prevRow.classList.contains("input-text-exercise")) {
+ row.parentNode.insertBefore(row, prevRow);
+ }
+ } else if (event.target.classList.contains("move-down")) {
+ let row = event.target.parentNode;
+ let nextRow = row.nextElementSibling;
+ if (nextRow && nextRow.classList.contains("input-text-exercise")) {
+ row.parentNode.insertBefore(nextRow, row);
+ }
+ }
+});
+
+sendWorkout.onclick = function () {
+ let listInputWorkouts = document.querySelectorAll(".input-text-exercise");
+ let workoutJSON = createSchede(listInputWorkouts);
+ if (
+ listInputWorkouts.length === 0 ||
+ document.querySelector("#input-workout-name").value === "" ||
+ workoutJSON === null
+ ) {
+ document.querySelector("#alert-text").textContent = "Valori Mancanti!";
+ $("#alert-modal").modal("show");
+ } else {
+ document.querySelector("#input-workout-name").value = "";
+
+ fetch("../workout", {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: workoutJSON,
+ })
+ .then(function (response) {
+ $("#modalAddWorkout").modal("hide");
+ // from fillPage.js
+ fillListWorkouts();
+ buttonExercise.previousElementSibling.innerHTML = "";
+
+ // Remove all input groups
+ let inputGroups = document.querySelectorAll(".input-text-exercise");
+ for (let i = 0; i < inputGroups.length; i++) {
+ inputGroups[i].remove();
+ }
+ })
+ .catch(function (error) {
+ document.querySelector("#alert-text").textContent =
+ "Impossibile inserire workout!";
+ $("#alert-modal").modal("show");
+ });
+ }
+};
+
+function createSchede(listInputElements) {
+ let workout = {
+ name: document.querySelector("#input-workout-name").value,
+ schede: [],
+ };
+
+ let scheda_name = "A".charCodeAt(0);
+ let listExercises = [];
+
+ try {
+ listInputElements.forEach((element) => {
+ let ex_JSON = createEx_JSON(element);
+
+ if (element.nextElementSibling.nodeName !== "DIV") {
+ listExercises.push(ex_JSON);
+
+ let scheda = {
+ name: String.fromCharCode(scheda_name),
+ esercizi: listExercises,
+ };
+
+ workout.schede.push(scheda);
+
+ scheda_name += 1;
+ listExercises = [];
+ } else {
+ listExercises.push(ex_JSON);
+ }
+ });
+
+ return JSON.stringify(workout);
+ } catch (error) {
+ return null;
+ }
+}
+
+function createEx_JSON(exerciseRAW) {
+ let titleEx = exerciseRAW.childNodes[0].value;
+ let howMany = exerciseRAW.childNodes[1].value;
+ let reps = exerciseRAW.childNodes[2].value;
+
+ if (titleEx === "" || howMany === "" || reps === "") {
+ throw "missing values!";
+ }
+
+ let esercizio = {
+ name: titleEx,
+ serie: [],
+ };
+
+ let serie = {
+ reps: reps,
+ carico: "0",
+ };
+
+ for (let i = 0; i < howMany; i++) {
+ esercizio.serie.push(serie);
+ }
+
+ return esercizio;
+}
diff --git a/app/public/assets/JS/timer.js b/app/public/assets/JS/timer.js
new file mode 100644
index 0000000..0882391
--- /dev/null
+++ b/app/public/assets/JS/timer.js
@@ -0,0 +1,87 @@
+let option1 = document.querySelector("#option1");
+let option2 = document.querySelector("#option2");
+let option3 = document.querySelector("#option3");
+let option4 = document.querySelector("#option4");
+let option5 = document.querySelector("#option5");
+
+let rest = document.querySelector("#rest-time");
+
+option1.onclick = function () {
+ minutes = parseInt(rest.innerText.split(":")[0]);
+ seconds = parseInt(rest.innerText.split(":")[1]);
+ time = seconds + minutes * 60;
+
+ time += 5;
+
+ minutes = parseInt(time / 60, 10);
+ seconds = parseInt(time % 60, 10);
+
+ minutes = minutes < 10 ? "0" + minutes : minutes;
+ seconds = seconds < 10 ? "0" + seconds : seconds;
+
+ rest.innerText = minutes + ":" + seconds;
+};
+
+option2.onclick = function () {
+ minutes = parseInt(rest.innerText.split(":")[0]);
+ seconds = parseInt(rest.innerText.split(":")[1]);
+ time = seconds + minutes * 60;
+
+ time += 10;
+
+ minutes = parseInt(time / 60, 10);
+ seconds = parseInt(time % 60, 10);
+
+ minutes = minutes < 10 ? "0" + minutes : minutes;
+ seconds = seconds < 10 ? "0" + sceconds : seconds;
+
+ rest.innerText = minutes + ":" + seconds;
+};
+
+option3.onclick = function () {
+ minutes = parseInt(rest.innerText.split(":")[0]);
+ seconds = parseInt(rest.innerText.split(":")[1]);
+ time = seconds + minutes * 60;
+
+ time += 15;
+
+ minutes = parseInt(time / 60, 10);
+ seconds = parseInt(time % 60, 10);
+
+ minutes = minutes < 10 ? "0" + minutes : minutes;
+ seconds = seconds < 10 ? "0" + seconds : seconds;
+
+ rest.innerText = minutes + ":" + seconds;
+};
+
+option4.onclick = function () {
+ minutes = parseInt(rest.innerText.split(":")[0]);
+ seconds = parseInt(rest.innerText.split(":")[1]);
+ time = seconds + minutes * 60;
+
+ time += 30;
+
+ minutes = parseInt(time / 60, 10);
+ seconds = parseInt(time % 60, 10);
+
+ minutes = minutes < 10 ? "0" + minutes : minutes;
+ seconds = seconds < 10 ? "0" + seconds : seconds;
+
+ rest.innerText = minutes + ":" + seconds;
+};
+
+option5.onclick = function () {
+ minutes = parseInt(rest.innerText.split(":")[0]);
+ seconds = parseInt(rest.innerText.split(":")[1]);
+ time = seconds + minutes * 60;
+
+ time += 60;
+
+ minutes = parseInt(time / 60, 10);
+ seconds = parseInt(time % 60, 10);
+
+ minutes = minutes < 10 ? "0" + minutes : minutes;
+ seconds = seconds < 10 ? "0" + seconds : seconds;
+
+ rest.innerText = minutes + ":" + seconds;
+};
diff --git a/app/public/assets/JS/updateScheda.js b/app/public/assets/JS/updateScheda.js
new file mode 100644
index 0000000..529c392
--- /dev/null
+++ b/app/public/assets/JS/updateScheda.js
@@ -0,0 +1,65 @@
+let sendUpdate = document.querySelector("#updateScheda");
+sendUpdate.parentElement.setAttribute("hidden", "");
+
+function updateScheda(workout_id) {
+ sendUpdate.onclick = function () {
+ let exercisesArray = document.querySelectorAll(".esercizi");
+ let scheda_name = document
+ .querySelector(".listExercises")
+ .getAttribute("scheda");
+
+ if (scheda_name !== "") {
+ let Scheda = {
+ name: scheda_name,
+ esercizi: [],
+ };
+
+ exercisesArray.forEach((el) => {
+ let exerciseName = el.firstElementChild.textContent;
+
+ let exercise = {
+ name: exerciseName,
+ serie: [],
+ };
+
+ let arraySerie = document.querySelectorAll(
+ "." + exerciseName.replace(/[^a-zA-Z0-9]+/g, "") + "Serie"
+ );
+
+ arraySerie.forEach((serie) => {
+ let carico = serie.firstElementChild.textContent;
+ let reps = serie.firstElementChild.nextElementSibling.textContent;
+
+ exercise.serie.push({
+ reps: reps,
+ carico: carico,
+ });
+ });
+
+ Scheda.esercizi.push(exercise);
+ });
+
+ fetch("../workout/" + workout_id, {
+ method: "PATCH",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(Scheda),
+ })
+ .then(function (response) {
+ // from fillPage.js
+ fillPage(workout_id);
+ sendUpdate.parentElement.setAttribute("hidden", "");
+ document.querySelector("#alert-text").textContent =
+ "Aggiornamento eseguito!";
+ $("#alert-modal").modal("show");
+ })
+ .catch(function (error) {
+ document.querySelector("#alert-text").textContent =
+ "Impossibile inserire workout!";
+ $("#alert-modal").modal("show");
+ });
+ }
+ };
+}
diff --git a/app/public/assets/muscle.png b/app/public/assets/muscle.png
new file mode 100644
index 0000000..216d64c
Binary files /dev/null and b/app/public/assets/muscle.png differ
diff --git a/app/public/index.html b/app/public/index.html
new file mode 100644
index 0000000..7d5528d
--- /dev/null
+++ b/app/public/index.html
@@ -0,0 +1,558 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ myFitnessAPP
+
+
+
+
+
+
+
+
+
+ myFitnessAPP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
01:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Carica nuovo workout
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e8c6731
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,35 @@
+version: '3.9'
+services:
+
+ myFitApp:
+ ports:
+ - "8000:8000"
+ image: my-fitapp
+ container_name: my-fitapp
+ build:
+ context: .
+ dockerfile: ./docker/Dockerfile
+ restart: on-failure
+
+ postgres:
+ image: postgres:alpine
+ container_name: postgres
+ ports:
+ - 5432:5432
+ environment:
+ - POSTGRES_USER=username
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=ferretdb
+
+ ferretdb:
+ image: ghcr.io/ferretdb/ferretdb:latest
+ container_name: ferretdb
+ restart: on-failure
+ ports:
+ - 27017:27017
+ environment:
+ - FERRETDB_POSTGRESQL_URL=postgres://postgres:5432/ferretdb
+
+networks:
+ default:
+ name: ferretdb
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000..5a8d703
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,17 @@
+# ─── Stage :: Generating Requirements ─────────────────────────────────────────
+
+FROM python:3.10 as requirements-stage
+WORKDIR /tmp
+RUN pip install poetry
+COPY ./pyproject.toml ./poetry.lock* /tmp/
+RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
+
+# ─── Stage :: Final Deploy ────────────────────────────────────────────────────
+
+FROM python:3.10-alpine as final-stage
+WORKDIR /myFitApp
+COPY --from=requirements-stage /tmp/requirements.txt /myFitApp/requirements.txt
+RUN pip install --no-cache-dir --upgrade -r /myFitApp/requirements.txt
+COPY ./app /myFitApp/app
+
+CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/fly.toml b/fly.toml
new file mode 100644
index 0000000..12bf65e
--- /dev/null
+++ b/fly.toml
@@ -0,0 +1,18 @@
+# fly.toml app configuration file generated for my-fitapp on 2023-08-27T10:09:25+02:00
+#
+# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
+#
+
+app = "my-fitapp"
+primary_region = "mad"
+
+[build]
+ dockerfile = "docker/Dockerfile"
+
+[http_service]
+ internal_port = 8000
+ force_https = true
+ auto_stop_machines = true
+ auto_start_machines = true
+ min_machines_running = 1
+ processes = ["app"]
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..d9c6870
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,449 @@
+# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
+
+[[package]]
+name = "annotated-types"
+version = "0.5.0"
+description = "Reusable constraint types to use with typing.Annotated"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"},
+ {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"},
+]
+
+[[package]]
+name = "anyio"
+version = "3.7.1"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
+ {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"]
+test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (<0.22)"]
+
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "dnspython"
+version = "2.4.2"
+description = "DNS toolkit"
+category = "main"
+optional = false
+python-versions = ">=3.8,<4.0"
+files = [
+ {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"},
+ {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"},
+]
+
+[package.extras]
+dnssec = ["cryptography (>=2.6,<42.0)"]
+doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"]
+doq = ["aioquic (>=0.9.20)"]
+idna = ["idna (>=2.1,<4.0)"]
+trio = ["trio (>=0.14,<0.23)"]
+wmi = ["wmi (>=1.5.1,<2.0.0)"]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.1.3"
+description = "Backport of PEP 654 (exception groups)"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
+ {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "fastapi"
+version = "0.102.0"
+description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "fastapi-0.102.0-py3-none-any.whl", hash = "sha256:34db27d0af54ee0d92c540e83c6ee6a11de93f63bbdf73fda54fff9a10c52569"},
+ {file = "fastapi-0.102.0.tar.gz", hash = "sha256:e3652cb9bdf4df1dedda4fce9454e9f75a8c969b5cdd3faf1bd5c6f5d2daab08"},
+]
+
+[package.dependencies]
+pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
+starlette = ">=0.27.0,<0.28.0"
+typing-extensions = ">=4.5.0"
+
+[package.extras]
+all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.3.0"
+description = "Data validation using Python type hints"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"},
+ {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.4.0"
+pydantic-core = "2.6.3"
+typing-extensions = ">=4.6.1"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+
+[[package]]
+name = "pydantic-core"
+version = "2.6.3"
+description = ""
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"},
+ {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"},
+ {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"},
+ {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"},
+ {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"},
+ {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"},
+ {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"},
+ {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"},
+ {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"},
+ {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"},
+ {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"},
+ {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"},
+ {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"},
+ {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"},
+ {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"},
+ {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"},
+ {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"},
+ {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"},
+ {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"},
+ {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"},
+ {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"},
+ {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"},
+ {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"},
+ {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"},
+ {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"},
+ {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+
+[[package]]
+name = "pymongo"
+version = "4.5.0"
+description = "Python driver for MongoDB "
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pymongo-4.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d4fa1b01fa7e5b7bb8d312e3542e211b320eb7a4e3d8dc884327039d93cb9e0"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:dfcd2b9f510411de615ccedd47462dae80e82fdc09fe9ab0f0f32f11cf57eeb5"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:3e33064f1984db412b34d51496f4ea785a9cff621c67de58e09fb28da6468a52"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:33faa786cc907de63f745f587e9879429b46033d7d97a7b84b37f4f8f47b9b32"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:76a262c41c1a7cbb84a3b11976578a7eb8e788c4b7bfbd15c005fb6ca88e6e50"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:0f4b125b46fe377984fbaecf2af40ed48b05a4b7676a2ff98999f2016d66b3ec"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:40d5f6e853ece9bfc01e9129b228df446f49316a4252bb1fbfae5c3c9dedebad"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:152259f0f1a60f560323aacf463a3642a65a25557683f49cfa08c8f1ecb2395a"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d64878d1659d2a5bdfd0f0a4d79bafe68653c573681495e424ab40d7b6d6d41"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1bb3a62395ffe835dbef3a1cbff48fbcce709c78bd1f52e896aee990928432b"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe48f50fb6348511a3268a893bfd4ab5f263f5ac220782449d03cd05964d1ae7"},
+ {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7591a3beea6a9a4fa3080d27d193b41f631130e3ffa76b88c9ccea123f26dc59"},
+ {file = "pymongo-4.5.0-cp310-cp310-win32.whl", hash = "sha256:3a7166d57dc74d679caa7743b8ecf7dc3a1235a9fd178654dddb2b2a627ae229"},
+ {file = "pymongo-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:21b953da14549ff62ea4ae20889c71564328958cbdf880c64a92a48dda4c9c53"},
+ {file = "pymongo-4.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ead4f19d0257a756b21ac2e0e85a37a7245ddec36d3b6008d5bfe416525967dc"},
+ {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aff6279e405dc953eeb540ab061e72c03cf38119613fce183a8e94f31be608f"},
+ {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4c8d6aa91d3e35016847cbe8d73106e3d1c9a4e6578d38e2c346bfe8edb3ca"},
+ {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08819da7864f9b8d4a95729b2bea5fffed08b63d3b9c15b4fea47de655766cf5"},
+ {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a253b765b7cbc4209f1d8ee16c7287c4268d3243070bf72d7eec5aa9dfe2a2c2"},
+ {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8027c9063579083746147cf401a7072a9fb6829678076cd3deff28bb0e0f50c8"},
+ {file = "pymongo-4.5.0-cp311-cp311-win32.whl", hash = "sha256:9d2346b00af524757576cc2406414562cced1d4349c92166a0ee377a2a483a80"},
+ {file = "pymongo-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c3c3525ea8658ee1192cdddf5faf99b07ebe1eeaa61bf32821126df6d1b8072b"},
+ {file = "pymongo-4.5.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e5a27f348909235a106a3903fc8e70f573d89b41d723a500869c6569a391cff7"},
+ {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9a9a39b7cac81dca79fca8c2a6479ef4c7b1aab95fad7544cc0e8fd943595a2"},
+ {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:496c9cbcb4951183d4503a9d7d2c1e3694aab1304262f831d5e1917e60386036"},
+ {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23cc6d7eb009c688d70da186b8f362d61d5dd1a2c14a45b890bd1e91e9c451f2"},
+ {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fff7d17d30b2cd45afd654b3fc117755c5d84506ed25fda386494e4e0a3416e1"},
+ {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6422b6763b016f2ef2beedded0e546d6aa6ba87910f9244d86e0ac7690f75c96"},
+ {file = "pymongo-4.5.0-cp312-cp312-win32.whl", hash = "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60"},
+ {file = "pymongo-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1d40ad09d9f5e719bc6f729cc6b17f31c0b055029719406bd31dde2f72fca7e7"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:076afa0a4a96ca9f77fec0e4a0d241200b3b3a1766f8d7be9a905ecf59a7416b"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:3fa3648e4f1e63ddfe53563ee111079ea3ab35c3b09cd25bc22dadc8269a495f"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:44ee985194c426ddf781fa784f31ffa29cb59657b2dba09250a4245431847d73"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b33c17d9e694b66d7e96977e9e56df19d662031483efe121a24772a44ccbbc7e"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d79ae3bb1ff041c0db56f138c88ce1dfb0209f3546d8d6e7c3f74944ecd2439"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67225f05f6ea27c8dc57f3fa6397c96d09c42af69d46629f71e82e66d33fa4f"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41771b22dd2822540f79a877c391283d4e6368125999a5ec8beee1ce566f3f82"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a1f26bc1f5ce774d99725773901820dfdfd24e875028da4a0252a5b48dcab5c"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3236cf89d69679eaeb9119c840f5c7eb388a2110b57af6bb6baf01a1da387c18"},
+ {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e1f61355c821e870fb4c17cdb318669cfbcf245a291ce5053b41140870c3e5cc"},
+ {file = "pymongo-4.5.0-cp37-cp37m-win32.whl", hash = "sha256:49dce6957598975d8b8d506329d2a3a6c4aee911fa4bbcf5e52ffc6897122950"},
+ {file = "pymongo-4.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2227a08b091bd41df5aadee0a5037673f691e2aa000e1968b1ea2342afc6880"},
+ {file = "pymongo-4.5.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:435228d3c16a375274ac8ab9c4f9aef40c5e57ddb8296e20ecec9e2461da1017"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8e559116e4128630ad3b7e788e2e5da81cbc2344dee246af44471fa650486a70"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:840eaf30ccac122df260b6005f9dfae4ac287c498ee91e3e90c56781614ca238"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b4fe46b58010115514b842c669a0ed9b6a342017b15905653a5b1724ab80917f"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:a8127437ebc196a6f5e8fddd746bd0903a400dc6b5ae35df672dd1ccc7170a2a"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:2988ef5e6b360b3ff1c6d55c53515499de5f48df31afd9f785d788cdacfbe2d3"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e249190b018d63c901678053b4a43e797ca78b93fb6d17633e3567d4b3ec6107"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1240edc1a448d4ada4bf1a0e55550b6292420915292408e59159fd8bbdaf8f63"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d2a56fc2354bb6378f3634402eec788a8f3facf0b3e7d468db5f2b5a78d763"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a0aade2b11dc0c326ccd429ee4134d2d47459ff68d449c6d7e01e74651bd255"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74c0da07c04d0781490b2915e7514b1adb265ef22af039a947988c331ee7455b"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3754acbd7efc7f1b529039fcffc092a15e1cf045e31f22f6c9c5950c613ec4d"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:631492573a1bef2f74f9ac0f9d84e0ce422c251644cd81207530af4aa2ee1980"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e2654d1278384cff75952682d17c718ecc1ad1d6227bb0068fd826ba47d426a5"},
+ {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:168172ef7856e20ec024fe2a746bfa895c88b32720138e6438fd765ebd2b62dd"},
+ {file = "pymongo-4.5.0-cp38-cp38-win32.whl", hash = "sha256:b25f7bea162b3dbec6d33c522097ef81df7c19a9300722fa6853f5b495aecb77"},
+ {file = "pymongo-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:b520aafc6cb148bac09ccf532f52cbd31d83acf4d3e5070d84efe3c019a1adbf"},
+ {file = "pymongo-4.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8543253adfaa0b802bfa88386db1009c6ebb7d5684d093ee4edc725007553d21"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:bc5d8c3647b8ae28e4312f1492b8f29deebd31479cd3abaa989090fb1d66db83"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:505f8519c4c782a61d94a17b0da50be639ec462128fbd10ab0a34889218fdee3"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:53f2dda54d76a98b43a410498bd12f6034b2a14b6844ca08513733b2b20b7ad8"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:9c04b9560872fa9a91251030c488e0a73bce9321a70f991f830c72b3f8115d0d"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58a63a26a1e3dc481dd3a18d6d9f8bd1d576cd1ffe0d479ba7dd38b0aeb20066"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:f076b779aa3dc179aa3ed861be063a313ed4e48ae9f6a8370a9b1295d4502111"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:1b1d7d9aabd8629a31d63cd106d56cca0e6420f38e50563278b520f385c0d86e"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37df8f6006286a5896d1cbc3efb8471ced42e3568d38e6cb00857277047b0d63"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56320c401f544d762fc35766936178fbceb1d9261cd7b24fbfbc8fb6f67aa8a5"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbd705d5f3c3d1ff2d169e418bb789ff07ab3c70d567cc6ba6b72b04b9143481"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a167081c75cf66b32f30e2f1eaee9365af935a86dbd76788169911bed9b5d5"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c42748ccc451dfcd9cef6c5447a7ab727351fd9747ad431db5ebb18a9b78a4d"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf62da7a4cdec9a4b2981fcbd5e08053edffccf20e845c0b6ec1e77eb7fab61d"},
+ {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b5bbb87fa0511bd313d9a2c90294c88db837667c2bda2ea3fa7a35b59fd93b1f"},
+ {file = "pymongo-4.5.0-cp39-cp39-win32.whl", hash = "sha256:465fd5b040206f8bce7016b01d7e7f79d2fcd7c2b8e41791be9632a9df1b4999"},
+ {file = "pymongo-4.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:63d8019eee119df308a075b8a7bdb06d4720bf791e2b73d5ab0e7473c115d79c"},
+ {file = "pymongo-4.5.0.tar.gz", hash = "sha256:681f252e43b3ef054ca9161635f81b730f4d8cadd28b3f2b2004f5a72f853982"},
+]
+
+[package.dependencies]
+dnspython = ">=1.16.0,<3.0.0"
+
+[package.extras]
+aws = ["pymongo-auth-aws (<2.0.0)"]
+encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"]
+gssapi = ["pykerberos", "winkerberos (>=0.5.0)"]
+ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"]
+snappy = ["python-snappy"]
+zstd = ["zstandard"]
+
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+description = "Sniff out which async library your code is running under"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
+
+[[package]]
+name = "starlette"
+version = "0.27.0"
+description = "The little ASGI library that shines."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"},
+ {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"},
+]
+
+[package.dependencies]
+anyio = ">=3.4.0,<5"
+
+[package.extras]
+full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
+
+[[package]]
+name = "typing-extensions"
+version = "4.7.1"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
+ {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.23.2"
+description = "The lightning-fast ASGI server."
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"},
+ {file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+h11 = ">=0.8"
+typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.10"
+content-hash = "6aa795d9fdab967b5efc8edaaf231a8817cf787aae92710bb6d91f8dbfe99713"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..8a0fa70
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,17 @@
+[tool.poetry]
+name = "myfitapp"
+version = "0.1.0"
+description = "a cloud native application for tracking gym workouts, built in FastAPI."
+authors = ["fioreale "]
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.10"
+fastapi = "^0.102.0"
+pymongo = "^4.5.0"
+uvicorn = "^0.23.2"
+
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/storage/ferretdb/Dockerfile b/storage/ferretdb/Dockerfile
new file mode 100644
index 0000000..511613e
--- /dev/null
+++ b/storage/ferretdb/Dockerfile
@@ -0,0 +1 @@
+FROM ghcr.io/ferretdb/ferretdb:latest
\ No newline at end of file
diff --git a/storage/ferretdb/fly.toml b/storage/ferretdb/fly.toml
new file mode 100644
index 0000000..9e69059
--- /dev/null
+++ b/storage/ferretdb/fly.toml
@@ -0,0 +1,20 @@
+# fly.toml app configuration file generated for ferretdb on 2023-08-27T10:56:36+02:00
+#
+# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
+#
+
+app = "ferretdb"
+primary_region = "mad"
+
+[build]
+
+[[services]]
+internal_port = 27017
+protocol = "tcp"
+auto_stop_machines = true
+auto_start_machines = true
+min_machines_running = 1
+processes = ["app"]
+
+[env]
+FERRETDB_POSTGRESQL_URL = "postgres://my-fitapp-postgres.internal:5432/ferretdb"
diff --git a/storage/postgres/Dockerfile b/storage/postgres/Dockerfile
new file mode 100644
index 0000000..1349d0c
--- /dev/null
+++ b/storage/postgres/Dockerfile
@@ -0,0 +1 @@
+FROM postgres:alpine
\ No newline at end of file
diff --git a/storage/postgres/fly.toml b/storage/postgres/fly.toml
new file mode 100644
index 0000000..faddfe8
--- /dev/null
+++ b/storage/postgres/fly.toml
@@ -0,0 +1,22 @@
+# fly.toml app configuration file generated for my-fitapp-postgres on 2023-08-27T10:27:48+02:00
+#
+# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
+#
+
+app = "my-fitapp-postgres"
+primary_region = "mad"
+
+[build]
+
+[[services]]
+internal_port = 5432
+protocol = "tcp"
+auto_stop_machines = true
+auto_start_machines = true
+min_machines_running = 1
+processes = ["app"]
+
+[env]
+POSTGRES_USER = "username"
+POSTGRES_PASSWORD = "password"
+POSTGRES_DB = "ferretdb"