use axum::{ extract::{Path, State}, response::IntoResponse, }; use serde::Serialize; use tokio::join; use crate::{ middlewares::{ auth_middleware::RequireUser, response_middleware::{HtmlError, TemplateResponse}, }, AuthSessionType, SessionType, }; use common::{ error::AppError, storage::types::{ file_info::FileInfo, ingestion_task::IngestionTask, knowledge_entity::KnowledgeEntity, knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk, text_content::TextContent, user::User, }, }; use crate::html_state::HtmlState; #[derive(Serialize)] pub struct IndexPageData { gdpr_accepted: bool, user: Option, latest_text_contents: Vec, active_jobs: Vec, } pub async fn index_handler( State(state): State, auth: AuthSessionType, session: SessionType, ) -> Result { let gdpr_accepted = auth.current_user.is_some() | session.get("gdpr_accepted").unwrap_or(false); let active_jobs = match auth.current_user.is_some() { true => { User::get_unfinished_ingestion_tasks(&auth.current_user.clone().unwrap().id, &state.db) .await? } false => vec![], }; let latest_text_contents = match auth.current_user.clone().is_some() { true => { User::get_latest_text_contents( auth.current_user.clone().unwrap().id.as_str(), &state.db, ) .await? } false => vec![], }; Ok(TemplateResponse::new_template( "index/index.html", IndexPageData { gdpr_accepted, user: auth.current_user, latest_text_contents, active_jobs, }, )) } #[derive(Serialize)] pub struct LatestTextContentData { latest_text_contents: Vec, user: User, } pub async fn delete_text_content( State(state): State, RequireUser(user): RequireUser, Path(id): Path, ) -> Result { // Get and validate TextContent let text_content = get_and_validate_text_content(&state, &id, &user).await?; // Perform concurrent deletions let (_res1, _res2, _res3, _res4, _res5) = join!( async { if let Some(file_info) = text_content.file_info { FileInfo::delete_by_id(&file_info.id, &state.db).await } else { Ok(()) } }, state.db.delete_item::(&text_content.id), TextChunk::delete_by_source_id(&text_content.id, &state.db), KnowledgeEntity::delete_by_source_id(&text_content.id, &state.db), KnowledgeRelationship::delete_relationships_by_source_id(&text_content.id, &state.db) ); // Render updated content let latest_text_contents = User::get_latest_text_contents(&user.id, &state.db).await?; Ok(TemplateResponse::new_partial( "index/signed_in/recent_content.html", "latest_content_section", LatestTextContentData { user: user.to_owned(), latest_text_contents, }, )) } // Helper function to get and validate text content async fn get_and_validate_text_content( state: &HtmlState, id: &str, user: &User, ) -> Result { let text_content = state .db .get_item::(id) .await? .ok_or_else(|| AppError::NotFound("Item was not found".to_string()))?; if text_content.user_id != user.id { return Err(AppError::Auth( "You are not the owner of that content".to_string(), )); } Ok(text_content) } #[derive(Serialize)] pub struct ActiveJobsData { pub active_jobs: Vec, pub user: User, } pub async fn delete_job( State(state): State, RequireUser(user): RequireUser, Path(id): Path, ) -> Result { User::validate_and_delete_job(&id, &user.id, &state.db).await?; let active_jobs = User::get_unfinished_ingestion_tasks(&user.id, &state.db).await?; Ok(TemplateResponse::new_partial( "index/signed_in/active_jobs.html", "active_jobs_section", ActiveJobsData { user: user.clone(), active_jobs, }, )) } pub async fn show_active_jobs( State(state): State, RequireUser(user): RequireUser, ) -> Result { let active_jobs = User::get_unfinished_ingestion_tasks(&user.id, &state.db).await?; Ok(TemplateResponse::new_partial( "index/signed_in/active_jobs.html", "active_jobs_section", ActiveJobsData { user: user.clone(), active_jobs, }, )) }