Other files

This commit is contained in:
2026-04-10 18:30:04 -04:00
parent ea4ddd7d41
commit bcbfe2797c
17 changed files with 2221 additions and 16 deletions
+152
View File
@@ -1139,3 +1139,155 @@ def get_user_stores(user_id):
'location': {'zip_code': user['zip_code'], 'radius_miles': user.get('radius_miles', 25)},
'stores': result
})
# =============================================================================
# Checkout Profile Routes
# =============================================================================
@api_bp.route('/users/<int:user_id>/profile/data', methods=['GET'])
def get_profile_data(user_id):
"""
Return non-sensitive profile fields for pre-filling the edit form.
Only works if the profile is already unlocked (in memory).
Card number is returned masked. Passwords are never returned.
"""
from src.profile_store import get_unlocked_profile, masked_card
db = get_database()
if not db.get_user(user_id):
return jsonify({'error': 'User not found'}), 404
profile = get_unlocked_profile(user_id)
if not profile:
return jsonify({'error': 'Profile not unlocked'}), 403
shipping = profile.get('shipping', {})
creds = profile.get('site_credentials', {})
card = masked_card(user_id)
return jsonify({
'shipping': shipping,
'masked_card': card,
'site_credentials': {
'target_email': creds.get('target_email', ''),
'bestbuy_email': creds.get('bestbuy_email', ''),
'gamestop_email': creds.get('gamestop_email', ''),
# passwords intentionally omitted
}
})
@api_bp.route('/users/<int:user_id>/profile', methods=['GET'])
def get_profile_status(user_id):
"""
Return profile metadata — never returns card data or decrypted fields.
Frontend uses this to show lock/unlock state and masked card info.
"""
from src.profile_store import is_unlocked, masked_card
db = get_database()
if not db.get_user(user_id):
return jsonify({'error': 'User not found'}), 404
has_profile = db.user_has_profile(user_id)
unlocked = is_unlocked(user_id)
return jsonify({
'has_profile': has_profile,
'is_unlocked': unlocked,
'masked_card': masked_card(user_id) if unlocked else None,
})
@api_bp.route('/users/<int:user_id>/profile', methods=['POST'])
def save_profile(user_id):
"""
Save (or replace) an encrypted checkout profile.
Expects JSON: { password, shipping: {...}, payment: {...} }
Card data is encrypted immediately — never stored in plaintext.
"""
from src.profile_store import save_profile as _save
db = get_database()
if not db.get_user(user_id):
return jsonify({'error': 'User not found'}), 404
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
password = data.get('password', '').strip()
if len(password) < 8:
return jsonify({'error': 'Password must be at least 8 characters'}), 400
shipping = data.get('shipping', {})
payment = data.get('payment', {})
site_credentials = data.get('site_credentials', {})
required_shipping = ['first_name', 'last_name', 'address1', 'city', 'state', 'zip', 'email']
missing = [f for f in required_shipping if not shipping.get(f)]
if missing:
return jsonify({'error': f'Missing shipping fields: {", ".join(missing)}'}), 400
# If card fields are blank and a profile already exists, keep the existing card data
if not payment.get('card_number') and db.user_has_profile(user_id):
from src.profile_store import get_unlocked_profile
existing = get_unlocked_profile(user_id)
if existing:
for k in ['card_number', 'expiry_month', 'expiry_year', 'cvv', 'name_on_card']:
if not payment.get(k):
payment[k] = existing.get('payment', {}).get(k, '')
required_payment = ['card_number', 'expiry_month', 'expiry_year', 'cvv']
missing = [f for f in required_payment if not payment.get(f)]
if missing:
return jsonify({'error': f'Missing payment fields: {", ".join(missing)}'}), 400
try:
ok = _save(user_id, password, shipping, payment, db, site_credentials=site_credentials)
except Exception as e:
return jsonify({'error': str(e)}), 500
if not ok:
return jsonify({'error': 'Encryption failed — is the cryptography package installed? Run: pip install cryptography'}), 500
return jsonify({'success': True, 'is_unlocked': True})
@api_bp.route('/users/<int:user_id>/profile/unlock', methods=['POST'])
def unlock_profile(user_id):
"""Decrypt profile into memory. Expects JSON: { password }"""
from src.profile_store import unlock_profile as _unlock
db = get_database()
if not db.get_user(user_id):
return jsonify({'error': 'User not found'}), 404
if not db.user_has_profile(user_id):
return jsonify({'error': 'No profile saved for this user'}), 404
data = request.get_json()
password = (data or {}).get('password', '')
if not _unlock(user_id, password, db):
return jsonify({'error': 'Incorrect password'}), 401
from src.profile_store import masked_card
return jsonify({'success': True, 'masked_card': masked_card(user_id)})
@api_bp.route('/users/<int:user_id>/profile/lock', methods=['POST'])
def lock_profile(user_id):
"""Clear profile from memory."""
from src.profile_store import lock_profile as _lock
_lock(user_id)
return jsonify({'success': True})
@api_bp.route('/users/<int:user_id>/profile', methods=['DELETE'])
def delete_profile(user_id):
"""Permanently delete a user's saved profile."""
from src.profile_store import lock_profile as _lock
db = get_database()
if not db.get_user(user_id):
return jsonify({'error': 'User not found'}), 404
_lock(user_id)
db.delete_user_profile(user_id)
return jsonify({'success': True})