mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-24 17:58:31 +02:00
feat: existing user_categories are options
This commit is contained in:
@@ -113,7 +113,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
setup_auth(&app_state.surreal_db_client).await?;
|
setup_auth(&app_state.surreal_db_client).await?;
|
||||||
Analytics::ensure_initialized(&app_state.surreal_db_client).await?;
|
Analytics::ensure_initialized(&app_state.surreal_db_client).await?;
|
||||||
SystemSettings::ensure_initialized(&app_state.surreal_db_client).await?;
|
SystemSettings::ensure_initialized(&app_state.surreal_db_client).await?;
|
||||||
|
// app_state.surreal_db_client.drop_table::<KnowledgeEntity>().await?;
|
||||||
// Create Axum router
|
// Create Axum router
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/api/v1", api_routes_v1(&app_state))
|
.nest("/api/v1", api_routes_v1(&app_state))
|
||||||
|
|||||||
@@ -176,88 +176,6 @@ async fn get_and_validate_text_content(
|
|||||||
|
|
||||||
Ok(text_content)
|
Ok(text_content)
|
||||||
}
|
}
|
||||||
// pub async fn delete_text_content(
|
|
||||||
// State(state): State<AppState>,
|
|
||||||
// auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
|
||||||
// Path(id): Path<String>,
|
|
||||||
// ) -> Result<impl IntoResponse, HtmlError> {
|
|
||||||
// let user = match &auth.current_user {
|
|
||||||
// Some(user) => user,
|
|
||||||
// None => return Ok(Redirect::to("/").into_response()),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // 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()))?;
|
|
||||||
|
|
||||||
// // Delete TextChunks
|
|
||||||
// TextChunk::delete_by_source_id(&text_content.id, &state.surreal_db_client)
|
|
||||||
// .await
|
|
||||||
// .map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
|
||||||
|
|
||||||
// // 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",
|
|
||||||
// "latest_content_section",
|
|
||||||
// LatestTextContentData {
|
|
||||||
// user: user.clone(),
|
|
||||||
// latest_text_contents,
|
|
||||||
// },
|
|
||||||
// state.templates.clone(),
|
|
||||||
// )?;
|
|
||||||
|
|
||||||
// Ok(output.into_response())
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ActiveJobsData {
|
pub struct ActiveJobsData {
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ use crate::{
|
|||||||
|
|
||||||
use super::render_template;
|
use super::render_template;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ShowIngressFormData {
|
||||||
|
user_categories: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn show_ingress_form(
|
pub async fn show_ingress_form(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||||
@@ -28,7 +33,15 @@ pub async fn show_ingress_form(
|
|||||||
return Ok(Redirect::to("/").into_response());
|
return Ok(Redirect::to("/").into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = render_template("ingress_form.html", {}, state.templates.clone())?;
|
let user_categories = User::get_user_categories(&auth.id, &state.surreal_db_client)
|
||||||
|
.await
|
||||||
|
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||||
|
|
||||||
|
let output = render_template(
|
||||||
|
"ingress_form.html",
|
||||||
|
ShowIngressFormData { user_categories },
|
||||||
|
state.templates.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(output.into_response())
|
Ok(output.into_response())
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/server/routes/html/knowledge/entities.rs
Normal file
41
src/server/routes/html/knowledge/entities.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
response::{IntoResponse, Redirect},
|
||||||
|
};
|
||||||
|
use axum_session::Session;
|
||||||
|
use axum_session_auth::AuthSession;
|
||||||
|
use axum_session_surreal::SessionSurrealPool;
|
||||||
|
use surrealdb::{engine::any::Any, Surreal};
|
||||||
|
use tokio::join;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{AppError, HtmlError},
|
||||||
|
page_data,
|
||||||
|
server::{
|
||||||
|
routes::html::{render_block, render_template},
|
||||||
|
AppState,
|
||||||
|
},
|
||||||
|
storage::{
|
||||||
|
db::{delete_item, get_item},
|
||||||
|
types::{
|
||||||
|
file_info::FileInfo, job::Job, knowledge_entity::KnowledgeEntity,
|
||||||
|
knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk,
|
||||||
|
text_content::TextContent, user::User,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
page_data!(KnowledgeEntitiesData, "todo", {
|
||||||
|
gdpr_accepted: bool,
|
||||||
|
user: Option<User>,
|
||||||
|
latest_text_contents: Vec<TextContent>,
|
||||||
|
active_jobs: Vec<Job>
|
||||||
|
});
|
||||||
|
pub async fn index_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||||
|
session: Session<SessionSurrealPool<Any>>,
|
||||||
|
) -> Result<impl IntoResponse, HtmlError> {
|
||||||
|
Ok("Hi".into_response())
|
||||||
|
}
|
||||||
@@ -11,6 +11,11 @@ use super::{
|
|||||||
knowledge_entity::KnowledgeEntity, system_settings::SystemSettings, text_content::TextContent,
|
knowledge_entity::KnowledgeEntity, system_settings::SystemSettings, text_content::TextContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CategoryResponse {
|
||||||
|
category: String,
|
||||||
|
}
|
||||||
|
|
||||||
stored_object!(User, "user", {
|
stored_object!(User, "user", {
|
||||||
email: String,
|
email: String,
|
||||||
password: String,
|
password: String,
|
||||||
@@ -245,4 +250,23 @@ impl User {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_categories(
|
||||||
|
user_id: &str,
|
||||||
|
db: &SurrealDbClient,
|
||||||
|
) -> Result<Vec<String>, AppError> {
|
||||||
|
// Query to select distinct categories for the user
|
||||||
|
let response: Vec<CategoryResponse> = db
|
||||||
|
.client
|
||||||
|
.query("SELECT category FROM type::table($table_name) WHERE user_id = $user_id GROUP BY category")
|
||||||
|
.bind(("user_id", user_id.to_owned()))
|
||||||
|
.bind(("table_name", TextContent::table_name()))
|
||||||
|
.await?
|
||||||
|
.take(0)?;
|
||||||
|
|
||||||
|
// Extract the categories from the response
|
||||||
|
let categories: Vec<String> = response.into_iter().map(|item| item.category).collect();
|
||||||
|
|
||||||
|
Ok(categories)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,34 +8,33 @@
|
|||||||
<div class="validator-hint hidden">Instructions are required</div>
|
<div class="validator-hint hidden">Instructions are required</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="floating-label">
|
<label class="floating-label">
|
||||||
<span>Content</span>
|
<span>Content</span>
|
||||||
<textarea name="content" class="textarea input-bordered w-full"
|
<textarea name="content" class="textarea input-bordered w-full"
|
||||||
placeholder="Enter the content you want to ingress, it can be an URL or a text snippet">{{ content
|
placeholder="Enter the content you want to ingress, it can be an URL or a text snippet">{{ content }}</textarea>
|
||||||
}}</textarea>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="floating-label">
|
<label class="floating-label">
|
||||||
<span>Category</span>
|
<span>Category</span>
|
||||||
<input type="text" name="category" class="input input-bordered validator w-full"
|
<input type="text" name="category" class="input input-bordered validator w-full"
|
||||||
placeholder="Category for ingress" value="{{ category }}" required />
|
placeholder="Category for ingress" value="{{ category }}" list="category-list" required />
|
||||||
|
<datalist id="category-list">
|
||||||
|
{% for category in user_categories %}
|
||||||
|
<option value="{{ category }}" />
|
||||||
|
{% endfor %}
|
||||||
|
</datalist>
|
||||||
<div class="validator-hint hidden">Category is required</div>
|
<div class="validator-hint hidden">Category is required</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label label-text">Files</label>
|
<label class="label label-text">Files</label>
|
||||||
<input type="file" name="files" multiple class="file-input file-input-bordered w-full" />
|
<input type="file" name="files" multiple class="file-input file-input-bordered w-full" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="error-message" class="text-error text-center {% if not error %}hidden{% endif %}">{{ error }}</div>
|
<div id="error-message" class="text-error text-center {% if not error %}hidden{% endif %}">{{ error }}</div>
|
||||||
|
|
||||||
<div class="form-control mt-6 flex flex-col sm:flex-row gap-1">
|
<div class="form-control mt-6 flex flex-col sm:flex-row gap-1">
|
||||||
<button hx-get="/hide-ingress-form" hx-target="#ingress-form" hx-swap=outerHTML"
|
<button hx-get="/hide-ingress-form" hx-target="#ingress-form" hx-swap="outerHTML"
|
||||||
class="btn btn-outline w-full sm:w-fit">Cancel</button>
|
class="btn btn-outline w-full sm:w-fit">Cancel</button>
|
||||||
<button type="submit" class="btn btn-primary w-full sm:w-fit">Submit</button>
|
<button type="submit" class="btn btn-primary w-full sm:w-fit">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user