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 + + + +
+
+

+ + logo + + myFitnessAPP +

+ +
+ +
+
+ + + +
+ +
+
+ +
+
+
+
+ + + + + + +
+
+
01:00
+
+ +
+
+
+ +
+
00:00
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + 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"