AstroResearch/src/api/notes.rs
Asfmq 8cc2b74abc feat: 手动上传绕防爬、下载错误诊断与健康检查工具;模块化重构 API 与批量同步
后端:
  - 将 handlers.rs (1338行) 拆分为 helpers/papers/notes/sync 四模块
  - 将 batch_sync.rs 拆分为 batch/{mod,meta,asset} 三模块
  - 新增 POST /api/upload 多部件文件上传接口
  - 新增 POST /api/no_resource 标记文献"无全文资源"
  - 新增 GET/POST /api/active_bibcode 追踪活跃文献
  - StandardPaper 结构体扩展 pdf_error / html_error 错误诊断字段
  - download.rs 记录下载失败详情至数据库
  - 新增 health_check 二进制工具,支持只读扫描与 --fix 自动修复
  - 移除 scratch/ 目录、recovered_handlers.rs 及调试日志

  前端:
  - 新建 CustomSelect 可复用组件,替换全部原生 select
  - LibraryPanel:同步按钮反馈动画、下载失败/无资源状态筛选与计数、
    文献类型筛选、状态优先排序、搜索一键清空
  - 详情弹窗:错误诊断展示、手动 PDF/HTML 上传区、无资源标记/恢复
  - SearchPanel:扩展文献类型徽章、下载失败状态提示
  - SyncPanel:同步启动乐观 UI 更新、日志容器内自动滚动
  - Tab 状态 localStorage 持久化、弹窗 z-index 修复
2026-06-11 22:56:36 +08:00

114 lines
3.2 KiB
Rust

// src/api/notes.rs
use axum::{
extract::{Query, State},
http::StatusCode,
Json,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use sqlx::Row;
use super::AppState;
#[derive(Debug, Serialize, Deserialize)]
pub struct NoteRecord {
pub id: i64,
pub bibcode: String,
pub paragraph_index: i64,
pub note_text: String,
pub highlight_color: String,
pub selected_text: String,
pub created_at: String,
}
#[derive(Deserialize)]
pub struct CreateNoteRequest {
pub bibcode: String,
pub paragraph_index: i64,
pub note_text: Option<String>,
pub highlight_color: Option<String>,
pub selected_text: Option<String>,
}
#[derive(Deserialize)]
pub struct DeleteNoteParams {
pub id: i64,
}
#[derive(Deserialize)]
pub struct GetNotesParams {
pub bibcode: String,
}
// 创建笔记
pub async fn create_note(
State(state): State<Arc<AppState>>,
Json(req): Json<CreateNoteRequest>,
) -> Result<Json<NoteRecord>, (StatusCode, String)> {
let note_text = req.note_text.unwrap_or_default();
let highlight_color = req.highlight_color.unwrap_or_else(|| "yellow".to_string());
let selected_text = req.selected_text.unwrap_or_default();
let row = sqlx::query(
"INSERT INTO notes (bibcode, paragraph_index, note_text, highlight_color, selected_text) VALUES (?, ?, ?, ?, ?) RETURNING id, bibcode, paragraph_index, note_text, highlight_color, selected_text, created_at"
)
.bind(&req.bibcode)
.bind(req.paragraph_index)
.bind(&note_text)
.bind(&highlight_color)
.bind(&selected_text)
.fetch_one(&state.db)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("保存笔记失败: {}", e)))?;
Ok(Json(NoteRecord {
id: row.get(0),
bibcode: row.get(1),
paragraph_index: row.get(2),
note_text: row.get(3),
highlight_color: row.get(4),
selected_text: row.get(5),
created_at: row.get(6),
}))
}
// 查询某篇文献的全部笔记
pub async fn get_notes(
State(state): State<Arc<AppState>>,
Query(params): Query<GetNotesParams>,
) -> Result<Json<Vec<NoteRecord>>, (StatusCode, String)> {
let rows = sqlx::query(
"SELECT id, bibcode, paragraph_index, note_text, highlight_color, selected_text, created_at FROM notes WHERE bibcode = ? ORDER BY paragraph_index, created_at"
)
.bind(&params.bibcode)
.fetch_all(&state.db)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("查询笔记失败: {}", e)))?;
let notes: Vec<NoteRecord> = rows.iter().map(|r| NoteRecord {
id: r.get(0),
bibcode: r.get(1),
paragraph_index: r.get(2),
note_text: r.get(3),
highlight_color: r.get(4),
selected_text: r.get(5),
created_at: r.get(6),
}).collect();
Ok(Json(notes))
}
// 删除指定 id 的笔记
pub async fn delete_note(
State(state): State<Arc<AppState>>,
Query(params): Query<DeleteNoteParams>,
) -> Result<StatusCode, (StatusCode, String)> {
sqlx::query("DELETE FROM notes WHERE id = ?")
.bind(params.id)
.execute(&state.db)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("删除笔记失败: {}", e)))?;
Ok(StatusCode::NO_CONTENT)
}