Removed unused access_tokens table from DB

[backend] Updated models to remove unused access_tokens table
[backend] Removed unused access_tokens table from DB using alembic
[backend] Removed unused methods and DB crud logic for access_tokens table
[backend] Removed scheduler to remove expired access tokens from the DB
This commit is contained in:
João Vitória Silva
2024-05-24 14:50:30 +01:00
parent a749cefede
commit af38545e48
5 changed files with 68 additions and 204 deletions

View File

@@ -0,0 +1,59 @@
"""Remove access_tokens table
Revision ID: 0ab200a7f196
Revises: 5fd61bc55e09
Create Date: 2024-05-24 13:39:50.917676
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision: str = '0ab200a7f196'
down_revision: Union[str, None] = '5fd61bc55e09'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# Drop the foreign key constraint first
op.drop_constraint('access_tokens_ibfk_1', 'access_tokens', type_='foreignkey')
# Then drop the index
op.drop_index('ix_access_tokens_user_id', table_name='access_tokens')
op.drop_table('access_tokens')
op.alter_column('users_integrations', 'user_id',
existing_type=mysql.INTEGER(display_width=11),
comment='User ID that the integration belongs',
existing_comment='User ID that the token belongs',
existing_nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('users_integrations', 'user_id',
existing_type=mysql.INTEGER(display_width=11),
comment='User ID that the token belongs',
existing_comment='User ID that the integration belongs',
existing_nullable=False)
op.create_table('access_tokens',
sa.Column('id', mysql.INTEGER(display_width=11), autoincrement=True, nullable=False),
sa.Column('token', mysql.VARCHAR(length=256), nullable=False, comment='User token'),
sa.Column('user_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False, comment='User ID that the token belongs'),
sa.Column('created_at', mysql.DATETIME(), nullable=False, comment='Token creation date (date)'),
sa.Column('expires_at', mysql.DATETIME(), nullable=False, comment='Token expiration date (date)'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='access_tokens_ibfk_1', ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
mysql_collate='utf8mb4_general_ci',
mysql_default_charset='utf8mb4',
mysql_engine='InnoDB'
)
# Recreate the index first
op.create_index('ix_access_tokens_user_id', 'access_tokens', ['user_id'])
# Then recreate the foreign key constraint
op.create_foreign_key('access_tokens_ibfk_1', 'access_tokens', 'users', ['user_id'], ['id'])
# ### end Alembic commands ###

View File

@@ -1,129 +0,0 @@
import logging
import models
from datetime import datetime
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
# Define a loggger created on main.py
logger = logging.getLogger("myLogger")
def get_acess_tokens_by_user_id(user_id: int, db: Session):
try:
access_tokens = (
db.query(models.AccessToken)
.filter(models.AccessToken.user_id == user_id)
.all()
)
if access_tokens is None:
# If the user was not found, return a 404 Not Found error
return None
return access_tokens
except Exception as err:
# Log the exception
logger.error(f"Error in get_acess_tokens_by_user_id: {err}", exc_info=True)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Server Error",
) from err
def create_access_token(token, db: Session):
try:
# Create a new access token in the database
db_access_token = models.AccessToken(
token=token.token,
user_id=token.user_id,
created_at=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"),
expires_at=token.expires_at,
)
# Add the access token to the database and commit the transaction
db.add(db_access_token)
db.commit()
db.refresh(db_access_token)
# return the access token
return db_access_token
except Exception as err:
# Handle database-related exceptions
db.rollback() # Rollback the transaction to maintain database consistency
# Log the exception
logger.error(f"Error in create_access_token: {err}", exc_info=True)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Server Error",
) from err
def delete_access_token(token: str, db: Session):
try:
# Delete the access token from the database
db_access_token = (
db.query(models.AccessToken)
.filter(models.AccessToken.token == token)
.delete()
)
# Commit the transaction to the database
if db_access_token:
db.delete(db_access_token)
db.commit()
logger.info(f"{db_access_token} access tokens deleted from the database")
return db_access_token
else:
# If the access token was not found, return a 404 Not Found error
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Access token not found",
)
except Exception as err:
# Handle database-related exceptions
db.rollback() # Rollback the transaction to maintain database consistency
# Log the exception
logger.error(f"Error in delete_access_token: {err}", exc_info=True)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Server Error",
) from err
def delete_access_tokens(expiration_time: str, db: Session):
try:
# Delete the access tokens from the database
db_access_tokens = (
db.query(models.AccessToken)
.filter(models.AccessToken.created_at < expiration_time)
.delete()
)
# Commit the transaction to the database
if db_access_tokens:
db.commit()
return db_access_tokens
else:
# If no access tokens were found, return 0
return 0
except Exception as err:
# Handle database-related exceptions
db.rollback() # Rollback the transaction to maintain database consistency
# Log the exception
logger.error(f"Error in delete_access_tokens: {err}", exc_info=True)
# Raise an HTTPException with a 500 Internal Server Error status code
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Internal Server Error",
) from err

View File

@@ -45,9 +45,7 @@ def startup_event():
# Create a scheduler to run background jobs
scheduler.start()
# Job to remove expired tokens every 5 minutes
logger.info("Added scheduler job to remove expired tokens every 5 minutes")
scheduler.add_job(remove_expired_tokens_job, "interval", minutes=5)
# Add scheduler jobs to refresh Strava tokens and retrieve last day activities
logger.info("Added scheduler job to refresh Strava user tokens every 60 minutes")
scheduler.add_job(refresh_strava_tokens_job, "interval", minutes=60)
logger.info(
@@ -66,17 +64,6 @@ def shutdown_event():
scheduler.shutdown()
def remove_expired_tokens_job():
# Create a new database session
db = SessionLocal()
try:
# Remove expired tokens from the database
schema_access_tokens.remove_expired_tokens(db=db)
finally:
# Ensure the session is closed after use
db.close()
def refresh_strava_tokens_job():
# Create a new database session
db = SessionLocal()

View File

@@ -103,12 +103,6 @@ class User(Base):
back_populates="user",
cascade="all, delete-orphan",
)
# Define a relationship to AccessToken model
access_tokens = relationship(
"AccessToken",
back_populates="user",
cascade="all, delete-orphan",
)
# Define a relationship to Gear model
gear = relationship(
"Gear",
@@ -147,7 +141,7 @@ class UserIntegrations(Base):
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
index=True,
comment="User ID that the token belongs",
comment="User ID that the integration belongs",
)
strava_state = Column(String(length=45), default=None, nullable=True)
strava_token = Column(String(length=250), default=None, nullable=True)
@@ -164,28 +158,6 @@ class UserIntegrations(Base):
user = relationship("User", back_populates="users_integrations")
# Data model for access_tokens table using SQLAlchemy's ORM
class AccessToken(Base):
__tablename__ = "access_tokens"
id = Column(Integer, primary_key=True)
token = Column(String(length=256), nullable=False, comment="User token")
user_id = Column(
Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
index=True,
comment="User ID that the token belongs",
)
created_at = Column(DateTime, nullable=False, comment="Token creation date (date)")
expires_at = Column(
DateTime, nullable=False, comment="Token expiration date (date)"
)
# Define a relationship to the User model
user = relationship("User", back_populates="access_tokens")
# Data model for gear table using SQLAlchemy's ORM
class Gear(Base):
__tablename__ = "gear"

View File

@@ -7,7 +7,6 @@ from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from crud import crud_access_tokens
from constants import (
JWT_EXPIRATION_IN_MINUTES,
JWT_ALGORITHM,
@@ -69,9 +68,10 @@ def validate_token_expiration(db: Session, token: str = Depends(oauth2_scheme)):
or datetime.utcfromtimestamp(expiration_timestamp) < datetime.utcnow()
):
logger.warning(
"Token expired | Will force remove_expired_tokens to run | Returning 401 response"
"Token expired | Returning 401 response"
)
remove_expired_tokens(db)
# Raise an HTTPException with a 401 Unauthorized status code
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token no longer valid",
@@ -80,10 +80,9 @@ def validate_token_expiration(db: Session, token: str = Depends(oauth2_scheme)):
except Exception:
# Log the error and raise the exception
logger.info(
"Token expired during validation | Will force remove_expired_tokens to run | Returning 401 response"
"Token expired during validation | Returning 401 response"
)
# Remove expired tokens from the database
remove_expired_tokens(db)
# Raise an HTTPException with a 401 Unauthorized status code
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -159,29 +158,5 @@ def create_access_token(
# Encode the data and return the token
encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
# Save the token in the database
db_access_token = crud_access_tokens.create_access_token(
CreateToken(
token=encoded_jwt,
user_id=data.get("id"),
expires_at=expire.strftime("%Y-%m-%dT%H:%M:%S"),
),
db,
)
if db_access_token:
# Return the token
return encoded_jwt
else:
# If the token could not be saved in the database return None
return None
def remove_expired_tokens(db: Session):
# Calculate the expiration time
expiration_time = datetime.utcnow() - timedelta(minutes=JWT_EXPIRATION_IN_MINUTES)
# Delete the expired tokens from the database
rows_deleted = crud_access_tokens.delete_access_tokens(expiration_time, db)
# Log the number of tokens deleted
logger.info(f"{rows_deleted} access tokens deleted from the database")
# Return the token
return encoded_jwt