Fix CI failures: add backend/conftest.py for module resolution and run black formatting
- Add backend/conftest.py that inserts the backend directory into sys.path, fixing ModuleNotFoundError when pytest runs from the backend/ directory (as CI does with `cd backend && pytest tests/`) - Run black formatter on all 28 backend files that needed reformatting - All 53 tests pass with both `pytest tests/` and `python -m pytest tests/` Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/beb47db2-da25-416a-8fb0-c1452a3b22a7
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Authentication endpoints (login, register, OAuth).
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -11,72 +12,65 @@ import logging
|
||||
from app.core.database import get_db
|
||||
from app.core.security import verify_password, get_password_hash
|
||||
from app.models.database_models import User, SubscriptionTier
|
||||
from app.models.schemas import (
|
||||
Token, UserCreate, UserResponse, GoogleAuthRequest
|
||||
)
|
||||
from app.models.schemas import Token, UserCreate, UserResponse, GoogleAuthRequest
|
||||
from app.services.auth_service import oauth_service
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
user_in: UserCreate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
@router.post(
|
||||
"/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def register(user_in: UserCreate, db: AsyncSession = Depends(get_db)):
|
||||
"""Register a new user with email and password"""
|
||||
|
||||
|
||||
# Check if user exists
|
||||
result = await db.execute(
|
||||
select(User).where(User.email == user_in.email)
|
||||
)
|
||||
result = await db.execute(select(User).where(User.email == user_in.email))
|
||||
existing_user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email already registered"
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered"
|
||||
)
|
||||
|
||||
|
||||
# Create new user
|
||||
user = User(
|
||||
email=user_in.email,
|
||||
full_name=user_in.full_name,
|
||||
hashed_password=get_password_hash(user_in.password) if user_in.password else None,
|
||||
hashed_password=(
|
||||
get_password_hash(user_in.password) if user_in.password else None
|
||||
),
|
||||
subscription_tier=SubscriptionTier.FREE,
|
||||
is_active=True
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
|
||||
db.add(user)
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
|
||||
logger.info(f"New user registered: {user.email}")
|
||||
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Login with email and password"""
|
||||
|
||||
|
||||
# Get user
|
||||
result = await db.execute(
|
||||
select(User).where(User.email == form_data.username)
|
||||
)
|
||||
result = await db.execute(select(User).where(User.email == form_data.username))
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not user or not user.hashed_password:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
# Verify password
|
||||
if not verify_password(form_data.password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
@@ -84,90 +78,85 @@ async def login(
|
||||
detail="Incorrect email or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
# Check if user is active
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User account is inactive"
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail="User account is inactive"
|
||||
)
|
||||
|
||||
|
||||
# Update last login
|
||||
user.last_login_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
|
||||
|
||||
# Create tokens
|
||||
tokens = oauth_service.create_tokens_for_user(user)
|
||||
|
||||
|
||||
logger.info(f"User logged in: {user.email}")
|
||||
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
@router.post("/google", response_model=Token)
|
||||
async def google_oauth(
|
||||
auth_request: GoogleAuthRequest,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
auth_request: GoogleAuthRequest, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Authenticate with Google OAuth2.
|
||||
Exchange authorization code for access token and user info.
|
||||
"""
|
||||
|
||||
|
||||
# Get user info from Google
|
||||
user_info = await oauth_service.get_google_user_info(
|
||||
code=auth_request.code,
|
||||
redirect_uri=auth_request.redirect_uri
|
||||
code=auth_request.code, redirect_uri=auth_request.redirect_uri
|
||||
)
|
||||
|
||||
if not user_info.get('verified_email'):
|
||||
|
||||
if not user_info.get("verified_email"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email not verified with Google"
|
||||
detail="Email not verified with Google",
|
||||
)
|
||||
|
||||
email = user_info['email']
|
||||
google_id = user_info['google_id']
|
||||
|
||||
|
||||
email = user_info["email"]
|
||||
google_id = user_info["google_id"]
|
||||
|
||||
# Check if user exists
|
||||
result = await db.execute(
|
||||
select(User).where(
|
||||
(User.email == email) | (User.google_id == google_id)
|
||||
)
|
||||
select(User).where((User.email == email) | (User.google_id == google_id))
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if user:
|
||||
# Update Google ID if not set
|
||||
if not user.google_id:
|
||||
user.google_id = google_id
|
||||
user.oauth_provider = "google"
|
||||
|
||||
|
||||
# Update last login
|
||||
user.last_login_at = datetime.utcnow()
|
||||
|
||||
|
||||
logger.info(f"Existing user logged in with Google: {user.email}")
|
||||
else:
|
||||
# Create new user
|
||||
user = User(
|
||||
email=email,
|
||||
full_name=user_info.get('full_name'),
|
||||
full_name=user_info.get("full_name"),
|
||||
google_id=google_id,
|
||||
oauth_provider="google",
|
||||
subscription_tier=SubscriptionTier.FREE,
|
||||
is_active=True,
|
||||
last_login_at=datetime.utcnow()
|
||||
last_login_at=datetime.utcnow(),
|
||||
)
|
||||
db.add(user)
|
||||
|
||||
|
||||
logger.info(f"New user registered with Google: {user.email}")
|
||||
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
|
||||
# Create tokens
|
||||
tokens = oauth_service.create_tokens_for_user(user)
|
||||
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
@@ -175,7 +164,7 @@ async def google_oauth(
|
||||
async def get_google_authorize_url(redirect_uri: str):
|
||||
"""Get Google OAuth2 authorization URL"""
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
auth_url = (
|
||||
f"https://accounts.google.com/o/oauth2/v2/auth?"
|
||||
f"client_id={settings.GOOGLE_CLIENT_ID}&"
|
||||
@@ -184,5 +173,5 @@ async def get_google_authorize_url(redirect_uri: str):
|
||||
f"redirect_uri={redirect_uri}&"
|
||||
f"access_type=offline"
|
||||
)
|
||||
|
||||
|
||||
return {"authorization_url": auth_url}
|
||||
|
||||
Reference in New Issue
Block a user