Skip to main content

yggdrasil/api/notes/
mod.rs

1//! 笔记的浏览器入口。认证适配后,统一调用 store 的版本与发布规则。
2#![allow(clippy::unused_unit, deprecated)]
3use crate::api::error::ApiError;
4use crate::models::note::*;
5use crate::models::public_note::PublicNote;
6use leptos::prelude::*;
7#[cfg(feature = "server")]
8pub mod attachments;
9#[cfg(feature = "server")]
10pub mod store;
11
12#[server(ListNotes, endpoint = "list_notes", input = leptos::server_fn::codec::Json)]
13pub async fn list_notes(filter: NoteFilter) -> Result<NotePage, ApiError> {
14    Ok(store::list(store::Access::Public, filter).await?)
15}
16
17#[server(GetNote, endpoint = "get_note", input = leptos::server_fn::codec::Json)]
18pub async fn get_note(slug: String) -> Result<PublicNote, ApiError> {
19    // 公开详情只返回阅读所需字段(P4.1);编辑 Markdown 与内部版本号不出接口。
20    // 后台编辑与 MCP 继续走 get_owned_note / store::get 的完整 Note 契约。
21    Ok(store::get_public(&slug).await?)
22}
23
24#[server(NoteStatsSummary, endpoint = "note_stats", input = leptos::server_fn::codec::Json)]
25pub async fn note_stats() -> Result<NoteStats, ApiError> {
26    Ok(store::stats(8).await?)
27}
28
29/// 阅读页的笔记本上下文;`book` 来自 `?book=`,无效时忽略。
30#[server(GetNoteContext, endpoint = "get_note_context", input = leptos::server_fn::codec::Json)]
31pub async fn get_note_context(
32    slug: String,
33    book: Option<i32>,
34) -> Result<Option<NoteContext>, ApiError> {
35    Ok(store::context(&slug, book).await?)
36}
37
38#[server(ListNotebooks, endpoint = "list_notebooks", input = leptos::server_fn::codec::Json)]
39pub async fn list_notebooks() -> Result<Vec<Notebook>, ApiError> {
40    Ok(store::notebooks(store::Access::Public).await?)
41}
42
43#[server(ListOwnedNotes, endpoint = "list_owned_notes", input = leptos::server_fn::codec::Json)]
44pub async fn list_owned_notes(filter: NoteFilter) -> Result<NotePage, ApiError> {
45    let user = crate::api::auth::get_current_admin_user().await?;
46    Ok(store::list(store::Access::Owner(user.id), filter).await?)
47}
48
49#[server(GetOwnedNote, endpoint = "get_owned_note", input = leptos::server_fn::codec::Json)]
50pub async fn get_owned_note(id: i32, version: Option<i32>) -> Result<Note, ApiError> {
51    let user = crate::api::auth::get_current_admin_user().await?;
52    Ok(store::get(store::Access::Owner(user.id), Some(id), None, version).await?)
53}
54
55#[server(SaveNote, endpoint = "save_note", input = leptos::server_fn::codec::Json)]
56pub async fn save_note(draft: NoteDraft) -> Result<Note, ApiError> {
57    let user = crate::api::auth::get_current_admin_user().await?;
58    Ok(store::save(user.id, draft, None).await?)
59}
60
61#[server(ActOnNote, endpoint = "act_on_note", input = leptos::server_fn::codec::Json)]
62pub async fn act_on_note(
63    id: i32,
64    expected_version: i32,
65    action: NoteAction,
66) -> Result<Note, ApiError> {
67    let user = crate::api::auth::get_current_admin_user().await?;
68    Ok(store::act(user.id, id, expected_version, action, None).await?)
69}
70
71#[server(BatchActOnNotes, endpoint = "batch_act_on_notes", input = leptos::server_fn::codec::Json)]
72pub async fn batch_act_on_notes(
73    targets: Vec<NoteBatchTarget>,
74    action: NoteAction,
75) -> Result<(), ApiError> {
76    let user = crate::api::auth::get_current_admin_user().await?;
77    Ok(store::batch_act(user.id, targets, action).await?)
78}
79
80#[server(NoteHistory, endpoint = "note_history", input = leptos::server_fn::codec::Json)]
81pub async fn note_history(id: i32) -> Result<Vec<NoteRevision>, ApiError> {
82    let user = crate::api::auth::get_current_admin_user().await?;
83    Ok(store::history(user.id, id).await?)
84}
85
86#[server(OwnedNotebooks, endpoint = "owned_notebooks", input = leptos::server_fn::codec::Json)]
87pub async fn owned_notebooks() -> Result<Vec<Notebook>, ApiError> {
88    let user = crate::api::auth::get_current_admin_user().await?;
89    Ok(store::notebooks(store::Access::Owner(user.id)).await?)
90}
91
92#[server(SaveNotebook, endpoint = "save_notebook", input = leptos::server_fn::codec::Json)]
93pub async fn save_notebook(input: NotebookInput) -> Result<(), ApiError> {
94    let user = crate::api::auth::get_current_admin_user().await?;
95    Ok(store::save_notebook(user.id, input).await?)
96}
97
98#[server(ReorderNotebook, endpoint = "reorder_notebook", input = leptos::server_fn::codec::Json)]
99pub async fn reorder_notebook(id: i32, note_ids: Vec<i32>) -> Result<(), ApiError> {
100    let user = crate::api::auth::get_current_admin_user().await?;
101    Ok(store::reorder(user.id, id, note_ids).await?)
102}
103
104#[server(ArchiveNotebook, endpoint = "archive_notebook", input = leptos::server_fn::codec::Json)]
105pub async fn archive_notebook(id: i32, archived: bool) -> Result<(), ApiError> {
106    let user = crate::api::auth::get_current_admin_user().await?;
107    Ok(store::archive_notebook(user.id, id, archived).await?)
108}
109
110#[server(NotebookOrder, endpoint = "notebook_order", input = leptos::server_fn::codec::Json)]
111pub async fn notebook_order(id: i32) -> Result<Vec<NotebookEntry>, ApiError> {
112    let user = crate::api::auth::get_current_admin_user().await?;
113    let client = crate::db::pool::get_conn()
114        .await
115        .map_err(crate::api::error::AppError::db_conn)?;
116    let rows=client.query("SELECT n.id, CASE WHEN r.title='' THEN r.summary ELSE r.title END,
117        n.published_version IS NOT NULL, n.version > COALESCE(n.published_version, n.version), n.deleted_at IS NOT NULL
118        FROM notebook_notes bn
119        JOIN notebooks b ON b.id=bn.notebook_id JOIN notes n ON n.id=bn.note_id
120        JOIN note_revisions r ON r.note_id=n.id AND r.version=n.version
121        WHERE b.id=$1 AND b.owner_id=$2 ORDER BY bn.position,n.id", &[&id,&user.id]).await.map_err(crate::api::error::AppError::query)?;
122    Ok(rows
123        .iter()
124        .map(|r| NotebookEntry {
125            id: r.get(0),
126            title: r.get(1),
127            published: r.get(2),
128            ahead: r.get(3),
129            trashed: r.get(4),
130        })
131        .collect())
132}