From 89f7bce75e977c94cc2cb547d806f80ccb20dc80 Mon Sep 17 00:00:00 2001 From: eric Date: Wed, 18 Mar 2026 02:01:52 -0500 Subject: [PATCH] 'env' --- app/routers/salon_meetup.py | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/app/routers/salon_meetup.py b/app/routers/salon_meetup.py index f626097..0012ed8 100644 --- a/app/routers/salon_meetup.py +++ b/app/routers/salon_meetup.py @@ -7,6 +7,7 @@ import json import logging import re import time +from datetime import datetime, timezone, timedelta from urllib.parse import parse_qs, unquote, urlparse import requests @@ -18,6 +19,12 @@ from ..config import PB_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD SITE_ID = "salon" router = APIRouter(prefix="/api/salon", tags=["salon"]) DEFAULT_PASSWORD = "12345678" +CHINA_TZ = timezone(timedelta(hours=8)) + + +def _now_china_str() -> str: + """当前中国公历时间,格式 YYYY-MM-DD HH:mm:ss,用于 submitted_at_cn 上传""" + return datetime.now(CHINA_TZ).strftime("%Y-%m-%d %H:%M:%S") _SALON_ADMIN_TOKEN_CACHE: tuple[str, float] | None = None _TOKEN_CACHE_TTL = 300 @@ -625,6 +632,72 @@ async def join_volunteers(): return await asyncio.to_thread(_fetch_volunteers_sync, token) +def _get_this_week_range_china() -> tuple[str, str]: + """中国公历本周一 00:00 至周日 23:59:59 的日期字符串""" + now = datetime.now(CHINA_TZ) + days_to_monday = now.weekday() # Mon=0, Sun=6 + monday = now - timedelta(days=days_to_monday) + sunday = monday + timedelta(days=6) + start = monday.strftime("%Y-%m-%d 00:00:00") + end = sunday.strftime("%Y-%m-%d 23:59:59") + return start, end + + +def _get_this_week_range_iso() -> tuple[str, str]: + """中国公历本周的 UTC 时间范围,用于 created 过滤。PocketBase 要求 Y-m-d H:i:s.uZ 格式(空格非 T)""" + now = datetime.now(CHINA_TZ) + days_to_monday = now.weekday() + monday = now - timedelta(days=days_to_monday) + sunday = monday + timedelta(days=6) + monday_start = monday.replace(hour=0, minute=0, second=0, microsecond=0) + sunday_end = sunday.replace(hour=23, minute=59, second=59, microsecond=999999) + start_utc = monday_start.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.000Z") + end_utc = sunday_end.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.999Z") + return start_utc, end_utc + + +def _fetch_stats_sync() -> dict: + """同步获取报名统计,按场次返回 count 和 joiners。使用 get_pb_client 与 by-wechat 一致""" + try: + from ..services import get_pb_client + pb_client = get_pb_client() + solan = pb_client.collection("solanRed") + except Exception as e: + logging.warning(f"salon join stats 初始化失败: {e}") + return {"ok": True, "stats": {"session-1": {"count": 0, "joiners": []}, "session-2": {"count": 0, "joiners": []}}} + sessions = [ + {"id": "session-1", "day_keyword": "周六"}, + {"id": "session-2", "day_keyword": "周日"}, + ] + result = {} + _get = lambda r, k, d=None: getattr(r, k, d) if hasattr(r, k) else (r.get(k, d) if isinstance(r, dict) else d) + for s in sessions: + sid = s["id"] + day_kw = s["day_keyword"] + meetupdate_filter = f'(meetupdate ~ "{day_kw}" || meetupdate = "")' if sid == "session-1" else f'meetupdate ~ "{day_kw}"' + try: + existing = solan.get_list(1, 6, {"filter": meetupdate_filter, "sort": "-created"}) + items = list(existing.items) if existing.items else [] + total = existing.total_items if hasattr(existing, "total_items") else len(items) + joiners = [] + for r in items: + nickname = str(_get(r, "xiaohongshu_nickname") or _get(r, "nickname") or "").strip() or "匿名" + avatar = str(_get(r, "xiaohongshu_avatar") or _get(r, "media") or "").strip() or None + xh_url = str(_get(r, "xiaohongshu_url") or "").strip() or None + joiners.append({"nickname": nickname, "avatar": avatar, "xiaohongshu_url": xh_url}) + result[sid] = {"count": total, "joiners": joiners} + except Exception as e: + logging.warning(f"salon join stats 查询失败 {sid}: {e}") + result[sid] = {"count": 0, "joiners": []} + return {"ok": True, "stats": result} + + +@router.get("/join/stats") +async def join_stats(): + """按场次返回报名人数和最近报名者""" + return await asyncio.to_thread(_fetch_stats_sync) + + class XiaohongshuProfileRequest(BaseModel): url: str | None = None id: str | None = None @@ -1501,11 +1574,13 @@ async def submit_join(item: dict): pb_client = get_pb_client() solan = pb_client.collection("solanRed") raw_type = item.get("type", "") - # PocketBase type 仅接受 session-1/session-2 等,digital-nomad/volunteer 映射为空 - pb_type = raw_type if raw_type in ("session-1", "session-2") else "" + # PocketBase type 字段为 select 枚举,可能仅接受 digital-nomad/volunteer 等,session-1/session-2 会报 validation_invalid_value + # 场次信息存 meetupdate,stats 按 meetupdate 过滤 + pb_type = "" qualify = bool(item.get("qualify", False)) # 女性且小红书帖子>10 时自动通过 user_info_val = (item.get("userInfo") or "").strip() + meetupdate_val = str(item.get("meetupdate", "") or "").strip() pb_data = { "user_id": user_id, "nickname": item.get("nickname", ""), @@ -1528,6 +1603,8 @@ async def submit_join(item: dict): "age_range": item.get("age_range", ""), "first_time": item.get("first_time", ""), "userInfo": user_info_val, + "meetupdate": meetupdate_val, + "submitted_at_cn": _now_china_str(), } phone_val = item.get("phone", "") if phone_val and str(phone_val).strip():