"""GET /api/moments — 员工朋友圈帖子列表(create_time desc)+账号头部。全本地。 对齐云客员工聊天窗口「朋友圈」tab:顶部账号资料卡(昵称/头像/别名)+ 帖子流(新→旧)。读镜像 ``moments_post`` 表(WeChat-only,所属员工由 ``wechat_id`` 标识),头部信息查 ``wechat_account``。 员工微信号不存在时不 404——返回 ``header`` 全 ``None`` + 空 ``items``, 前端资料卡自行兜底(云客侧同一员工必定有账号行,本地镜像可能滞后)。 """ from __future__ import annotations from typing import Annotated import sqlalchemy as sa from fastapi import APIRouter, Depends, Query from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_session from app.api.routers.organization import account_avatar from app.api.schemas import MomentsHeaderOut, MomentsListOut, MomentsPostOut from app.db.models import MomentsPost, WechatAccount router = APIRouter(prefix="/api", tags=["moments"]) SessionDep = Annotated[AsyncSession, Depends(get_session)] PageParam = Annotated[int, Query(ge=1)] PageSizeParam = Annotated[int, Query(ge=1, le=100)] @router.get("/moments") async def list_moments( session: SessionDep, wechat_id: Annotated[str, Query(min_length=1)], page: PageParam = 1, page_size: PageSizeParam = 20, ) -> MomentsListOut: """员工朋友圈帖子列表(``create_time`` desc,分页)+ 账号头部。 排序 ``nullslast(create_time desc)`` + ``id`` 稳定断尾(MySQL 下 compat 补丁剥掉 NULLS LAST,但 DESC 语义本就把 NULL 排后,等价——项目已有验证)。 """ account = ( await session.execute( sa.select( WechatAccount.nick_name, WechatAccount.alias, WechatAccount.raw_json, ).where(WechatAccount.wx_id == wechat_id) ) ).first() header = MomentsHeaderOut( nickname=account.nick_name if account else None, avatar=account_avatar(account.raw_json) if account else None, alias=account.alias if account else None, ) total = ( await session.execute( sa.select(sa.func.count()) .select_from(MomentsPost) .where(MomentsPost.wechat_id == wechat_id) ) ).scalar_one() rows = ( await session.execute( sa.select(MomentsPost) .where(MomentsPost.wechat_id == wechat_id) .order_by( sa.nullslast(MomentsPost.create_time.desc()), MomentsPost.id.asc(), ) .offset((page - 1) * page_size) .limit(page_size) ) ).scalars().all() return MomentsListOut( wechat_id=wechat_id, header=header, total=total, page=page, page_size=page_size, items=[MomentsPostOut.model_validate(r) for r in rows], )