ZHIWEI666 commited on
Commit
d5dffcf
·
verified ·
1 Parent(s): c00dff0

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +11 -353
  2. models.py +65 -0
  3. notifications.py +29 -0
  4. routers.py +275 -0
app.py CHANGED
@@ -1,15 +1,12 @@
1
  # ⚙️ 后端逻辑/核心服务端.py (Hugging Face Spaces app.py)
2
- from fastapi import FastAPI, HTTPException, File, UploadFile, Form
3
  from fastapi.middleware.cors import CORSMiddleware
4
- from pydantic import BaseModel
5
- from typing import Optional
6
- import time
7
- import uuid
8
  import hashlib
9
-
10
- # 导入数据库操作模块
11
  import 数据库连接 as db
12
 
 
 
 
13
  app = FastAPI(title="ComfyUI Ranking Community API")
14
 
15
  app.add_middleware(
@@ -20,361 +17,22 @@ app.add_middleware(
20
  allow_headers=["*"],
21
  )
22
 
 
 
 
23
  @app.get("/")
24
- def read_root():
25
- return {"status": "ok", "message": "ComfyUI Ranking API is running!"}
26
 
27
  @app.post("/api/upload")
28
  async def upload_file(file: UploadFile = File(...), file_type: str = Form(...)):
29
  content = await file.read()
30
  file_hash = hashlib.md5(content).hexdigest()[:10]
31
- original_name = file.filename
32
- new_filename = f"{file_hash}_{original_name}"
33
-
34
  dir_mapping = {"avatar": "avatars", "cover": "covers", "tool": "tools", "app": "apps"}
35
  target_dir = dir_mapping.get(file_type, "others")
36
  full_path_in_repo = f"{target_dir}/{new_filename}"
37
 
38
  db.save_file(full_path_in_repo, content)
39
  url = f"https://huggingface.co/datasets/{db.DATASET_REPO_ID}/resolve/main/{full_path_in_repo}"
40
-
41
- return {
42
- "status": "success", "url": url,
43
- "display_name": original_name, "hashed_name": new_filename
44
- }
45
-
46
- # --- 数据模型 ---
47
- class UserRegister(BaseModel):
48
- account: str
49
- password: str
50
- email: str
51
- phone: str
52
- name: str
53
- gender: str
54
- avatarDataUrl: Optional[str] = None
55
- age: Optional[int] = None
56
- country: Optional[str] = None
57
- region: Optional[str] = None
58
- intro: Optional[str] = None
59
-
60
- class UserLogin(BaseModel):
61
- account: str
62
- password: str
63
-
64
- class UserUpdate(BaseModel):
65
- name: Optional[str] = None
66
- intro: Optional[str] = None
67
- age: Optional[int] = None
68
- country: Optional[str] = None
69
- region: Optional[str] = None
70
- gender: Optional[str] = None
71
- avatarDataUrl: Optional[str] = None
72
-
73
- class PasswordReset(BaseModel):
74
- old_password: str
75
- new_password: str
76
-
77
- class InteractionToggle(BaseModel):
78
- item_id: str
79
- user_id: str
80
- action_type: str
81
- is_active: bool
82
-
83
- class CommentCreate(BaseModel):
84
- item_id: str
85
- author: str
86
- content: str
87
- reply_to_user: Optional[str] = None
88
- parent_id: Optional[str] = None
89
-
90
- class ItemCreate(BaseModel):
91
- type: str
92
- title: str
93
- shortDesc: str
94
- fullDesc: str
95
- link: str
96
- coverUrl: Optional[str] = None
97
- author: str
98
- price: float = 0.0
99
-
100
- class FollowToggle(BaseModel):
101
- user_id: str
102
- target_account: str
103
- is_active: bool
104
-
105
- # 【核心新增】:私信数据模型
106
- class PrivateMessage(BaseModel):
107
- sender: str
108
- receiver: str
109
- content: str
110
-
111
- # --- 消息分发引擎 ---
112
- def add_notification(target_account: str, notif_data: dict):
113
- from_user = notif_data.get("from_user")
114
- if target_account == from_user or not target_account:
115
- return # 不给自己发通知
116
-
117
- users_db = db.load_data("users.json", default_data={})
118
- user_info = users_db.get(from_user, {})
119
-
120
- notif = {
121
- "id": f"msg_{int(time.time())}_{uuid.uuid4().hex[:6]}",
122
- "type": notif_data.get("type"), # like, favorite, comment, reply, follow, private
123
- "from_user": from_user,
124
- "from_name": user_info.get("name", from_user),
125
- "from_avatar": user_info.get("avatarDataUrl", "https://via.placeholder.com/150"),
126
- "target_item_id": notif_data.get("target_item_id", ""),
127
- "target_item_title": notif_data.get("target_item_title", ""),
128
- "content": notif_data.get("content", ""),
129
- "is_read": False,
130
- "created_at": int(time.time())
131
- }
132
-
133
- msgs_db = db.load_data("messages.json", default_data={})
134
- if target_account not in msgs_db: msgs_db[target_account] = []
135
- msgs_db[target_account].insert(0, notif)
136
- db.save_data("messages.json", msgs_db)
137
-
138
- # --- 1. 用户系统 ---
139
- @app.post("/api/users/register")
140
- async def register_user(user: UserRegister):
141
- users_db = db.load_data("users.json", default_data={})
142
- if user.account in users_db: raise HTTPException(status_code=400, detail="该账号已被注册,请尝试其他账号名")
143
- for existing_user in users_db.values():
144
- if existing_user.get("email") == user.email: raise HTTPException(status_code=400, detail="该邮箱已被绑定")
145
- if existing_user.get("phone") == user.phone: raise HTTPException(status_code=400, detail="该手机号已被绑定")
146
-
147
- new_user = user.dict()
148
- new_user.update({
149
- "receivedLikes": 0, "receivedFollows": 0, "created_at": int(time.time()), "followers": [], "following": [],
150
- "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}
151
- })
152
-
153
- users_db[user.account] = new_user
154
- db.save_data("users.json", users_db)
155
- return {"status": "success", "message": "注册成功", "data": {k: v for k, v in new_user.items() if k != "password"}}
156
-
157
- @app.post("/api/users/login")
158
- async def login_user(user: UserLogin):
159
- users_db = db.load_data("users.json", default_data={})
160
- if user.account not in users_db: raise HTTPException(status_code=404, detail="账号不存在")
161
- user_data = users_db[user.account]
162
- if user_data.get("password") != user.password: raise HTTPException(status_code=401, detail="密码错误")
163
- 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")}
164
-
165
- @app.get("/api/users/{account}")
166
- async def get_user_profile(account: str):
167
- users_db = db.load_data("users.json", default_data={})
168
- if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
169
- user_data = users_db[account]
170
-
171
- items_db = db.load_data("items.json", default_data=[])
172
- user_items = [item for item in items_db if item.get("author") == account]
173
- user_data["receivedLikes"] = sum(item.get("likes", 0) for item in user_items)
174
- user_data["receivedFavorites"] = sum(item.get("favorites", 0) for item in user_items)
175
- user_data["receivedUses"] = sum(item.get("uses", 0) for item in user_items)
176
- return {"status": "success", "data": {k: v for k, v in user_data.items() if k != "password"}}
177
-
178
- @app.put("/api/users/{account}")
179
- async def update_user_profile(account: str, update_data: UserUpdate):
180
- users_db = db.load_data("users.json", default_data={})
181
- if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
182
- user = users_db[account]
183
- update_dict = update_data.dict(exclude_unset=True)
184
- for k, v in update_dict.items():
185
- if v is not None: user[k] = v
186
- db.save_data("users.json", users_db)
187
- return {"status": "success", "data": {k: v for k, v in user.items() if k != "password"}}
188
-
189
- @app.post("/api/users/{account}/reset-password")
190
- async def reset_password(account: str, pwd_data: PasswordReset):
191
- users_db = db.load_data("users.json", default_data={})
192
- if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
193
- user = users_db[account]
194
- if user.get("password") != pwd_data.old_password: raise HTTPException(status_code=401, detail="原密码错误")
195
- user["password"] = pwd_data.new_password
196
- db.save_data("users.json", users_db)
197
- return {"status": "success"}
198
-
199
- @app.post("/api/users/follow")
200
- async def toggle_follow(follow: FollowToggle):
201
- users_db = db.load_data("users.json", default_data={})
202
- if follow.target_account not in users_db or follow.user_id not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
203
- target_followers = users_db[follow.target_account].setdefault("followers", [])
204
- current_following = users_db[follow.user_id].setdefault("following", [])
205
-
206
- if follow.is_active:
207
- if follow.user_id not in target_followers:
208
- target_followers.append(follow.user_id)
209
- add_notification(follow.target_account, {"type": "follow", "from_user": follow.user_id})
210
- if follow.target_account not in current_following: current_following.append(follow.target_account)
211
- else:
212
- if follow.user_id in target_followers: target_followers.remove(follow.user_id)
213
- if follow.target_account in current_following: current_following.remove(follow.target_account)
214
- db.save_data("users.json", users_db)
215
- return {"status": "success"}
216
-
217
- # --- 2. 排行榜核心数据流 ---
218
- @app.get("/api/items")
219
- async def get_items(type: str = "tool", sort: str = "time", limit: int = 20):
220
- items_db = db.load_data("items.json", default_data=[])
221
- comments_db = db.load_data("comments.json", default_data={})
222
-
223
- filtered_items = [item for item in items_db if item.get("type") == type]
224
- for item in filtered_items:
225
- item["commentsData"] = comments_db.get(item["id"], [])
226
- item["comments"] = len(item["commentsData"])
227
-
228
- if sort == "likes": filtered_items.sort(key=lambda x: x.get("likes", 0), reverse=True)
229
- elif sort == "favorites": filtered_items.sort(key=lambda x: x.get("favorites", 0), reverse=True)
230
- elif sort == "downloads": filtered_items.sort(key=lambda x: x.get("uses", 0), reverse=True)
231
- else: filtered_items.sort(key=lambda x: x.get("created_at", 0), reverse=True)
232
- return {"status": "success", "data": filtered_items[:limit]}
233
-
234
- @app.get("/api/creators")
235
- async def get_creators(sort: str = "downloads", limit: int = 20):
236
- users_db = db.load_data("users.json", default_data={})
237
- items_db = db.load_data("items.json", default_data=[])
238
- comments_db = db.load_data("comments.json", default_data={})
239
-
240
- creators = []
241
- for account, u in users_db.items():
242
- u_items = [i for i in items_db if i.get("author") == account]
243
- likes = sum(i.get("likes", 0) for i in u_items)
244
- favorites = sum(i.get("favorites", 0) for i in u_items)
245
- uses = sum(i.get("uses", 0) for i in u_items)
246
- tools_count = sum(1 for i in u_items if i.get("type") == "tool")
247
- apps_count = sum(1 for i in u_items if i.get("type") == "app")
248
-
249
- creators.append({
250
- "account": account, "name": u.get("name", account), "avatar": u.get("avatarDataUrl", "https://via.placeholder.com/150"),
251
- "shortDesc": u.get("intro", "这个人很懒,什么都没写..."), "fullDesc": u.get("intro", "这个人很懒,什么都没写..."),
252
- "likes": likes, "favorites": favorites, "downloads": uses, "toolsCount": tools_count, "appsCount": apps_count,
253
- "followers": len(u.get("followers", [])), "created_at": u.get("created_at", 0),
254
- "commentsData": comments_db.get(account, []),
255
- "trendData": {"tools": [0,0,0,0,0, tools_count], "apps": [0,0,0,0,0, apps_count]}
256
- })
257
-
258
- if sort == "likes": creators.sort(key=lambda x: x.get("likes", 0), reverse=True)
259
- elif sort == "favorites": creators.sort(key=lambda x: x.get("favorites", 0), reverse=True)
260
- elif sort == "downloads": creators.sort(key=lambda x: x.get("downloads", 0), reverse=True)
261
- else: creators.sort(key=lambda x: x.get("created_at", 0), reverse=True)
262
- return {"status": "success", "data": creators[:limit]}
263
-
264
- @app.post("/api/items")
265
- async def create_item(item: ItemCreate):
266
- items_db = db.load_data("items.json", default_data=[])
267
- new_id = f"{item.type}_{int(time.time())}_{uuid.uuid4().hex[:6]}"
268
- new_item = {
269
- "id": new_id, "type": item.type, "title": item.title, "author": item.author,
270
- "shortDesc": item.shortDesc, "fullDesc": item.fullDesc, "link": item.link,
271
- "coverUrl": item.coverUrl, "price": item.price, "likes": 0, "favorites": 0,
272
- "comments": 0, "uses": 0, "created_at": int(time.time()), "liked_by": [], "favorited_by": []
273
- }
274
- items_db.insert(0, new_item)
275
- db.save_data("items.json", items_db)
276
- return {"status": "success", "data": new_item}
277
-
278
- # --- 3. 社交与互动 (点赞/收藏防抖与通知派发) ---
279
- @app.post("/api/interactions/toggle")
280
- async def toggle_interaction(interaction: InteractionToggle):
281
- items_db = db.load_data("items.json", default_data=[])
282
- target_item = next((item for item in items_db if item["id"] == interaction.item_id), None)
283
- if not target_item: raise HTTPException(status_code=404, detail="目标存在问题")
284
-
285
- list_key = f"{interaction.action_type}d_by"
286
- count_key = "likes" if interaction.action_type == "like" else "favorites"
287
- if list_key not in target_item: target_item[list_key] = []; target_item[count_key] = 0
288
-
289
- user_list = target_item[list_key]
290
- if interaction.is_active:
291
- if interaction.user_id not in user_list:
292
- user_list.append(interaction.user_id)
293
- target_item[count_key] += 1
294
- add_notification(target_item.get("author", ""), {
295
- "type": interaction.action_type, "from_user": interaction.user_id,
296
- "target_item_id": target_item["id"], "target_item_title": target_item["title"]
297
- })
298
- else:
299
- if interaction.user_id in user_list:
300
- user_list.remove(interaction.user_id)
301
- target_item[count_key] = max(0, target_item[count_key] - 1)
302
-
303
- db.save_data("items.json", items_db)
304
- return {"status": "success", "new_count": target_item[count_key]}
305
-
306
- # --- 4. 评论系统与通知 ---
307
- @app.post("/api/comments")
308
- async def post_comment(comment: CommentCreate):
309
- comments_db = db.load_data("comments.json", default_data={})
310
- users_db = db.load_data("users.json", default_data={})
311
-
312
- author_info = users_db.get(comment.author, {})
313
- reply_name = users_db.get(comment.reply_to_user, {}).get("name", comment.reply_to_user) if comment.reply_to_user else None
314
-
315
- item_comments = comments_db.get(comment.item_id, [])
316
- new_comment = {
317
- "id": f"c_{int(time.time())}_{uuid.uuid4().hex[:6]}", "author": comment.author,
318
- "authorName": author_info.get("name", comment.author), "avatar": author_info.get("avatarDataUrl", "https://via.placeholder.com/150"),
319
- "content": comment.content, "replyToUser": comment.reply_to_user, "replyToUserName": reply_name,
320
- "isDeleted": False, "replies": [], "created_at": int(time.time())
321
- }
322
-
323
- if comment.parent_id:
324
- parent = next((c for c in item_comments if c["id"] == comment.parent_id), None)
325
- if parent:
326
- parent["replies"].append(new_comment)
327
- if comment.reply_to_user:
328
- add_notification(comment.reply_to_user, {"type": "reply", "from_user": comment.author, "target_item_id": comment.item_id, "target_item_title": "收到新的回复", "content": comment.content})
329
- else: raise HTTPException(status_code=404, detail="找不到要回复的父级评论")
330
- else:
331
- item_comments.append(new_comment)
332
- items_db = db.load_data("items.json", default_data=[])
333
- target_item = next((item for item in items_db if item["id"] == comment.item_id), None)
334
- if target_item:
335
- add_notification(target_item.get("author", ""), {"type": "comment", "from_user": comment.author, "target_item_id": comment.item_id, "target_item_title": target_item["title"], "content": comment.content})
336
- elif comment.item_id in users_db:
337
- add_notification(comment.item_id, {"type": "comment", "from_user": comment.author, "target_item_id": comment.item_id, "target_item_title": "您的个人留言板", "content": comment.content})
338
-
339
- comments_db[comment.item_id] = item_comments
340
- db.save_data("comments.json", comments_db)
341
- return {"status": "success", "data": new_comment}
342
-
343
- @app.delete("/api/comments/{item_id}/{comment_id}")
344
- async def soft_delete_comment(item_id: str, comment_id: str, author: str):
345
- comments_db = db.load_data("comments.json", default_data={})
346
- item_comments = comments_db.get(item_id, [])
347
-
348
- def find_and_delete(comments_list):
349
- for c in comments_list:
350
- if c["id"] == comment_id:
351
- if c["author"] != author: raise HTTPException(status_code=403, detail="无权删除他人的评论")
352
- c["isDeleted"] = True; c["content"] = ""; return True
353
- if "replies" in c and find_and_delete(c["replies"]): return True
354
- return False
355
-
356
- if find_and_delete(item_comments):
357
- db.save_data("comments.json", comments_db)
358
- return {"status": "success", "message": "评论已删除"}
359
- raise HTTPException(status_code=404, detail="找不到该评论")
360
-
361
- @app.post("/api/messages/private")
362
- async def send_private_message(msg: PrivateMessage):
363
- add_notification(msg.receiver, {"type": "private", "from_user": msg.sender, "content": msg.content})
364
- return {"status": "success"}
365
-
366
- @app.get("/api/messages/{account}")
367
- async def get_messages(account: str):
368
- msgs_db = db.load_data("messages.json", default_data={})
369
- user_msgs = msgs_db.get(account, [])
370
- unread_count = sum(1 for m in user_msgs if not m.get("is_read"))
371
- return {"status": "success", "data": user_msgs, "unread_count": unread_count}
372
-
373
- @app.post("/api/messages/{account}/read")
374
- async def mark_messages_read(account: str):
375
- msgs_db = db.load_data("messages.json", default_data={})
376
- user_msgs = msgs_db.get(account, [])
377
- for m in user_msgs: m["is_read"] = True
378
- msgs_db[account] = user_msgs
379
- db.save_data("messages.json", msgs_db)
380
- return {"status": "success"}
 
1
  # ⚙️ 后端逻辑/核心服务端.py (Hugging Face Spaces app.py)
2
+ from fastapi import FastAPI, File, UploadFile, Form
3
  from fastapi.middleware.cors import CORSMiddleware
 
 
 
 
4
  import hashlib
 
 
5
  import 数据库连接 as db
6
 
7
+ # 引入抽离出去的业务路由
8
+ from routers import router
9
+
10
  app = FastAPI(title="ComfyUI Ranking Community API")
11
 
12
  app.add_middleware(
 
17
  allow_headers=["*"],
18
  )
19
 
20
+ # 挂载业务路由
21
+ app.include_router(router)
22
+
23
  @app.get("/")
24
+ def read_root():
25
+ return {"status": "ok", "message": "API is running and fully modularized!"}
26
 
27
  @app.post("/api/upload")
28
  async def upload_file(file: UploadFile = File(...), file_type: str = Form(...)):
29
  content = await file.read()
30
  file_hash = hashlib.md5(content).hexdigest()[:10]
31
+ new_filename = f"{file_hash}_{file.filename}"
 
 
32
  dir_mapping = {"avatar": "avatars", "cover": "covers", "tool": "tools", "app": "apps"}
33
  target_dir = dir_mapping.get(file_type, "others")
34
  full_path_in_repo = f"{target_dir}/{new_filename}"
35
 
36
  db.save_file(full_path_in_repo, content)
37
  url = f"https://huggingface.co/datasets/{db.DATASET_REPO_ID}/resolve/main/{full_path_in_repo}"
38
+ return {"status": "success", "url": url, "display_name": file.filename, "hashed_name": new_filename}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
models.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import Optional
3
+
4
+ class UserRegister(BaseModel):
5
+ account: str
6
+ password: str
7
+ email: str
8
+ phone: str
9
+ name: str
10
+ gender: str
11
+ avatarDataUrl: Optional[str] = None
12
+ age: Optional[int] = None
13
+ country: Optional[str] = None
14
+ region: Optional[str] = None
15
+ intro: Optional[str] = None
16
+
17
+ class UserLogin(BaseModel):
18
+ account: str
19
+ password: str
20
+
21
+ class UserUpdate(BaseModel):
22
+ name: Optional[str] = None
23
+ intro: Optional[str] = None
24
+ age: Optional[int] = None
25
+ country: Optional[str] = None
26
+ region: Optional[str] = None
27
+ gender: Optional[str] = None
28
+ avatarDataUrl: Optional[str] = None
29
+
30
+ class PasswordReset(BaseModel):
31
+ old_password: str
32
+ new_password: str
33
+
34
+ class InteractionToggle(BaseModel):
35
+ item_id: str
36
+ user_id: str
37
+ action_type: str
38
+ is_active: bool
39
+
40
+ class CommentCreate(BaseModel):
41
+ item_id: str
42
+ author: str
43
+ content: str
44
+ reply_to_user: Optional[str] = None
45
+ parent_id: Optional[str] = None
46
+
47
+ class ItemCreate(BaseModel):
48
+ type: str
49
+ title: str
50
+ shortDesc: str
51
+ fullDesc: str
52
+ link: str
53
+ coverUrl: Optional[str] = None
54
+ author: str
55
+ price: float = 0.0
56
+
57
+ class FollowToggle(BaseModel):
58
+ user_id: str
59
+ target_account: str
60
+ is_active: bool
61
+
62
+ class PrivateMessage(BaseModel):
63
+ sender: str
64
+ receiver: str
65
+ content: str
notifications.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import uuid
3
+ import 数据库连接 as db
4
+
5
+ def add_notification(target_account: str, notif_data: dict):
6
+ from_user = notif_data.get("from_user")
7
+ if target_account == from_user or not target_account:
8
+ return # 不给自己发通知
9
+
10
+ users_db = db.load_data("users.json", default_data={})
11
+ user_info = users_db.get(from_user, {})
12
+
13
+ notif = {
14
+ "id": f"msg_{int(time.time())}_{uuid.uuid4().hex[:6]}",
15
+ "type": notif_data.get("type"),
16
+ "from_user": from_user,
17
+ "from_name": user_info.get("name", from_user),
18
+ "from_avatar": user_info.get("avatarDataUrl", "https://via.placeholder.com/150"),
19
+ "target_item_id": notif_data.get("target_item_id", ""),
20
+ "target_item_title": notif_data.get("target_item_title", ""),
21
+ "content": notif_data.get("content", ""),
22
+ "is_read": False,
23
+ "created_at": int(time.time())
24
+ }
25
+
26
+ msgs_db = db.load_data("messages.json", default_data={})
27
+ if target_account not in msgs_db: msgs_db[target_account] = []
28
+ msgs_db[target_account].insert(0, notif)
29
+ db.save_data("messages.json", msgs_db)
routers.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ import time
3
+ import uuid
4
+ import re
5
+ import 数据库连接 as db
6
+ from notifications import add_notification
7
+ from models import (UserRegister, UserLogin, UserUpdate, PasswordReset,
8
+ InteractionToggle, CommentCreate, ItemCreate,
9
+ FollowToggle, PrivateMessage)
10
+
11
+ router = APIRouter()
12
+
13
+ # --- 1. 用户系统 ---
14
+ @router.post("/api/users/register")
15
+ async def register_user(user: UserRegister):
16
+ users_db = db.load_data("users.json", default_data={})
17
+ if len(user.account) <= 5: raise HTTPException(status_code=400, detail="账号必须大于5个字符")
18
+ if not re.match(r'^[a-zA-Z0-9_]{6,20}$', user.account): raise HTTPException(status_code=400, detail="账号仅支持大小写英文字母、数字及下划线")
19
+ if len(user.password) < 6: raise HTTPException(status_code=400, detail="密码必须大于等于6个字符")
20
+ if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', user.password): raise HTTPException(status_code=400, detail="密码包含不支持的特殊字符")
21
+ if user.intro and len(user.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
22
+ if user.account in users_db: raise HTTPException(status_code=400, detail="该账号已被注册")
23
+ for existing_user in users_db.values():
24
+ if existing_user.get("email") == user.email: raise HTTPException(status_code=400, detail="该邮箱已被绑定")
25
+ if existing_user.get("phone") == user.phone: raise HTTPException(status_code=400, detail="该手机号已被绑定")
26
+
27
+ new_user = user.dict()
28
+ new_user.update({"created_at": int(time.time()), "followers": [], "following": [], "privacy": {"follows": False, "likes": False, "favorites": False, "downloads": False}})
29
+ users_db[user.account] = new_user
30
+ db.save_data("users.json", users_db)
31
+ return {"status": "success", "message": "注册成功", "data": {k: v for k, v in new_user.items() if k != "password"}}
32
+
33
+ @router.post("/api/users/login")
34
+ async def login_user(user: UserLogin):
35
+ users_db = db.load_data("users.json", default_data={})
36
+ if user.account not in users_db: raise HTTPException(status_code=404, detail="账号不存在")
37
+ user_data = users_db[user.account]
38
+ if user_data.get("password") != user.password: raise HTTPException(status_code=401, detail="密码错误")
39
+ 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")}
40
+
41
+ @router.get("/api/users/{account}")
42
+ async def get_user_profile(account: str):
43
+ users_db = db.load_data("users.json", default_data={})
44
+ if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
45
+ user_data = users_db[account]
46
+ items_db = db.load_data("items.json", default_data=[])
47
+ user_items = [item for item in items_db if item.get("author") == account]
48
+ user_data["receivedLikes"] = sum(item.get("likes", 0) for item in user_items)
49
+ user_data["receivedFavorites"] = sum(item.get("favorites", 0) for item in user_items)
50
+ user_data["receivedUses"] = sum(item.get("uses", 0) for item in user_items)
51
+ return {"status": "success", "data": {k: v for k, v in user_data.items() if k != "password"}}
52
+
53
+ @router.put("/api/users/{account}")
54
+ async def update_user_profile(account: str, update_data: UserUpdate):
55
+ users_db = db.load_data("users.json", default_data={})
56
+ if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
57
+ if update_data.intro and len(update_data.intro) > 100: raise HTTPException(status_code=400, detail="个人介绍不能超过100个字符")
58
+ user = users_db[account]
59
+ for k, v in update_data.dict(exclude_unset=True).items():
60
+ if v is not None: user[k] = v
61
+ db.save_data("users.json", users_db)
62
+ return {"status": "success", "data": {k: v for k, v in user.items() if k != "password"}}
63
+
64
+ @router.post("/api/users/{account}/reset-password")
65
+ async def reset_password(account: str, pwd_data: PasswordReset):
66
+ users_db = db.load_data("users.json", default_data={})
67
+ if account not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
68
+ if len(pwd_data.new_password) < 6: raise HTTPException(status_code=400, detail="新密码必须大于等于6个字符")
69
+ if not re.match(r'^[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]{6,}$', pwd_data.new_password): raise HTTPException(status_code=400, detail="新密码包含不支持的特殊字符")
70
+ user = users_db[account]
71
+ if user.get("password") != pwd_data.old_password: raise HTTPException(status_code=401, detail="原密码错误")
72
+ user["password"] = pwd_data.new_password
73
+ db.save_data("users.json", users_db)
74
+ return {"status": "success"}
75
+
76
+ @router.post("/api/users/follow")
77
+ async def toggle_follow(follow: FollowToggle):
78
+ users_db = db.load_data("users.json", default_data={})
79
+ if follow.target_account not in users_db or follow.user_id not in users_db: raise HTTPException(status_code=404, detail="用户不存在")
80
+ target_followers = users_db[follow.target_account].setdefault("followers", [])
81
+ current_following = users_db[follow.user_id].setdefault("following", [])
82
+ if follow.is_active:
83
+ if follow.user_id not in target_followers:
84
+ target_followers.append(follow.user_id)
85
+ add_notification(follow.target_account, {"type": "follow", "from_user": follow.user_id})
86
+ if follow.target_account not in current_following: current_following.append(follow.target_account)
87
+ else:
88
+ if follow.user_id in target_followers: target_followers.remove(follow.user_id)
89
+ if follow.target_account in current_following: current_following.remove(follow.target_account)
90
+ db.save_data("users.json", users_db)
91
+ return {"status": "success"}
92
+
93
+ # --- 2. 排行榜核心 ---
94
+ @router.get("/api/items")
95
+ async def get_items(type: str = "tool", sort: str = "time", limit: int = 20):
96
+ items_db = db.load_data("items.json", default_data=[])
97
+ comments_db = db.load_data("comments.json", default_data={})
98
+ filtered_items = [item for item in items_db if item.get("type") == type]
99
+ for item in filtered_items:
100
+ item["commentsData"] = comments_db.get(item["id"], [])
101
+ item["comments"] = len(item["commentsData"])
102
+ if sort == "likes": filtered_items.sort(key=lambda x: x.get("likes", 0), reverse=True)
103
+ elif sort == "favorites": filtered_items.sort(key=lambda x: x.get("favorites", 0), reverse=True)
104
+ elif sort == "downloads": filtered_items.sort(key=lambda x: x.get("uses", 0), reverse=True)
105
+ else: filtered_items.sort(key=lambda x: x.get("created_at", 0), reverse=True)
106
+ return {"status": "success", "data": filtered_items[:limit]}
107
+
108
+ @router.get("/api/creators")
109
+ async def get_creators(sort: str = "downloads", limit: int = 20):
110
+ users_db = db.load_data("users.json", default_data={})
111
+ items_db = db.load_data("items.json", default_data=[])
112
+ comments_db = db.load_data("comments.json", default_data={})
113
+ creators = []
114
+ for account, u in users_db.items():
115
+ u_items = [i for i in items_db if i.get("author") == account]
116
+ tools_count = sum(1 for i in u_items if i.get("type") == "tool")
117
+ apps_count = sum(1 for i in u_items if i.get("type") == "app")
118
+ creators.append({
119
+ "account": account, "name": u.get("name", account), "avatar": u.get("avatarDataUrl", "https://via.placeholder.com/150"),
120
+ "shortDesc": u.get("intro", "这个人很懒,什么都没写..."), "fullDesc": u.get("intro", "这个人很懒,什么都没写..."),
121
+ "likes": sum(i.get("likes", 0) for i in u_items), "favorites": sum(i.get("favorites", 0) for i in u_items), "downloads": sum(i.get("uses", 0) for i in u_items),
122
+ "toolsCount": tools_count, "appsCount": apps_count, "followers": len(u.get("followers", [])), "created_at": u.get("created_at", 0),
123
+ "commentsData": comments_db.get(account, []), "trendData": {"tools": [0,0,0,0,0, tools_count], "apps": [0,0,0,0,0, apps_count]}
124
+ })
125
+ if sort == "likes": creators.sort(key=lambda x: x.get("likes", 0), reverse=True)
126
+ elif sort == "favorites": creators.sort(key=lambda x: x.get("favorites", 0), reverse=True)
127
+ elif sort == "downloads": creators.sort(key=lambda x: x.get("downloads", 0), reverse=True)
128
+ else: creators.sort(key=lambda x: x.get("created_at", 0), reverse=True)
129
+ return {"status": "success", "data": creators[:limit]}
130
+
131
+ @router.post("/api/items")
132
+ async def create_item(item: ItemCreate):
133
+ items_db = db.load_data("items.json", default_data=[])
134
+ new_item = {
135
+ "id": f"{item.type}_{int(time.time())}_{uuid.uuid4().hex[:6]}", "type": item.type, "title": item.title, "author": item.author,
136
+ "shortDesc": item.shortDesc, "fullDesc": item.fullDesc, "link": item.link, "coverUrl": item.coverUrl, "price": item.price,
137
+ "likes": 0, "favorites": 0, "comments": 0, "uses": 0, "created_at": int(time.time()), "liked_by": [], "favorited_by": []
138
+ }
139
+ items_db.insert(0, new_item)
140
+ db.save_data("items.json", items_db)
141
+ return {"status": "success", "data": new_item}
142
+
143
+ # --- 3. 互动与评论 ---
144
+ @router.post("/api/interactions/toggle")
145
+ async def toggle_interaction(interaction: InteractionToggle):
146
+ items_db = db.load_data("items.json", default_data=[])
147
+ target_item = next((item for item in items_db if item["id"] == interaction.item_id), None)
148
+ if not target_item: raise HTTPException(status_code=404, detail="目标存在问题")
149
+
150
+ list_key = f"{interaction.action_type}d_by"
151
+ count_key = "likes" if interaction.action_type == "like" else "favorites"
152
+ if list_key not in target_item: target_item[list_key] = []; target_item[count_key] = 0
153
+
154
+ user_list = target_item[list_key]
155
+ if interaction.is_active:
156
+ if interaction.user_id not in user_list:
157
+ user_list.append(interaction.user_id)
158
+ target_item[count_key] += 1
159
+ add_notification(target_item.get("author", ""), {"type": interaction.action_type, "from_user": interaction.user_id, "target_item_id": target_item["id"], "target_item_title": target_item["title"]})
160
+ else:
161
+ if interaction.user_id in user_list:
162
+ user_list.remove(interaction.user_id)
163
+ target_item[count_key] = max(0, target_item[count_key] - 1)
164
+ db.save_data("items.json", items_db)
165
+ return {"status": "success", "new_count": target_item[count_key]}
166
+
167
+ @router.post("/api/comments")
168
+ async def post_comment(comment: CommentCreate):
169
+ comments_db = db.load_data("comments.json", default_data={})
170
+ users_db = db.load_data("users.json", default_data={})
171
+ author_info = users_db.get(comment.author, {})
172
+ reply_name = users_db.get(comment.reply_to_user, {}).get("name", comment.reply_to_user) if comment.reply_to_user else None
173
+
174
+ item_comments = comments_db.get(comment.item_id, [])
175
+ new_comment = {
176
+ "id": f"c_{int(time.time())}_{uuid.uuid4().hex[:6]}", "author": comment.author,
177
+ "authorName": author_info.get("name", comment.author), "avatar": author_info.get("avatarDataUrl", "https://via.placeholder.com/150"),
178
+ "content": comment.content, "replyToUser": comment.reply_to_user, "replyToUserName": reply_name,
179
+ "isDeleted": False, "replies": [], "created_at": int(time.time())
180
+ }
181
+
182
+ if comment.parent_id:
183
+ parent = next((c for c in item_comments if c["id"] == comment.parent_id), None)
184
+ if parent:
185
+ parent["replies"].append(new_comment)
186
+ if comment.reply_to_user:
187
+ add_notification(comment.reply_to_user, {"type": "reply", "from_user": comment.author, "target_item_id": comment.item_id, "target_item_title": "收到新的回复", "content": comment.content})
188
+ else: raise HTTPException(status_code=404, detail="找不到要回复的评论")
189
+ else:
190
+ item_comments.append(new_comment)
191
+ items_db = db.load_data("items.json", default_data=[])
192
+ target_item = next((item for item in items_db if item["id"] == comment.item_id), None)
193
+ if target_item:
194
+ add_notification(target_item.get("author", ""), {"type": "comment", "from_user": comment.author, "target_item_id": comment.item_id, "target_item_title": target_item["title"], "content": comment.content})
195
+ elif comment.item_id in users_db:
196
+ add_notification(comment.item_id, {"type": "comment", "from_user": comment.author, "target_item_id": comment.item_id, "target_item_title": "您的个人留言板", "content": comment.content})
197
+
198
+ comments_db[comment.item_id] = item_comments
199
+ db.save_data("comments.json", comments_db)
200
+ return {"status": "success", "data": new_comment}
201
+
202
+ @router.delete("/api/comments/{item_id}/{comment_id}")
203
+ async def soft_delete_comment(item_id: str, comment_id: str, author: str):
204
+ comments_db = db.load_data("comments.json", default_data={})
205
+ item_comments = comments_db.get(item_id, [])
206
+ def find_and_delete(comments_list):
207
+ for c in comments_list:
208
+ if c["id"] == comment_id:
209
+ if c["author"] != author: raise HTTPException(status_code=403, detail="无权删除他人的评论")
210
+ c["isDeleted"] = True; c["content"] = ""; return True
211
+ if "replies" in c and find_and_delete(c["replies"]): return True
212
+ return False
213
+ if find_and_delete(item_comments):
214
+ db.save_data("comments.json", comments_db)
215
+ return {"status": "success"}
216
+ raise HTTPException(status_code=404, detail="找不到该评论")
217
+
218
+ # --- 4. 专属聊天信箱聚合 ---
219
+ @router.post("/api/messages/private")
220
+ async def send_private_message(msg: PrivateMessage):
221
+ chats_db = db.load_data("chats.json", default_data={})
222
+ conv_id = f"{min(msg.sender, msg.receiver)}_{max(msg.sender, msg.receiver)}"
223
+ if conv_id not in chats_db: chats_db[conv_id] = []
224
+
225
+ chat_msg = {"id": f"chat_{int(time.time())}", "sender": msg.sender, "receiver": msg.receiver, "content": msg.content, "created_at": int(time.time()), "is_read": False}
226
+ chats_db[conv_id].append(chat_msg)
227
+ db.save_data("chats.json", chats_db)
228
+
229
+ add_notification(msg.receiver, {"type": "private", "from_user": msg.sender, "content": msg.content})
230
+ return {"status": "success"}
231
+
232
+ @router.get("/api/chats/{account}")
233
+ async def get_chat_list(account: str):
234
+ chats_db = db.load_data("chats.json", default_data={})
235
+ users_db = db.load_data("users.json", default_data={})
236
+ chat_list = []
237
+ for conv_id, msgs in chats_db.items():
238
+ accs = conv_id.split("_")
239
+ if account in accs and msgs:
240
+ target = accs[0] if accs[1] == account else accs[1]
241
+ target_info = users_db.get(target, {})
242
+ chat_list.append({
243
+ "target_account": target, "target_name": target_info.get("name", target),
244
+ "last_message": msgs[-1]["content"], "last_time": msgs[-1]["created_at"],
245
+ "unread": sum(1 for m in msgs if m["receiver"] == account and not m["is_read"])
246
+ })
247
+ chat_list.sort(key=lambda x: x["last_time"], reverse=True)
248
+ return {"status": "success", "data": chat_list}
249
+
250
+ @router.get("/api/chats/{account}/{target}")
251
+ async def get_chat_history(account: str, target: str):
252
+ chats_db = db.load_data("chats.json", default_data={})
253
+ conv_id = f"{min(account, target)}_{max(account, target)}"
254
+ msgs = chats_db.get(conv_id, [])
255
+
256
+ modified = False
257
+ for m in msgs:
258
+ if m["receiver"] == account and not m["is_read"]: m["is_read"] = True; modified = True
259
+ if modified: db.save_data("chats.json", chats_db)
260
+ return {"status": "success", "data": msgs}
261
+
262
+ @router.get("/api/messages/{account}")
263
+ async def get_messages(account: str):
264
+ msgs_db = db.load_data("messages.json", default_data={})
265
+ user_msgs = msgs_db.get(account, [])
266
+ return {"status": "success", "data": user_msgs, "unread_count": sum(1 for m in user_msgs if not m.get("is_read"))}
267
+
268
+ @router.post("/api/messages/{account}/read")
269
+ async def mark_messages_read(account: str):
270
+ msgs_db = db.load_data("messages.json", default_data={})
271
+ user_msgs = msgs_db.get(account, [])
272
+ for m in user_msgs: m["is_read"] = True
273
+ msgs_db[account] = user_msgs
274
+ db.save_data("messages.json", msgs_db)
275
+ return {"status": "success"}