feat: complete deletion of items on request

This commit is contained in:
Per Stark
2025-01-30 16:38:32 +01:00
parent 5d86be457c
commit fe9505e39d
8 changed files with 178 additions and 26 deletions

View File

@@ -2406,6 +2406,9 @@
}
}
}
.z-4 {
z-index: 4;
}
.tab-content {
order {
}
@@ -3503,6 +3506,9 @@
.h-5 {
height: calc(var(--spacing) * 5);
}
.h-6 {
height: calc(var(--spacing) * 6);
}
.h-32 {
height: calc(var(--spacing) * 32);
}
@@ -3512,6 +3518,9 @@
.min-h-screen {
min-height: 100vh;
}
.w-2 {
width: calc(var(--spacing) * 2);
}
.w-5 {
width: calc(var(--spacing) * 5);
}

View File

@@ -66,7 +66,7 @@ impl LLMGraphAnalysisResult {
.await?;
// Process relationships
let relationships = self.process_relationships(Arc::clone(&mapper))?;
let relationships = self.process_relationships(source_id, Arc::clone(&mapper))?;
Ok((entities, relationships))
}
@@ -116,6 +116,7 @@ impl LLMGraphAnalysisResult {
fn process_relationships(
&self,
source_id: &str,
mapper: Arc<Mutex<GraphMapper>>,
) -> Result<Vec<KnowledgeRelationship>, AppError> {
let mut mapper_guard = mapper
@@ -130,6 +131,7 @@ impl LLMGraphAnalysisResult {
Ok(KnowledgeRelationship::new(
source_db_id.to_string(),
target_db_id.to_string(),
source_id.to_string(),
rel.type_.clone(),
None,
))

View File

@@ -16,8 +16,12 @@ use crate::{
AppState,
},
storage::{
db::delete_item,
types::{job::Job, text_content::TextContent, user::User},
db::{delete_item, get_all_stored_items, get_item},
types::{
file_info::FileInfo, job::Job, knowledge_entity::KnowledgeEntity,
knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk,
text_content::TextContent, user::User,
},
},
};
@@ -56,19 +60,15 @@ pub async fn index_handler(
false => vec![],
};
info!("{:?}", latest_text_contents);
let latest_knowledge_entities = match auth.current_user.is_some() {
true => User::get_latest_knowledge_entities(
auth.current_user.clone().unwrap().id.as_str(),
&state.surreal_db_client,
)
.await
.map_err(|e| HtmlError::new(e, state.templates.clone()))?,
false => vec![],
};
info!("{:?}", latest_knowledge_entities);
// let latest_knowledge_entities = match auth.current_user.is_some() {
// true => User::get_latest_knowledge_entities(
// auth.current_user.clone().unwrap().id.as_str(),
// &state.surreal_db_client,
// )
// .await
// .map_err(|e| HtmlError::new(e, state.templates.clone()))?,
// false => vec![],
// };
let output = render_template(
IndexData::template_name(),
@@ -100,15 +100,65 @@ pub async fn delete_text_content(
None => return Ok(Redirect::to("/").into_response()),
};
delete_item::<TextContent>(&state.surreal_db_client, &id)
// Get TextContent from db
let text_content = match get_item::<TextContent>(&state.surreal_db_client, &id)
.await
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?
{
Some(text_content) => text_content,
None => {
return Err(HtmlError::new(
AppError::NotFound("No item found".to_string()),
state.templates,
))
}
};
// Validate that the user is the owner
if text_content.user_id != user.id {
return Err(HtmlError::new(
AppError::Auth("You are not the owner of that content".to_string()),
state.templates,
));
}
// If TextContent has file_info, delete it from db and file from disk.
if text_content.file_info.is_some() {
FileInfo::delete_by_id(
&text_content.file_info.unwrap().id,
&state.surreal_db_client,
)
.await
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
}
// Delete textcontent from db
delete_item::<TextContent>(&state.surreal_db_client, &text_content.id)
.await
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
let latest_text_contents = User::get_latest_text_contents(&user.id, &state.surreal_db_client)
// Delete TextChunks
TextChunk::delete_by_source_id(&text_content.id, &state.surreal_db_client)
.await
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
info!("{:?}", latest_text_contents);
// Delete KnowledgeEntities
KnowledgeEntity::delete_by_source_id(&text_content.id, &state.surreal_db_client)
.await
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
// Delete KnowledgeRelationships
KnowledgeRelationship::delete_relationships_by_source_id(
&text_content.id,
&state.surreal_db_client,
)
.await
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
// Get latest text contents after updates
let latest_text_contents = User::get_latest_text_contents(&user.id, &state.surreal_db_client)
.await
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
let output = render_block(
"index/signed_in/recent_content.html",

View File

@@ -7,11 +7,12 @@ use std::{
};
use tempfile::NamedTempFile;
use thiserror::Error;
use tokio::fs::remove_dir_all;
use tracing::info;
use uuid::Uuid;
use crate::{
storage::db::{store_item, SurrealDbClient},
storage::db::{delete_item, get_item, store_item, SurrealDbClient},
stored_object,
};
@@ -198,4 +199,50 @@ impl FileInfo {
.next()
.ok_or(FileError::FileNotFound(sha256.to_string()))
}
/// Removes FileInfo from database and file from disk
///
/// # Arguments
/// * `id` - Id of the FileInfo
/// * `db_client` - Reference to SurrealDbClient
///
/// # Returns
/// `Result<(), FileError>`
pub async fn delete_by_id(id: &str, db_client: &SurrealDbClient) -> Result<(), FileError> {
// Get the FileInfo from the database
let file_info = match get_item::<FileInfo>(db_client, id).await? {
Some(info) => info,
None => {
return Err(FileError::FileNotFound(format!(
"File with id {} was not found",
id
)))
}
};
// Remove the file and its parent directory
let file_path = Path::new(&file_info.path);
if file_path.exists() {
// Get the parent directory of the file
if let Some(parent_dir) = file_path.parent() {
// Remove the entire directory containing the file
remove_dir_all(parent_dir).await?;
info!("Removed directory {:?} and its contents", parent_dir);
} else {
return Err(FileError::FileNotFound(
"File has no parent directory".to_string(),
));
}
} else {
return Err(FileError::FileNotFound(format!(
"File at path {:?} was not found",
file_path
)));
}
// Delete the FileInfo from the database
delete_item::<FileInfo>(db_client, id).await?;
Ok(())
}
}

View File

@@ -1,4 +1,4 @@
use crate::stored_object;
use crate::{error::AppError, storage::db::SurrealDbClient, stored_object};
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -58,4 +58,18 @@ impl KnowledgeEntity {
user_id,
}
}
pub async fn delete_by_source_id(
source_id: &str,
db_client: &SurrealDbClient,
) -> Result<(), AppError> {
let query = format!(
"DELETE {} WHERE source_id = '{}'",
Self::table_name(),
source_id
);
db_client.query(query).await?;
Ok(())
}
}

View File

@@ -1,13 +1,14 @@
use crate::{error::AppError, stored_object};
use surrealdb::{engine::any::Any, Surreal};
use crate::{error::AppError, storage::db::SurrealDbClient, stored_object};
use surrealdb::{engine::any::Any, sql::Subquery, Surreal};
use tracing::debug;
use uuid::Uuid;
stored_object!(KnowledgeRelationship, "knowledge_relationship", {
stored_object!(KnowledgeRelationship, "relates_to", {
#[serde(rename = "in")]
in_: String,
out: String,
relationship_type: String,
source_id: String,
metadata: Option<serde_json::Value>
});
@@ -15,6 +16,7 @@ impl KnowledgeRelationship {
pub fn new(
in_: String,
out: String,
source_id: String,
relationship_type: String,
metadata: Option<serde_json::Value>,
) -> Self {
@@ -25,6 +27,7 @@ impl KnowledgeRelationship {
updated_at: now,
in_,
out,
source_id,
relationship_type,
metadata,
}
@@ -41,4 +44,18 @@ impl KnowledgeRelationship {
Ok(())
}
pub async fn delete_relationships_by_source_id(
source_id: &str,
db_client: &SurrealDbClient,
) -> Result<(), AppError> {
let query = format!(
"DELETE knowledge_entity -> relates_to WHERE source_id = '{}'",
source_id
);
db_client.query(query).await?;
Ok(())
}
}

View File

@@ -1,4 +1,4 @@
use crate::stored_object;
use crate::{error::AppError, storage::db::SurrealDbClient, stored_object};
use uuid::Uuid;
stored_object!(TextChunk, "text_chunk", {
@@ -21,4 +21,18 @@ impl TextChunk {
user_id,
}
}
pub async fn delete_by_source_id(
source_id: &str,
db_client: &SurrealDbClient,
) -> Result<(), AppError> {
let query = format!(
"DELETE {} WHERE source_id = '{}'",
Self::table_name(),
source_id
);
db_client.query(query).await?;
Ok(())
}
}

View File

@@ -7,7 +7,6 @@ use super::file_info::FileInfo;
stored_object!(TextContent, "text_content", {
text: String,
file_info: Option<FileInfo>,
url: Option<String>,
instructions: String,
category: String,