feat: Implement account deletion (mark for removal) feature
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 23s
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 23s
- Added `marked_for_deletion` and `marked_for_deletion_at` fields to User model (Python and TypeScript) with serialization updates - Created POST /api/user/mark-for-deletion endpoint with JWT auth, error handling, and SSE event trigger - Blocked login and password reset for marked users; added new error codes ACCOUNT_MARKED_FOR_DELETION and ALREADY_MARKED - Updated UserProfile.vue with "Delete My Account" button, confirmation modal (email input), loading state, success/error modals, and sign-out/redirect logic - Synced error codes and model fields between backend and frontend - Added and updated backend and frontend tests to cover all flows and edge cases - All Acceptance Criteria from the spec are complete and verified
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from events.types.user_modified import UserModified
|
||||
from models.user import User
|
||||
from tinydb import Query
|
||||
from db.db import users_db
|
||||
@@ -6,8 +7,11 @@ import jwt
|
||||
import random
|
||||
import string
|
||||
import utils.email_sender as email_sender
|
||||
from datetime import datetime, timedelta
|
||||
from api.utils import get_validated_user_id
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from api.utils import get_validated_user_id, normalize_email, send_event_for_current_user
|
||||
from api.error_codes import ACCOUNT_MARKED_FOR_DELETION, ALREADY_MARKED
|
||||
from events.types.event_types import EventType
|
||||
from events.types.event import Event
|
||||
|
||||
user_api = Blueprint('user_api', __name__)
|
||||
UserQuery = Query()
|
||||
@@ -170,3 +174,36 @@ def set_pin():
|
||||
user.pin_setup_code_created = None
|
||||
users_db.update(user.to_dict(), UserQuery.email == user.email)
|
||||
return jsonify({'message': 'Parent PIN set'}), 200
|
||||
|
||||
@user_api.route('/user/mark-for-deletion', methods=['POST'])
|
||||
def mark_for_deletion():
|
||||
user_id = get_validated_user_id()
|
||||
if not user_id:
|
||||
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
||||
user = get_current_user()
|
||||
if not user:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
# Validate email from request body
|
||||
data = request.get_json()
|
||||
email = data.get('email', '').strip()
|
||||
if not email:
|
||||
return jsonify({'error': 'Email is required', 'code': 'EMAIL_REQUIRED'}), 400
|
||||
|
||||
# Verify email matches the logged-in user - make sure to normalize the email address first
|
||||
if normalize_email(email) != normalize_email(user.email):
|
||||
return jsonify({'error': 'Email does not match your account', 'code': 'EMAIL_MISMATCH'}), 400
|
||||
|
||||
# Check if already marked
|
||||
if user.marked_for_deletion:
|
||||
return jsonify({'error': 'Account already marked for deletion', 'code': ALREADY_MARKED}), 400
|
||||
|
||||
# Mark for deletion
|
||||
user.marked_for_deletion = True
|
||||
user.marked_for_deletion_at = datetime.now(timezone.utc).isoformat()
|
||||
users_db.update(user.to_dict(), UserQuery.id == user.id)
|
||||
|
||||
# Trigger SSE event
|
||||
send_event_for_current_user(Event(EventType.USER_MARKED_FOR_DELETION.value, UserModified(user.id, UserModified.OPERATION_DELETE)))
|
||||
|
||||
return jsonify({'success': True}), 200
|
||||
|
||||
Reference in New Issue
Block a user