Spaces:
Running
Running
Delete router_users.py
Browse files- router_users.py +0 -367
router_users.py
DELETED
|
@@ -1,367 +0,0 @@
|
|
| 1 |
-
# router_users.py
|
| 2 |
-
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request
|
| 3 |
-
import time
|
| 4 |
-
import re
|
| 5 |
-
import random
|
| 6 |
-
import os
|
| 7 |
-
import json
|
| 8 |
-
import urllib.request
|
| 9 |
-
import urllib.parse
|
| 10 |
-
import base64
|
| 11 |
-
import 数据库连接 as db
|
| 12 |
-
from notifications import add_notification
|
| 13 |
-
from models import UserRegister, UserLogin, UserUpdate, PasswordReset, FollowToggle, PrivacySettings, SendCodeRequest
|
| 14 |
-
|
| 15 |
-
router = APIRouter()
|
| 16 |
-
|
| 17 |
-
# ==========================================
|
| 18 |
-
# 验证码内存缓存与发送引擎
|
| 19 |
-
# ==========================================
|
| 20 |
-
VERIFY_CODES = {}
|
| 21 |
-
|
| 22 |
-
def send_email_code(to_email: str, code: str, action: str):
|
| 23 |
-
"""利用自动化 Webhook 作为 HTTPS 到 SMTP 的代理桥梁"""
|
| 24 |
-
webhook_url = os.environ.get("MAKE_WEBHOOK_URL")
|
| 25 |
-
|
| 26 |
-
if not webhook_url:
|
| 27 |
-
print("警告: 未配置 MAKE_WEBHOOK_URL,跳过邮件发送")
|
| 28 |
-
return
|
| 29 |
-
|
| 30 |
-
action_str = "注册账号" if action == "register" else "修改/找回密码"
|
| 31 |
-
subject = f"ComfyUI 社区 - {action_str}验证码"
|
| 32 |
-
|
| 33 |
-
html_content = f"""
|
| 34 |
-
<div style="background:#f9f9f9; padding:20px; font-family:sans-serif;">
|
| 35 |
-
<div style="background:#fff; padding:20px; border-radius:8px; max-width:500px; margin:0 auto; box-shadow:0 2px 10px rgba(0,0,0,0.05);">
|
| 36 |
-
<h2 style="color:#4CAF50; margin-top:0;">ComfyUI 社区精选</h2>
|
| 37 |
-
<p>您好,</p>
|
| 38 |
-
<p>您正在请求<strong>{action_str}</strong>,您的验证码是:</p>
|
| 39 |
-
<div style="font-size:24px; font-weight:bold; color:#2196F3; background:#e3f2fd; padding:15px; text-align:center; border-radius:6px; letter-spacing: 5px;">{code}</div>
|
| 40 |
-
<p style="color:#888; font-size:12px; margin-top:20px;">该验证码在 10 分钟内有效。如非本人操作,请忽略此邮件。</p>
|
| 41 |
-
</div>
|
| 42 |
-
</div>
|
| 43 |
-
"""
|
| 44 |
-
|
| 45 |
-
data = {
|
| 46 |
-
"to": to_email,
|
| 47 |
-
"subject": subject,
|
| 48 |
-
"html": html_content
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
req = urllib.request.Request(
|
| 52 |
-
webhook_url,
|
| 53 |
-
data=json.dumps(data).encode('utf-8'),
|
| 54 |
-
headers={'Content-Type': 'application/json'}
|
| 55 |
-
)
|
| 56 |
-
try:
|
| 57 |
-
with urllib.request.urlopen(req, timeout=10) as response:
|
| 58 |
-
print(f"✅ 成功触发 Webhook 发送验证码 {code} 至 {to_email}")
|
| 59 |
-
except Exception as e:
|
| 60 |
-
print(f"❌ Webhook 触发失败: {e}")
|
| 61 |
-
|
| 62 |
-
def send_sms_code(phone: str, code: str, action: str):
|
| 63 |
-
"""支持 阿里云 与 Twilio 的双引擎短信发送路由"""
|
| 64 |
-
action_str = "注册账号" if action == "register" else "修改/找回密码"
|
| 65 |
-
|
| 66 |
-
# ==========================================
|
| 67 |
-
# 引擎 A:Twilio (无需装 SDK,走纯 HTTP 接口,适合海外或免审核测试)
|
| 68 |
-
# ==========================================
|
| 69 |
-
twilio_sid = os.environ.get("TWILIO_SID")
|
| 70 |
-
if twilio_sid:
|
| 71 |
-
token = os.environ.get("TWILIO_TOKEN")
|
| 72 |
-
from_phone = os.environ.get("TWILIO_FROM")
|
| 73 |
-
|
| 74 |
-
body = f"【ComfyUI社区】您正在请求{action_str},验证码是:{code},10分钟内有效。"
|
| 75 |
-
url = f"https://api.twilio.com/2010-04-01/Accounts/{twilio_sid}/Messages.json"
|
| 76 |
-
auth = base64.b64encode(f"{twilio_sid}:{token}".encode('utf-8')).decode('utf-8')
|
| 77 |
-
data = urllib.parse.urlencode({'To': phone, 'From': from_phone, 'Body': body}).encode('utf-8')
|
| 78 |
-
|
| 79 |
-
req = urllib.request.Request(url, data=data)
|
| 80 |
-
req.add_header("Authorization", f"Basic {auth}")
|
| 81 |
-
try:
|
| 82 |
-
with urllib.request.urlopen(req, timeout=10) as response:
|
| 83 |
-
print(f"✅ Twilio 短信已成功下发至 {phone}")
|
| 84 |
-
except Exception as e:
|
| 85 |
-
print(f"❌ Twilio 发送失败: {e}")
|
| 86 |
-
return
|
| 87 |
-
|
| 88 |
-
# ==========================================
|
| 89 |
-
# 引擎 B:阿里云 (国内首选,到达率最高)
|
| 90 |
-
# ==========================================
|
| 91 |
-
aliyun_ak = os.environ.get("ALIYUN_AK")
|
| 92 |
-
if aliyun_ak:
|
| 93 |
-
try:
|
| 94 |
-
from alibabacloud_dysmsapi20170525.client import Client as DysmsapiClient
|
| 95 |
-
from alibabacloud_tea_openapi import models as open_api_models
|
| 96 |
-
from alibabacloud_dysmsapi20170525 import models as dysmsapi_models
|
| 97 |
-
except ImportError:
|
| 98 |
-
print("❌ 缺少阿里云 SDK,请在 requirements.txt 中添加 alibabacloud_dysmsapi20170525")
|
| 99 |
-
return
|
| 100 |
-
|
| 101 |
-
sk = os.environ.get("ALIYUN_SK")
|
| 102 |
-
sign_name = os.environ.get("ALIYUN_SIGN_NAME") # 短信签名,例如 "阿里云"
|
| 103 |
-
tpl_code = os.environ.get("ALIYUN_TPL_REGISTER") if action == "register" else os.environ.get("ALIYUN_TPL_RESET")
|
| 104 |
-
|
| 105 |
-
config = open_api_models.Config(access_key_id=aliyun_ak, access_key_secret=sk)
|
| 106 |
-
config.endpoint = 'dysmsapi.aliyuncs.com'
|
| 107 |
-
client = DysmsapiClient(config)
|
| 108 |
-
|
| 109 |
-
send_req = dysmsapi_models.SendSmsRequest(
|
| 110 |
-
phone_numbers=phone,
|
| 111 |
-
sign_name=sign_name,
|
| 112 |
-
template_code=tpl_code,
|
| 113 |
-
template_param=json.dumps({"code": code})
|
| 114 |
-
)
|
| 115 |
-
try:
|
| 116 |
-
response = client.send_sms(send_req)
|
| 117 |
-
if response.body.code == "OK":
|
| 118 |
-
print(f"✅ 阿里云短信已成功下发至 {phone}")
|
| 119 |
-
else:
|
| 120 |
-
print(f"❌ 阿里云下发失败: {response.body.message}")
|
| 121 |
-
except Exception as e:
|
| 122 |
-
print(f"❌ 阿里云请求异常: {e}")
|
| 123 |
-
return
|
| 124 |
-
|
| 125 |
-
# 如果都没有配置,则降级为控制台打印模拟
|
| 126 |
-
print(f"⚠️ 未配置短信秘钥,模拟下发 -> 手机号: {phone}, 验证码: {code}")
|
| 127 |
-
|
| 128 |
-
# ==========================================
|
| 129 |
-
# 接口路由
|
| 130 |
-
# ==========================================
|
| 131 |
-
@router.post("/api/users/send-code")
|
| 132 |
-
async def send_verify_code(req: SendCodeRequest, bg_tasks: BackgroundTasks):
|
| 133 |
-
if req.action_type == "reset":
|
| 134 |
-
if not req.account:
|
| 135 |
-
raise HTTPException(status_code=400, detail="找回密码需先填写当前账号")
|
| 136 |
-
|
| 137 |
-
users_db = db.load_data("users.json", default_data={})
|
| 138 |
-
user = users_db.get(req.account)
|
| 139 |
-
if not user:
|
| 140 |
-
raise HTTPException(status_code=404, detail="该账号不存在")
|
| 141 |
-
|
| 142 |
-
if req.contact_type == "email" and user.get("email") != req.contact:
|
| 143 |
-
raise HTTPException(status_code=400, detail="填写的邮箱与该账号绑定的邮箱不一致")
|
| 144 |
-
if req.contact_type == "phone" and user.get("phone") != req.contact:
|
| 145 |
-
raise HTTPException(status_code=400, detail="填写的手机号与该账号绑定的手机号不一致")
|
| 146 |
-
|
| 147 |
-
code = str(random.randint(100000, 999999))
|
| 148 |
-
cache_key = f"{req.contact}_{req.action_type}"
|
| 149 |
-
|
| 150 |
-
VERIFY_CODES[cache_key] = {
|
| 151 |
-
"code": code,
|
| 152 |
-
"expires_at": int(time.time()) + 600
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
if req.contact_type == "email":
|
| 156 |
-
bg_tasks.add_task(send_email_code, req.contact, code, req.action_type)
|
| 157 |
-
elif req.contact_type == "phone":
|
| 158 |
-
bg_tasks.add_task(send_sms_code, req.contact, code, req.action_type)
|
| 159 |
-
else:
|
| 160 |
-
raise HTTPException(status_code=400, detail="不支持的验证方式")
|
| 161 |
-
|
| 162 |
-
return {"status": "success", "message": "验证码发送请求已提交"}
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
@router.post("/api/users/register")
|
| 166 |
-
async def register_user(user: UserRegister):
|
| 167 |
-
users_db = db.load_data("users.json", default_data={})
|
| 168 |
-
|
| 169 |
-
# 🚀 核心修复 1:将查重逻辑移到最前面!一旦重复直接拦截,绝不会发生 500 崩溃
|
| 170 |
-
if user.account in users_db:
|
| 171 |
-
raise HTTPException(status_code=400, detail="该账号已被注册,请更换一个")
|
| 172 |
-
for existing_user in users_db.values():
|
| 173 |
-
if user.email and existing_user.get("email") == user.email:
|
| 174 |
-
raise HTTPException(status_code=400, detail="此邮箱已注册,请直接登录或找回密码")
|
| 175 |
-
if user.phone and existing_user.get("phone") == user.phone:
|
| 176 |
-
raise HTTPException(status_code=400, detail="该手机号已被绑定")
|
| 177 |
-
|
| 178 |
-
# 获取验证码缓存记录
|
| 179 |
-
cache_key = f"{user.email}_register" if user.email else f"{user.phone}_register"
|
| 180 |
-
cached = VERIFY_CODES.get(cache_key)
|
| 181 |
-
|
| 182 |
-
# 🚀 核心修复 2:安全地获取过期时间,兼容新老写法,彻底消灭 KeyError 导致的 500
|
| 183 |
-
expire_time = cached.get("expires_at", cached.get("expires", 0)) if cached else 0
|
| 184 |
-
|
| 185 |
-
if not cached or cached["code"] != user.code or time.time() > expire_time:
|
| 186 |
-
raise HTTPException(status_code=400, detail="验证码不正确或已过期")
|
| 187 |
-
|
| 188 |
-
if len(user.account) <= 5: raise HTTPException(status_code=400, detail="账号必须大于5个字符")
|
| 189 |
-
if not re.match(r'^[a-zA-Z0-9_]{6,20}$', user.account): raise HTTPException(status_code=400, detail="账号仅支持大小写英文字母、数字及下划线")
|
| 190 |
-
if len(user.password) < 6: raise HTTPException(status_code=400, detail="密码必须大于等于6个字符")
|
| 191 |
-
if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', user.password): raise HTTPException(status_code=400, detail="密码包含不支持的特殊字符")
|
| 192 |
-
if user.intro and len(user.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
|
| 193 |
-
|
| 194 |
-
VERIFY_CODES.pop(cache_key, None)
|
| 195 |
-
|
| 196 |
-
new_user = user.dict()
|
| 197 |
-
new_user.pop("code", None)
|
| 198 |
-
new_user.update({"created_at": int(time.time()), "followers": [], "following": [], "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}})
|
| 199 |
-
users_db[user.account] = new_user
|
| 200 |
-
db.save_data("users.json", users_db)
|
| 201 |
-
return {"status": "success", "message": "注册成功", "data": {k: v for k, v in new_user.items() if k != "password"}}
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
@router.post("/api/users/login")
|
| 205 |
-
async def login_user(user: UserLogin):
|
| 206 |
-
users_db = db.load_data("users.json", default_data={})
|
| 207 |
-
if user.account not in users_db: raise HTTPException(status_code=404, detail="账号不存在")
|
| 208 |
-
user_data = users_db[user.account]
|
| 209 |
-
if user_data.get("password") != user.password: raise HTTPException(status_code=401, detail="密码错误")
|
| 210 |
-
return {"status": "success", "token": f"mock_token_{user.account}", "account": user.account, "name": user_data["name"], "avatar": user_data.get("avatarDataUrl", "https://via.placeholder.com/150")}
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
# ==========================================
|
| 214 |
-
# 补充:发送验证码的 API 接口
|
| 215 |
-
# ==========================================
|
| 216 |
-
@router.post("/api/users/send_code")
|
| 217 |
-
async def send_code_api(req: SendCodeRequest):
|
| 218 |
-
code = str(random.randint(100000, 999999))
|
| 219 |
-
key = f"{req.contact}_{req.action_type}"
|
| 220 |
-
|
| 221 |
-
# 🚀 核心修复 3:统一改成 expires_at,与全局校验逻辑完美对齐
|
| 222 |
-
VERIFY_CODES[key] = {
|
| 223 |
-
"code": code,
|
| 224 |
-
"expires_at": time.time() + 600
|
| 225 |
-
}
|
| 226 |
-
|
| 227 |
-
if req.contact_type == "email":
|
| 228 |
-
try:
|
| 229 |
-
send_email_code(req.contact, code, req.action_type)
|
| 230 |
-
return {"status": "success", "message": "验证码已成功发送至邮箱"}
|
| 231 |
-
except Exception as e:
|
| 232 |
-
raise HTTPException(status_code=500, detail=f"邮件发送失败: {str(e)}")
|
| 233 |
-
|
| 234 |
-
elif req.contact_type == "phone":
|
| 235 |
-
return {"status": "success", "message": "验证码已成功发送至手机"}
|
| 236 |
-
|
| 237 |
-
else:
|
| 238 |
-
raise HTTPException(status_code=400, detail="不支持的验证方式")
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
@router.get("/api/users/{account}")
|
| 242 |
-
async def get_user_profile(account: str):
|
| 243 |
-
users_db = db.load_data("users.json", default_data={})
|
| 244 |
-
if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
|
| 245 |
-
user_data = users_db[account]
|
| 246 |
-
items_db = db.load_data("items.json", default_data=[])
|
| 247 |
-
user_items = [item for item in items_db if item.get("author") == account]
|
| 248 |
-
user_data["receivedLikes"] = sum(item.get("likes", 0) for item in user_items)
|
| 249 |
-
user_data["receivedFavorites"] = sum(item.get("favorites", 0) for item in user_items)
|
| 250 |
-
user_data["receivedUses"] = sum(item.get("uses", 0) for item in user_items)
|
| 251 |
-
return {"status": "success", "data": {k: v for k, v in user_data.items() if k != "password"}}
|
| 252 |
-
|
| 253 |
-
@router.put("/api/users/{account}")
|
| 254 |
-
async def update_user_profile(account: str, update_data: UserUpdate):
|
| 255 |
-
users_db = db.load_data("users.json", default_data={})
|
| 256 |
-
if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
|
| 257 |
-
if update_data.intro and len(update_data.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
|
| 258 |
-
user = users_db[account]
|
| 259 |
-
for k, v in update_data.dict(exclude_unset=True).items():
|
| 260 |
-
if v is not None: user[k] = v
|
| 261 |
-
db.save_data("users.json", users_db)
|
| 262 |
-
return {"status": "success", "data": {k: v for k, v in user.items() if k != "password"}}
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
# ==========================================
|
| 266 |
-
# 🚀 究极重置密码接口:带智能纠错与透视报错
|
| 267 |
-
# ==========================================
|
| 268 |
-
@router.post("/api/users/reset_password")
|
| 269 |
-
async def reset_password(request: Request):
|
| 270 |
-
# 1. 万能解析器:兼容 JSON、双重字符串化、以及 FormData
|
| 271 |
-
try:
|
| 272 |
-
data = await request.json()
|
| 273 |
-
if isinstance(data, str):
|
| 274 |
-
data = json.loads(data)
|
| 275 |
-
except:
|
| 276 |
-
try:
|
| 277 |
-
form = await request.form()
|
| 278 |
-
data = dict(form)
|
| 279 |
-
except:
|
| 280 |
-
raise HTTPException(status_code=400, detail="请求数据解析失败,请检查网络")
|
| 281 |
-
|
| 282 |
-
if not isinstance(data, dict):
|
| 283 |
-
raise HTTPException(status_code=400, detail=f"前端数据格式异常,收到的是: {type(data).__name__}")
|
| 284 |
-
|
| 285 |
-
# 2. 万能提取器 + 强力空格清洗
|
| 286 |
-
account = data.get("account")
|
| 287 |
-
new_password = data.get("new_password") or data.get("password")
|
| 288 |
-
verify_contact = data.get("verifyContact") or data.get("verify_contact") or data.get("email") or data.get("phone")
|
| 289 |
-
verify_type = data.get("verifyType") or data.get("verify_type") or data.get("contact_type")
|
| 290 |
-
|
| 291 |
-
# 🚀 强力洗消:不管前端传什么脏数据,统统转字符串并去掉头尾空格
|
| 292 |
-
code = str(data.get("code", "")).strip()
|
| 293 |
-
|
| 294 |
-
if not all([account, new_password, verify_contact, verify_type, code]):
|
| 295 |
-
raise HTTPException(status_code=400, detail="缺失必要参数 (账号/密码/验证码/联系方式),请检查表单")
|
| 296 |
-
|
| 297 |
-
# 3. 核心业务逻辑
|
| 298 |
-
users_db = db.load_data("users.json", default_data={})
|
| 299 |
-
if account not in users_db: raise HTTPException(status_code=404, detail="该用户不存在")
|
| 300 |
-
user = users_db[account]
|
| 301 |
-
|
| 302 |
-
if verify_type == "email" and user.get("email") != verify_contact:
|
| 303 |
-
raise HTTPException(status_code=400, detail="填写的邮箱与该账号绑定的邮箱不匹配")
|
| 304 |
-
if verify_type == "phone" and user.get("phone") != verify_contact:
|
| 305 |
-
raise HTTPException(status_code=400, detail="填写的手机号与该账号绑定的手机号不匹配")
|
| 306 |
-
|
| 307 |
-
# ==========================================
|
| 308 |
-
# 🚀 核心排雷:智能内存查找与透视报错
|
| 309 |
-
# ==========================================
|
| 310 |
-
cache_key = f"{verify_contact}_reset"
|
| 311 |
-
cached = VERIFY_CODES.get(cache_key)
|
| 312 |
-
|
| 313 |
-
# 如果精准匹配找不到,就去内存池里进行“模糊搜索”(解决前端 action_type 传参误差)
|
| 314 |
-
if not cached:
|
| 315 |
-
fallback_keys = [k for k in VERIFY_CODES.keys() if verify_contact in k]
|
| 316 |
-
if fallback_keys:
|
| 317 |
-
cache_key = fallback_keys[0]
|
| 318 |
-
cached = VERIFY_CODES.get(cache_key)
|
| 319 |
-
else:
|
| 320 |
-
# 💥 透视眼:如果真找不到,直接把后端真实收到的邮箱弹出来给你看!
|
| 321 |
-
raise HTTPException(status_code=400, detail=f"验证码内存已丢失!请重新点击发送。当前识别提取的邮箱是: [{verify_contact}]")
|
| 322 |
-
|
| 323 |
-
expire_time = cached.get("expires_at", cached.get("expires", 0)) if cached else 0
|
| 324 |
-
|
| 325 |
-
if time.time() > expire_time:
|
| 326 |
-
raise HTTPException(status_code=400, detail="验证码已过期,请重新获取")
|
| 327 |
-
|
| 328 |
-
if cached["code"] != code:
|
| 329 |
-
# 💥 透视眼:如果输错了,直接把正确答案亮出来!
|
| 330 |
-
raise HTTPException(status_code=400, detail=f"验证码不匹配!你输入的是 [{code}],系统记录的是 [{cached['code']}]")
|
| 331 |
-
|
| 332 |
-
if len(new_password) < 6: raise HTTPException(status_code=400, detail="新密码必须大于等于6个字符")
|
| 333 |
-
if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', new_password): raise HTTPException(status_code=400, detail="新密码包含不支持的特殊字符")
|
| 334 |
-
|
| 335 |
-
VERIFY_CODES.pop(cache_key, None)
|
| 336 |
-
user["password"] = new_password
|
| 337 |
-
db.save_data("users.json", users_db)
|
| 338 |
-
|
| 339 |
-
return {"status": "success", "message": "密码修改成功"}
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
@router.post("/api/users/follow")
|
| 343 |
-
async def toggle_follow(follow: FollowToggle):
|
| 344 |
-
users_db = db.load_data("users.json", default_data={})
|
| 345 |
-
if follow.target_account not in users_db or follow.user_id not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
|
| 346 |
-
target_followers = users_db[follow.target_account].setdefault("followers", [])
|
| 347 |
-
current_following = users_db[follow.user_id].setdefault("following", [])
|
| 348 |
-
if follow.is_active:
|
| 349 |
-
if follow.user_id not in target_followers:
|
| 350 |
-
target_followers.append(follow.user_id)
|
| 351 |
-
add_notification(follow.target_account, {"type": "follow", "from_user": follow.user_id})
|
| 352 |
-
if follow.target_account not in current_following: current_following.append(follow.target_account)
|
| 353 |
-
else:
|
| 354 |
-
if follow.user_id in target_followers: target_followers.remove(follow.user_id)
|
| 355 |
-
if follow.target_account in current_following: current_following.remove(follow.target_account)
|
| 356 |
-
db.save_data("users.json", users_db)
|
| 357 |
-
return {"status": "success"}
|
| 358 |
-
|
| 359 |
-
@router.put("/api/users/{account}/privacy")
|
| 360 |
-
async def update_privacy(account: str, privacy: PrivacySettings):
|
| 361 |
-
users_db = db.load_data("users.json", default_data={})
|
| 362 |
-
if account not in users_db:
|
| 363 |
-
raise HTTPException(status_code=404, detail="用户不存在")
|
| 364 |
-
|
| 365 |
-
users_db[account]["privacy"] = privacy.dict()
|
| 366 |
-
db.save_data("users.json", users_db)
|
| 367 |
-
return {"status": "success"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|