mirror of
https://github.com/perstarkse/minne.git
synced 2026-01-18 15:56:55 +01:00
feat: complete deletion of items on request
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user