'env'
This commit is contained in:
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user