nomadweb/backend/app/routers/community.py
eric 48408d3079 feat: complete NomadCNA parity layer with platform APIs and pages
Add services, videos, AI assistant, map/weather, gigs post, notifications settings, static legal pages, Google OAuth, payment router, real-user matching, newsletter, and content submission — all using nomadweb UI patterns.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 10:13:17 -05:00

140 lines
4.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, "讨论不存在")
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", "申请失败"))
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("/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