diff --git a/Cargo.toml b/Cargo.toml index 0c881467..6ccca4cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" actix-web = "4.3.1" actix-rt = "2.8.0" actix-files = "0.6.2" +actix-multipart = "0.4" postgres = "0.19.7" sea-orm = {version = "0.12.9", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"]} serde = { version = "1", features = ["derive"] } @@ -18,6 +19,7 @@ dotenvy = "0.15.7" listenfd = "1.0.1" chrono = "0.4.31" migration = { path = "./migration" } +futures-util = "0.3.29" [[bin]] name = "doc_gpt" diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index 4269fdcf..928a1e61 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -130,6 +130,7 @@ impl MigrationTrait for Migration { .primary_key()) .col(ColumnDef::new(DocInfo::Uid).big_integer().not_null()) .col(ColumnDef::new(DocInfo::DocName).string().not_null()) + .col(ColumnDef::new(DocInfo::Location).string().not_null()) .col(ColumnDef::new(DocInfo::Size).big_integer().not_null()) .col(ColumnDef::new(DocInfo::Type).string().not_null()).comment("doc|folder") .col(ColumnDef::new(DocInfo::KbProgress).float().default(0)) @@ -280,6 +281,7 @@ enum DocInfo { Did, Uid, DocName, + Location, Size, Type, KbProgress, diff --git a/src/api/dialog_info.rs b/src/api/dialog_info.rs new file mode 100644 index 00000000..da81dedd --- /dev/null +++ b/src/api/dialog_info.rs @@ -0,0 +1,77 @@ +use std::collections::HashMap; +use actix_web::{get, HttpResponse, post, web}; +use actix_web::http::Error; +use crate::api::JsonResponse; +use crate::AppState; +use crate::entity::dialog_info; +use crate::service::dialog_info::Query; +use crate::service::dialog_info::Mutation; + +#[get("/v1.0/dialogs")] +async fn list(model: web::Json, data: web::Data) -> Result { + let dialogs = Query::find_dialog_infos_by_uid(&data.conn, model.uid).await.unwrap(); + + let mut result = HashMap::new(); + result.insert("dialogs", dialogs); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: result, + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} + +#[get("/v1.0/dialog")] +async fn detail(model: web::Json, data: web::Data) -> Result { + let dialogs = Query::find_dialog_info_by_id(&data.conn, model.dialog_id).await.unwrap(); + + let mut result = HashMap::new(); + result.insert("dialogs", dialogs); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: result, + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} + +#[post("/v1.0/delete_dialog")] +async fn delete(model: web::Json, data: web::Data) -> Result { + let _ = Mutation::delete_dialog_info(&data.conn, model.dialog_id).await.unwrap(); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: (), + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} + +#[post("/v1.0/create_kb")] +async fn create(model: web::Json, data: web::Data) -> Result { + let model = Mutation::create_dialog_info(&data.conn, model.into_inner()).await.unwrap(); + + let mut result = HashMap::new(); + result.insert("dialog_id", model.dialog_id.unwrap()); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: result, + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} \ No newline at end of file diff --git a/src/api/doc_info.rs b/src/api/doc_info.rs new file mode 100644 index 00000000..da6079c8 --- /dev/null +++ b/src/api/doc_info.rs @@ -0,0 +1,127 @@ +use std::collections::HashMap; +use actix_multipart::Multipart; +use actix_web::{get, HttpResponse, post, web}; +use actix_web::http::Error; +use chrono::Local; +use futures_util::{AsyncWriteExt, StreamExt}; +use serde::Deserialize; +use std::io::Write; +use crate::api::JsonResponse; +use crate::AppState; +use crate::entity::doc_info::Model; +use crate::service::doc_info::{Mutation, Query}; + +#[derive(Debug, Deserialize)] +pub struct Params { + pub uid: i64, + pub filter: FilterParams, + pub sortby: String, + pub page: u64, + pub per_page: u64, +} + +#[derive(Debug, Deserialize)] +pub struct FilterParams { + pub keywords: Option, + pub folder_id: Option, + pub tag_id: Option, + pub kb_id: Option, +} + +#[derive(Debug, Deserialize)] +pub struct MvParams { + pub dids: Vec, + pub dest_did: i64, +} + +#[get("/v1.0/docs")] +async fn list(params: web::Json, data: web::Data) -> Result { + let docs = Query::find_doc_infos_by_params(&data.conn, params.into_inner()) + .await + .unwrap(); + + let mut result = HashMap::new(); + result.insert("docs", docs); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: result, + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} + +#[post("/v1.0/upload")] +async fn upload(mut payload: Multipart, filename: web::Data, did: web::Data, uid: web::Data, data: web::Data) -> Result { + let mut size = 0; + + while let Some(item) = payload.next().await { + let mut field = item.unwrap(); + + let filepath = format!("./uploads/{}", filename.as_str()); + + let mut file = web::block(|| std::fs::File::create(filepath)) + .await + .unwrap() + .unwrap(); + + while let Some(chunk) = field.next().await { + let data = chunk.unwrap(); + size += data.len() as u64; + file = web::block(move || file.write_all(&data).map(|_| file)) + .await + .unwrap() + .unwrap(); + } + } + + let _ = Mutation::create_doc_info(&data.conn, Model { + did: *did.into_inner(), + uid: *uid.into_inner(), + doc_name: filename.to_string(), + size, + kb_infos: Vec::new(), + kb_progress: 0.0, + location: "".to_string(), + r#type: "".to_string(), + created_at: Local::now().date_naive(), + updated_at: Local::now().date_naive(), + }).await.unwrap(); + + Ok(HttpResponse::Ok().body("File uploaded successfully")) +} + +#[post("/v1.0/delete_docs")] +async fn delete(doc_ids: web::Json>, data: web::Data) -> Result { + for doc_id in doc_ids.iter() { + let _ = Mutation::delete_doc_info(&data.conn, *doc_id).await.unwrap(); + } + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: (), + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} + +#[post("/v1.0/mv_docs")] +async fn mv(params: web::Json, data: web::Data) -> Result { + Mutation::mv_doc_info(&data.conn, params.dest_did, ¶ms.dids).await.unwrap(); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: (), + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} \ No newline at end of file diff --git a/src/api/kb_info.rs b/src/api/kb_info.rs new file mode 100644 index 00000000..d6867957 --- /dev/null +++ b/src/api/kb_info.rs @@ -0,0 +1,59 @@ +use std::collections::HashMap; +use actix_web::{get, HttpResponse, post, web}; +use actix_web::http::Error; +use crate::api::JsonResponse; +use crate::AppState; +use crate::entity::kb_info; +use crate::service::kb_info::Mutation; +use crate::service::kb_info::Query; + +#[post("/v1.0/create_kb")] +async fn create(model: web::Json, data: web::Data) -> Result { + let model = Mutation::create_kb_info(&data.conn, model.into_inner()).await.unwrap(); + + let mut result = HashMap::new(); + result.insert("kb_id", model.kb_id.unwrap()); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: result, + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} + +#[get("/v1.0/kbs")] +async fn list(model: web::Json, data: web::Data) -> Result { + let kbs = Query::find_kb_infos_by_uid(&data.conn, model.uid).await.unwrap(); + + let mut result = HashMap::new(); + result.insert("kbs", kbs); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: result, + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} + +#[post("/v1.0/delete_kb")] +async fn delete(model: web::Json, data: web::Data) -> Result { + let _ = Mutation::delete_kb_info(&data.conn, model.kb_id).await.unwrap(); + + let json_response = JsonResponse { + code: 200, + err: "".to_owned(), + data: (), + }; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&json_response).unwrap())) +} \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index f8cf91d9..65f6a22d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,6 +1,9 @@ use serde::{Deserialize, Serialize}; pub(crate) mod tag; +pub(crate) mod kb_info; +pub(crate) mod dialog_info; +pub(crate) mod doc_info; #[derive(Debug, Deserialize, Serialize)] struct JsonResponse { diff --git a/src/entity/doc_info.rs b/src/entity/doc_info.rs index 0f39a8d6..61e0a371 100644 --- a/src/entity/doc_info.rs +++ b/src/entity/doc_info.rs @@ -1,5 +1,6 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; +use crate::entity::kb_info; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)] #[sea_orm(table_name = "doc_info")] @@ -9,10 +10,13 @@ pub struct Model { #[sea_orm(index)] pub uid: i64, pub doc_name: String, - pub size: i64, + pub size: u64, #[sea_orm(column_name = "type")] pub r#type: String, pub kb_progress: f64, + pub location: String, + #[sea_orm(ignore)] + pub kb_infos: Vec, #[serde(skip_deserializing)] pub created_at: Date, diff --git a/src/entity/mod.rs b/src/entity/mod.rs index bc48fdc1..6fbdee3c 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod tag_info; mod tag2_doc; mod kb2_doc; mod dialog2_kb; -mod doc2_doc; +pub(crate) mod doc2_doc; pub(crate) mod kb_info; pub(crate) mod doc_info; pub(crate) mod dialog_info; \ No newline at end of file diff --git a/src/service/dialog_info.rs b/src/service/dialog_info.rs index b2f9f9d3..bdd1d6db 100644 --- a/src/service/dialog_info.rs +++ b/src/service/dialog_info.rs @@ -1,5 +1,9 @@ -use sea_orm::{DbConn, DbErr, EntityTrait, PaginatorTrait, QueryOrder}; -use crate::entity::dialog_info; +use chrono::Local; +use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder}; +use sea_orm::ActiveValue::Set; +use sea_orm::QueryFilter; +use sea_orm::ColumnTrait; +use crate::entity::{dialog_info, kb_info}; use crate::entity::dialog_info::Entity; pub struct Query; @@ -9,6 +13,17 @@ impl Query { Entity::find_by_id(id).one(db).await } + pub async fn find_dialog_infos(db: &DbConn) -> Result, DbErr> { + Entity::find().all(db).await + } + + pub async fn find_dialog_infos_by_uid(db: &DbConn, uid: i64) -> Result, DbErr> { + Entity::find() + .filter(dialog_info::Column::Uid.eq(uid)) + .all(db) + .await + } + pub async fn find_dialog_infos_in_page( db: &DbConn, page: u64, @@ -23,4 +38,61 @@ impl Query { // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) } +} + +pub struct Mutation; + +impl Mutation { + pub async fn create_dialog_info( + db: &DbConn, + form_data: dialog_info::Model, + ) -> Result { + dialog_info::ActiveModel { + dialog_id: Default::default(), + uid: Set(form_data.uid.to_owned()), + dialog_name: Set(form_data.dialog_name.to_owned()), + history: Set(form_data.history.to_owned()), + created_at: Set(Local::now().date_naive()), + updated_at: Set(Local::now().date_naive()), + } + .save(db) + .await + } + + pub async fn update_dialog_info_by_id( + db: &DbConn, + id: i64, + form_data: dialog_info::Model, + ) -> Result { + let dialog_info: dialog_info::ActiveModel = Entity::find_by_id(id) + .one(db) + .await? + .ok_or(DbErr::Custom("Cannot find.".to_owned())) + .map(Into::into)?; + + dialog_info::ActiveModel { + dialog_id: dialog_info.dialog_id, + uid: dialog_info.uid, + dialog_name: Set(form_data.dialog_name.to_owned()), + history: Set(form_data.history.to_owned()), + created_at: Default::default(), + updated_at: Set(Local::now().date_naive()), + } + .update(db) + .await + } + + pub async fn delete_dialog_info(db: &DbConn, kb_id: i64) -> Result { + let tag: dialog_info::ActiveModel = Entity::find_by_id(kb_id) + .one(db) + .await? + .ok_or(DbErr::Custom("Cannot find.".to_owned())) + .map(Into::into)?; + + tag.delete(db).await + } + + pub async fn delete_all_dialog_infos(db: &DbConn) -> Result { + Entity::delete_many().exec(db).await + } } \ No newline at end of file diff --git a/src/service/doc_info.rs b/src/service/doc_info.rs new file mode 100644 index 00000000..f05685e3 --- /dev/null +++ b/src/service/doc_info.rs @@ -0,0 +1,154 @@ +use chrono::Local; +use postgres::fallible_iterator::FallibleIterator; +use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder}; +use sea_orm::ActiveValue::Set; +use sea_orm::QueryFilter; +use crate::api::doc_info::{FilterParams, Params}; +use crate::entity::{doc2_doc, doc_info, kb_info, tag_info}; +use crate::entity::doc_info::Entity; + +pub struct Query; + +impl Query { + pub async fn find_doc_info_by_id(db: &DbConn, id: i64) -> Result, DbErr> { + Entity::find_by_id(id).one(db).await + } + + pub async fn find_doc_infos(db: &DbConn) -> Result, DbErr> { + Entity::find().all(db).await + } + + pub async fn find_doc_infos_by_uid(db: &DbConn, uid: i64) -> Result, DbErr> { + Entity::find() + .filter(doc_info::Column::Uid.eq(uid)) + .all(db) + .await + } + + pub async fn find_doc_infos_by_params(db: &DbConn, params: Params) -> Result, DbErr> { + // Setup paginator + let paginator = Entity::find(); + + // Fetch paginated posts + let mut query = paginator + .find_with_related(kb_info::Entity); + if let Some(kb_id) = params.filter.kb_id { + query = query.filter(kb_info::Column::KbId.eq(kb_id)); + } + if let Some(folder_id) = params.filter.folder_id { + + } + if let Some(tag_id) = params.filter.tag_id { + query = query.filter(tag_info::Column::Tid.eq(tag_id)); + } + if let Some(keywords) = params.filter.keywords { + + } + Ok(query.order_by_asc(doc_info::Column::Did) + .all(db) + .await? + .into_iter() + .map(|(mut doc_info, kb_infos)| { + doc_info.kb_infos = kb_infos; + doc_info + }) + .collect()) + } + + pub async fn find_doc_infos_in_page( + db: &DbConn, + page: u64, + posts_per_page: u64, + ) -> Result<(Vec, u64), DbErr> { + // Setup paginator + let paginator = Entity::find() + .order_by_asc(doc_info::Column::Did) + .paginate(db, posts_per_page); + let num_pages = paginator.num_pages().await?; + + // Fetch paginated posts + paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) + } +} + +pub struct Mutation; + +impl Mutation { + + pub async fn mv_doc_info( + db: &DbConn, + dest_did: i64, + dids: &[i64] + ) -> Result<(), DbErr> { + for did in dids { + let _ = doc2_doc::ActiveModel { + parent_id: Set(dest_did), + did: Set(*did), + } + .save(db) + .await + .unwrap(); + } + + Ok(()) + } + + pub async fn create_doc_info( + db: &DbConn, + form_data: doc_info::Model, + ) -> Result { + doc_info::ActiveModel { + did: Default::default(), + uid: Set(form_data.uid.to_owned()), + doc_name: Set(form_data.doc_name.to_owned()), + size: Set(form_data.size.to_owned()), + r#type: Set(form_data.r#type.to_owned()), + kb_progress: Set(form_data.kb_progress.to_owned()), + location: Set(form_data.location.to_owned()), + created_at: Set(Local::now().date_naive()), + updated_at: Set(Local::now().date_naive()), + } + .save(db) + .await + } + + pub async fn update_doc_info_by_id( + db: &DbConn, + id: i64, + form_data: doc_info::Model, + ) -> Result { + let doc_info: doc_info::ActiveModel = Entity::find_by_id(id) + .one(db) + .await? + .ok_or(DbErr::Custom("Cannot find.".to_owned())) + .map(Into::into)?; + + doc_info::ActiveModel { + did: doc_info.did, + uid: Set(form_data.uid.to_owned()), + doc_name: Set(form_data.doc_name.to_owned()), + size: Set(form_data.size.to_owned()), + r#type: Set(form_data.r#type.to_owned()), + kb_progress: Set(form_data.kb_progress.to_owned()), + location: Set(form_data.location.to_owned()), + created_at: Default::default(), + updated_at: Set(Local::now().date_naive()), + } + .update(db) + .await + } + + pub async fn delete_doc_info(db: &DbConn, doc_id: i64) -> Result { + let tag: doc_info::ActiveModel = Entity::find_by_id(doc_id) + .one(db) + .await? + .ok_or(DbErr::Custom("Cannot find.".to_owned())) + .map(Into::into)?; + + tag.delete(db).await + } + + pub async fn delete_all_doc_infos(db: &DbConn) -> Result { + Entity::delete_many().exec(db).await + } +} \ No newline at end of file diff --git a/src/service/kb_info.rs b/src/service/kb_info.rs new file mode 100644 index 00000000..99933545 --- /dev/null +++ b/src/service/kb_info.rs @@ -0,0 +1,96 @@ +use chrono::{Local, NaiveDate}; +use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; +use sea_orm::ActiveValue::Set; +use crate::entity::kb_info; +use crate::entity::kb_info::Entity; + +pub struct Query; + +impl Query { + pub async fn find_kb_info_by_id(db: &DbConn, id: i64) -> Result, DbErr> { + Entity::find_by_id(id).one(db).await + } + + pub async fn find_kb_infos(db: &DbConn) -> Result, DbErr> { + Entity::find().all(db).await + } + + pub async fn find_kb_infos_by_uid(db: &DbConn, uid: i64) -> Result, DbErr> { + Entity::find() + .filter(kb_info::Column::Uid.eq(uid)) + .all(db) + .await + } + + pub async fn find_kb_infos_in_page( + db: &DbConn, + page: u64, + posts_per_page: u64, + ) -> Result<(Vec, u64), DbErr> { + // Setup paginator + let paginator = Entity::find() + .order_by_asc(kb_info::Column::KbId) + .paginate(db, posts_per_page); + let num_pages = paginator.num_pages().await?; + + // Fetch paginated posts + paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) + } +} + +pub struct Mutation; + +impl Mutation { + pub async fn create_kb_info( + db: &DbConn, + form_data: kb_info::Model, + ) -> Result { + kb_info::ActiveModel { + kb_id: Default::default(), + uid: Set(form_data.uid.to_owned()), + kn_name: Set(form_data.kn_name.to_owned()), + icon: Set(form_data.icon.to_owned()), + created_at: Set(Local::now().date_naive()), + updated_at: Set(Local::now().date_naive()), + } + .save(db) + .await + } + + pub async fn update_kb_info_by_id( + db: &DbConn, + id: i64, + form_data: kb_info::Model, + ) -> Result { + let kb_info: kb_info::ActiveModel = Entity::find_by_id(id) + .one(db) + .await? + .ok_or(DbErr::Custom("Cannot find.".to_owned())) + .map(Into::into)?; + + kb_info::ActiveModel { + kb_id: kb_info.kb_id, + uid: kb_info.uid, + kn_name: Set(form_data.kn_name.to_owned()), + icon: Set(form_data.icon.to_owned()), + created_at: Default::default(), + updated_at: Set(Local::now().date_naive()), + } + .update(db) + .await + } + + pub async fn delete_kb_info(db: &DbConn, kb_id: i64) -> Result { + let tag: kb_info::ActiveModel = Entity::find_by_id(kb_id) + .one(db) + .await? + .ok_or(DbErr::Custom("Cannot find.".to_owned())) + .map(Into::into)?; + + tag.delete(db).await + } + + pub async fn delete_all_kb_infos(db: &DbConn) -> Result { + Entity::delete_many().exec(db).await + } +} \ No newline at end of file diff --git a/src/service/mod.rs b/src/service/mod.rs index ceecbc1b..333b9f18 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,2 +1,4 @@ pub(crate) mod dialog_info; -pub(crate) mod tag_info; \ No newline at end of file +pub(crate) mod tag_info; +pub(crate) mod kb_info; +pub(crate) mod doc_info; \ No newline at end of file diff --git a/src/service/tag_info.rs b/src/service/tag_info.rs index 656eb2c9..f7d3f5ba 100644 --- a/src/service/tag_info.rs +++ b/src/service/tag_info.rs @@ -22,7 +22,7 @@ impl Query { ) -> Result<(Vec, u64), DbErr> { // Setup paginator let paginator = Entity::find() - .order_by_asc(tag_info::Column::Uid) + .order_by_asc(tag_info::Column::Tid) .paginate(db, posts_per_page); let num_pages = paginator.num_pages().await?;