Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@
## 2024-06-28 - Optimize array lookups in high-frequency React event handlers
**Learning:** Even if data is pre-processed/memoized (like cached `parsedTimestamps`), performing an O(N) linear search on that array inside a high-frequency event handler (like `<video onTimeUpdate>`, which fires multiple times per second) can cause UI stuttering for large arrays.
**Action:** When searching sorted arrays (like ordered video timestamps) in high-frequency event loops, replace linear iteration loops with an O(log N) binary search to minimize main thread blocking and ensure smooth UI execution.

## 2024-11-09 - Offload bcrypt operations to threadpool in async routes
**Learning:** CPU-bound operations like `bcrypt` password hashing and verification can block the asyncio event loop in FastAPI `async def` endpoints, leading to performance degradation for concurrent requests.
**Action:** Use `starlette.concurrency.run_in_threadpool` to offload blocking CPU-bound tasks like `bcrypt.checkpw` and `bcrypt.hashpw` to a separate thread, freeing the event loop.
12 changes: 7 additions & 5 deletions backend/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from models import User
from database import get_db
import time
from starlette.concurrency import run_in_threadpool

SECRET_KEY = os.getenv("JWT_SECRET_KEY")
if not SECRET_KEY:
Expand All @@ -22,11 +23,12 @@

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")

def verify_password(plain_password, hashed_password):
return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
async def verify_password(plain_password, hashed_password):
return await run_in_threadpool(bcrypt.checkpw, plain_password.encode('utf-8'), hashed_password.encode('utf-8'))

def get_password_hash(password):
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
async def get_password_hash(password):
hashed_bytes = await run_in_threadpool(bcrypt.hashpw, password.encode('utf-8'), bcrypt.gensalt())
return hashed_bytes.decode('utf-8')

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
import uuid
Expand Down Expand Up @@ -65,7 +67,7 @@ async def initialize_admin_from_env(db: AsyncSession):
if admin_password_hash:
hashed = admin_password_hash
elif admin_password:
hashed = get_password_hash(admin_password)
hashed = await get_password_hash(admin_password)
else:
return False

Expand Down
6 changes: 3 additions & 3 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ async def register(user: UserCreate, db: AsyncSession = Depends(get_db)):
user_count = result.scalar()

is_first_user = user_count == 0
hashed_password = get_password_hash(user.password)
hashed_password = await get_password_hash(user.password)
db_user = User(
email=user.email,
hashed_password=hashed_password,
Expand All @@ -267,7 +267,7 @@ async def register(user: UserCreate, db: AsyncSession = Depends(get_db)):
@app.post("/api/auth/login")
async def login(user: UserLogin, db: AsyncSession = Depends(get_db)):
db_user = await get_user_by_email(db, user.email)
if not db_user or not verify_password(user.password, db_user.hashed_password):
if not db_user or not await verify_password(user.password, db_user.hashed_password):
raise HTTPException(status_code=401, detail="Incorrect email or password")

if not db_user.is_approved:
Expand Down Expand Up @@ -320,7 +320,7 @@ async def setup_initial_admin(user: UserCreate, db: AsyncSession = Depends(get_d
if not user.email or not user.password:
raise HTTPException(status_code=400, detail="Email and password required")

hashed_password = get_password_hash(user.password)
hashed_password = await get_password_hash(user.password)
admin_user = User(
email=user.email,
hashed_password=hashed_password,
Expand Down
Loading