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-05-22 - Offload CPU-bound tasks in FastAPI
**Learning:** In FastAPI, CPU-bound tasks like `bcrypt.checkpw` and `bcrypt.hashpw` block the event loop when executed synchronously, significantly reducing API concurrency and throughput for other endpoints.
**Action:** Always offload CPU-bound hashing operations using `starlette.concurrency.run_in_threadpool` and make the wrapper functions `async` to keep the event loop non-blocking.
17 changes: 12 additions & 5 deletions backend/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from fastapi.security import OAuth2PasswordBearer
import bcrypt
import jwt
from starlette.concurrency import run_in_threadpool
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from models import User
Expand All @@ -22,11 +23,17 @@

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):
return await run_in_threadpool(
lambda: bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
)

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
import uuid
Expand Down Expand Up @@ -65,7 +72,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