Skip to content

Commit

Permalink
Merge pull request #18 from wednesday-solutions/feat/restructuring-part2
Browse files Browse the repository at this point in the history
feat - restructured the app structure to be more scalable
  • Loading branch information
himanshu-wedensday authored Mar 11, 2024
2 parents 7505606 + e0b3118 commit a034614
Show file tree
Hide file tree
Showing 20 changed files with 97 additions and 101 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ DB_PORT=
DB_NAME=
OPENAI_API_KEY_GPT4=
OPENAI_API_KEY_WEDNESDAY=
YOUR_SECRET_KEY=
SECRET_KEY=
REDIS_URL=
SLACK_WEBHOOK_URL=
15 changes: 7 additions & 8 deletions alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
from logging.config import fileConfig

from app.config.base import db_settings
from dotenv import load_dotenv
from sqlalchemy import engine_from_config
from sqlalchemy import pool
Expand All @@ -12,6 +13,12 @@

print("==" * 50, "\n\n\n", "OS ENVIRONMENT", os.environ, "\n\n\n", "==" * 50)

HOST = db_settings.DB_HOSTNAME
PORT = db_settings.DB_PORT
DBNAME = db_settings.DB_NAME
USERNAME = db_settings.DB_USERNAME
PASSWORD = db_settings.DB_PASSWORD

if "PYTHON_FASTAPI_TEMPLATE_CLUSTER_SECRET" in os.environ:
print("Connecting to database on RDS..\n")
dbSecretJSON = os.environ["PYTHON_FASTAPI_TEMPLATE_CLUSTER_SECRET"]
Expand All @@ -23,14 +30,6 @@
USERNAME = dbSecretParsed["username"]
PASSWORD = dbSecretParsed["password"]

else:
print("Connecting local database..\n")
HOST = os.environ["DB_HOSTNAME"]
PORT = os.environ["DB_PORT"]
DBNAME = os.environ["DB_NAME"]
USERNAME = os.environ["DB_USERNAME"]
PASSWORD = os.environ["DB_PASSWORD"]

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
Expand Down
4 changes: 0 additions & 4 deletions app/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
from .config import get_secret_key
from .db import create_local_session, engine
from .redis_config import get_redis_pool


__all__ = [
"get_secret_key",
"create_local_session",
"engine",
"get_redis_pool",
]
25 changes: 25 additions & 0 deletions app/config/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from pydantic import BaseSettings


class DBSettings(BaseSettings):
DB_HOSTNAME: str
DB_PORT: str
DB_NAME: str
DB_USERNAME: str
DB_PASSWORD: str

class Config:
env_file = ".env"


class Settings(BaseSettings):
SECRET_KEY: str
REDIS_URL: str
SLACK_WEBHOOK_URL: str

class Config:
env_file = ".env"


db_settings = DBSettings()
settings = Settings()
12 changes: 0 additions & 12 deletions app/config/config.py

This file was deleted.

8 changes: 3 additions & 5 deletions app/config/redis_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import aioredis
import os

from .base import settings
from redis import asyncio

async def get_redis_pool():
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
return aioredis.from_url(redis_url)
return asyncio.from_url(settings.REDIS_URL, encoding="utf-8", decode_responses=True)
6 changes: 3 additions & 3 deletions app/constants/jwt_utils.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from fastapi import HTTPException
import jwt
import datetime
from app.config.config import get_secret_key
from app.config.base import settings


def create_access_token(data: dict):
expiration = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
return jwt.encode({"exp": expiration, **data}, get_secret_key(), algorithm="HS256")
return jwt.encode({"exp": expiration, **data}, settings.SECRET_KEY, algorithm="HS256")


def decode_access_token(token: str):
try:
payload = jwt.decode(token, get_secret_key(), algorithms=["HS256"])
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Signature has expired")
Expand Down
3 changes: 0 additions & 3 deletions app/daos/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
from .users import get_user, list_users, create_user, login

__all__ = ["get_user", "list_users", "create_user", "login"]
5 changes: 3 additions & 2 deletions app/daos/users.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import json
from aioredis import Redis
from redis import Redis
from fastapi import HTTPException
from sqlalchemy.orm import Session
from app.constants import jwt_utils
from app.constants.messages.users import user_messages as messages
from app.models import User
from app.schemas import CreateUser, UserOutResponse, Login
from app.schemas.users.users_request import CreateUser, Login
from app.schemas.users.users_response import UserOutResponse
from werkzeug.security import check_password_hash
from fastapi_pagination.ext.sqlalchemy import paginate
from sqlalchemy import select
Expand Down
14 changes: 6 additions & 8 deletions app/middlewares/rate_limiter_middleware.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import os
import datetime

from app.config.redis_config import get_redis_pool
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from fastapi.responses import JSONResponse
import aioredis
import datetime
import os

