68671784f6
deploy notes / build-and-deploy (push) Failing after 2m2s
- backend: POST /api/recordings/:id/feishu → 拼 markdown (总结在最上 + 附件链接到转录/录音 + 转写全文) → 写 /data/feishu-tmp/<id>/ → HTTP POST 到 feishu sidecar
- 复用:已有 feishu_doc_id 时 --update 同一个 doc,前端按钮文案变「↻ 重新生成」
- schema 加 feishu_doc_id + feishu_url 两列(ALTER TABLE 兼容旧 db)
- LLM prompt 改:行动项用 markdown checkbox `- [ ] 谁·做什么·何时`
- sidecar apps/notes/feishu: node:20 + python3 + python3-markdown + @larksuite/cli + COPY 自己的 markdown-to-feishu script + FastAPI /convert
- k8s: deployment 加 feishu container 共享 PVC;lark-cli-creds Secret 挂 /root/.lark-cli/config.json
- CI: 主 image --no-cache(cube 规矩),sidecar 保留 layer cache(chromium-free,但 apt/npm 也大)
- 前端: content 头部加「📤 一键转飞书文档」按钮;已转过显示飞书链接 + 按钮变重生成
91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
"""notes feishu sidecar:HTTP 包一层 markdown-to-feishu。
|
||
|
||
POST /convert {md_path, title?, existing_doc_id?}
|
||
→ 跑 markdown-to-feishu,parse 最后那段 JSON,返回 {doc_id, url}
|
||
"""
|
||
|
||
import json
|
||
import logging
|
||
import os
|
||
import re
|
||
import subprocess
|
||
from pathlib import Path
|
||
from typing import Optional
|
||
|
||
from fastapi import FastAPI, HTTPException
|
||
from pydantic import BaseModel
|
||
|
||
logging.basicConfig(level=logging.INFO,
|
||
format='%(asctime)s %(levelname)s %(name)s: %(message)s')
|
||
log = logging.getLogger('feishu')
|
||
|
||
app = FastAPI()
|
||
|
||
|
||
@app.get('/healthz')
|
||
def healthz():
|
||
return {'ok': True}
|
||
|
||
|
||
class ConvertReq(BaseModel):
|
||
md_path: str
|
||
title: Optional[str] = None
|
||
existing_doc_id: Optional[str] = None
|
||
|
||
|
||
@app.post('/convert')
|
||
def convert(req: ConvertReq):
|
||
md = Path(req.md_path)
|
||
if not md.exists():
|
||
raise HTTPException(400, f'md not found: {md}')
|
||
|
||
cmd = ['/usr/local/bin/markdown-to-feishu', str(md), '--as', 'user']
|
||
if req.existing_doc_id:
|
||
cmd += ['--update', req.existing_doc_id]
|
||
if req.title:
|
||
cmd += ['--title', req.title]
|
||
log.info("run: %s", ' '.join(cmd))
|
||
|
||
env = os.environ.copy()
|
||
# markdown-to-feishu state file 放 PVC,重启不丢
|
||
env['MD2FEISHU_STATE_DIR'] = '/data/feishu-state'
|
||
Path('/data/feishu-state').mkdir(parents=True, exist_ok=True)
|
||
|
||
try:
|
||
proc = subprocess.run(
|
||
cmd, capture_output=True, text=True, timeout=600, env=env,
|
||
cwd=str(md.parent),
|
||
)
|
||
except subprocess.TimeoutExpired:
|
||
raise HTTPException(504, 'markdown-to-feishu timeout (>10min)')
|
||
|
||
# exit code 2 = embeds 有失败,但 doc 创建成功,仍 parse stdout
|
||
if proc.returncode not in (0, 2):
|
||
log.warning("md2feishu exit=%d stderr=%s", proc.returncode, proc.stderr[-500:])
|
||
raise HTTPException(502, f'md2feishu exit {proc.returncode}: '
|
||
f'{proc.stderr.strip()[-400:]}')
|
||
|
||
# 取 stdout 里最后一段 JSON 对象(script 的 final print)
|
||
out = proc.stdout.strip()
|
||
# 从后往前找第一个 '{',取到末尾
|
||
last_open = out.rfind('{')
|
||
if last_open < 0:
|
||
raise HTTPException(502, f'md2feishu no json output. stdout tail: {out[-400:]}')
|
||
try:
|
||
data = json.loads(out[last_open:])
|
||
except json.JSONDecodeError as e:
|
||
raise HTTPException(502, f'md2feishu json parse: {e}; tail: {out[-400:]}')
|
||
|
||
doc_id = data.get('doc_id')
|
||
url = data.get('url')
|
||
if not doc_id or not url:
|
||
raise HTTPException(502, f'md2feishu missing doc_id/url: {data}')
|
||
log.info("ok: doc_id=%s url=%s embeds=%s",
|
||
doc_id, url, data.get('embeds_inserted'))
|
||
return {
|
||
'doc_id': doc_id,
|
||
'url': url,
|
||
'embeds_inserted': data.get('embeds_inserted', 0),
|
||
'embeds_failed': data.get('embeds_failed', 0),
|
||
}
|