from contextlib import asynccontextmanager from fastapi import FastAPI, Depends, HTTPException, Response, status from fastapi.middleware.cors import CORSMiddleware from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from database import engine, get_db, Base from models import Counter, User from auth import ( ACCESS_TOKEN_EXPIRE_MINUTES, COOKIE_NAME, UserCreate, UserLogin, UserResponse, get_password_hash, get_user_by_email, authenticate_user, create_access_token, get_current_user, ) @asynccontextmanager async def lifespan(app: FastAPI): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield app = FastAPI(lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], allow_methods=["*"], allow_headers=["*"], allow_credentials=True, ) def set_auth_cookie(response: Response, token: str) -> None: response.set_cookie( key=COOKIE_NAME, value=token, httponly=True, secure=False, # Set to True in production with HTTPS samesite="lax", max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60, ) # Auth endpoints @app.post("/api/auth/register", response_model=UserResponse) async def register( user_data: UserCreate, response: Response, db: AsyncSession = Depends(get_db), ): existing_user = await get_user_by_email(db, user_data.email) if existing_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered", ) user = User( email=user_data.email, hashed_password=get_password_hash(user_data.password), ) db.add(user) await db.commit() await db.refresh(user) access_token = create_access_token(data={"sub": str(user.id)}) set_auth_cookie(response, access_token) return UserResponse(id=user.id, email=user.email) @app.post("/api/auth/login", response_model=UserResponse) async def login( user_data: UserLogin, response: Response, db: AsyncSession = Depends(get_db), ): user = await authenticate_user(db, user_data.email, user_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password", ) access_token = create_access_token(data={"sub": str(user.id)}) set_auth_cookie(response, access_token) return UserResponse(id=user.id, email=user.email) @app.post("/api/auth/logout") async def logout(response: Response): response.delete_cookie(key=COOKIE_NAME) return {"ok": True} @app.get("/api/auth/me", response_model=UserResponse) async def get_me(current_user: User = Depends(get_current_user)): return UserResponse(id=current_user.id, email=current_user.email) # Counter endpoints async def get_or_create_counter(db: AsyncSession) -> Counter: result = await db.execute(select(Counter).where(Counter.id == 1)) counter = result.scalar_one_or_none() if not counter: counter = Counter(id=1, value=0) db.add(counter) await db.commit() await db.refresh(counter) return counter @app.get("/api/counter") async def get_counter( db: AsyncSession = Depends(get_db), _current_user: User = Depends(get_current_user), ): counter = await get_or_create_counter(db) return {"value": counter.value} @app.post("/api/counter/increment") async def increment_counter( db: AsyncSession = Depends(get_db), _current_user: User = Depends(get_current_user), ): counter = await get_or_create_counter(db) counter.value += 1 await db.commit() return {"value": counter.value}