From 0582b26be72884998862992359ef9c9d3d886092 Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Tue, 9 Dec 2025 21:45:40 +0100 Subject: [PATCH] feat: Add user deletion in admin panel and fix OAuth authentication - Add delete user functionality with cascade deletion of all user data - Fix OAuth URLs to include /api/v1 path - Fix token storage key consistency in OAuth callback - Update user model to cascade delete price alerts - Improve email templates with minimalist design - Add confirmation dialog for user deletion - Prevent deletion of admin users --- backend/app/api/admin.py | 24 +- backend/app/models/price_alert.py | 2 +- backend/app/models/user.py | 3 + backend/app/services/email_service.py | 309 ++++++++++------------- frontend/src/app/admin/page.tsx | 59 ++++- frontend/src/app/oauth/callback/page.tsx | 4 +- frontend/src/lib/api.ts | 4 +- 7 files changed, 209 insertions(+), 196 deletions(-) diff --git a/backend/app/api/admin.py b/backend/app/api/admin.py index caadfa3..1cb1c93 100644 --- a/backend/app/api/admin.py +++ b/backend/app/api/admin.py @@ -390,6 +390,9 @@ async def delete_user( admin: User = Depends(require_admin), ): """Delete a user and all their data.""" + from app.models.blog import BlogPost + from app.models.admin_log import AdminActivityLog + result = await db.execute(select(User).where(User.id == user_id)) user = result.scalar_one_or_none() @@ -399,10 +402,29 @@ async def delete_user( if user.is_admin: raise HTTPException(status_code=400, detail="Cannot delete admin user") + user_email = user.email + + # Delete user's blog posts (or set author_id to NULL if you want to keep them) + await db.execute( + BlogPost.__table__.delete().where(BlogPost.author_id == user_id) + ) + + # Delete user's admin activity logs (if any) + await db.execute( + AdminActivityLog.__table__.delete().where(AdminActivityLog.admin_id == user_id) + ) + + # Now delete the user (cascades to domains, subscriptions, portfolio, price_alerts) await db.delete(user) await db.commit() - return {"message": f"User {user.email} deleted"} + # Log this action + await log_admin_activity( + db, admin.id, "user_delete", + f"Deleted user {user_email} and all their data" + ) + + return {"message": f"User {user_email} and all their data have been deleted"} @router.post("/users/{user_id}/upgrade") diff --git a/backend/app/models/price_alert.py b/backend/app/models/price_alert.py index 56f4c49..a93f2d5 100644 --- a/backend/app/models/price_alert.py +++ b/backend/app/models/price_alert.py @@ -48,7 +48,7 @@ class PriceAlert(Base): ) # Relationship to user - user: Mapped["User"] = relationship("User", backref="price_alerts") + user: Mapped["User"] = relationship("User", back_populates="price_alerts") def __repr__(self) -> str: status = "active" if self.is_active else "paused" diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 1e2f67f..20931d8 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -57,6 +57,9 @@ class User(Base): portfolio_domains: Mapped[List["PortfolioDomain"]] = relationship( "PortfolioDomain", back_populates="user", cascade="all, delete-orphan" ) + price_alerts: Mapped[List["PriceAlert"]] = relationship( + "PriceAlert", cascade="all, delete-orphan", passive_deletes=True + ) def __repr__(self) -> str: return f"" diff --git a/backend/app/services/email_service.py b/backend/app/services/email_service.py index 0f3b58e..0f5b460 100644 --- a/backend/app/services/email_service.py +++ b/backend/app/services/email_service.py @@ -48,114 +48,33 @@ SMTP_CONFIG = { CONTACT_EMAIL = os.getenv("CONTACT_EMAIL", "hello@pounce.ch") -# Base email wrapper template +# Minimalistic Professional Email Template BASE_TEMPLATE = """ - - -
- - {{ content }} -