diff --git a/backend/app/api/admin.py b/backend/app/api/admin.py index 2561ea2..111d5fb 100644 --- a/backend/app/api/admin.py +++ b/backend/app/api/admin.py @@ -67,7 +67,8 @@ async def update_source( body: SourceUpdate, session: AsyncSession = Depends(get_session), ): - result = await session.execute(select(Source).where(Source.id == source_id))).scalar_one_or_none() + result = await session.execute(select(Source).where(Source.id == source_id)) + src = result.scalar_one_or_none() if not src: raise HTTPException(status.HTTP_404_NOT_FOUND, "Source not found") for k, v in body.model_dump(exclude_unset=True).items(): @@ -79,7 +80,8 @@ async def update_source( @router.delete("/sources/{source_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_source(source_id: int, session: AsyncSession = Depends(get_session)): - src = (await session.execute(select(Source).where(Source.id == source_id))).scalar_one_or_none() + result = await session.execute(select(Source).where(Source.id == source_id)) + src = result.scalar_one_or_none() if not src: raise HTTPException(status.HTTP_404_NOT_FOUND, "Source not found") await session.delete(src) @@ -99,7 +101,8 @@ async def refresh_source( background: BackgroundTasks, session: AsyncSession = Depends(get_session), ): - src = (await session.execute(select(Source).where(Source.id == source_id))).scalar_one_or_none() + result = await session.execute(select(Source).where(Source.id == source_id)) + src = result.scalar_one_or_none() if not src: raise HTTPException(status.HTTP_404_NOT_FOUND, "Source not found") if not src.enabled: @@ -125,7 +128,8 @@ async def rerun_translation( background: BackgroundTasks, session: AsyncSession = Depends(get_session), ): - art = (await session.execute(select(Article).where(Article.id == article_id))).scalar_one_or_none() + result = await session.execute(select(Article).where(Article.id == article_id)) + art = result.scalar_one_or_none() if not art: raise HTTPException(status.HTTP_404_NOT_FOUND, "Article not found") art.translation_status = "pending" diff --git a/backend/app/api/bookmarks.py b/backend/app/api/bookmarks.py index eb6ccad..830cb77 100644 --- a/backend/app/api/bookmarks.py +++ b/backend/app/api/bookmarks.py @@ -21,7 +21,8 @@ async def add( user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ): - art = (await session.execute(select(Article).where(Article.id == body.article_id))).scalar_one_or_none() + result = await session.execute(select(Article).where(Article.id == body.article_id)) + art = result.scalar_one_or_none() if not art: raise HTTPException(status.HTTP_404_NOT_FOUND, "Article not found") # 已存在则直接返回 diff --git a/scripts/_smoke_test.py b/scripts/_smoke_test.py index cda6061..b66aadf 100644 --- a/scripts/_smoke_test.py +++ b/scripts/_smoke_test.py @@ -1,4 +1,4 @@ -import os, paramiko, json, time +import os, paramiko, time PW = os.environ["REMOTE_PASS"] c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) @@ -15,39 +15,34 @@ def run(cmd, t=60): print(f" rc={rc}") return out -# 1) 取 owner 密码 +run("cd /srv/news && sudo -u news git pull --rebase 2>&1 | tail -3") +# 只重建 api(代码变更) +run("cd /srv/news && sg docker -c 'docker compose up -d --force-recreate --no-deps --build api' 2>&1 | tail -5", t=120) +time.sleep(8) + +# 测试登录 +print("\n=== 测登录 ===") owner_pw = run("cat /root/.owner_pass").strip() -print(f"\n=== owner 密码: {owner_pw} ===\n") - -# 2) 登录拿 token -login_cmd = f"""curl -s -X POST http://localhost/api/v1/auth/login -H 'Content-Type: application/json' -d '{{"username":"owner","password":"{owner_pw}"}}'""" -login_resp = run(login_cmd) -token = json.loads(login_resp).get("access_token", "") -print(f"\n=== token(前 40): {token[:40]}... ===\n") - -# 3) 触发一次抓取 -print("=== 触发一次抓取 ===") -run("cd /srv/news && sg docker -c \"docker compose exec -T worker python -c 'import asyncio; from app.workers.pipeline import run_once; asyncio.run(run_once())'\" 2>&1 | tail -30", t=180) - -# 4) 等翻译完成,看文章数 -time.sleep(10) -print("\n=== 查文章数 ===") -run("cd /srv/news && sg docker -c \"docker compose exec -T postgres psql -U news -d news -c 'SELECT count(*) FROM articles; SELECT count(*) FROM articles WHERE translation_status = '\\''ok'\\'';'\" 2>&1 | tail -10") - -# 5) 拿一条翻译好的文章 -print("\n=== 拉一条文章 ===") -list_cmd = f"curl -s -H 'Authorization: Bearer {token}' http://localhost/api/v1/articles?limit=1" -art_resp = run(list_cmd) +login_cmd = f"curl -s -X POST http://localhost/api/v1/auth/login -H 'Content-Type: application/json' -d '{{\"username\":\"owner\",\"password\":\"{owner_pw}\"}}'" +out = run(login_cmd) +import json try: - data = json.loads(art_resp) - items = data.get("items", []) - if items: - art = items[0] - print(f" id: {art['id']}") - print(f" source: {art['source']['name']}") - print(f" title (en): {art['title'][:80]}") - print(f" title (zh): {art.get('title_zh', '(none)')[:80] if art.get('title_zh') else '(none)'}") - print(f" status: {art['translation_status']}") + data = json.loads(out) + token = data.get("access_token", "") + if token: + print(f"\n[OK] 登录成功,token 前 40: {token[:40]}...") + # 测拉 articles + art = run(f"curl -s -H 'Authorization: Bearer {token}' 'http://localhost/api/v1/articles?limit=3'") + try: + ad = json.loads(art) + print(f" articles 数: {len(ad.get('items', []))}") + except Exception as e: + print(f" parse err: {e}") + print(f" raw: {art[:200]}") + else: + print(f"\n[FAIL] 登录失败,响应: {out[:300]}") + # 看 api 日志 + log_out = run("cd /srv/news && sg docker -c 'docker compose logs --tail=30 api' 2>&1 | tail -20") except Exception as e: - print(f"parse err: {e}\n raw: {art_resp[:300]}") + print(f"parse err: {e}\n raw: {out[:300]}") c.close()