Backend optimization

This commit is contained in:
João Vitória Silva
2024-01-06 23:37:57 +00:00
parent ad74fd7104
commit 51ec61c3e1
41 changed files with 1066 additions and 680 deletions

7
.gitignore vendored
View File

@@ -4,5 +4,8 @@ backend/*/__pycache__/
backend/*.pyc backend/*.pyc
# Logs # Logs
backend/logs/ backend/logs/*.log
backend/*.log backend/*.log
# Frontend
frontend/img/users_img/*.*

View File

@@ -91,6 +91,9 @@ JAEGER_PROTOCOL | http | Yes
JAEGER_HOST | jaeger | Yes JAEGER_HOST | jaeger | Yes
JAGGER_PORT | 4317 | Yes JAGGER_PORT | 4317 | Yes
STRAVA_DAYS_ACTIVITIES_ONLINK | 30 | Yes STRAVA_DAYS_ACTIVITIES_ONLINK | 30 | Yes
API_ENDPOINT* | changeme | Yes
*API_ENDPOINT needs to be set if you want to enable Strava integration
Table bellow shows the obligatory environemnt variables for mariadb container. You should set them based on what was also set for backend container. Table bellow shows the obligatory environemnt variables for mariadb container. You should set them based on what was also set for backend container.

View File

@@ -14,4 +14,5 @@ JAEGER_ENABLED=true
JAEGER_PROTOCOL=http JAEGER_PROTOCOL=http
JAEGER_HOST=jaeger JAEGER_HOST=jaeger
JAGGER_PORT=4317 JAGGER_PORT=4317
STRAVA_DAYS_ACTIVITIES_ONLINK=30 STRAVA_DAYS_ACTIVITIES_ONLINK=30
API_ENDPOINT=changeme

View File

@@ -416,7 +416,15 @@ async def read_activities_useractivities_thisweek_distances(
distances = calculate_activity_distances(activity_records) distances = calculate_activity_distances(activity_records)
# Return the queried values using JSONResponse # Return the queried values using JSONResponse
return JSONResponse(content=distances) #return JSONResponse(content=distances)
# Include metadata in the response
metadata = {"total_records": 1}
# Return the queried values using JSONResponse
return JSONResponse(
content={"metadata": metadata, "content": distances}
)
except JWTError: except JWTError:
# Return an error response if the user is not authenticated # Return an error response if the user is not authenticated
return create_error_response("UNAUTHORIZED", "Unauthorized", 401) return create_error_response("UNAUTHORIZED", "Unauthorized", 401)
@@ -479,7 +487,15 @@ async def read_activities_useractivities_thismonth_distances(
distances = calculate_activity_distances(activity_records) distances = calculate_activity_distances(activity_records)
# Return the queried values using JSONResponse # Return the queried values using JSONResponse
return JSONResponse(content=distances) #return JSONResponse(content=distances)
# Include metadata in the response
metadata = {"total_records": 1}
# Return the queried values using JSONResponse
return JSONResponse(
content={"metadata": metadata, "content": distances}
)
except JWTError: except JWTError:
# Return an error response if the user is not authenticated # Return an error response if the user is not authenticated
return create_error_response("UNAUTHORIZED", "Unauthorized", 401) return create_error_response("UNAUTHORIZED", "Unauthorized", 401)

View File

@@ -36,7 +36,7 @@ Logger:
""" """
import logging import logging
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from . import sessionController from . import sessionController
from jose import JWTError from jose import JWTError

View File

@@ -1,58 +1,224 @@
import os """
import logging Authentication and User Management Module
from fastapi import APIRouter, Depends, HTTPException
from datetime import datetime, timedelta
from jose import jwt, JWTError
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from db.db import (
User,
AccessToken,
) # Import your SQLAlchemy session management from db.db and models
from controllers.userController import UserResponse
from pydantic import BaseModel
from dependencies import get_db_session
This module defines FastAPI routes and functions for user authentication, access token management,
and CRUD operations on user records. It integrates with a relational database using SQLAlchemy
and provides endpoints for handling user login, token validation, user data retrieval,
and logout functionality.
Endpoints:
- POST /token: Endpoint for user login to obtain an access token.
- GET /validate_token: Endpoint for validating the integrity and expiration of an access token.
- GET /users/me: Endpoint to retrieve user data based on the provided access token.
- DELETE /logout/{user_id}: Endpoint for user logout, revoking the associated access token.
Dependencies:
- OAuth2PasswordBearer: FastAPI security scheme for handling OAuth2 password bearer tokens.
- get_db_session: Dependency function to get a database session.
- create_error_response: Function to create a standardized error response.
Models:
- TokenBase: Base Pydantic model for token attributes.
- CreateTokenRequest: Pydantic model for creating token records.
Functions:
- authenticate_user: Function to authenticate a user and generate an access token.
- create_access_token: Function to create and store a new access token.
- remove_expired_tokens: Function to remove expired access tokens from the database.
- get_user_data: Function to retrieve user data based on the provided access token.
- validate_token: Function to validate the integrity and expiration of an access token.
- validate_admin_access: Function to validate if a user has admin access based on the token.
Logger:
- Logger named "myLogger" for logging errors and exceptions.
"""
# OS module for interacting with the operating system
import os
# Logging module for adding log statements to your code
import logging
# FastAPI framework imports
from fastapi import APIRouter, Depends
# Datetime module for working with date and time
from datetime import datetime, timedelta
# JOSE (JavaScript Object Signing and Encryption) library for JWT (JSON Web Tokens)
from jose import jwt, JWTError
# FastAPI security module for handling OAuth2 password bearer authentication
from fastapi.security import OAuth2PasswordBearer
# SQLAlchemy module for working with relational databases
from sqlalchemy.orm import Session
# Importing User and AccessToken models from the 'db' module
from db.db import User, AccessToken
# Importing UserResponse model from the 'controllers.userController' module
from controllers.userController import UserResponse
# Pydantic module for data validation and parsing
from pydantic import BaseModel
# Custom dependencies for dependency injection in FastAPI
from dependencies import get_db_session, create_error_response
# Define the API router
router = APIRouter() router = APIRouter()
# Define a loggger created on main.py
logger = logging.getLogger("myLogger") logger = logging.getLogger("myLogger")
# Define the OAuth2 scheme for handling bearer tokens
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_user_id_from_token(token: str): class TokenBase(BaseModel):
"""
Base Pydantic model for representing token attributes.
Attributes:
- username (str): The username of the user.
- password (str): The user password in hash format.
- neverExpires (str): True or false value to set the token to expire.
"""
username: str
password: str
neverExpires: bool
class CreateTokenRequest(TokenBase):
"""
Pydantic model for creating token records.
Inherits from TokenBase, which defines the base attributes for token.
This class extends the TokenBase Pydantic model and is specifically tailored for
creating new records.
"""
pass
def decode_token(token: str):
"""
Decode a JSON Web Token (JWT) and extract its payload.
Parameters:
- token (str): The JWT string to be decoded.
Returns:
- dict: A dictionary containing the decoded payload of the JWT.
This function decodes a given JWT using the provided secret key and algorithm. It extracts and returns the payload
of the JWT, which typically includes information such as user ID, access type, and expiration time.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
"""
try: try:
payload = jwt.decode( payload = jwt.decode(
token, token,
os.environ.get("SECRET_KEY"), os.environ.get("SECRET_KEY"),
algorithms=[os.environ.get("ALGORITHM")], algorithms=[os.environ.get("ALGORITHM")],
) )
return payload.get("id") return payload
except JWTError: except JWTError:
raise HTTPException(status_code=401, detail="Unauthorized") # Return an error response if the user is not authenticated
return ("UNAUTHORIZED", "Unauthorized", 401)
def get_user_id_from_token(token: str):
"""
Extract the user ID from a decoded JSON Web Token (JWT) payload.
Parameters:
- token (str): The decoded JWT string.
Returns:
- Union[int, Tuple[str, str, int]]: The user ID extracted from the JWT payload,
or a tuple representing an error response if the token is invalid.
This function retrieves the user ID from the decoded payload of a JWT. It is used for
obtaining the user ID associated with a valid token during user authentication.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the extraction process.
"""
try:
return decode_token(token).get("id")
except JWTError:
# Return an error response if the user is not authenticated
return ("UNAUTHORIZED", "Unauthorized", 401)
except Exception as err:
# Log the error and return an error response
logger.error(f"Error in get_user_id_from_token: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
def get_exp_from_token(token: str = Depends(oauth2_scheme)): def get_exp_from_token(token: str = Depends(oauth2_scheme)):
"""
Extract the expiration time from a decoded JSON Web Token (JWT) payload.
Parameters:
- token (str): The decoded JWT string.
Returns:
- Union[int, Tuple[str, str, int]]: The expiration time (UNIX timestamp) extracted
from the JWT payload, or a tuple representing an error response if the token is invalid.
This function retrieves the expiration time from the decoded payload of a JWT.
It is used to check the validity and expiration status of an access token.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the extraction process.
"""
try: try:
payload = jwt.decode( return decode_token(token).get("exp")
token,
os.environ.get("SECRET_KEY"),
algorithms=[os.environ.get("ALGORITHM")],
)
return payload.get("exp")
except JWTError: except JWTError:
raise HTTPException(status_code=401, detail="Unauthorized") # Return an error response if the user is not authenticated
return ("UNAUTHORIZED", "Unauthorized", 401)
except Exception as err:
# Log the error and return an error response
logger.error(f"Error in get_exp_from_token: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
def get_access_type_from_token(token: str = Depends(oauth2_scheme)): def get_access_type_from_token(token: str = Depends(oauth2_scheme)):
"""
Extract the access type from a decoded JSON Web Token (JWT) payload.
Parameters:
- token (str): The decoded JWT string.
Returns:
- Union[int, Tuple[str, str, int]]: The access type extracted from the JWT payload,
or a tuple representing an error response if the token is invalid.
This function retrieves the access type from the decoded payload of a JWT.
It is used to determine the level of access associated with a user's token.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the extraction process.
"""
try: try:
payload = jwt.decode( return decode_token(token).get("access_type")
token,
os.environ.get("SECRET_KEY"),
algorithms=[os.environ.get("ALGORITHM")],
)
return payload.get("access_type")
except JWTError: except JWTError:
raise HTTPException(status_code=401, detail="Unauthorized") # Return an error response if the user is not authenticated
return ("UNAUTHORIZED", "Unauthorized", 401)
except Exception as err:
# Log the error and return an error response
logger.error(f"Error in get_access_type_from_token: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
async def authenticate_user( async def authenticate_user(
@@ -61,6 +227,26 @@ async def authenticate_user(
neverExpires: bool, neverExpires: bool,
db_session: Session, db_session: Session,
): ):
"""
Authenticate a user and generate an access token.
Parameters:
- username (str): The username of the user attempting to authenticate.
- password (str): The password of the user attempting to authenticate.
- neverExpires (bool): Flag indicating whether the access token should never expire.
- db_session (Session): SQLAlchemy database session.
Returns:
- Union[str, Tuple[str, str, int]]: The generated access token,
or a tuple representing an error response if authentication fails.
This function verifies the user's credentials, checks for an existing access token,
and generates a new access token if necessary. The token's expiration is determined
based on the 'neverExpires' flag.
Raises:
- Exception: If an unexpected error occurs during the authentication process.
"""
try: try:
# Use SQLAlchemy ORM to query the database # Use SQLAlchemy ORM to query the database
user = ( user = (
@@ -69,8 +255,8 @@ async def authenticate_user(
.first() .first()
) )
if not user: if not user:
raise HTTPException( return create_error_response(
status_code=400, detail="Incorrect username or password" "BAD_REQUEST", "Incorrect username or password", 400
) )
# Check if there is an existing access token for the user # Check if there is an existing access token for the user
@@ -93,9 +279,12 @@ async def authenticate_user(
return access_token return access_token
except Exception as e: except Exception as err:
logger.error(e) # Log the error and return an error response
return False logger.error(f"Error in authenticate_user: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
async def create_access_token( async def create_access_token(
@@ -104,34 +293,79 @@ async def create_access_token(
db_session: Session, db_session: Session,
expires_delta: timedelta = None, expires_delta: timedelta = None,
): ):
to_encode = data.copy() """
if never_expire: Create and store a new access token.
expire = datetime.utcnow() + timedelta(days=90)
elif expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire}) Parameters:
encoded_jwt = jwt.encode( - data (dict): The payload data to be encoded in the access token.
to_encode, os.environ.get("SECRET_KEY"), algorithm=os.environ.get("ALGORITHM") - never_expire (bool): Flag indicating whether the access token should never expire.
) - db_session (Session): SQLAlchemy database session.
- expires_delta (timedelta, optional): Duration until the access token expires.
# Insert the access token into the database using SQLAlchemy Returns:
access_token = AccessToken( - Union[str, Tuple[str, str, int]]: The generated access token,
token=encoded_jwt, or a tuple representing an error response if token creation fails.
user_id=data["id"],
created_at=datetime.utcnow(),
expires_at=expire,
)
db_session.add(access_token) This function creates a new access token by encoding the provided payload data.
db_session.commit() The token is then stored in the database, and the generated token string is returned.
return encoded_jwt Raises:
- Exception: If an unexpected error occurs during the token creation process.
"""
try:
to_encode = data.copy()
if never_expire:
expire = datetime.utcnow() + timedelta(days=90)
elif expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode,
os.environ.get("SECRET_KEY"),
algorithm=os.environ.get("ALGORITHM"),
)
# Insert the access token into the database using SQLAlchemy
access_token = AccessToken(
token=encoded_jwt,
user_id=data["id"],
created_at=datetime.utcnow(),
expires_at=expire,
)
db_session.add(access_token)
db_session.commit()
return encoded_jwt
except Exception as err:
# Log the error, rollback the transaction, and return an error response
db_session.rollback()
logger.error(f"Error in create_access_token: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
def remove_expired_tokens(db_session: Session): def remove_expired_tokens(db_session: Session):
"""
Remove expired access tokens from the database.
Parameters:
- db_session (Session): SQLAlchemy database session.
Returns:
- Union[None, Tuple[str, str, int]]: None on successful removal,
or a tuple representing an error response if removal fails.
This function deletes access tokens from the database that have exceeded their expiration time.
It helps maintain the database's integrity by regularly purging expired access tokens.
Raises:
- Exception: If an unexpected error occurs during the removal process.
"""
try: try:
# Calculate the expiration time # Calculate the expiration time
expiration_time = datetime.utcnow() - timedelta( expiration_time = datetime.utcnow() - timedelta(
@@ -148,24 +382,45 @@ def remove_expired_tokens(db_session: Session):
logger.info(f"{rows_deleted} access tokens deleted from the database") logger.info(f"{rows_deleted} access tokens deleted from the database")
except Exception as err: except Exception as err:
# Log the error and return an error response
logger.error(f"Error in remove_expired_tokens: {err}", exc_info=True) logger.error(f"Error in remove_expired_tokens: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
def get_user_data(db_session: Session, token: str = Depends(oauth2_scheme)): def get_user_data(db_session: Session, token: str = Depends(oauth2_scheme)):
"""
Retrieve user data based on the provided access token.
Parameters:
- db_session (Session): SQLAlchemy database session.
- token (str): The access token for which user data is requested.
Returns:
- Union[dict, Tuple[str, str, int]]: A dictionary containing user data,
or a tuple representing an error response if retrieval fails.
This function fetches user data from the database using the provided access token.
It validates the token, retrieves the associated user ID, and returns the user's details
in a dictionary format.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the data retrieval process.
"""
try: try:
validate_token(db_session=db_session, token=token) validate_token(db_session=db_session, token=token)
user_id = get_user_id_from_token(token) user_id = get_user_id_from_token(token)
if user_id is None: if user_id is None:
raise HTTPException( return create_error_response("UNAUTHORIZED", "Unauthorized", 401)
status_code=401, detail="Invalid authentication credentials"
)
# Retrieve the user details from the database using the user ID # Retrieve the user details from the database using the user ID
user = db_session.query(User).filter(User.id == user_id).first() user = db_session.query(User).filter(User.id == user_id).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") return create_error_response("NOT_FOUND", "User not found", 404)
if user.strava_token is None: if user.strava_token is None:
is_strava_linked = 0 is_strava_linked = 0
@@ -191,10 +446,35 @@ def get_user_data(db_session: Session, token: str = Depends(oauth2_scheme)):
return user_data return user_data
except JWTError: except JWTError:
raise HTTPException(status_code=401, detail="Unauthorized") # Return an error response if the user is not authenticated
return ("UNAUTHORIZED", "Unauthorized", 401)
except Exception as err:
# Log the error and return an error response
logger.error(f"Error in get_user_data: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
def validate_token(db_session: Session, token: str): def validate_token(db_session: Session, token: str):
"""
Validate the integrity and expiration of an access token.
Parameters:
- db_session (Session): SQLAlchemy database session.
- token (str): The access token to be validated.
Returns:
- Union[dict, Tuple[str, str, int]]: A dictionary with a success message if the token is valid,
or a tuple representing an error response if validation fails.
This function checks the integrity and expiration of the provided access token.
It ensures that the token is associated with a valid user in the database and has not expired.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the validation process.
"""
try: try:
user_id = get_user_id_from_token(token) user_id = get_user_id_from_token(token)
@@ -206,56 +486,159 @@ def validate_token(db_session: Session, token: str):
.first() .first()
) )
if access_token: if not access_token or datetime.utcnow() > datetime.fromtimestamp(exp):
expiration_datetime = datetime.fromtimestamp(exp)
current_time = datetime.utcnow()
if current_time > expiration_datetime:
raise JWTError("Token expired")
else:
return {"message": "Token is valid"}
else:
raise JWTError("Token expired") raise JWTError("Token expired")
else:
# if 'exp' not in decoded_token: return {"message": "Token is valid"}
# return {"message": "Token is valid"} except JWTError as jwt_error:
# else: raise jwt_error
except JWTError: except Exception as err:
raise JWTError("Invalid token") logger.error(f"Error in token validation: {err}", exc_info=True)
raise JWTError("Token validation failed")
def validate_admin_access(token: str): def validate_admin_access(token: str):
"""
Validate if the user associated with the provided token has administrative access.
Parameters:
- token (str): The access token to be validated.
Returns:
- Union[None, Tuple[str, str, int]]: None if the user has admin access,
or a tuple representing an error response if validation fails.
This function checks if the user associated with the provided access token has administrative access.
It verifies the access type stored in the token, allowing or denying access based on the user's privileges.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the validation process.
"""
try: try:
user_access_type = get_access_type_from_token(token) user_access_type = get_access_type_from_token(token)
if user_access_type != 2: if user_access_type != 2:
raise HTTPException(status_code=401, detail="Unauthorized") return create_error_response("UNAUTHORIZED", "Unauthorized", 401)
except JWTError: except JWTError:
raise JWTError("Invalid token") raise JWTError("Invalid token")
class CreateTokenRequest(BaseModel):
username: str
password: str
neverExpires: bool
@router.post("/token") @router.post("/token")
async def login_for_access_token( async def login_for_access_token(
token: CreateTokenRequest, db_session: Session = Depends(get_db_session) token: CreateTokenRequest, db_session: Session = Depends(get_db_session)
): ):
"""
Endpoint for user login to obtain an access token.
Parameters:
- token (CreateTokenRequest): The request model containing username, password, and neverExpires flag.
- db_session (Session, optional): SQLAlchemy database session. Obtained through dependency injection.
Returns:
- Union[dict, Tuple[str, str, int]]: A dictionary containing the access token if login is successful,
or a tuple representing an error response if login fails.
This endpoint handles user authentication by verifying the provided credentials.
If successful, it generates an access token and returns it to the user.
Raises:
- Exception: If an unexpected error occurs during the authentication process.
"""
access_token = await authenticate_user( access_token = await authenticate_user(
token.username, token.password, token.neverExpires, db_session token.username, token.password, token.neverExpires, db_session
) )
if not access_token: if not access_token:
raise HTTPException(status_code=400, detail="Unable to retrieve access token") return create_error_response(
"BAD_REQUEST", "Unable to retrieve access token", 400
)
return {"access_token": access_token, "token_type": "bearer"} return {"access_token": access_token, "token_type": "bearer"}
@router.get("/validate_token")
async def check_validate_token(
token: str = Depends(oauth2_scheme), db_session: Session = Depends(get_db_session)
):
"""
Endpoint for validating the integrity and expiration of an access token.
Parameters:
- token (str): The access token to be validated.
- db_session (Session, optional): SQLAlchemy database session. Obtained through dependency injection.
Returns:
- Union[dict, Tuple[str, str, int]]: A dictionary with a success message if the token is valid,
or a tuple representing an error response if validation fails.
This endpoint checks the integrity and expiration of the provided access token.
If the token is valid, it returns a success message; otherwise, it returns an error response.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the validation process.
"""
try:
return validate_token(db_session, token)
except JWTError:
# Return an error response if the user is not authenticated
return ("UNAUTHORIZED", "Unauthorized", 401)
except Exception as err:
# Log the error and return an error response
logger.error(f"Error in get_user_data: {err}", exc_info=True)
return create_error_response(
"INTERNAL_SERVER_ERROR", "Internal Server Error", 500
)
@router.get("/users/me", response_model=UserResponse)
async def read_users_me(
token: str = Depends(oauth2_scheme), db_session: Session = Depends(get_db_session)
):
"""
Endpoint to retrieve user data based on the provided access token.
Parameters:
- token (str): The access token used to identify the user.
- db_session (Session, optional): SQLAlchemy database session. Obtained through dependency injection.
Returns:
- Union[dict, Tuple[str, str, int]]: A dictionary containing user data,
or a tuple representing an error response if retrieval fails.
This endpoint fetches and returns the user's data based on the provided access token.
It validates the token, retrieves the associated user ID, and returns the user's details
in a format consistent with the UserResponse Pydantic model.
Raises:
- JWTError: If there is an issue with JWT decoding or the token is invalid.
- Exception: If an unexpected error occurs during the data retrieval process.
"""
return get_user_data(db_session, token)
@router.delete("/logout/{user_id}") @router.delete("/logout/{user_id}")
async def logout( async def logout(
user_id: int, user_id: int,
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
db_session: Session = Depends(get_db_session), db_session: Session = Depends(get_db_session),
): ):
"""
Endpoint for user logout, revoking the associated access token.
Parameters:
- user_id (int): The user ID for which logout is requested.
- token (str): The access token used to identify the user.
- db_session (Session, optional): SQLAlchemy database session. Obtained through dependency injection.
Returns:
- Union[dict, Tuple[str, str, int]]: A dictionary with a success message if logout is successful,
or a tuple representing an error response if logout fails.
This endpoint revokes the access token associated with the provided user ID, effectively logging the user out.
If the token is found and successfully revoked, it returns a success message; otherwise, it returns an error response.
Raises:
- Exception: If an unexpected error occurs during the logout process.
"""
try: try:
access_token = ( access_token = (
db_session.query(AccessToken) db_session.query(AccessToken)
@@ -267,24 +650,11 @@ async def logout(
db_session.commit() db_session.commit()
return {"message": "Logged out successfully"} return {"message": "Logged out successfully"}
else: else:
raise HTTPException(status_code=404, detail="Token not found") return create_error_response("NOT_FOUND", "Token not found", 404)
except Exception as e:
logger.error(e)
raise HTTPException(status_code=500, detail="Failed to invalidate access token")
except Exception as err:
@router.get("/validate_token") # Log the error and return an error response
async def check_validate_token( logger.error(f"Error in logout: {err}", exc_info=True)
token: str = Depends(oauth2_scheme), db_session: Session = Depends(get_db_session) return create_error_response(
): "INTERNAL_SERVER_ERROR", "Internal Server Error", 500
try: )
return validate_token(db_session, token)
except JWTError:
raise HTTPException(status_code=401, detail="Unauthorized")
@router.get("/users/me", response_model=UserResponse)
async def read_users_me(
token: str = Depends(oauth2_scheme), db_session: Session = Depends(get_db_session)
):
return get_user_data(db_session, token)

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,7 @@ Logger:
- Logger named "myLogger" for logging errors and exceptions. - Logger named "myLogger" for logging errors and exceptions.
""" """
import logging import logging
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional

View File

@@ -26,7 +26,7 @@ def configure_logger():
Returns: Returns:
- logging.Logger: Configured logger instance. - logging.Logger: Configured logger instance.
""" """
logging.config.fileConfig('logging_config.ini') logging.config.fileConfig('logs/logging_config.ini')
return logging.getLogger('myLogger') return logging.getLogger('myLogger')

0
backend/logs/__init__.py Normal file
View File

View File

@@ -62,6 +62,8 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
# Import OS module for handling environment variables # Import OS module for handling environment variables
import os import os
import logging
# Import database-related functions and dependencies # Import database-related functions and dependencies
from db.db import create_database_tables from db.db import create_database_tables
from dependencies import get_db_session, configure_logger from dependencies import get_db_session, configure_logger
@@ -70,10 +72,20 @@ from dependencies import get_db_session, configure_logger
app = FastAPI() app = FastAPI()
# Create loggger # Create loggger
logger = configure_logger() #logger = configure_logger()
logger = logging.getLogger("myLogger")
logger.setLevel(logging.DEBUG)
file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# Check for required environment variables # Check for required environment variables
required_env_vars = ["DB_HOST", "DB_PORT", "DB_USER", "DB_PASSWORD", "DB_DATABASE", "SECRET_KEY", "ALGORITHM", "ACCESS_TOKEN_EXPIRE_MINUTES", "STRAVA_CLIENT_ID", "STRAVA_CLIENT_SECRET", "STRAVA_AUTH_CODE", "JAEGER_ENABLED", "JAEGER_PROTOCOL", "JAEGER_HOST", "JAGGER_PORT", "STRAVA_DAYS_ACTIVITIES_ONLINK"] required_env_vars = ["DB_HOST", "DB_PORT", "DB_USER", "DB_PASSWORD", "DB_DATABASE", "SECRET_KEY", "ALGORITHM", "ACCESS_TOKEN_EXPIRE_MINUTES", "STRAVA_CLIENT_ID", "STRAVA_CLIENT_SECRET", "STRAVA_AUTH_CODE", "JAEGER_ENABLED", "JAEGER_PROTOCOL", "JAEGER_HOST", "JAGGER_PORT", "STRAVA_DAYS_ACTIVITIES_ONLINK", "API_ENDPOINT"]
for var in required_env_vars: for var in required_env_vars:
if var not in os.environ: if var not in os.environ:
@@ -96,6 +108,8 @@ def startup_event():
logger.info("Backend startup event") logger.info("Backend startup event")
# Create the database and tables if they don't exist # Create the database and tables if they don't exist
create_database_tables() create_database_tables()
logger.info("Will check if there is expired tokens to remove")
sessionController.remove_expired_tokens(db_session=get_db_session())
def shutdown_event(): def shutdown_event():

0
frontend/.env Normal file → Executable file
View File

0
frontend/activities/activity.php Normal file → Executable file
View File

0
frontend/gear/gear.php Normal file → Executable file
View File

0
frontend/gear/gears.php Normal file → Executable file
View File

0
frontend/img/avatar/bicycle1.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

0
frontend/img/avatar/bicycle2.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

0
frontend/img/avatar/female1.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

0
frontend/img/avatar/male1.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

0
frontend/img/avatar/running_shoe1.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

0
frontend/img/avatar/running_shoe2.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

0
frontend/img/avatar/wetsuit1.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

0
frontend/img/avatar/wetsuit2.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

0
frontend/img/logo/logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

0
frontend/img/strava/btn_strava_connectwith_orange.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -55,7 +55,7 @@ function getUserActivitiesThisWeekDistances($userID)
return -1; return -1;
} else { } else {
if ($response[1] === 200) { if ($response[1] === 200) {
return json_decode($response[0], true); return json_decode($response[0], true)["content"];
} else { } else {
return -2; return -2;
} }
@@ -70,7 +70,7 @@ function getUserActivitiesThisMonthDistances($userID)
return -1; return -1;
} else { } else {
if ($response[1] === 200) { if ($response[1] === 200) {
return json_decode($response[0], true); return json_decode($response[0], true)["content"];
} else { } else {
return -2; return -2;
} }
@@ -116,7 +116,7 @@ function getActivitiesPagination($pageNumber, $numRecords)
return -1; return -1;
} else { } else {
if ($response[1] === 200) { if ($response[1] === 200) {
return json_decode($response[0], true); return json_decode($response[0], true)["content"];
} else { } else {
return -2; return -2;
} }

0
frontend/inc/func/session-funcs.php Normal file → Executable file
View File

0
frontend/inc/func/strava-funcs.php Normal file → Executable file
View File

0
frontend/inc/sqlFunctions.php Normal file → Executable file
View File

0
frontend/lang/activities/en.php Normal file → Executable file
View File

0
frontend/lang/gear/gear/en.php Normal file → Executable file
View File

0
frontend/lang/gear/gears/en.php Normal file → Executable file
View File

0
frontend/lang/inc/Template-Top/en.php Normal file → Executable file
View File

0
frontend/lang/index/en.php Normal file → Executable file
View File

0
frontend/lang/login/en.php Normal file → Executable file
View File

0
frontend/lang/settings/en.php Normal file → Executable file
View File

0
frontend/lang/users/user/en.php Normal file → Executable file
View File

0
frontend/settings/settings.php Normal file → Executable file
View File