Enhance documentation and admin UI: Add detailed implementation guidelines in CLAUDE.md, introduce a referrer index in README.md, and update admin UI translations for improved user experience. Update package dependencies for better functionality and performance.

This commit is contained in:
Peter Meier
2026-03-13 10:55:33 +01:00
parent 7754d800f5
commit 606455c59b
42 changed files with 3814 additions and 421 deletions

View File

@@ -11,6 +11,7 @@ use axum::Json;
use serde_json::{json, Value};
use tokio::sync::RwLock;
use crate::referrers::{Referrer, ReferrerIndex};
use crate::schema::types::{SchemaDefinition, VALID_FIELD_TYPES};
use crate::schema::validator;
use crate::schema::SchemaRegistry;
@@ -53,6 +54,10 @@ pub struct AppState {
pub stores: Option<HashMap<String, Arc<dyn ContentStore>>>,
/// Assets dir per environment when environments is set. Key = env name.
pub assets_dirs: Option<HashMap<String, PathBuf>>,
/// Reverse index for "who references this entry?". Updated on create/update/delete. None when environments are used.
pub referrer_index: Option<Arc<RwLock<ReferrerIndex>>>,
/// Path to persist referrer index (e.g. content/_referrers.json).
pub referrer_index_path: Option<PathBuf>,
}
impl AppState {
@@ -726,6 +731,7 @@ pub async fn create_entry(
// Normalize reference arrays: objects with _slug → slug strings (so clients can send resolved refs)
validator::normalize_reference_arrays(&schema, &mut body);
validator::normalize_multi_select(&schema, &mut body);
// Validate against schema (type checks, constraints, strict mode, …)
let errors = validator::validate_content(&schema, &body);
@@ -766,6 +772,27 @@ pub async fn create_entry(
.map_err(ApiError::from)?;
state.cache.invalidate_collection(&env, &collection).await;
// Update referrer index: this entry references (ref_coll, ref_slug)
if let (Some(ref idx), Some(ref path)) = (&state.referrer_index, &state.referrer_index_path) {
let refs = validator::extract_references(&schema, &body);
let referrer = Referrer {
collection: collection.clone(),
slug: slug.clone(),
field: String::new(), // we add per (ref_coll, ref_slug, field) below
locale: locale_ref.map(str::to_string),
};
let mut index = idx.write().await;
for (ref_coll, ref_slug, field) in refs {
let mut r = referrer.clone();
r.field = field;
index.add_referrer(&ref_coll, &ref_slug, r);
}
drop(index);
if let Err(e) = idx.read().await.save(path) {
tracing::warn!("Failed to save referrer index: {}", e);
}
}
// Return created entry (with reference format)
let entry = store
.get(&collection, &slug, locale_ref)
@@ -846,6 +873,7 @@ pub async fn update_entry(
// Normalize reference arrays: objects with _slug → slug strings (so clients can send resolved refs)
validator::normalize_reference_arrays(&schema, &mut body);
validator::normalize_multi_select(&schema, &mut body);
// Load existing content for readonly check
let existing = store
@@ -902,6 +930,28 @@ pub async fn update_entry(
.map_err(ApiError::from)?;
state.cache.invalidate_collection(&env, &collection).await;
// Update referrer index: remove old refs from this entry, add new refs
if let (Some(ref idx), Some(ref path)) = (&state.referrer_index, &state.referrer_index_path) {
let mut index = idx.write().await;
index.remove_all_referrers_from(&collection, &slug);
let refs = validator::extract_references(&schema, &body);
let referrer = Referrer {
collection: collection.clone(),
slug: slug.clone(),
field: String::new(),
locale: locale_ref.map(str::to_string),
};
for (ref_coll, ref_slug, field) in refs {
let mut r = referrer.clone();
r.field = field;
index.add_referrer(&ref_coll, &ref_slug, r);
}
drop(index);
if let Err(e) = idx.read().await.save(path) {
tracing::warn!("Failed to save referrer index: {}", e);
}
}
// Return updated entry (with reference format)
let entry = store
.get(&collection, &slug, locale_ref)
@@ -933,6 +983,22 @@ pub async fn update_entry(
Ok(Json(formatted))
}
// ---------------------------------------------------------------------------
// GET /api/content/:collection/:slug/referrers
// ---------------------------------------------------------------------------
pub async fn get_referrers(
State(state): State<Arc<AppState>>,
Path((collection, slug)): Path<(String, String)>,
) -> Result<Json<Value>, ApiError> {
let referrers = match &state.referrer_index {
Some(idx) => idx.read().await.get_referrers(&collection, &slug),
None => vec![],
};
Ok(Json(
serde_json::to_value(&referrers).unwrap_or_else(|_| json!([])),
))
}
// ---------------------------------------------------------------------------
// GET /api/locales
// ---------------------------------------------------------------------------
@@ -982,6 +1048,17 @@ pub async fn delete_entry(
)));
}
// Update referrer index: remove this entry from every (ref_coll, ref_slug) it referenced; and remove key (collection, slug)
if let (Some(ref idx), Some(ref path)) = (&state.referrer_index, &state.referrer_index_path) {
let mut index = idx.write().await;
index.remove_all_referrers_from(&collection, &slug);
index.remove_referenced_entry(&collection, &slug);
drop(index);
if let Err(e) = idx.read().await.save(path) {
tracing::warn!("Failed to save referrer index: {}", e);
}
}
store
.delete(&collection, &slug, locale_ref)
.await