""" Moderation app routes - main moderation functionality """ from datetime import datetime, timedelta from typing import Optional, List from fastapi import APIRouter, HTTPException, status, Depends, Query from bson import ObjectId from models import ( UserResponse, PostResponse, ReportResponse, AdminResponse, UpdatePostRequest, UpdateReportRequest, BanUserRequest ) from database import ( users_collection, posts_collection, reports_collection, moderation_admins_collection ) from utils.auth import require_moderator, require_owner, normalize_username router = APIRouter() @router.get("/users") async def get_users( filter: str = Query('active', description="Filter: active, banned, all"), page: int = Query(1, ge=1), limit: int = Query(50, ge=1, le=100), user: dict = Depends(require_moderator) ): """Get users list with filtering""" try: query = {} if filter == 'active': query['banned'] = {'$ne': True} elif filter == 'banned': query['banned'] = True # 'all' - no filter skip = (page - 1) * limit # Get users cursor = users_collection().find(query).sort('createdAt', -1).skip(skip).limit(limit) users = await cursor.to_list(length=limit) # Get total count total = await users_collection().count_documents(query) # Serialize users serialized_users = [] for u in users: serialized_users.append({ 'id': str(u['_id']), 'telegramId': u.get('telegramId'), 'username': u.get('username'), 'firstName': u.get('firstName'), 'lastName': u.get('lastName'), 'photoUrl': u.get('photoUrl'), 'role': u.get('role', 'user'), 'banned': u.get('banned', False), 'bannedUntil': u.get('bannedUntil').isoformat() if u.get('bannedUntil') else None, 'createdAt': u.get('createdAt', datetime.utcnow()).isoformat(), 'lastActiveAt': u.get('lastActiveAt').isoformat() if u.get('lastActiveAt') else None }) return { 'users': serialized_users, 'total': total, 'totalPages': (total + limit - 1) // limit, 'currentPage': page } except Exception as e: print(f"[ModApp] Ошибка получения пользователей: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.put("/users/{user_id}/ban") async def ban_user( user_id: str, request: BanUserRequest, current_user: dict = Depends(require_moderator) ): """Ban or unban user""" try: target_user = await users_collection().find_one({'_id': ObjectId(user_id)}) if not target_user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пользователь не найден" ) update_data = {'banned': request.banned} if request.banned and request.days: update_data['bannedUntil'] = datetime.utcnow() + timedelta(days=request.days) elif not request.banned: update_data['bannedUntil'] = None await users_collection().update_one( {'_id': ObjectId(user_id)}, {'$set': update_data} ) return {"success": True} except HTTPException: raise except Exception as e: print(f"[ModApp] Ошибка бана пользователя: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.get("/posts") async def get_posts( page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100), author: Optional[str] = None, tag: Optional[str] = None, user: dict = Depends(require_moderator) ): """Get posts list with filtering""" try: query = {} if author: query['author'] = ObjectId(author) if tag: query['tags'] = tag skip = (page - 1) * limit # Get posts with populated author pipeline = [ {'$match': query}, {'$sort': {'createdAt': -1}}, {'$skip': skip}, {'$limit': limit}, { '$lookup': { 'from': 'users', 'localField': 'author', 'foreignField': '_id', 'as': 'author' } }, {'$unwind': {'path': '$author', 'preserveNullAndEmptyArrays': True}} ] posts = await posts_collection().aggregate(pipeline).to_list(length=limit) total = await posts_collection().count_documents(query) # Serialize posts serialized_posts = [] for post in posts: author_data = None if post.get('author'): author_data = { 'id': str(post['author']['_id']), 'username': post['author'].get('username'), 'firstName': post['author'].get('firstName'), 'lastName': post['author'].get('lastName'), 'photoUrl': post['author'].get('photoUrl') } serialized_posts.append({ 'id': str(post['_id']), 'author': author_data, 'content': post.get('content'), 'hashtags': post.get('hashtags', []), 'tags': post.get('tags', []), 'images': post.get('images', []) or ([post.get('imageUrl')] if post.get('imageUrl') else []), 'commentsCount': len(post.get('comments', [])), 'likesCount': len(post.get('likes', [])), 'isNSFW': post.get('isNSFW', False), 'isArt': post.get('isArt', False), 'publishedToChannel': post.get('publishedToChannel', False), 'adminNumber': post.get('adminNumber'), 'editedAt': post.get('editedAt').isoformat() if post.get('editedAt') else None, 'createdAt': post.get('createdAt', datetime.utcnow()).isoformat() }) return { 'posts': serialized_posts, 'total': total, 'totalPages': (total + limit - 1) // limit, 'currentPage': page } except Exception as e: print(f"[ModApp] Ошибка получения постов: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.get("/posts/{post_id}") async def get_post( post_id: str, user: dict = Depends(require_moderator) ): """Get single post with comments""" try: pipeline = [ {'$match': {'_id': ObjectId(post_id)}}, { '$lookup': { 'from': 'users', 'localField': 'author', 'foreignField': '_id', 'as': 'author' } }, {'$unwind': {'path': '$author', 'preserveNullAndEmptyArrays': True}} ] posts = await posts_collection().aggregate(pipeline).to_list(length=1) if not posts: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пост не найден" ) post = posts[0] # Serialize author_data = None if post.get('author'): author_data = { 'id': str(post['author']['_id']), 'username': post['author'].get('username'), 'firstName': post['author'].get('firstName'), 'lastName': post['author'].get('lastName'), 'photoUrl': post['author'].get('photoUrl') } return { 'post': { 'id': str(post['_id']), 'author': author_data, 'content': post.get('content'), 'hashtags': post.get('hashtags', []), 'tags': post.get('tags', []), 'images': post.get('images', []) or ([post.get('imageUrl')] if post.get('imageUrl') else []), 'comments': post.get('comments', []), 'likes': post.get('likes', []), 'isNSFW': post.get('isNSFW', False), 'isArt': post.get('isArt', False), 'publishedToChannel': post.get('publishedToChannel', False), 'adminNumber': post.get('adminNumber'), 'createdAt': post.get('createdAt', datetime.utcnow()).isoformat() } } except HTTPException: raise except Exception as e: print(f"[ModApp] Ошибка получения поста: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.put("/posts/{post_id}") async def update_post( post_id: str, request: UpdatePostRequest, user: dict = Depends(require_moderator) ): """Update post""" try: post = await posts_collection().find_one({'_id': ObjectId(post_id)}) if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пост не найден" ) update_data = {} if request.content is not None: update_data['content'] = request.content if request.tags is not None: update_data['tags'] = [t.lower() for t in request.tags] if request.isNSFW is not None: update_data['isNSFW'] = request.isNSFW if request.isHomo is not None: update_data['isHomo'] = request.isHomo if request.isArt is not None: update_data['isArt'] = request.isArt if update_data: update_data['editedAt'] = datetime.utcnow() await posts_collection().update_one( {'_id': ObjectId(post_id)}, {'$set': update_data} ) return {"success": True} except HTTPException: raise except Exception as e: print(f"[ModApp] Ошибка обновления поста: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.delete("/posts/{post_id}") async def delete_post( post_id: str, user: dict = Depends(require_moderator) ): """Delete post""" try: post = await posts_collection().find_one({'_id': ObjectId(post_id)}) if not post: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Пост не найден" ) # Delete post await posts_collection().delete_one({'_id': ObjectId(post_id)}) return {"success": True, "message": "Пост удален"} except HTTPException: raise except Exception as e: print(f"[ModApp] Ошибка удаления поста: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.get("/reports") async def get_reports( status_filter: str = Query('pending', alias='status'), page: int = Query(1, ge=1), limit: int = Query(20, ge=1, le=100), user: dict = Depends(require_moderator) ): """Get reports list""" try: query = {} if status_filter != 'all': query['status'] = status_filter skip = (page - 1) * limit # Get reports with populated fields pipeline = [ {'$match': query}, {'$sort': {'createdAt': -1}}, {'$skip': skip}, {'$limit': limit}, { '$lookup': { 'from': 'users', 'localField': 'reporter', 'foreignField': '_id', 'as': 'reporter' } }, {'$unwind': {'path': '$reporter', 'preserveNullAndEmptyArrays': True}}, { '$lookup': { 'from': 'posts', 'localField': 'post', 'foreignField': '_id', 'as': 'post' } }, {'$unwind': {'path': '$post', 'preserveNullAndEmptyArrays': True}} ] reports = await reports_collection().aggregate(pipeline).to_list(length=limit) total = await reports_collection().count_documents(query) # Serialize serialized_reports = [] for report in reports: reporter_data = None if report.get('reporter'): reporter_data = { 'id': str(report['reporter']['_id']), 'username': report['reporter'].get('username'), 'firstName': report['reporter'].get('firstName') } post_data = None if report.get('post'): post_data = { 'id': str(report['post']['_id']), 'content': report['post'].get('content'), 'images': report['post'].get('images', []) } serialized_reports.append({ 'id': str(report['_id']), 'reporter': reporter_data, 'post': post_data, 'reason': report.get('reason'), 'status': report.get('status'), 'createdAt': report.get('createdAt', datetime.utcnow()).isoformat() }) return { 'reports': serialized_reports, 'total': total, 'totalPages': (total + limit - 1) // limit, 'currentPage': page } except Exception as e: print(f"[ModApp] Ошибка получения репортов: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.put("/reports/{report_id}") async def update_report( report_id: str, request: UpdateReportRequest, user: dict = Depends(require_moderator) ): """Update report status""" try: report = await reports_collection().find_one({'_id': ObjectId(report_id)}) if not report: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Репорт не найден" ) await reports_collection().update_one( {'_id': ObjectId(report_id)}, { '$set': { 'status': request.status, 'reviewedBy': user['_id'] } } ) return {"success": True} except HTTPException: raise except Exception as e: print(f"[ModApp] Ошибка обновления репорта: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" ) @router.get("/admins") async def get_admins(user: dict = Depends(require_moderator)): """Get list of moderation admins""" try: admins = await moderation_admins_collection().find().sort('adminNumber', 1).to_list(length=100) serialized_admins = [] for admin in admins: serialized_admins.append({ 'id': str(admin['_id']), 'telegramId': admin.get('telegramId'), 'username': admin.get('username'), 'firstName': admin.get('firstName'), 'lastName': admin.get('lastName'), 'adminNumber': admin.get('adminNumber'), 'addedBy': admin.get('addedBy'), 'createdAt': admin.get('createdAt', datetime.utcnow()).isoformat() }) return {'admins': serialized_admins} except Exception as e: print(f"[ModApp] Ошибка получения админов: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка получения списка админов" ) @router.post("/auth/verify") async def verify_auth(user: dict = Depends(require_moderator)): """Verify authentication and get admin list""" try: # Get admin list admins = await moderation_admins_collection().find().sort('adminNumber', 1).to_list(length=100) admin_list = [] for admin in admins: admin_list.append({ 'adminNumber': admin.get('adminNumber'), 'username': admin.get('username'), 'firstName': admin.get('firstName'), 'telegramId': admin.get('telegramId') }) return { 'success': True, 'admins': admin_list, 'user': { 'id': str(user['_id']), 'username': user.get('username'), 'role': user.get('role') } } except Exception as e: print(f"[ModApp] Ошибка верификации: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Ошибка сервера" )