destinyebuka commited on
Commit
2bcedf3
Β·
1 Parent(s): 8ac226e
app/__pycache__/database.cpython-313.pyc CHANGED
Binary files a/app/__pycache__/database.cpython-313.pyc and b/app/__pycache__/database.cpython-313.pyc differ
 
app/routes/admin.py CHANGED
@@ -177,6 +177,45 @@ async def list_shadow_users(
177
  return {"success": True, "data": users, "total": len(users)}
178
 
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  @router.get("/shadow-users/{shadow_user_id}", status_code=status.HTTP_200_OK)
181
  async def get_shadow_user(
182
  shadow_user_id: str,
 
177
  return {"success": True, "data": users, "total": len(users)}
178
 
179
 
180
+ @router.delete("/users/{user_id}", status_code=status.HTTP_200_OK)
181
+ async def delete_user(
182
+ user_id: str,
183
+ admin: dict = Depends(require_admin),
184
+ ):
185
+ """
186
+ Permanently delete any user account (regular or shadow) and their listings.
187
+ Cannot delete other admin accounts.
188
+ """
189
+ if not ObjectId.is_valid(user_id):
190
+ raise HTTPException(status_code=400, detail="Invalid user ID.")
191
+
192
+ db = await get_db()
193
+ target = await db.users.find_one({"_id": ObjectId(user_id)})
194
+ if not target:
195
+ raise HTTPException(status_code=404, detail="User not found.")
196
+
197
+ # Prevent deleting other admins
198
+ if target.get("is_admin") or target.get("role") == "admin":
199
+ raise HTTPException(status_code=403, detail="Cannot delete admin accounts.")
200
+
201
+ oid = ObjectId(user_id)
202
+
203
+ # Delete user's listings
204
+ listings_result = await db.listings.delete_many({"owner_id": user_id})
205
+
206
+ # Delete the user
207
+ await db.users.delete_one({"_id": oid})
208
+
209
+ logger.info(
210
+ f"πŸ—‘οΈ Admin deleted user {user_id} | listings_removed={listings_result.deleted_count}"
211
+ )
212
+ return {
213
+ "success": True,
214
+ "message": "User and their listings have been deleted.",
215
+ "listings_removed": listings_result.deleted_count,
216
+ }
217
+
218
+
219
  @router.get("/shadow-users/{shadow_user_id}", status_code=status.HTTP_200_OK)
220
  async def get_shadow_user(
221
  shadow_user_id: str,
scripts/delete_shadow_users.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ delete_shadow_users.py
3
+ ======================
4
+ One-time utility to wipe all shadow users (and their listings) from MongoDB.
5
+
6
+ Run from the AIDA/ directory:
7
+ python scripts/delete_shadow_users.py
8
+
9
+ Pass --dry-run to preview what would be deleted without actually deleting:
10
+ python scripts/delete_shadow_users.py --dry-run
11
+ """
12
+
13
+ import asyncio
14
+ import logging
15
+ import os
16
+ import sys
17
+
18
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
19
+
20
+ from app.database import connect_db, get_db, disconnect_db
21
+
22
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
23
+ logger = logging.getLogger(__name__)
24
+
25
+ DRY_RUN = "--dry-run" in sys.argv
26
+
27
+
28
+ async def run():
29
+ await connect_db()
30
+ try:
31
+ db = await get_db()
32
+
33
+ # ── 1. Find all shadow users ──────────────────────────────────────
34
+ shadow_cursor = db.users.find(
35
+ {"account_type": "shadow"},
36
+ {"_id": 1, "display_name": 1, "whatsapp_number": 1},
37
+ )
38
+ shadow_users = await shadow_cursor.to_list(length=None)
39
+
40
+ if not shadow_users:
41
+ logger.info("No shadow users found β€” nothing to delete.")
42
+ return
43
+
44
+ logger.info(f"Found {len(shadow_users)} shadow user(s):")
45
+ for u in shadow_users:
46
+ logger.info(
47
+ f" β€’ {u.get('display_name', 'β€”')} | {u.get('whatsapp_number', 'β€”')} | id={u['_id']}"
48
+ )
49
+
50
+ if DRY_RUN:
51
+ logger.info("\n[DRY RUN] No changes made. Remove --dry-run to execute.")
52
+ return
53
+
54
+ # ── 2. Collect their IDs ──────────────────────────────────────────
55
+ ids = [str(u["_id"]) for u in shadow_users]
56
+
57
+ # ── 3. Delete their listings ─────────────────────────────────────
58
+ listings_result = await db.listings.delete_many({"owner_id": {"$in": ids}})
59
+ logger.info(f"Deleted {listings_result.deleted_count} listing(s) owned by shadow users.")
60
+
61
+ # ── 4. Delete the shadow users themselves ─────────────────────────
62
+ from bson import ObjectId
63
+ oids = [u["_id"] for u in shadow_users]
64
+ users_result = await db.users.delete_many({"_id": {"$in": oids}})
65
+ logger.info(f"Deleted {users_result.deleted_count} shadow user(s).")
66
+
67
+ logger.info("\nβœ… Done. All shadow users and their listings have been removed.")
68
+
69
+ finally:
70
+ await disconnect_db()
71
+
72
+
73
+ if __name__ == "__main__":
74
+ asyncio.run(run())