Skip to content

Commit a920d07

Browse files
committed
feat: add rate limiter
1 parent fd2d3ba commit a920d07

22 files changed

Lines changed: 187 additions & 29 deletions

File tree

.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
APP_NAME=Todo Modulith API
22

3-
DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/todo_db
3+
DATABASE_URL=postgresql+asyncpg://postgres:postgres@127.0.0.1:5432/todo_db
4+
5+
REDIS_URL=redis://:password@127.0.0.1:6379/0
46

57
SECRET_KEY=your-super-secret-production-key-here
68

79
ALGORITHM=HS256
810
ACCESS_TOKEN_EXPIRE_MINUTES=30
911
REFRESH_TOKEN_EXPIRE_MINUTES=10080
12+
13+
RATE_LIMIT="100/minute"

alembic/env.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from sqlalchemy.ext.asyncio import async_engine_from_config
88

99
from alembic import context
10-
from src.core.config.setting import settings
1110
from src.core.authorization.infrastructure.models.casbin_rule_model import (
1211
CasbinRuleModel, # noqa: F401
1312
)
@@ -23,13 +22,16 @@
2322
from src.core.authorization.infrastructure.models.user_has_role_model import (
2423
UserHasRoleModel, # noqa: F401
2524
)
25+
from src.core.config.setting import get_settings
2626
from src.modules.todo.infrastructure.models.todo_model import TodoModel # noqa: F401
2727
from src.modules.user.infrastructure.models.refresh_token_model import (
2828
RefreshTokenModel, # noqa: F401
2929
)
3030
from src.modules.user.infrastructure.models.user_model import UserModel # noqa: F401
3131
from src.shared.database.model import Base
3232

33+
settings = get_settings()
34+
3335
print(
3436
"🔍 ALEMBIC DEBUG: Tables found in metadata ->", list(Base.metadata.tables.keys())
3537
)

poetry.lock

Lines changed: 38 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description = ""
55
authors = [
66
{name = "fiqri khoirul m",email = "fiqrikm18@gmail.com"}
77
]
8-
requires-python = ">=3.14"
8+
requires-python = ">=3.14,<4.0"
99
dependencies = [
1010
"fastapi (>=0.137.1,<0.138.0)",
1111
"uvicorn[standard] (>=0.49.0,<0.50.0)",
@@ -17,7 +17,9 @@ dependencies = [
1717
"python-jose[cryptography] (>=3.5.0,<4.0.0)",
1818
"python-multipart (>=0.0.32,<0.0.33)",
1919
"greenlet (>=3.5.1,<4.0.0)",
20-
"casbin (>=1.43.0,<2.0.0)"
20+
"casbin (>=1.43.0,<2.0.0)",
21+
"fastapi-limiter (==0.1.6)",
22+
"redis (>=8.0.0,<9.0.0)"
2123
]
2224

2325
[tool.poetry]

src/core/authorization/dependencies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from src.core.authorization.infrastructure.services.casbin_authorization_service import (
1111
CasbinAuthorizationService,
1212
)
13-
from src.core.database.session import get_db
14-
from src.core.di import get_current_user
13+
from src.core.database.postgres.session import get_db
14+
from src.core.dependency.auth import get_current_user
1515

1616

1717
def get_authorization_service(

src/core/config/setting.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
from functools import lru_cache
2+
13
from pydantic_settings import BaseSettings
24

35

46
class Settings(BaseSettings):
57
APP_NAME: str = "Todo Modulith API"
68
DATABASE_URL: str = "postgresql+asyncpg://user:password@localhost:5432/todo_db"
9+
REDIS_URL: str = "redis://:eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81@127.0.0.1:6379/0"
710
SECRET_KEY: str = "super-secret-key-change-in-production"
811
ALGORITHM: str = "HS256"
912
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
1013
REFRESH_TOKEN_EXPIRE_MINUTES: int = 10080
14+
RATE_LIMIT: str = "100/minute"
1115

1216
class Config:
1317
env_file = ".env"
1418

1519

16-
settings = Settings()
20+
@lru_cache
21+
def get_settings() -> Settings:
22+
return Settings()
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
from fastapi import Depends
44
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
55

6-
from src.core.config.setting import settings
6+
from src.core.config.setting import get_settings
77
from src.shared.database.unit_of_work import SQLAlchemyUnitOfWork
88
from src.shared.unit_of_work import UnitOfWork
99

10+
settings = get_settings()
11+
1012
engine = create_async_engine(settings.DATABASE_URL, echo=False, future=True)
1113
AsyncSessionLocal = async_sessionmaker(
1214
engine, class_=AsyncSession, expire_on_commit=False, autoflush=False

src/core/database/redis/client.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# src/core/rate_limiter.py
2+
import redis.asyncio as redis
3+
4+
from src.core.config.setting import get_settings
5+
6+
redis_client: redis.Redis | None = None
7+
8+
settings = get_settings()
9+
10+
11+
async def get_redis_client() -> redis.Redis:
12+
global redis_client
13+
if redis_client is None:
14+
redis_client = redis.from_url(
15+
settings.REDIS_URL, encoding="utf-8", decode_responses=True
16+
)
17+
return redis_client

src/core/di.py renamed to src/core/dependency/auth.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi.security import OAuth2PasswordBearer
55
from sqlalchemy.ext.asyncio import AsyncSession
66

7-
from src.core.database.session import get_db
7+
from src.core.database.postgres.session import get_db
88
from src.modules.user.infrastructure.repositories.user_repository import (
99
SQLAlchemyUserRepository,
1010
)
@@ -15,10 +15,6 @@
1515
)
1616

1717

18-
def get_db_session(db: AsyncSession = Depends(get_db)) -> AsyncSession:
19-
return db
20-
21-
2218
async def get_current_user(
2319
request: Request,
2420
token: str = Depends(oauth2_scheme),

src/core/dependency/database.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from fastapi import Depends
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
4+
from src.core.database.postgres.session import get_db
5+
6+
7+
def get_db_session(db: AsyncSession = Depends(get_db)) -> AsyncSession:
8+
return db

0 commit comments

Comments
 (0)