Port meetups, discussions, gigs, dating/chat, VIP pay (ZPay/XorPay), MiroTalk live, digital academy, and ebook reader. Add persistent community_store, git-first deploy docs, and env template for production secrets. Co-authored-by: Cursor <cursoragent@cursor.com>
122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
"""Community mutations — discussions, meetups, gigs, notifications."""
|
|
|
|
from fastapi import APIRouter, Header, HTTPException, Query
|
|
|
|
from app.schemas import (
|
|
CreateDiscussionRequest, 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, "讨论不存在")
|
|
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/{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", "申请失败"))
|
|
return res
|
|
|
|
|
|
@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("/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
|