mirror of
https://github.com/thegeneralist01/archivr
synced 2026-07-21 18:55:36 +02:00
feat(collections): routes — PATCH|DELETE /api/archives/:id/collections/:uid, visibility_bits in GET response
This commit is contained in:
parent
533e9d5d2e
commit
383cec1f81
1 changed files with 72 additions and 2 deletions
|
|
@ -135,7 +135,9 @@ pub fn app(registry: ServerRegistry, auth_db_path: std::path::PathBuf) -> Router
|
||||||
.route("/api/archives/:archive_id/collections",
|
.route("/api/archives/:archive_id/collections",
|
||||||
get(list_collections_handler).post(create_collection_handler))
|
get(list_collections_handler).post(create_collection_handler))
|
||||||
.route("/api/archives/:archive_id/collections/:coll_uid",
|
.route("/api/archives/:archive_id/collections/:coll_uid",
|
||||||
get(get_collection_handler))
|
get(get_collection_handler)
|
||||||
|
.patch(patch_collection_handler)
|
||||||
|
.delete(delete_collection_handler))
|
||||||
.route("/api/archives/:archive_id/collections/:coll_uid/entries",
|
.route("/api/archives/:archive_id/collections/:coll_uid/entries",
|
||||||
post(add_entry_to_collection_handler))
|
post(add_entry_to_collection_handler))
|
||||||
.route("/api/archives/:archive_id/collections/:coll_uid/entries/:entry_uid",
|
.route("/api/archives/:archive_id/collections/:coll_uid/entries/:entry_uid",
|
||||||
|
|
@ -320,6 +322,12 @@ struct UpdateVisibilityBody {
|
||||||
visibility_bits: u32,
|
visibility_bits: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct PatchCollectionBody {
|
||||||
|
name: Option<String>,
|
||||||
|
default_visibility_bits: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_tags(
|
async fn list_tags(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(archive_id): Path<String>,
|
Path(archive_id): Path<String>,
|
||||||
|
|
@ -939,13 +947,41 @@ async fn get_collection_handler(
|
||||||
.ok_or(ApiError::not_found("collection not found"))?;
|
.ok_or(ApiError::not_found("collection not found"))?;
|
||||||
let caller_bits = auth_to_caller_bits(&auth);
|
let caller_bits = auth_to_caller_bits(&auth);
|
||||||
let entries = archive::list_entries_for_collection(&conn, record.id, caller_bits)?;
|
let entries = archive::list_entries_for_collection(&conn, record.id, caller_bits)?;
|
||||||
|
// Collect per-entry visibility bits from collection_entries
|
||||||
|
let mut vis_map: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
|
||||||
|
{
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT ae.entry_uid, ce.visibility_bits \
|
||||||
|
FROM collection_entries ce \
|
||||||
|
JOIN archived_entries ae ON ae.id = ce.entry_id \
|
||||||
|
WHERE ce.collection_id = ?1",
|
||||||
|
)?;
|
||||||
|
let rows = stmt.query_map([record.id], |row| {
|
||||||
|
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)? as u32))
|
||||||
|
})?;
|
||||||
|
for r in rows {
|
||||||
|
if let Ok((uid, bits)) = r {
|
||||||
|
vis_map.insert(uid, bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let entries_json: Vec<serde_json::Value> = entries.iter().map(|e| {
|
||||||
|
let vis = vis_map.get(&e.entry_uid).copied().unwrap_or(record.default_visibility_bits);
|
||||||
|
serde_json::json!({
|
||||||
|
"entry_uid": e.entry_uid,
|
||||||
|
"title": e.title,
|
||||||
|
"source_kind": e.source_kind,
|
||||||
|
"archived_at": e.archived_at,
|
||||||
|
"collection_visibility_bits": vis,
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
Ok(Json(serde_json::json!({
|
Ok(Json(serde_json::json!({
|
||||||
"collection_uid": record.collection_uid,
|
"collection_uid": record.collection_uid,
|
||||||
"name": record.name,
|
"name": record.name,
|
||||||
"slug": record.slug,
|
"slug": record.slug,
|
||||||
"default_visibility_bits": record.default_visibility_bits,
|
"default_visibility_bits": record.default_visibility_bits,
|
||||||
"created_at": record.created_at,
|
"created_at": record.created_at,
|
||||||
"entries": entries,
|
"entries": entries_json,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1041,6 +1077,40 @@ async fn list_entry_collections_handler(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn patch_collection_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthUser,
|
||||||
|
Path((archive_id, coll_uid)): Path<(String, String)>,
|
||||||
|
Json(body): Json<PatchCollectionBody>,
|
||||||
|
) -> Result<StatusCode, ApiError> {
|
||||||
|
auth.require_role(ROLE_USER)?;
|
||||||
|
let mounted = mounted_archive(&state, &archive_id)?;
|
||||||
|
let conn = database::open_or_initialize(&mounted.archive_path)?;
|
||||||
|
let name_ref: Option<&str> = body.name.as_deref();
|
||||||
|
let updated = database::update_collection(&conn, &coll_uid, name_ref, body.default_visibility_bits)?;
|
||||||
|
if updated {
|
||||||
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
} else {
|
||||||
|
Err(ApiError::not_found("collection not found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_collection_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthUser,
|
||||||
|
Path((archive_id, coll_uid)): Path<(String, String)>,
|
||||||
|
) -> Result<StatusCode, ApiError> {
|
||||||
|
auth.require_role(ROLE_USER)?;
|
||||||
|
let mounted = mounted_archive(&state, &archive_id)?;
|
||||||
|
let conn = database::open_or_initialize(&mounted.archive_path)?;
|
||||||
|
let deleted = database::delete_collection(&conn, &coll_uid)?;
|
||||||
|
if deleted {
|
||||||
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
} else {
|
||||||
|
Err(ApiError::not_found("collection not found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue