diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/main.py | 52 | ||||
| -rw-r--r-- | app/routes/auth.py | 10 | ||||
| -rw-r--r-- | app/routes/me.py | 2 | ||||
| -rw-r--r-- | app/routes/register.py | 49 | ||||
| -rw-r--r-- | app/utils/db.py | 38 | ||||
| -rw-r--r-- | app/utils/env.py | 0 | ||||
| -rw-r--r-- | app/utils/hash_cfg.py | 13 |
7 files changed, 75 insertions, 89 deletions
diff --git a/app/main.py b/app/main.py index 59ed4b7..f1c556a 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,5 @@ import time +from contextlib import asynccontextmanager from fastapi import FastAPI from sqlalchemy import text @@ -12,58 +13,45 @@ from app.utils.logger_cfg import logger app_start_time = time.perf_counter() logger.debug("App start timestamp recorded") -app = FastAPI() -setup_cors(app) -logger.info("FastAPI application instance created") -logger.debug("FastAPI instance created successfully") - -app.include_router(api_router, prefix="/api") -logger.info("API router registered with prefix /api") -logger.debug("Router object: %s", api_router) - -@app.on_event("startup") -async def startup(): +@asynccontextmanager +async def lifespan(app: FastAPI): logger.info("Application startup initiated") - logger.debug("Starting database connection attempt") try: async with engine.begin() as conn: logger.debug("Executing test query: SELECT 1") - result = await conn.execute(text("SELECT 1")) - db_status = result.scalar() - logger.info(f"Database connection successful: {db_status}") - logger.debug("Database test query executed successfully") + db_status = await conn.scalar(text("SELECT 1")) + logger.info("Database connection successful: {}", db_status) logger.debug("Starting database table initialization") await init_db() logger.info("Database initialization completed") - logger.debug("Database tables initialized successfully") - except Exception as e: - logger.exception(f"Database startup failed: {e}") + except Exception: + logger.exception("Database startup failed") + raise elapsed = time.perf_counter() - app_start_time + app.state.startup_elapsed = elapsed + app.state.startup_completed = True + logger.success( - f"Application startup completed in {elapsed*1000:.2f} ms ({elapsed:.6f} s)" + "Application startup completed in {:.2f} ms ({:.6f} s)", + elapsed * 1000, + elapsed, ) - logger.debug("Startup complete timestamp recorded") + yield -@app.on_event("shutdown") -async def shutdown(): logger.info("Application shutdown initiated") - logger.debug("Starting engine disposal") try: await engine.dispose() logger.info("Database engine disposed successfully") - logger.debug("Engine disposal finished") - except Exception as e: - logger.exception(f"Error during shutdown: {e}") + except Exception: + logger.exception("Error during shutdown") -@app.get("/") -async def read_root(): - logger.info("Root endpoint accessed") - logger.debug("Processing root endpoint request") - return {"message": "Hello new asyncpg ci!"} +app = FastAPI(lifespan=lifespan) +setup_cors(app) +app.include_router(api_router, prefix="/api") diff --git a/app/routes/auth.py b/app/routes/auth.py index a2de6db..6e0d410 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -19,9 +19,15 @@ async def login( ): user = await User.get_user_by_email(form_data.username, session=session) if not user: - user = await User.get_user_by_username(form_data.username, session=session) + user = await User.get_user_by_username( + form_data.username, session=session + ) - if not user or not verify_password(form_data.password, user.password): + if not user or not user.password: + logger.warning("Login failed | username/email={}", form_data.username) + raise HTTPException(status_code=401, detail="Invalid credentials") + + if not verify_password(form_data.password, user.password): logger.warning("Login failed | username/email={}", form_data.username) raise HTTPException(status_code=401, detail="Invalid credentials") diff --git a/app/routes/me.py b/app/routes/me.py index 65368e3..6d28a80 100644 --- a/app/routes/me.py +++ b/app/routes/me.py @@ -29,7 +29,6 @@ async def get_current_user_from_cookie( if not user or user.token_version != payload.get("token_version"): return {"authenticated": False, "user": None} - # Return authenticated user return { "authenticated": True, "user": { @@ -49,5 +48,4 @@ async def get_current_user_from_cookie( async def read_current_user( user_info: dict = Depends(get_current_user_from_cookie), ): - # Directly return the dict to the frontend return user_info diff --git a/app/routes/register.py b/app/routes/register.py index 779cf73..f0b36ed 100644 --- a/app/routes/register.py +++ b/app/routes/register.py @@ -4,9 +4,9 @@ from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Response from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select -from sqlalchemy.orm import selectinload from app.auth.jwt import create_access_token, create_refresh_token +from app.models.integrations import UserIntegration from app.models.profile import Profile from app.models.user import User from app.schemas.user import UserCreate, UserRead @@ -27,7 +27,9 @@ async def register_user( email: Optional[str] = user.email.strip() if user.email else None logger.debug("Normalized email value: {}", email) - logger.info("Registration attempt | username={} email={}", user.username, email) + logger.info( + "Registration attempt | username={} email={}", user.username, email + ) if not ( re.search(r"[A-Za-z]", user.password) @@ -46,10 +48,13 @@ async def register_user( }, ) - result = await session.execute(select(User).where(User.username == user.username)) + result = await session.execute( + select(User).where(User.username == user.username) + ) if result.scalars().first(): logger.warning( - "Registration failed | username already exists | username={}", user.username + "Registration failed | username already exists | username={}", + user.username, ) raise HTTPException( status_code=400, @@ -69,34 +74,17 @@ async def register_user( hashed_password = hash_password(user.password) - new_user = User(username=user.username, email=email, password=hashed_password) - session.add(new_user) - await session.flush() - - new_profile = Profile(user_id=new_user.id) - session.add(new_profile) - - from app.models.integrations import UserIntegration - - new_integrations = UserIntegration(user_id=new_user.id) - session.add(new_integrations) - - await session.commit() - - result = await session.execute( - select(User) - .options( - selectinload(User.profile), - selectinload(User.integrations), - ) - .where(User.id == new_user.id) + new_user = User( + username=user.username, + email=email, + password=hashed_password, + profile=Profile(), + integrations=UserIntegration(), ) - new_user = result.scalars().first() - result = await session.execute( - select(User).options(selectinload(User.profile)).where(User.id == new_user.id) - ) - new_user = result.scalars().first() + session.add(new_user) + await session.commit() + await session.refresh(new_user, ["profile", "integrations"]) logger.success( "User successfully registered | id={} username={} email={}", @@ -104,7 +92,6 @@ async def register_user( new_user.username, new_user.email, ) - access_token = create_access_token( {"sub": str(new_user.id), "token_version": new_user.token_version} ) diff --git a/app/utils/db.py b/app/utils/db.py index 4daca74..5531998 100644 --- a/app/utils/db.py +++ b/app/utils/db.py @@ -1,34 +1,38 @@ -import asyncio import os +from typing import AsyncGenerator from dotenv import load_dotenv -from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine -from sqlalchemy.orm import DeclarativeBase, sessionmaker +from sqlalchemy.ext.asyncio import ( + AsyncEngine, + AsyncSession, + async_sessionmaker, + create_async_engine, +) +from sqlalchemy.orm import DeclarativeBase load_dotenv() + DATABASE_URL = os.getenv("DATABASE_URL") if not DATABASE_URL: raise ValueError("DATABASE_URL not found in .env") -engine = create_async_engine(DATABASE_URL, echo=True, future=True) -async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False) + +engine: AsyncEngine = create_async_engine( + DATABASE_URL, + echo=True, + pool_pre_ping=True, +) + +async_session = async_sessionmaker( + bind=engine, + expire_on_commit=False, +) class Base(DeclarativeBase): pass -async def get_async_session() -> AsyncSession: +async def get_async_session() -> AsyncGenerator[AsyncSession, None]: async with async_session() as session: yield session - - -async def init_db(): - - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) - - -if __name__ == "__main__": - asyncio.run(init_db()) - print("All tables created!") diff --git a/app/utils/env.py b/app/utils/env.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/utils/env.py diff --git a/app/utils/hash_cfg.py b/app/utils/hash_cfg.py index 9937c99..48242d2 100644 --- a/app/utils/hash_cfg.py +++ b/app/utils/hash_cfg.py @@ -1,7 +1,7 @@ -from passlib.hash import argon2 +from argon2 import PasswordHasher +from argon2.exceptions import VerifyMismatchError -argon2_hasher = argon2.using( - type="ID", +ph = PasswordHasher( time_cost=3, memory_cost=65536, parallelism=4, @@ -9,8 +9,11 @@ argon2_hasher = argon2.using( def hash_password(password: str) -> str: - return argon2_hasher.hash(password) + return ph.hash(password) def verify_password(password: str, hashed: str) -> bool: - return argon2_hasher.verify(password, hashed) + try: + return ph.verify(hashed, password) + except VerifyMismatchError: + return False |