MAX_REQUESTS = 10
TIME_WINDOW = 60
Expand All @@ -15,10 +16,7 @@ async def dispatch(self, request: Request, call_next):
client_ip = request.client.host
now = datetime.datetime.now()

# Updated for aioredis v2.x
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
redis = aioredis.from_url(redis_url, encoding="utf-8", decode_responses=True)

redis = await get_redis_pool()
try:
request_count = await redis.get(client_ip)
request_count = int(request_count) if request_count else 0
Expand Down
4 changes: 2 additions & 2 deletions app/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from werkzeug.security import generate_password_hash
from sqlalchemy import event

from app.config.db import engine
from app.sessions.db import engine

Base = declarative_base()

Expand All @@ -30,7 +30,7 @@ class User(Base):
def hash_password_before_insert(mapper, connection, target):
print("IN EVENT LISTENER")
if target.password:
target.password = generate_password_hash(target.password)
target.password = generate_password_hash(target.password, method="pbkdf2")


Base.metadata.create_all(bind=engine)
7 changes: 4 additions & 3 deletions app/routes/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
from fastapi import Depends
from sqlalchemy.orm import Session
from fastapi_pagination import Page
from app.config.db import create_local_session
from app.daos import (
from app.sessions.db import create_local_session
from app.daos.users import (
create_user as create_user_dao,
get_user as get_user_dao,
list_users as list_users_dao,
login as signin,
)
from app.models import User
from app.schemas import CreateUser, UserOutResponse, Login
from app.schemas.users.users_request import CreateUser, Login
from app.schemas.users.users_response import UserOutResponse
from app.utils.redis_utils import get_redis
from app.utils.user_utils import get_current_user
from typing import Annotated
Expand Down
3 changes: 0 additions & 3 deletions app/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
from .users import CreateUser, Login, UserOutResponse

__all__ = ["CreateUser", "Login", "UserOutResponse"]
Empty file added app/schemas/users/__init__.py
Empty file.
37 changes: 15 additions & 22 deletions app/schemas/users.py → app/schemas/users/users_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ def validate_password_strength(cls, password):
)
return password

class Config:
schema_extra = {
"example": {
"name": "Anas Nadeem",
"email": "anas@gmail.com",
"mobile": "1234567890",
"password": "Test@123"
}
}

class Login(BaseModel):
email: str
Expand All @@ -59,26 +68,10 @@ def validate_email(cls, email):
raise ValueError("Invalid email address format")
return email

@validator("password")
def validate_password_strength(cls, password):
if (
len(password) < 8
or not any(char.isupper() for char in password)
or not any(char.islower() for char in password)
or not any(char.isdigit() for char in password)
or not re.search(
r'[!@#$%^&*(),.?":{}|<>]', password
) # The regular expression [!@#$%^&*(),.?":{}|<>] matches any of these special characters.
):
raise HTTPException(status_code=400, detail=f"{str(messages['INVALID_CREDENTIALS'])}")
return password


class UserOutResponse(BaseModel):
id: int = Field(alias="id")
name: str
email: str
mobile: str

class Config:
orm_mode = True
schema_extra = {
"example": {
"email": "anas@gmail.com",
"password": "Test@123"
}
}
14 changes: 14 additions & 0 deletions app/schemas/users/users_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import re

from pydantic import BaseModel
from pydantic import Field


class UserOutResponse(BaseModel):
id: int = Field(alias="id")
name: str
email: str
mobile: str

class Config:
orm_mode = True
Empty file added app/sessions/__init__.py
Empty file.
17 changes: 6 additions & 11 deletions app/config/db.py → app/sessions/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import sys

from app.config.base import db_settings
from dotenv import load_dotenv
from sqlalchemy import create_engine
from sqlalchemy import MetaData
Expand All @@ -13,12 +14,11 @@
load_dotenv()

# Set the default values for connecting locally
HOST = os.environ.get("DB_HOSTNAME", "localhost")
PORT = os.environ.get("DB_PORT", "3306")
DBNAME = os.environ.get("DB_NAME", "mydbname")
USERNAME = os.environ.get("DB_USERNAME", "user")
PASSWORD = os.environ.get("DB_PASSWORD", "password")

HOST = db_settings.DB_HOSTNAME
PORT = db_settings.DB_PORT
DBNAME = db_settings.DB_NAME
USERNAME = db_settings.DB_USERNAME
PASSWORD = db_settings.DB_PASSWORD

if "pytest" in sys.modules:
SQLALCHEMY_DATABASE_URL = "sqlite://"
Expand All @@ -44,11 +44,6 @@

else:
print("Connecting local database..\n")
HOST = os.environ["DB_HOSTNAME"]
PORT = os.environ["DB_PORT"]
DBNAME = os.environ["DB_NAME"]
USERNAME = os.environ["DB_USERNAME"]
PASSWORD = os.environ["DB_PASSWORD"]
engine = create_engine(f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}/{DBNAME}")

meta = MetaData()
Expand Down
Loading

0 comments on commit a034614

Please sign in to comment.