import json from adrf.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import MultiPartParser, FormParser from asgiref.sync import sync_to_async from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from user.models import UserDevice from utils.response_codes import create_standardized_response, create_standardized_error_response success_response = openapi.Response('成功', examples={'application/json': {'code': 10000, 'message': 'success'}}) unauthorized_response = openapi.Response('未授权', examples={'application/json': {'code': 40101, 'message': '未登录'}}) class UploadCoverAPIView(APIView): permission_classes = [IsAuthenticated] parser_classes = [MultiPartParser, FormParser] @swagger_auto_schema( tags=['用户'], operation_summary='上传个人主页封面', operation_description='上传个人主页封面图片,支持jpg/png/jpeg格式,最大5MB', manual_parameters=[ openapi.Parameter('cover', openapi.IN_FORM, description='封面图片文件', type=openapi.TYPE_FILE, required=True), ], responses={200: success_response, 400: '参数错误', 401: unauthorized_response}, ) async def post(self, request): cover_file = request.FILES.get('cover') if not cover_file: return create_standardized_error_response( code=40001, message='请选择要上传的封面图片' ) if cover_file.size > 5 * 1024 * 1024: return create_standardized_error_response( code=40002, message='图片大小不能超过5MB' ) allowed_types = ['image/jpeg', 'image/png', 'image/jpg', 'image/webp'] if cover_file.content_type not in allowed_types: return create_standardized_error_response( code=40003, message='仅支持 jpg/png/webp 格式的图片' ) user = request.user if user.cover_image: try: # FieldFile.delete 是同步存储 I/O,线程池兜底 await sync_to_async(user.cover_image.delete)(save=False) except Exception: pass user.cover_image = cover_file await user.asave(update_fields=['cover_image']) return create_standardized_response( data={ 'cover_image': user.cover_image.url if user.cover_image else '', 'message': '封面上传成功', } ) class PrivacySettingsAPIView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( tags=['用户'], operation_summary='获取隐私设置', operation_description='获取当前用户的隐私设置', responses={200: success_response, 401: unauthorized_response}, ) async def get(self, request): user = request.user return create_standardized_response( data={ 'privacy_profile': user.privacy_profile, 'privacy_articles': user.privacy_articles, 'privacy_friends': user.privacy_friends, } ) @swagger_auto_schema( tags=['用户'], operation_summary='更新隐私设置', operation_description='更新当前用户的隐私设置', request_body=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'privacy_profile': openapi.Schema(type=openapi.TYPE_STRING, description='主页可见性', enum=['public', 'friends_only', 'private']), 'privacy_articles': openapi.Schema(type=openapi.TYPE_STRING, description='文章可见性', enum=['public', 'friends_only', 'private']), 'privacy_friends': openapi.Schema(type=openapi.TYPE_STRING, description='好友列表可见性', enum=['public', 'friends_only', 'private']), }, ), responses={200: success_response, 400: '参数错误', 401: unauthorized_response}, ) async def put(self, request): user = request.user valid_choices = ['public', 'friends_only', 'private'] fields = ['privacy_profile', 'privacy_articles', 'privacy_friends'] for field in fields: value = request.data.get(field) if value is not None: if value not in valid_choices: return create_standardized_error_response( code=40001, message=f'{field} 的值不合法' ) setattr(user, field, value) await user.asave(update_fields=fields) return create_standardized_response( data={ 'privacy_profile': user.privacy_profile, 'privacy_articles': user.privacy_articles, 'privacy_friends': user.privacy_friends, 'message': '隐私设置已更新', } ) class NotificationSettingsAPIView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( tags=['用户'], operation_summary='获取消息通知设置', operation_description='获取当前用户的消息通知偏好设置', responses={200: success_response, 401: unauthorized_response}, ) async def get(self, request): user = request.user return create_standardized_response( data={ 'notify_email': user.notify_email, 'notify_browser': user.notify_browser, 'notify_reply': user.notify_reply, 'notify_like': user.notify_like, 'notify_follow': user.notify_follow, 'notify_system': user.notify_system, } ) @swagger_auto_schema( tags=['用户'], operation_summary='更新消息通知设置', operation_description='更新当前用户的消息通知偏好设置', request_body=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'notify_email': openapi.Schema(type=openapi.TYPE_BOOLEAN, description='邮件通知'), 'notify_browser': openapi.Schema(type=openapi.TYPE_BOOLEAN, description='浏览器通知'), 'notify_reply': openapi.Schema(type=openapi.TYPE_BOOLEAN, description='回复通知'), 'notify_like': openapi.Schema(type=openapi.TYPE_BOOLEAN, description='点赞通知'), 'notify_follow': openapi.Schema(type=openapi.TYPE_BOOLEAN, description='关注通知'), 'notify_system': openapi.Schema(type=openapi.TYPE_BOOLEAN, description='系统通知'), }, ), responses={200: success_response, 401: unauthorized_response}, ) async def put(self, request): user = request.user fields = ['notify_email', 'notify_browser', 'notify_reply', 'notify_like', 'notify_follow', 'notify_system'] for field in fields: value = request.data.get(field) if value is not None: if not isinstance(value, bool): value = str(value).lower() in ('true', '1', 'yes') setattr(user, field, value) await user.asave(update_fields=fields) return create_standardized_response( data={ 'notify_email': user.notify_email, 'notify_browser': user.notify_browser, 'notify_reply': user.notify_reply, 'notify_like': user.notify_like, 'notify_follow': user.notify_follow, 'notify_system': user.notify_system, 'message': '通知设置已更新', } ) class UserDeviceListView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( tags=['用户'], operation_summary='获取登录设备列表', operation_description='获取当前用户的所有登录设备列表', responses={200: success_response, 401: unauthorized_response}, ) async def get(self, request): user = request.user devices = UserDevice.objects.filter(user=user).order_by('-last_active') data = [] async for d in devices: data.append({ 'id': d.id, 'device_name': d.device_name, 'device_type': d.device_type, 'browser': d.browser, 'os': d.os, 'ip_address': str(d.ip_address) if d.ip_address else '', 'location': d.location, 'last_active': d.last_active.strftime('%Y-%m-%d %H:%M:%S'), 'created_at': d.created_at.strftime('%Y-%m-%d %H:%M:%S'), 'is_current': d.is_current, }) return create_standardized_response(data={'devices': data, 'total': len(data)}) class UserDeviceRemoveView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( tags=['用户'], operation_summary='移除登录设备', operation_description='移除指定的登录设备(远程登出)', manual_parameters=[ openapi.Parameter('pk', openapi.IN_PATH, description='设备ID', type=openapi.TYPE_INTEGER, required=True), ], responses={200: success_response, 404: '设备不存在', 401: unauthorized_response}, ) async def delete(self, request, pk): user = request.user device = await UserDevice.objects.filter(user=user, pk=pk).afirst() if not device: return create_standardized_error_response( code=40401, message='设备不存在' ) if device.is_current: return create_standardized_error_response( code=40001, message='不能移除当前设备' ) await device.adelete() return create_standardized_response(data={'message': '设备已移除'}) class UserDeviceClearOthersView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( tags=['用户'], operation_summary='清除其他设备', operation_description='移除除当前设备外的所有其他登录设备', responses={200: success_response, 401: unauthorized_response}, ) async def post(self, request): user = request.user count, _ = await UserDevice.objects.filter(user=user).exclude(is_current=True).adelete() return create_standardized_response(data={'message': f'已移除 {count} 个设备', 'removed_count': count})