171 lines
5.9 KiB
Python
171 lines
5.9 KiB
Python
"""Community mutations — discussions, meetups, gigs, notifications."""
|
|
|
|
from fastapi import APIRouter, Header, HTTPException, Query
|
|
|
|
from app.schemas import (
|
|
CreateDiscussionRequest, CreateGigRequest, CreateMeetupRequest, CreateReplyRequest,
|
|
FeedbackRequest, GigApplyRequest, GigItem, NotificationItem, StatsOverview,
|
|
)
|
|
from app.services.auth import get_user_by_token
|
|
from app.services import community_store, social_store
|
|
|
|
router = APIRouter(tags=["community"])
|
|
|
|
|
|
def _user(authorization: str | None) -> dict:
|
|
if not authorization or not authorization.startswith("Bearer "):
|
|
raise HTTPException(401, "未登录")
|
|
user = get_user_by_token(authorization[7:])
|
|
if not user:
|
|
raise HTTPException(401, "未登录")
|
|
return user
|
|
|
|
|
|
@router.post("/discussions")
|
|
async def create_discussion(body: CreateDiscussionRequest, authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
d = community_store.create_discussion(user["id"], user["name"], body.model_dump())
|
|
return {"success": True, "discussion": d}
|
|
|
|
|
|
@router.post("/discussions/{discussion_id}/replies")
|
|
async def post_reply(
|
|
discussion_id: str,
|
|
body: CreateReplyRequest,
|
|
authorization: str | None = Header(None),
|
|
):
|
|
user = _user(authorization)
|
|
reply = community_store.add_reply(discussion_id, user["id"], user["name"], body.content, body.author_emoji)
|
|
if not reply:
|
|
raise HTTPException(404, "讨论不存在")
|
|
discussion = community_store.get_discussion(discussion_id, increment_view=False) or {}
|
|
author_id = discussion.get("author_id") or ""
|
|
if author_id and author_id != user["id"]:
|
|
from app.services.notify import notify_user
|
|
|
|
notify_user(
|
|
author_id,
|
|
"讨论有新回复",
|
|
f"{user['name']} 回复了「{discussion.get('title', '话题')}」",
|
|
f"/community/{discussion_id}",
|
|
"community",
|
|
)
|
|
return {"success": True, "reply": reply}
|
|
|
|
|
|
@router.post("/discussions/{discussion_id}/like")
|
|
async def like_discussion(discussion_id: str, authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
return community_store.toggle_discussion_like(discussion_id, user["id"])
|
|
|
|
|
|
@router.post("/discussions/{discussion_id}/pin")
|
|
async def pin_discussion(discussion_id: str, authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
d = community_store.toggle_pin(discussion_id, user["id"])
|
|
if not d:
|
|
raise HTTPException(403, "仅作者可置顶")
|
|
return {"success": True, "discussion": d}
|
|
|
|
|
|
@router.post("/meetups")
|
|
async def create_meetup(body: CreateMeetupRequest, authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
m = community_store.create_meetup(user["id"], user["name"], body.model_dump())
|
|
return {"success": True, "meetup": m}
|
|
|
|
|
|
@router.get("/meetups/my-rsvp")
|
|
async def my_rsvps(authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
return {"ids": list(community_store.user_rsvp_ids(user["id"]))}
|
|
|
|
|
|
@router.get("/gigs", response_model=list[GigItem])
|
|
async def list_gigs():
|
|
return [GigItem(**g) for g in community_store.list_gigs()]
|
|
|
|
|
|
@router.post("/gigs")
|
|
async def create_gig(body: CreateGigRequest, authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
g = community_store.create_gig(user["id"], user["name"], body.model_dump())
|
|
return {"success": True, "gig": g}
|
|
|
|
|
|
@router.post("/gigs/{gig_id}/apply")
|
|
async def apply_gig(gig_id: str, body: GigApplyRequest, authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
res = community_store.apply_gig(gig_id, user["id"], user["name"], body.message)
|
|
if not res.get("ok"):
|
|
raise HTTPException(400, res.get("error", "申请失败"))
|
|
poster_id = res.get("poster_id") or ""
|
|
if not poster_id:
|
|
email = (res.get("poster_email") or "").strip().lower()
|
|
if email:
|
|
from app.services.auth import find_user_public_by_email
|
|
|
|
peer = find_user_public_by_email(email)
|
|
if peer:
|
|
poster_id = peer["id"]
|
|
if poster_id and poster_id != user["id"]:
|
|
from app.services.notify import notify_user
|
|
|
|
notify_user(
|
|
poster_id,
|
|
"赏金任务有新申请",
|
|
f"{user['name']} 申请了「{res.get('title') or '任务'}」",
|
|
"/gigs",
|
|
"gig",
|
|
)
|
|
return {"ok": True, "message": res.get("message", "申请已提交")}
|
|
|
|
|
|
@router.get("/notifications", response_model=list[NotificationItem])
|
|
async def list_notifications(authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
return [NotificationItem(**n) for n in community_store.list_notifications(user["id"])]
|
|
|
|
|
|
@router.post("/notifications/read")
|
|
async def mark_read(authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
community_store.mark_notifications_read(user["id"])
|
|
return {"success": True}
|
|
|
|
|
|
@router.post("/notifications/{notif_id}/{action}")
|
|
async def notification_action(notif_id: str, action: str, authorization: str | None = Header(None)):
|
|
user = _user(authorization)
|
|
if action not in ("read", "unread", "archive", "restore", "pin", "unpin"):
|
|
raise HTTPException(400, "无效操作")
|
|
n = community_store.update_notification(user["id"], notif_id, action)
|
|
if not n:
|
|
raise HTTPException(404, "通知不存在")
|
|
return {"success": True, "notification": n}
|
|
|
|
|
|
@router.post("/feedback")
|
|
async def submit_feedback(body: FeedbackRequest, authorization: str | None = Header(None)):
|
|
user = None
|
|
name = body.name or "访客"
|
|
if authorization and authorization.startswith("Bearer "):
|
|
user = get_user_by_token(authorization[7:])
|
|
if user:
|
|
name = user["name"]
|
|
item = community_store.add_feedback(user["id"] if user else None, name, body.content, body.category)
|
|
return {"success": True, "id": item["id"]}
|
|
|
|
|
|
@router.get("/stats/overview", response_model=StatsOverview)
|
|
async def stats_overview():
|
|
return StatsOverview(**community_store.stats_overview())
|
|
|
|
|
|
@router.get("/members/{user_id}")
|
|
async def public_member(user_id: str):
|
|
profile = social_store.get_public_profile(user_id)
|
|
if not profile:
|
|
raise HTTPException(404, "用户不存在")
|
|
return profile
|