pagnation
This commit is contained in:
+46
-3
@@ -19,7 +19,7 @@ pub use pads_public::*;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Multipart, Path, State},
|
||||
extract::{Multipart, Path, Query, State},
|
||||
http::{HeaderMap, HeaderName, HeaderValue, StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
@@ -283,6 +283,31 @@ pub struct WorkspaceInfo {
|
||||
pub struct WorkspaceOpenResponse {
|
||||
workspace: WorkspaceInfo,
|
||||
notes: Vec<NoteListItem>,
|
||||
pagination: ListPaginationMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WorkspaceNotesQuery {
|
||||
#[serde(default)]
|
||||
q: String,
|
||||
#[serde(default = "default_list_page")]
|
||||
page: usize,
|
||||
#[serde(default = "default_list_per_page")]
|
||||
per_page: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ListPaginationMeta {
|
||||
page: usize,
|
||||
per_page: usize,
|
||||
total: usize,
|
||||
total_pages: usize,
|
||||
}
|
||||
|
||||
fn default_list_page() -> usize { 1 }
|
||||
fn default_list_per_page() -> usize { 25 }
|
||||
fn normalize_list_per_page(value: usize) -> usize {
|
||||
match value { 25 | 50 | 100 => value, _ => 25 }
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -552,6 +577,7 @@ pub async fn open_workspace(
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
Path(workspace_slug): Path<String>,
|
||||
Query(query): Query<WorkspaceNotesQuery>,
|
||||
Json(payload): Json<PasswordRequest>,
|
||||
) -> Result<Json<WorkspaceOpenResponse>, ApiError> {
|
||||
let workspace = authorized_workspace(
|
||||
@@ -573,9 +599,16 @@ pub async fn open_workspace(
|
||||
.into_iter()
|
||||
.map(|stats| (stats.note_id, stats))
|
||||
.collect::<std::collections::HashMap<_, _>>();
|
||||
let notes = db::list_notes(&state.db, workspace.id)
|
||||
let search = query.q.trim().to_lowercase();
|
||||
let mut notes = db::list_notes(&state.db, workspace.id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|note| {
|
||||
search.is_empty()
|
||||
|| note.title.to_lowercase().contains(&search)
|
||||
|| note.slug.to_lowercase().contains(&search)
|
||||
|| note.created_by.as_deref().unwrap_or_default().to_lowercase().contains(&search)
|
||||
})
|
||||
.map(|note| {
|
||||
let stats = stats.get(¬e.id);
|
||||
NoteListItem {
|
||||
@@ -592,11 +625,21 @@ pub async fn open_workspace(
|
||||
revision_count: stats.map_or(0, |value| value.revision_count),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>();
|
||||
notes.sort_by(|left, right| right.updated_at.cmp(&left.updated_at));
|
||||
|
||||
let page = query.page.max(1);
|
||||
let per_page = normalize_list_per_page(query.per_page);
|
||||
let total = notes.len();
|
||||
let total_pages = ((total + per_page - 1) / per_page).max(1);
|
||||
let page = page.min(total_pages);
|
||||
let start = (page - 1) * per_page;
|
||||
let notes = notes.into_iter().skip(start).take(per_page).collect();
|
||||
|
||||
Ok(Json(WorkspaceOpenResponse {
|
||||
workspace: workspace_info_from(&workspace),
|
||||
notes,
|
||||
pagination: ListPaginationMeta { page, per_page, total, total_pages },
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+74
-11
@@ -16,7 +16,7 @@ use argon2::{
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path as AxumPath, State},
|
||||
extract::{Path as AxumPath, Query, State},
|
||||
http::{HeaderMap, StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
@@ -116,6 +116,8 @@ pub struct ResourceActionRequest {
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
pub struct ResourceItem {
|
||||
#[serde(default)]
|
||||
kind: String,
|
||||
slug: String,
|
||||
title: String,
|
||||
protected: i64,
|
||||
@@ -201,6 +203,7 @@ impl<'r> sqlx::FromRow<'r, AnyRow> for User {
|
||||
impl<'r> sqlx::FromRow<'r, AnyRow> for ResourceItem {
|
||||
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> {
|
||||
Ok(Self {
|
||||
kind: String::new(),
|
||||
slug: crate::row_decode::text(row, "slug")?,
|
||||
title: crate::row_decode::text(row, "title")?,
|
||||
protected: row.try_get("protected")?,
|
||||
@@ -257,8 +260,32 @@ pub struct RevokeShareLinkRequest {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ResourceList {
|
||||
workspaces: Vec<ResourceItem>,
|
||||
pads: Vec<ResourceItem>,
|
||||
items: Vec<ResourceItem>,
|
||||
pagination: PaginationMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ResourceListQuery {
|
||||
#[serde(default)]
|
||||
q: String,
|
||||
#[serde(default = "default_page")]
|
||||
page: usize,
|
||||
#[serde(default = "default_per_page")]
|
||||
per_page: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PaginationMeta {
|
||||
page: usize,
|
||||
per_page: usize,
|
||||
total: usize,
|
||||
total_pages: usize,
|
||||
}
|
||||
|
||||
fn default_page() -> usize { 1 }
|
||||
fn default_per_page() -> usize { 25 }
|
||||
fn normalized_per_page(value: usize) -> usize {
|
||||
match value { 25 | 50 | 100 => value, _ => 25 }
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
pub struct SessionResponse {
|
||||
@@ -1142,6 +1169,7 @@ async fn send_account_action(
|
||||
pub async fn resources(
|
||||
State(state): State<SharedState>,
|
||||
headers: HeaderMap,
|
||||
Query(query): Query<ResourceListQuery>,
|
||||
) -> Result<Json<ResourceList>, AuthError> {
|
||||
let user = require_user(&state, &headers).await?;
|
||||
let workspaces = sqlx::query_as::<_, ResourceItem>(queries::get(
|
||||
@@ -1153,14 +1181,49 @@ pub async fn resources(
|
||||
.fetch_all(state.db.pool())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
let pads =
|
||||
sqlx::query_as::<_, ResourceItem>(queries::get(state.db.kind(), queries::USER_LIST_PADS))
|
||||
.bind(user.id)
|
||||
.bind(user.id)
|
||||
.fetch_all(state.db.pool())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
Ok(Json(ResourceList { workspaces, pads }))
|
||||
let pads = sqlx::query_as::<_, ResourceItem>(
|
||||
queries::get(state.db.kind(), queries::USER_LIST_PADS),
|
||||
)
|
||||
.bind(user.id)
|
||||
.bind(user.id)
|
||||
.fetch_all(state.db.pool())
|
||||
.await
|
||||
.map_err(AuthError::database)?;
|
||||
|
||||
let search = query.q.trim().to_lowercase();
|
||||
let mut items = workspaces
|
||||
.into_iter()
|
||||
.map(|item| (item, "workspace"))
|
||||
.chain(pads.into_iter().map(|item| (item, "pad")))
|
||||
.filter(|(item, kind)| {
|
||||
search.is_empty()
|
||||
|| item.title.to_lowercase().contains(&search)
|
||||
|| item.slug.to_lowercase().contains(&search)
|
||||
|| kind.contains(&search)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|(left, _), (right, _)| right.updated_at.cmp(&left.updated_at));
|
||||
|
||||
let page = query.page.max(1);
|
||||
let per_page = normalized_per_page(query.per_page);
|
||||
let total = items.len();
|
||||
let total_pages = ((total + per_page - 1) / per_page).max(1);
|
||||
let page = page.min(total_pages);
|
||||
let start = (page - 1) * per_page;
|
||||
let items = items
|
||||
.into_iter()
|
||||
.skip(start)
|
||||
.take(per_page)
|
||||
.map(|(mut item, kind)| {
|
||||
item.kind = kind.to_string();
|
||||
item
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(ResourceList {
|
||||
items,
|
||||
pagination: PaginationMeta { page, per_page, total, total_pages },
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn update_resource(
|
||||
|
||||
Reference in New Issue
Block a user