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,17 +1,34 @@
|
||||
"""
|
||||
API v1 router aggregation.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1.endpoints import auth, users, mail_accounts, notifications, subscriptions, admin, providers
|
||||
from app.api.v1.endpoints import (
|
||||
auth,
|
||||
users,
|
||||
mail_accounts,
|
||||
notifications,
|
||||
subscriptions,
|
||||
admin,
|
||||
providers,
|
||||
)
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# Include all endpoint routers
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
||||
api_router.include_router(users.router, prefix="/users", tags=["Users"])
|
||||
api_router.include_router(mail_accounts.router, prefix="/mail-accounts", tags=["Mail Accounts"])
|
||||
api_router.include_router(providers.router, prefix="/providers", tags=["Providers & Gmail"])
|
||||
api_router.include_router(notifications.router, prefix="/notifications", tags=["Notifications"])
|
||||
api_router.include_router(subscriptions.router, prefix="/subscriptions", tags=["Subscriptions"])
|
||||
api_router.include_router(
|
||||
mail_accounts.router, prefix="/mail-accounts", tags=["Mail Accounts"]
|
||||
)
|
||||
api_router.include_router(
|
||||
providers.router, prefix="/providers", tags=["Providers & Gmail"]
|
||||
)
|
||||
api_router.include_router(
|
||||
notifications.router, prefix="/notifications", tags=["Notifications"]
|
||||
)
|
||||
api_router.include_router(
|
||||
subscriptions.router, prefix="/subscriptions", tags=["Subscriptions"]
|
||||
)
|
||||
api_router.include_router(admin.router, prefix="/admin", tags=["Admin"])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Admin endpoints"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
@@ -13,24 +14,24 @@ router = APIRouter()
|
||||
@router.get("/stats")
|
||||
async def get_admin_stats(
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Get overall system statistics (admin only)"""
|
||||
|
||||
|
||||
# Count users
|
||||
user_count = await db.execute(select(func.count(User.id)))
|
||||
total_users = user_count.scalar()
|
||||
|
||||
|
||||
# Count accounts
|
||||
account_count = await db.execute(select(func.count(MailAccount.id)))
|
||||
total_accounts = account_count.scalar()
|
||||
|
||||
|
||||
# Count processing runs
|
||||
run_count = await db.execute(select(func.count(ProcessingRun.id)))
|
||||
total_runs = run_count.scalar()
|
||||
|
||||
|
||||
return {
|
||||
"total_users": total_users,
|
||||
"total_mail_accounts": total_accounts,
|
||||
"total_processing_runs": total_runs
|
||||
"total_processing_runs": total_runs,
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Mail account management endpoints"""
|
||||
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -9,9 +10,13 @@ from app.core.deps import get_current_active_user
|
||||
from app.core.security import encrypt_credential, decrypt_credential
|
||||
from app.models.database_models import User, MailAccount
|
||||
from app.models.schemas import (
|
||||
MailAccountCreate, MailAccountResponse, MailAccountUpdate,
|
||||
MailAccountTestRequest, MailAccountTestResponse,
|
||||
MailAccountAutoDetectRequest, MailAccountAutoDetectResponse
|
||||
MailAccountCreate,
|
||||
MailAccountResponse,
|
||||
MailAccountUpdate,
|
||||
MailAccountTestRequest,
|
||||
MailAccountTestResponse,
|
||||
MailAccountAutoDetectRequest,
|
||||
MailAccountAutoDetectResponse,
|
||||
)
|
||||
from app.services.mail_processor import MailProcessor, MailServerAutoDetect
|
||||
from app.core.config import settings
|
||||
@@ -19,38 +24,40 @@ from app.core.config import settings
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("", response_model=MailAccountResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"", response_model=MailAccountResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_mail_account(
|
||||
account_in: MailAccountCreate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Create a new mail account"""
|
||||
|
||||
|
||||
# Check subscription limits
|
||||
result = await db.execute(
|
||||
select(MailAccount).where(MailAccount.user_id == current_user.id)
|
||||
)
|
||||
existing_accounts = result.scalars().all()
|
||||
|
||||
|
||||
tier_limits = {
|
||||
"free": settings.TIER_FREE_MAX_ACCOUNTS,
|
||||
"basic": settings.TIER_BASIC_MAX_ACCOUNTS,
|
||||
"pro": settings.TIER_PRO_MAX_ACCOUNTS,
|
||||
"enterprise": settings.TIER_ENTERPRISE_MAX_ACCOUNTS,
|
||||
}
|
||||
|
||||
|
||||
max_accounts = tier_limits.get(current_user.subscription_tier.value, 1)
|
||||
|
||||
|
||||
if len(existing_accounts) >= max_accounts:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_402_PAYMENT_REQUIRED,
|
||||
detail=f"Account limit reached. Upgrade your subscription to add more accounts."
|
||||
detail=f"Account limit reached. Upgrade your subscription to add more accounts.",
|
||||
)
|
||||
|
||||
|
||||
# Encrypt password
|
||||
encrypted_password = encrypt_credential(account_in.password)
|
||||
|
||||
|
||||
# Create account
|
||||
account = MailAccount(
|
||||
user_id=current_user.id,
|
||||
@@ -68,20 +75,20 @@ async def create_mail_account(
|
||||
is_enabled=account_in.is_enabled,
|
||||
check_interval_minutes=account_in.check_interval_minutes,
|
||||
max_emails_per_check=account_in.max_emails_per_check,
|
||||
delete_after_forward=account_in.delete_after_forward
|
||||
delete_after_forward=account_in.delete_after_forward,
|
||||
)
|
||||
|
||||
|
||||
db.add(account)
|
||||
await db.commit()
|
||||
await db.refresh(account)
|
||||
|
||||
|
||||
return account
|
||||
|
||||
|
||||
@router.get("", response_model=List[MailAccountResponse])
|
||||
async def list_mail_accounts(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List all mail accounts for current user"""
|
||||
result = await db.execute(
|
||||
@@ -97,23 +104,21 @@ async def list_mail_accounts(
|
||||
async def get_mail_account(
|
||||
account_id: int,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Get a specific mail account"""
|
||||
result = await db.execute(
|
||||
select(MailAccount).where(
|
||||
MailAccount.id == account_id,
|
||||
MailAccount.user_id == current_user.id
|
||||
MailAccount.id == account_id, MailAccount.user_id == current_user.id
|
||||
)
|
||||
)
|
||||
account = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not account:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Mail account not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Mail account not found"
|
||||
)
|
||||
|
||||
|
||||
return account
|
||||
|
||||
|
||||
@@ -122,35 +127,35 @@ async def update_mail_account(
|
||||
account_id: int,
|
||||
account_update: MailAccountUpdate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Update a mail account"""
|
||||
result = await db.execute(
|
||||
select(MailAccount).where(
|
||||
MailAccount.id == account_id,
|
||||
MailAccount.user_id == current_user.id
|
||||
MailAccount.id == account_id, MailAccount.user_id == current_user.id
|
||||
)
|
||||
)
|
||||
account = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not account:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Mail account not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Mail account not found"
|
||||
)
|
||||
|
||||
|
||||
# Update fields
|
||||
update_data = account_update.dict(exclude_unset=True)
|
||||
|
||||
|
||||
if "password" in update_data:
|
||||
update_data["encrypted_password"] = encrypt_credential(update_data.pop("password"))
|
||||
|
||||
update_data["encrypted_password"] = encrypt_credential(
|
||||
update_data.pop("password")
|
||||
)
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(account, field, value)
|
||||
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(account)
|
||||
|
||||
|
||||
return account
|
||||
|
||||
|
||||
@@ -158,23 +163,21 @@ async def update_mail_account(
|
||||
async def delete_mail_account(
|
||||
account_id: int,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Delete a mail account"""
|
||||
result = await db.execute(
|
||||
select(MailAccount).where(
|
||||
MailAccount.id == account_id,
|
||||
MailAccount.user_id == current_user.id
|
||||
MailAccount.id == account_id, MailAccount.user_id == current_user.id
|
||||
)
|
||||
)
|
||||
account = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not account:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Mail account not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Mail account not found"
|
||||
)
|
||||
|
||||
|
||||
await db.delete(account)
|
||||
await db.commit()
|
||||
|
||||
@@ -185,7 +188,7 @@ async def test_mail_connection(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""Test connection to mail server"""
|
||||
|
||||
|
||||
# Create temporary account for testing
|
||||
temp_account = MailAccount(
|
||||
user_id=current_user.id,
|
||||
@@ -198,16 +201,13 @@ async def test_mail_connection(
|
||||
use_tls=test_request.use_tls,
|
||||
username=test_request.username,
|
||||
encrypted_password="", # Not used for test
|
||||
forward_to="test@test.com"
|
||||
forward_to="test@test.com",
|
||||
)
|
||||
|
||||
|
||||
processor = MailProcessor(temp_account, test_request.password)
|
||||
success, message = await processor.test_connection()
|
||||
|
||||
return MailAccountTestResponse(
|
||||
success=success,
|
||||
message=message
|
||||
)
|
||||
|
||||
return MailAccountTestResponse(success=success, message=message)
|
||||
|
||||
|
||||
@router.post("/auto-detect", response_model=MailAccountAutoDetectResponse)
|
||||
@@ -216,10 +216,9 @@ async def auto_detect_mail_settings(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""Auto-detect mail server settings for an email address"""
|
||||
|
||||
|
||||
suggestions = MailServerAutoDetect.detect(detect_request.email_address)
|
||||
|
||||
|
||||
return MailAccountAutoDetectResponse(
|
||||
success=len(suggestions) > 0,
|
||||
suggestions=suggestions
|
||||
success=len(suggestions) > 0, suggestions=suggestions
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Notification configuration endpoints"""
|
||||
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -8,23 +9,24 @@ from app.core.database import get_db
|
||||
from app.core.deps import get_current_active_user
|
||||
from app.models.database_models import User, NotificationConfig
|
||||
from app.models.schemas import (
|
||||
NotificationConfigCreate, NotificationConfigResponse, NotificationConfigUpdate
|
||||
NotificationConfigCreate,
|
||||
NotificationConfigResponse,
|
||||
NotificationConfigUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("", response_model=NotificationConfigResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"", response_model=NotificationConfigResponse, status_code=status.HTTP_201_CREATED
|
||||
)
|
||||
async def create_notification_config(
|
||||
config_in: NotificationConfigCreate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Create notification configuration"""
|
||||
config = NotificationConfig(
|
||||
user_id=current_user.id,
|
||||
**config_in.dict()
|
||||
)
|
||||
config = NotificationConfig(user_id=current_user.id, **config_in.dict())
|
||||
db.add(config)
|
||||
await db.commit()
|
||||
await db.refresh(config)
|
||||
@@ -34,7 +36,7 @@ async def create_notification_config(
|
||||
@router.get("", response_model=List[NotificationConfigResponse])
|
||||
async def list_notification_configs(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List all notification configurations"""
|
||||
result = await db.execute(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Provider presets and Gmail credential management endpoints"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
@@ -156,7 +157,11 @@ async def get_provider_preset(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/gmail-credential", response_model=GmailCredentialResponse, status_code=status.HTTP_201_CREATED)
|
||||
@router.post(
|
||||
"/gmail-credential",
|
||||
response_model=GmailCredentialResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def save_gmail_credential(
|
||||
credential_in: GmailCredentialCreate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
@@ -189,7 +194,9 @@ async def save_gmail_credential(
|
||||
|
||||
encrypted_access = encrypt_credential(credential_in.access_token)
|
||||
encrypted_refresh = (
|
||||
encrypt_credential(credential_in.refresh_token) if credential_in.refresh_token else None
|
||||
encrypt_credential(credential_in.refresh_token)
|
||||
if credential_in.refresh_token
|
||||
else None
|
||||
)
|
||||
|
||||
if existing:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Subscription and payment endpoints"""
|
||||
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -13,9 +14,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/plans", response_model=List[SubscriptionPlanResponse])
|
||||
async def list_subscription_plans(
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
async def list_subscription_plans(db: AsyncSession = Depends(get_db)):
|
||||
"""List all available subscription plans"""
|
||||
result = await db.execute(
|
||||
select(SubscriptionPlan).where(SubscriptionPlan.is_active == True)
|
||||
@@ -25,11 +24,11 @@ async def list_subscription_plans(
|
||||
|
||||
@router.get("/current")
|
||||
async def get_current_subscription(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""Get current user's subscription details"""
|
||||
return {
|
||||
"tier": current_user.subscription_tier,
|
||||
"status": current_user.subscription_status,
|
||||
"expires_at": current_user.subscription_expires_at
|
||||
"expires_at": current_user.subscription_expires_at,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""User management endpoints"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -12,7 +13,7 @@ router = APIRouter()
|
||||
|
||||
@router.get("/me", response_model=UserDetailResponse)
|
||||
async def get_current_user_profile(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
):
|
||||
"""Get current user profile"""
|
||||
return current_user
|
||||
@@ -22,14 +23,14 @@ async def get_current_user_profile(
|
||||
async def update_current_user_profile(
|
||||
user_update: UserUpdate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Update current user profile"""
|
||||
if user_update.email:
|
||||
current_user.email = user_update.email
|
||||
if user_update.full_name:
|
||||
current_user.full_name = user_update.full_name
|
||||
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(current_user)
|
||||
return current_user
|
||||
|
||||
Reference in New Issue
Block a user