ZHIWEI666 commited on
Commit
85494ee
·
verified ·
1 Parent(s): edebab6

Upload 13 files

Browse files
Files changed (7) hide show
  1. app.py +74 -19
  2. database_sql.py +26 -0
  3. models.py +18 -2
  4. models_sql.py +43 -0
  5. requirements.txt +2 -1
  6. router_items.py +8 -0
  7. router_wallet.py +124 -0
app.py CHANGED
@@ -1,22 +1,31 @@
1
  # ⚙️ 后端逻辑/核心服务端.py (Hugging Face Spaces app.py)
2
- from fastapi import FastAPI, File, UploadFile, Form
3
  from fastapi.middleware.cors import CORSMiddleware
4
  from fastapi.responses import Response, JSONResponse
5
- from pydantic import BaseModel # 【新增】:引入标准数据模型
 
 
6
  import hashlib
7
  import urllib.parse
8
  import urllib.request
9
  import os
10
  import 数据库连接 as db
11
 
12
- # 引入拆分后的四大业务模块
13
  from router_users import router as users_router
14
  from router_items import router as items_router
15
  from router_comments import router as comments_router
16
  from router_messages import router as messages_router
 
 
 
17
 
18
  app = FastAPI(title="ComfyUI Ranking Community API")
19
 
 
 
 
 
 
20
  app.add_middleware(
21
  CORSMiddleware,
22
  allow_origins=["*"],
@@ -29,10 +38,11 @@ app.include_router(users_router)
29
  app.include_router(items_router)
30
  app.include_router(comments_router)
31
  app.include_router(messages_router)
 
32
 
33
  @app.get("/")
34
  def read_root():
35
- return {"status": "ok", "message": "API is running and fully modularized!"}
36
 
37
  @app.post("/api/upload")
38
  async def upload_file(file: UploadFile = File(...), file_type: str = Form(...)):
@@ -52,36 +62,82 @@ async def upload_file(file: UploadFile = File(...), file_type: str = Form(...)):
52
  url = f"https://huggingface.co/datasets/{db.DATASET_REPO_ID}/resolve/main/{target_dir}/{safe_url_filename}"
53
  return {"status": "success", "url": url, "display_name": file.filename, "hashed_name": new_filename}
54
 
 
 
 
 
 
55
 
56
- # =========================================================
57
- # 【核心修复】:使用 huggingface_hub 官方库替代 urllib 解决重定向 401 问题
58
- # =========================================================
59
- from huggingface_hub import hf_hub_download
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
 
 
 
61
  class ProxyDownloadRequest(BaseModel):
62
  url: str
 
 
63
 
64
  @app.post("/api/proxy_download")
65
- async def proxy_download(req_data: ProxyDownloadRequest):
66
  target_url = req_data.url
67
-
68
- # 校验 URL 格式
69
  if not target_url or "resolve/main/" not in target_url:
70
  return JSONResponse(content={"error": "无效的 Hugging Face 下载链接"}, status_code=400)
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  hf_token = os.environ.get("HF_TOKEN")
73
- if not hf_token:
74
- return JSONResponse(content={"error": "云端环境变量未配置 HF_TOKEN,无法读取私有库"}, status_code=401)
75
 
76
  try:
77
- # 1. 从前端传来的 URL 中切割出仓库内的真实相对路径
78
- # 例如从 https://.../resolve/main/apps/123_%E6%B5%8B.json 提取出 apps/123_%E6%B5%8B.json
79
  repo_path_encoded = target_url.split("resolve/main/")[-1]
80
-
81
- # 2. 对可能存在的被编码的中文字符进行解码 (恢复为 apps/123_测.json)
82
  repo_path = urllib.parse.unquote(repo_path_encoded)
83
 
84
- # 3. 调用官方库直接从私有 Dataset 拉取文件(完美处理鉴权和重定向)
85
  cached_file_path = hf_hub_download(
86
  repo_id=db.DATASET_REPO_ID,
87
  repo_type="dataset",
@@ -89,7 +145,6 @@ async def proxy_download(req_data: ProxyDownloadRequest):
89
  token=hf_token
90
  )
91
 
92
- # 4. 读取缓存在云端的本地文件流并返回给前端
93
  with open(cached_file_path, "rb") as f:
94
  content = f.read()
95
 
 
1
  # ⚙️ 后端逻辑/核心服务端.py (Hugging Face Spaces app.py)
2
+ from fastapi import FastAPI, File, UploadFile, Form, Depends
3
  from fastapi.middleware.cors import CORSMiddleware
4
  from fastapi.responses import Response, JSONResponse
5
+ from sqlalchemy.orm import Session
6
+ from pydantic import BaseModel
7
+ from huggingface_hub import hf_hub_download, HfApi
8
  import hashlib
9
  import urllib.parse
10
  import urllib.request
11
  import os
12
  import 数据库连接 as db
13
 
 
14
  from router_users import router as users_router
15
  from router_items import router as items_router
16
  from router_comments import router as comments_router
17
  from router_messages import router as messages_router
18
+ from router_wallet import router as wallet_router
19
+ from database_sql import init_sql_db, get_db
20
+ from models_sql import Ownership
21
 
22
  app = FastAPI(title="ComfyUI Ranking Community API")
23
 
24
+ @app.on_event("startup")
25
+ def on_startup():
26
+ init_sql_db()
27
+ print("关系型数据库加载完毕,金融表同步完成。")
28
+
29
  app.add_middleware(
30
  CORSMiddleware,
31
  allow_origins=["*"],
 
38
  app.include_router(items_router)
39
  app.include_router(comments_router)
40
  app.include_router(messages_router)
41
+ app.include_router(wallet_router)
42
 
43
  @app.get("/")
44
  def read_root():
45
+ return {"status": "ok"}
46
 
47
  @app.post("/api/upload")
48
  async def upload_file(file: UploadFile = File(...), file_type: str = Form(...)):
 
62
  url = f"https://huggingface.co/datasets/{db.DATASET_REPO_ID}/resolve/main/{target_dir}/{safe_url_filename}"
63
  return {"status": "success", "url": url, "display_name": file.filename, "hashed_name": new_filename}
64
 
65
+ # =======================================================
66
+ # 【核心新增】:死链与资源有效性检测 (解决问题1:防买空)
67
+ # =======================================================
68
+ class ValidateRequest(BaseModel):
69
+ item_id: str
70
 
71
+ @app.post("/api/validate_resource")
72
+ async def validate_resource(req: ValidateRequest):
73
+ items_db = db.load_data("items.json", default_data=[])
74
+ item = next((i for i in items_db if i["id"] == req.item_id), None)
75
+ if not item:
76
+ return JSONResponse(content={"error": "该资源已被原作者删除"}, status_code=404)
77
+
78
+ link = item.get("link", "")
79
+ itype = item.get("type", "")
80
+
81
+ if itype.startswith("tool"):
82
+ # 探测 Git 仓库是否为 404 死链
83
+ try:
84
+ req_obj = urllib.request.Request(link, method="HEAD", headers={'User-Agent': 'Mozilla/5.0'})
85
+ with urllib.request.urlopen(req_obj, timeout=5) as response:
86
+ if response.status >= 400:
87
+ return JSONResponse(content={"error": "原作者的 Git 仓库已失效或设为私有"}, status_code=400)
88
+ except Exception as e:
89
+ return JSONResponse(content={"error": "原作者的 Git 仓库无法访问,链接已失效"}, status_code=400)
90
+
91
+ elif itype.startswith("app"):
92
+ # 探测 HF 云端的 JSON 文件是否丢失
93
+ if "resolve/main/" in link:
94
+ repo_path = urllib.parse.unquote(link.split("resolve/main/")[-1])
95
+ hf_token = os.environ.get("HF_TOKEN")
96
+ try:
97
+ api = HfApi()
98
+ exists = api.file_exists(repo_id=db.DATASET_REPO_ID, filename=repo_path, repo_type="dataset", token=hf_token)
99
+ if not exists:
100
+ return JSONResponse(content={"error": "该工作流的 JSON 文件已在云端损坏或丢失"}, status_code=400)
101
+ except Exception:
102
+ pass # 忽略 HF API 自身的偶发网络波动,不强行阻断
103
+
104
+ return {"status": "success"}
105
 
106
+ # =======================================================
107
+ # 代理下载与所有权鉴权防线
108
+ # =======================================================
109
  class ProxyDownloadRequest(BaseModel):
110
  url: str
111
+ item_id: str
112
+ account: str
113
 
114
  @app.post("/api/proxy_download")
115
+ async def proxy_download(req_data: ProxyDownloadRequest, sql_db: Session = Depends(get_db)):
116
  target_url = req_data.url
 
 
117
  if not target_url or "resolve/main/" not in target_url:
118
  return JSONResponse(content={"error": "无效的 Hugging Face 下载链接"}, status_code=400)
119
 
120
+ items_db = db.load_data("items.json", default_data=[])
121
+ item = next((i for i in items_db if i["id"] == req_data.item_id), None)
122
+
123
+ if not item: return JSONResponse(content={"error": "资源不存在或已被删除"}, status_code=404)
124
+
125
+ price = int(item.get("price", 0))
126
+ author = item.get("author")
127
+
128
+ # 所有权拦截:如果收费且不是作者本人,严查 SQL 所有权表
129
+ if price > 0 and req_data.account != author:
130
+ owned = sql_db.query(Ownership).filter(Ownership.account == req_data.account, Ownership.item_id == req_data.item_id).first()
131
+ if not owned:
132
+ return JSONResponse(content={"error": "🚨 非法下载:云端数据库未找到您的购买凭证!"}, status_code=403)
133
+
134
  hf_token = os.environ.get("HF_TOKEN")
135
+ if not hf_token: return JSONResponse(content={"error": "云端环境变量未配置 HF_TOKEN"}, status_code=401)
 
136
 
137
  try:
 
 
138
  repo_path_encoded = target_url.split("resolve/main/")[-1]
 
 
139
  repo_path = urllib.parse.unquote(repo_path_encoded)
140
 
 
141
  cached_file_path = hf_hub_download(
142
  repo_id=db.DATASET_REPO_ID,
143
  repo_type="dataset",
 
145
  token=hf_token
146
  )
147
 
 
148
  with open(cached_file_path, "rb") as f:
149
  content = f.read()
150
 
database_sql.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # database_sql.py
2
+ import os
3
+ from sqlalchemy import create_engine
4
+ from sqlalchemy.orm import sessionmaker
5
+ from models_sql import Base
6
+
7
+ # 优先读取环境变量中的 PostgreSQL 数据库连接,如果没有则使用 SQLite 降级方案
8
+ # 生产环境建议在 HF Spaces 的 Secrets 中配置 DATABASE_URL = postgresql://user:pass@host/dbname
9
+ SQLALCHEMY_DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:////tmp/comfy_financial.db")
10
+
11
+ # 如果是 SQLite,需要 check_same_thread=False
12
+ connect_args = {"check_same_thread": False} if "sqlite" in SQLALCHEMY_DATABASE_URL else {}
13
+
14
+ engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args=connect_args)
15
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
16
+
17
+ def init_sql_db():
18
+ # 启动时自动建表
19
+ Base.metadata.create_all(bind=engine)
20
+
21
+ def get_db():
22
+ db = SessionLocal()
23
+ try:
24
+ yield db
25
+ finally:
26
+ db.close()
models.py CHANGED
@@ -63,7 +63,7 @@ class ItemCreate(BaseModel):
63
  link: str
64
  coverUrl: Optional[str] = None
65
  author: str
66
- price: float = 0.0
67
 
68
  class FollowToggle(BaseModel):
69
  user_id: str
@@ -87,4 +87,20 @@ class ItemUpdate(BaseModel):
87
  fullDesc: Optional[str] = None
88
  link: Optional[str] = None
89
  coverUrl: Optional[str] = None
90
- price: Optional[float] = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  link: str
64
  coverUrl: Optional[str] = None
65
  author: str
66
+ price: int = 0
67
 
68
  class FollowToggle(BaseModel):
69
  user_id: str
 
87
  fullDesc: Optional[str] = None
88
  link: Optional[str] = None
89
  coverUrl: Optional[str] = None
90
+ price: Optional[int] = None
91
+
92
+ class RechargeRequest(BaseModel):
93
+ account: str
94
+ amount: int # 充值的积分数量
95
+ method: str # "alipay" 或 "wechat"
96
+
97
+ class WithdrawRequest(BaseModel):
98
+ account: str
99
+ amount: int # 提现积分数量
100
+ alipay_account: str
101
+ real_name: str
102
+ code: str # 邮箱安全验证码
103
+
104
+ class PurchaseRequest(BaseModel):
105
+ account: str
106
+ item_id: str
models_sql.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models_sql.py
2
+ from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float
3
+ from sqlalchemy.orm import declarative_base
4
+ import datetime
5
+
6
+ Base = declarative_base()
7
+
8
+ class Wallet(Base):
9
+ """用户钱包表"""
10
+ __tablename__ = "wallets"
11
+
12
+ account = Column(String, primary_key=True, index=True)
13
+ balance = Column(Integer, default=0) # 用于消费的余额 (充值获得)
14
+ earn_balance = Column(Integer, default=0) # 创作者收益余额 (别人购买获得)
15
+ frozen_balance = Column(Integer, default=0) # 提现审核冻结中的余额
16
+
17
+ # 乐观锁版本号,防止并发扣款被击穿
18
+ version = Column(Integer, default=1)
19
+
20
+ class Ownership(Base):
21
+ """资源所有权表 (解决Req 4:一次购买,终身免费)"""
22
+ __tablename__ = "ownerships"
23
+
24
+ id = Column(Integer, primary_key=True, autoincrement=True)
25
+ account = Column(String, index=True)
26
+ item_id = Column(String, index=True)
27
+ purchased_at = Column(DateTime, default=datetime.datetime.utcnow)
28
+
29
+ class Transaction(Base):
30
+ """交易流水表 (复式记账,解决Req 6, 7:税务对账与哈希防篡改)"""
31
+ __tablename__ = "transactions"
32
+
33
+ tx_id = Column(String, primary_key=True) # 全局唯一流水号 (如 ORDER_uuid)
34
+ account = Column(String, index=True)
35
+ tx_type = Column(String) # RECHARGE, CONSUME, EARN, WITHDRAW
36
+ amount = Column(Integer) # 交易额 (正负值)
37
+ target_id = Column(String, nullable=True) # 关联的 item_id 或第三方支付宝流水号
38
+
39
+ # 区块链级安全:哈希防篡改链
40
+ prev_hash = Column(String, nullable=True) # 上一笔交易的哈希值
41
+ tx_hash = Column(String) # 当前交易哈希值
42
+
43
+ created_at = Column(DateTime, default=datetime.datetime.utcnow)
requirements.txt CHANGED
@@ -4,4 +4,5 @@ pydantic
4
  huggingface_hub
5
  datasets
6
  python-multipart
7
- alibabacloud_dysmsapi20170525==2.0.24
 
 
4
  huggingface_hub
5
  datasets
6
  python-multipart
7
+ alibabacloud_dysmsapi20170525==2.0.24
8
+ sqlalchemy
router_items.py CHANGED
@@ -94,6 +94,10 @@ async def get_creators(sort: str = "downloads", limit: int = 20):
94
 
95
  @router.post("/api/items")
96
  async def create_item(item: ItemCreate):
 
 
 
 
97
  items_db = db.load_data("items.json", default_data=[])
98
  new_item = {
99
  "id": f"{item.type}_{int(time.time())}_{uuid.uuid4().hex[:6]}", "type": item.type, "title": item.title, "author": item.author,
@@ -106,6 +110,10 @@ async def create_item(item: ItemCreate):
106
 
107
  @router.put("/api/items/{item_id}")
108
  async def update_item(item_id: str, update_data: ItemUpdate, author: str):
 
 
 
 
109
  items_db = db.load_data("items.json", default_data=[])
110
  for item in items_db:
111
  if item["id"] == item_id:
 
94
 
95
  @router.post("/api/items")
96
  async def create_item(item: ItemCreate):
97
+ # 【安全新增】:禁止负向消费防线
98
+ if item.price < 0:
99
+ raise HTTPException(status_code=400, detail="🚨 安全拦截:商品价格不能为负数")
100
+
101
  items_db = db.load_data("items.json", default_data=[])
102
  new_item = {
103
  "id": f"{item.type}_{int(time.time())}_{uuid.uuid4().hex[:6]}", "type": item.type, "title": item.title, "author": item.author,
 
110
 
111
  @router.put("/api/items/{item_id}")
112
  async def update_item(item_id: str, update_data: ItemUpdate, author: str):
113
+ # 【安全新增】:禁止负向消费防线
114
+ if update_data.price is not None and update_data.price < 0:
115
+ raise HTTPException(status_code=400, detail="🚨 安全拦截:商品价格不能为负数")
116
+
117
  items_db = db.load_data("items.json", default_data=[])
118
  for item in items_db:
119
  if item["id"] == item_id:
router_wallet.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # router_wallet.py
2
+ from fastapi import APIRouter, Depends, HTTPException
3
+ from sqlalchemy.orm import Session
4
+ import time
5
+ import uuid
6
+ import hashlib
7
+ from database_sql import get_db
8
+ from models_sql import Wallet, Transaction, Ownership
9
+ from models import RechargeRequest, WithdrawRequest, PurchaseRequest
10
+ import 数据库连接 as json_db
11
+ from router_users import VERIFY_CODES
12
+
13
+ router = APIRouter()
14
+
15
+ def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
16
+ """计算哈希值:依赖前一笔订单的哈希,实现链式防篡改"""
17
+ data = f"{tx_id}{account}{tx_type}{amount}{prev_hash}"
18
+ return hashlib.sha256(data.encode()).hexdigest()
19
+
20
+ def record_transaction(db: Session, account: str, tx_type: str, amount: int, target_id: str = None):
21
+ """安全记录交易流水"""
22
+ last_tx = db.query(Transaction).filter(Transaction.account == account).order_by(Transaction.created_at.desc()).first()
23
+ prev_hash = last_tx.tx_hash if last_tx else "GENESIS"
24
+
25
+ tx_id = f"TX_{int(time.time())}_{uuid.uuid4().hex[:8]}"
26
+ tx_hash = calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash)
27
+
28
+ new_tx = Transaction(tx_id=tx_id, account=account, tx_type=tx_type, amount=amount, target_id=target_id, prev_hash=prev_hash, tx_hash=tx_hash)
29
+ db.add(new_tx)
30
+ return new_tx
31
+
32
+ @router.post("/api/wallet/recharge")
33
+ async def recharge_points(req: RechargeRequest, db: Session = Depends(get_db)):
34
+ """充值接口"""
35
+ wallet = db.query(Wallet).filter(Wallet.account == req.account).first()
36
+ if not wallet:
37
+ wallet = Wallet(account=req.account)
38
+ db.add(wallet)
39
+
40
+ # 模拟支付成功后直接给用户加钱
41
+ wallet.balance += req.amount
42
+ record_transaction(db, req.account, "RECHARGE", req.amount, "MOCK_ALIPAY_ORDER")
43
+
44
+ db.commit()
45
+ return {"status": "success", "balance": wallet.balance}
46
+
47
+ @router.post("/api/wallet/purchase")
48
+ async def purchase_item(req: PurchaseRequest, db: Session = Depends(get_db)):
49
+ """消费购买接口:严密的并发防刷控制,以及早鸟保护机制"""
50
+ items_db = json_db.load_data("items.json", default_data=[])
51
+ item = next((i for i in items_db if i["id"] == req.item_id), None)
52
+ if not item: raise HTTPException(status_code=404, detail="商品不存在")
53
+
54
+ price = int(item.get("price", 0))
55
+ author = item.get("author")
56
+
57
+ # 1. 优先检查是否已经购买或获取过 (核心:无论后续怎么改价,只要所有权表里有你,直接放行)
58
+ owned = db.query(Ownership).filter(Ownership.account == req.account, Ownership.item_id == req.item_id).first()
59
+ if owned: return {"status": "success", "message": "已永久授权,无需重复扣费"}
60
+
61
+ # ==========================================
62
+ # 2. 【核心修改】免费商品或创作者本人的“0元购”固化
63
+ # ==========================================
64
+ if price <= 0 or req.account == author:
65
+ # 直接写入所有权表,赋予永久权限
66
+ new_owner = Ownership(account=req.account, item_id=req.item_id)
67
+ db.add(new_owner)
68
+ # 记录一笔金额为 0 的流水,保证账本的完整溯源
69
+ record_transaction(db, req.account, "CONSUME", 0, req.item_id)
70
+ db.commit()
71
+ return {"status": "success", "message": "免费商品或创作者本人,已永久授权"}
72
+
73
+ # 3. 开启数据库锁防止并发重复扣款 (排他锁)
74
+ buyer_wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
75
+ if not buyer_wallet or buyer_wallet.balance < price:
76
+ raise HTTPException(status_code=400, detail="余额不足,请先充值")
77
+
78
+ # 4. 执行扣费
79
+ buyer_wallet.balance -= price
80
+ record_transaction(db, req.account, "CONSUME", -price, req.item_id)
81
+
82
+ # 5. 记录所有权
83
+ new_owner = Ownership(account=req.account, item_id=req.item_id)
84
+ db.add(new_owner)
85
+
86
+ # 6. 卖家获得收益
87
+ author_wallet = db.query(Wallet).filter(Wallet.account == author).with_for_update().first()
88
+ if not author_wallet:
89
+ author_wallet = Wallet(account=author)
90
+ db.add(author_wallet)
91
+
92
+ author_wallet.earn_balance += price
93
+ record_transaction(db, author, "EARN", price, req.item_id)
94
+
95
+ db.commit()
96
+ return {"status": "success"}
97
+
98
+ @router.post("/api/wallet/withdraw")
99
+ async def withdraw_earnings(req: WithdrawRequest, db: Session = Depends(get_db)):
100
+ """提现接口"""
101
+ users_db = json_db.load_data("users.json", default_data={})
102
+ user = users_db.get(req.account)
103
+ if not user: raise HTTPException(status_code=404, detail="账号异常")
104
+
105
+ cache_key = f"{user.get('email')}_withdraw"
106
+ cached = VERIFY_CODES.get(cache_key)
107
+ if not cached or cached["code"] != req.code or int(time.time()) > cached["expires_at"]:
108
+ raise HTTPException(status_code=400, detail="验证码��正确或已过期")
109
+
110
+ wallet = db.query(Wallet).filter(Wallet.account == req.account).with_for_update().first()
111
+ if not wallet or wallet.earn_balance < req.amount:
112
+ raise HTTPException(status_code=400, detail="可提现收益不足")
113
+
114
+ if req.amount < 100:
115
+ raise HTTPException(status_code=400, detail="最低提现金额为 100 积分")
116
+
117
+ wallet.earn_balance -= req.amount
118
+ wallet.frozen_balance += req.amount
119
+ record_transaction(db, req.account, "WITHDRAW_FREEZE", -req.amount, req.alipay_account)
120
+
121
+ VERIFY_CODES.pop(cache_key, None)
122
+
123
+ db.commit()
124
+ return {"status": "success", "earn_balance": wallet.earn_balance}