Skip to content

Commit

Permalink
Merge pull request #2 from fioreale/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
fioreale authored Aug 27, 2023
2 parents ad8fe8b + 7e58e6e commit 43e3180
Show file tree
Hide file tree
Showing 28 changed files with 1,925 additions and 13 deletions.
22 changes: 22 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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/
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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/
14 changes: 1 addition & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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.


Empty file added app/api/__init__.py
Empty file.
Empty file added app/api/models/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions app/api/models/workout.py
Original file line number Diff line number Diff line change
@@ -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]
79 changes: 79 additions & 0 deletions app/db/workout_db.py
Original file line number Diff line number Diff line change
@@ -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
88 changes: 88 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions app/public/assets/CSS/font.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
font-family: 'Nunito', sans-serif;
}
32 changes: 32 additions & 0 deletions app/public/assets/CSS/got_top.css
Original file line number Diff line number Diff line change
@@ -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;
}
11 changes: 11 additions & 0 deletions app/public/assets/JS/changeWorkout.js
Original file line number Diff line number Diff line change
@@ -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");
};
});
}
Loading

0 comments on commit 43e3180

Please sign in to comment.