diff --git a/core/backend/apps/accounts/urls.py b/core/backend/apps/accounts/urls.py index df4b81e..f0473fa 100644 --- a/core/backend/apps/accounts/urls.py +++ b/core/backend/apps/accounts/urls.py @@ -1,6 +1,16 @@ from django.urls import path -from .views import login, logout, me, register, team_member_detail, team_member_password, team_members +from .views import ( + change_password, + login, + logout, + me, + register, + team_member_detail, + team_member_password, + team_members, + update_avatar, +) urlpatterns = [ @@ -8,6 +18,8 @@ urlpatterns = [ path("login/", login, name="auth-login"), path("logout/", logout, name="auth-logout"), path("me/", me, name="auth-me"), + path("me/password/", change_password, name="auth-change-password"), + path("me/avatar/", update_avatar, name="auth-avatar"), path("team/members/", team_members, name="team-members"), path("team/members//", team_member_detail, name="team-member-detail"), path("team/members//password/", team_member_password, name="team-member-password"), diff --git a/core/backend/apps/accounts/views.py b/core/backend/apps/accounts/views.py index e964528..f343559 100644 --- a/core/backend/apps/accounts/views.py +++ b/core/backend/apps/accounts/views.py @@ -1,8 +1,12 @@ +import uuid +from pathlib import Path + from django.contrib.auth import authenticate from django.db import transaction from rest_framework import status from rest_framework.authtoken.models import Token -from rest_framework.decorators import api_view, permission_classes +from rest_framework.decorators import api_view, parser_classes, permission_classes +from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -54,18 +58,69 @@ def logout(request): return Response(status=status.HTTP_204_NO_CONTENT) -@api_view(["GET"]) +@api_view(["GET", "PATCH"]) @permission_classes([IsAuthenticated]) def me(request): - team = get_current_team(request.user) + user = request.user + if request.method == "PATCH": + if "name" in request.data: + user.first_name = str(request.data.get("name") or "").strip() + if "phone" in request.data: + user.phone = str(request.data.get("phone") or "").strip()[:32] + email = str(request.data.get("email") or "").strip() + if email: + user.email = email + user.save(update_fields=["first_name", "phone", "email"]) + team = get_current_team(user) return Response( { - "user": UserSerializer(request.user).data, + "user": UserSerializer(user).data, "team": TeamSerializer(team).data, } ) +@api_view(["POST"]) +@permission_classes([IsAuthenticated]) +def change_password(request): + user = request.user + old_password = str(request.data.get("old_password") or "") + new_password = str(request.data.get("new_password") or "").strip() + if not user.check_password(old_password): + return Response({"old_password": ["原密码不正确"]}, status=status.HTTP_400_BAD_REQUEST) + if len(new_password) < 8: + return Response({"new_password": ["新密码至少 8 位"]}, status=status.HTTP_400_BAD_REQUEST) + user.set_password(new_password) + user.save(update_fields=["password"]) + Token.objects.filter(user=user).delete() + token, _ = Token.objects.get_or_create(user=user) + return Response({"token": token.key}) + + +@api_view(["POST"]) +@parser_classes([MultiPartParser, FormParser]) +@permission_classes([IsAuthenticated]) +def update_avatar(request): + from apps.assets.storage import TosStorage + + upload = request.FILES.get("file") + if upload is None: + return Response({"detail": "no file"}, status=status.HTTP_400_BAD_REQUEST) + user = request.user + suffix = Path(upload.name).suffix.lower() or ".png" + object_key = f"users/{user.id}/avatar/{uuid.uuid4()}{suffix}" + storage = TosStorage() + storage.upload_fileobj( + fileobj=upload.file, + object_key=object_key, + content_type=upload.content_type or "image/png", + ) + # 头像直接存可访问的预签名 URL(长有效期);后续如需永久化可改为读时签发 + user.avatar_url = storage.presigned_get_url(object_key=object_key, expires_in=7 * 24 * 3600) + user.save(update_fields=["avatar_url"]) + return Response(UserSerializer(user).data) + + def normalize_member_role(role): if role == "super": return TeamMember.Role.OWNER