Add vault listing endpoint
This commit is contained in:
parent
9ae1a5e09e
commit
44947dc3a5
6 changed files with 314 additions and 5 deletions
|
|
@ -41,13 +41,17 @@ pub async fn auth_middleware(
|
|||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
pub fn auth(state: &AppState, token: &str, vault_id: &VaultId) -> Result<User, SyncServerError> {
|
||||
let user = state
|
||||
pub fn authenticate(state: &AppState, token: &str) -> Result<User, SyncServerError> {
|
||||
state
|
||||
.config
|
||||
.users
|
||||
.get_user(token)
|
||||
.cloned()
|
||||
.ok_or_else(|| unauthenticated_error(anyhow::anyhow!("Invalid token")))?;
|
||||
.ok_or_else(|| unauthenticated_error(anyhow::anyhow!("Invalid token")))
|
||||
}
|
||||
|
||||
pub fn auth(state: &AppState, token: &str, vault_id: &VaultId) -> Result<User, SyncServerError> {
|
||||
let user = authenticate(state, token)?;
|
||||
|
||||
if match user.vault_access {
|
||||
VaultAccess::AllowAccessToAll => true,
|
||||
|
|
|
|||
82
sync-server/src/server/list_vaults.rs
Normal file
82
sync-server/src/server/list_vaults.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
};
|
||||
use axum_extra::{
|
||||
TypedHeader,
|
||||
headers::{Authorization, authorization::Bearer},
|
||||
};
|
||||
use log::debug;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{
|
||||
auth::authenticate,
|
||||
responses::{ListVaultsResponse, VaultInfo},
|
||||
};
|
||||
use crate::{
|
||||
app_state::AppState,
|
||||
config::user_config::{AllowListedVaults, VaultAccess},
|
||||
errors::{SyncServerError, server_error, unauthenticated_error},
|
||||
};
|
||||
|
||||
const DEFAULT_LIMIT: usize = 50;
|
||||
const MAX_LIMIT: usize = 200;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryParams {
|
||||
limit: Option<usize>,
|
||||
after: Option<String>,
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn list_vaults(
|
||||
auth_header: Option<TypedHeader<Authorization<Bearer>>>,
|
||||
Query(QueryParams { limit, after }): Query<QueryParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<ListVaultsResponse>, SyncServerError> {
|
||||
let auth_header = auth_header
|
||||
.ok_or_else(|| unauthenticated_error(anyhow::anyhow!("Missing Authorization header")))?;
|
||||
|
||||
let user = authenticate(&state, auth_header.token().trim())?;
|
||||
|
||||
debug!("User `{}` listing accessible vaults", user.name);
|
||||
|
||||
let existing_vaults = state.database.list_vaults().await.map_err(server_error)?;
|
||||
|
||||
let mut accessible: Vec<String> = match user.vault_access {
|
||||
VaultAccess::AllowAccessToAll => existing_vaults,
|
||||
VaultAccess::AllowList(AllowListedVaults { ref allowed }) => existing_vaults
|
||||
.into_iter()
|
||||
.filter(|v| allowed.contains(v))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// Cursor-based pagination: skip vaults up to and including `after`
|
||||
if let Some(ref cursor) = after {
|
||||
accessible.retain(|v| v.as_str() > cursor.as_str());
|
||||
}
|
||||
|
||||
let limit = limit.unwrap_or(DEFAULT_LIMIT).clamp(1, MAX_LIMIT);
|
||||
let has_more = accessible.len() > limit;
|
||||
accessible.truncate(limit);
|
||||
|
||||
let mut vaults = Vec::with_capacity(accessible.len());
|
||||
for name in accessible {
|
||||
let stats = state
|
||||
.database
|
||||
.get_vault_stats(&name)
|
||||
.await
|
||||
.map_err(server_error)?;
|
||||
vaults.push(VaultInfo {
|
||||
name,
|
||||
document_count: stats.document_count,
|
||||
created_at: stats.created_at,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(ListVaultsResponse {
|
||||
vaults,
|
||||
has_more,
|
||||
user_name: user.name,
|
||||
}))
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{self, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
|
|
@ -36,6 +37,35 @@ pub struct FetchLatestDocumentsResponse {
|
|||
pub last_update_id: VaultUpdateId,
|
||||
}
|
||||
|
||||
/// Response to a vault history request (paginated).
|
||||
#[derive(TS, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct VaultHistoryResponse {
|
||||
pub versions: Vec<DocumentVersionWithoutContent>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
/// Summary of a single vault returned by the list-vaults endpoint.
|
||||
#[derive(TS, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct VaultInfo {
|
||||
pub name: String,
|
||||
pub document_count: u32,
|
||||
pub created_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Response to listing vaults accessible to the authenticated user.
|
||||
#[derive(TS, Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct ListVaultsResponse {
|
||||
pub vaults: Vec<VaultInfo>,
|
||||
pub has_more: bool,
|
||||
pub user_name: String,
|
||||
}
|
||||
|
||||
/// Response to an update document request.
|
||||
#[derive(TS, Debug, Clone, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue