mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-07-05 22:05:08 +08:00
feat: Implement authentication (#10)
Co-authored-by: kould <2435992353@qq.com>
This commit is contained in:
parent
c64dcb929b
commit
d94c6df4a4
@ -10,6 +10,10 @@ actix-web = "4.3.1"
|
|||||||
actix-rt = "2.8.0"
|
actix-rt = "2.8.0"
|
||||||
actix-files = "0.6.2"
|
actix-files = "0.6.2"
|
||||||
actix-multipart = "0.4"
|
actix-multipart = "0.4"
|
||||||
|
actix-session = { version = "0.5" }
|
||||||
|
actix-identity = { version = "0.4" }
|
||||||
|
actix-web-httpauth = { version = "0.6" }
|
||||||
|
thiserror = "1.0"
|
||||||
postgres = "0.19.7"
|
postgres = "0.19.7"
|
||||||
sea-orm = {version = "0.12.9", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"]}
|
sea-orm = {version = "0.12.9", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"]}
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
@ -24,6 +24,7 @@ impl MigrationTrait for Migration {
|
|||||||
.col(ColumnDef::new(UserInfo::ColorSchema).string().default("dark"))
|
.col(ColumnDef::new(UserInfo::ColorSchema).string().default("dark"))
|
||||||
.col(ColumnDef::new(UserInfo::ListStyle).string().default("list"))
|
.col(ColumnDef::new(UserInfo::ListStyle).string().default("list"))
|
||||||
.col(ColumnDef::new(UserInfo::Language).string().default("chinese"))
|
.col(ColumnDef::new(UserInfo::Language).string().default("chinese"))
|
||||||
|
.col(ColumnDef::new(UserInfo::Password).string().not_null())
|
||||||
.col(ColumnDef::new(UserInfo::CreatedAt).date().not_null())
|
.col(ColumnDef::new(UserInfo::CreatedAt).date().not_null())
|
||||||
.col(ColumnDef::new(UserInfo::UpdatedAt).date().not_null())
|
.col(ColumnDef::new(UserInfo::UpdatedAt).date().not_null())
|
||||||
.col(ColumnDef::new(UserInfo::IsDeleted).boolean().default(false))
|
.col(ColumnDef::new(UserInfo::IsDeleted).boolean().default(false))
|
||||||
@ -215,6 +216,7 @@ enum UserInfo {
|
|||||||
ColorSchema,
|
ColorSchema,
|
||||||
ListStyle,
|
ListStyle,
|
||||||
Language,
|
Language,
|
||||||
|
Password,
|
||||||
CreatedAt,
|
CreatedAt,
|
||||||
UpdatedAt,
|
UpdatedAt,
|
||||||
IsDeleted,
|
IsDeleted,
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use actix_web::{get, HttpResponse, post, web};
|
use actix_web::{get, HttpResponse, post, web};
|
||||||
use actix_web::http::Error;
|
|
||||||
use crate::api::JsonResponse;
|
use crate::api::JsonResponse;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::entity::dialog_info;
|
use crate::entity::dialog_info;
|
||||||
|
use crate::errors::AppError;
|
||||||
use crate::service::dialog_info::Query;
|
use crate::service::dialog_info::Query;
|
||||||
use crate::service::dialog_info::Mutation;
|
use crate::service::dialog_info::Mutation;
|
||||||
|
|
||||||
#[get("/v1.0/dialogs")]
|
#[get("/v1.0/dialogs")]
|
||||||
async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let dialogs = Query::find_dialog_infos_by_uid(&data.conn, model.uid).await.unwrap();
|
let dialogs = Query::find_dialog_infos_by_uid(&data.conn, model.uid).await?;
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("dialogs", dialogs);
|
result.insert("dialogs", dialogs);
|
||||||
@ -22,12 +22,12 @@ async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/v1.0/dialog")]
|
#[get("/v1.0/dialog")]
|
||||||
async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let dialogs = Query::find_dialog_info_by_id(&data.conn, model.dialog_id).await.unwrap();
|
let dialogs = Query::find_dialog_info_by_id(&data.conn, model.dialog_id).await?;
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("dialogs", dialogs);
|
result.insert("dialogs", dialogs);
|
||||||
@ -40,12 +40,12 @@ async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>)
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1.0/delete_dialog")]
|
#[post("/v1.0/delete_dialog")]
|
||||||
async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let _ = Mutation::delete_dialog_info(&data.conn, model.dialog_id).await.unwrap();
|
let _ = Mutation::delete_dialog_info(&data.conn, model.dialog_id).await?;
|
||||||
|
|
||||||
let json_response = JsonResponse {
|
let json_response = JsonResponse {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -55,12 +55,12 @@ async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>)
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1.0/create_kb")]
|
#[post("/v1.0/create_kb")]
|
||||||
async fn create(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn create(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let model = Mutation::create_dialog_info(&data.conn, model.into_inner()).await.unwrap();
|
let model = Mutation::create_dialog_info(&data.conn, model.into_inner()).await?;
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("dialog_id", model.dialog_id.unwrap());
|
result.insert("dialog_id", model.dialog_id.unwrap());
|
||||||
@ -73,5 +73,5 @@ async fn create(model: web::Json<dialog_info::Model>, data: web::Data<AppState>)
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use actix_multipart::Multipart;
|
use actix_multipart::Multipart;
|
||||||
use actix_web::{get, HttpResponse, post, web};
|
use actix_web::{get, HttpResponse, post, web};
|
||||||
use actix_web::http::Error;
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use futures_util::{AsyncWriteExt, StreamExt};
|
use futures_util::StreamExt;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use crate::api::JsonResponse;
|
use crate::api::JsonResponse;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::entity::doc_info::Model;
|
use crate::entity::doc_info::Model;
|
||||||
|
use crate::errors::AppError;
|
||||||
use crate::service::doc_info::{Mutation, Query};
|
use crate::service::doc_info::{Mutation, Query};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -35,10 +35,9 @@ pub struct MvParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/v1.0/docs")]
|
#[get("/v1.0/docs")]
|
||||||
async fn list(params: web::Json<Params>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn list(params: web::Json<Params>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let docs = Query::find_doc_infos_by_params(&data.conn, params.into_inner())
|
let docs = Query::find_doc_infos_by_params(&data.conn, params.into_inner())
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("docs", docs);
|
result.insert("docs", docs);
|
||||||
@ -51,11 +50,11 @@ async fn list(params: web::Json<Params>, data: web::Data<AppState>) -> Result<Ht
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1.0/upload")]
|
#[post("/v1.0/upload")]
|
||||||
async fn upload(mut payload: Multipart, filename: web::Data<String>, did: web::Data<i64>, uid: web::Data<i64>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn upload(mut payload: Multipart, filename: web::Data<String>, did: web::Data<i64>, uid: web::Data<i64>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let mut size = 0;
|
let mut size = 0;
|
||||||
|
|
||||||
while let Some(item) = payload.next().await {
|
while let Some(item) = payload.next().await {
|
||||||
@ -65,16 +64,14 @@ async fn upload(mut payload: Multipart, filename: web::Data<String>, did: web::D
|
|||||||
|
|
||||||
let mut file = web::block(|| std::fs::File::create(filepath))
|
let mut file = web::block(|| std::fs::File::create(filepath))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
while let Some(chunk) = field.next().await {
|
while let Some(chunk) = field.next().await {
|
||||||
let data = chunk.unwrap();
|
let data = chunk.unwrap();
|
||||||
size += data.len() as u64;
|
size += data.len() as u64;
|
||||||
file = web::block(move || file.write_all(&data).map(|_| file))
|
file = web::block(move || file.write_all(&data).map(|_| file))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()?;
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,15 +86,15 @@ async fn upload(mut payload: Multipart, filename: web::Data<String>, did: web::D
|
|||||||
r#type: "".to_string(),
|
r#type: "".to_string(),
|
||||||
created_at: Local::now().date_naive(),
|
created_at: Local::now().date_naive(),
|
||||||
updated_at: Local::now().date_naive(),
|
updated_at: Local::now().date_naive(),
|
||||||
}).await.unwrap();
|
}).await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().body("File uploaded successfully"))
|
Ok(HttpResponse::Ok().body("File uploaded successfully"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1.0/delete_docs")]
|
#[post("/v1.0/delete_docs")]
|
||||||
async fn delete(doc_ids: web::Json<Vec<i64>>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn delete(doc_ids: web::Json<Vec<i64>>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
for doc_id in doc_ids.iter() {
|
for doc_id in doc_ids.iter() {
|
||||||
let _ = Mutation::delete_doc_info(&data.conn, *doc_id).await.unwrap();
|
let _ = Mutation::delete_doc_info(&data.conn, *doc_id).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let json_response = JsonResponse {
|
let json_response = JsonResponse {
|
||||||
@ -108,12 +105,12 @@ async fn delete(doc_ids: web::Json<Vec<i64>>, data: web::Data<AppState>) -> Resu
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1.0/mv_docs")]
|
#[post("/v1.0/mv_docs")]
|
||||||
async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
Mutation::mv_doc_info(&data.conn, params.dest_did, ¶ms.dids).await.unwrap();
|
Mutation::mv_doc_info(&data.conn, params.dest_did, ¶ms.dids).await?;
|
||||||
|
|
||||||
let json_response = JsonResponse {
|
let json_response = JsonResponse {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -123,5 +120,5 @@ async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<Ht
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
@ -1,15 +1,15 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use actix_web::{get, HttpResponse, post, web};
|
use actix_web::{get, HttpResponse, post, web};
|
||||||
use actix_web::http::Error;
|
|
||||||
use crate::api::JsonResponse;
|
use crate::api::JsonResponse;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::entity::kb_info;
|
use crate::entity::kb_info;
|
||||||
|
use crate::errors::AppError;
|
||||||
use crate::service::kb_info::Mutation;
|
use crate::service::kb_info::Mutation;
|
||||||
use crate::service::kb_info::Query;
|
use crate::service::kb_info::Query;
|
||||||
|
|
||||||
#[post("/v1.0/create_kb")]
|
#[post("/v1.0/create_kb")]
|
||||||
async fn create(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn create(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let model = Mutation::create_kb_info(&data.conn, model.into_inner()).await.unwrap();
|
let model = Mutation::create_kb_info(&data.conn, model.into_inner()).await?;
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("kb_id", model.kb_id.unwrap());
|
result.insert("kb_id", model.kb_id.unwrap());
|
||||||
@ -22,12 +22,12 @@ async fn create(model: web::Json<kb_info::Model>, data: web::Data<AppState>) ->
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/v1.0/kbs")]
|
#[get("/v1.0/kbs")]
|
||||||
async fn list(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn list(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let kbs = Query::find_kb_infos_by_uid(&data.conn, model.uid).await.unwrap();
|
let kbs = Query::find_kb_infos_by_uid(&data.conn, model.uid).await?;
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("kbs", kbs);
|
result.insert("kbs", kbs);
|
||||||
@ -40,12 +40,12 @@ async fn list(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Re
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1.0/delete_kb")]
|
#[post("/v1.0/delete_kb")]
|
||||||
async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let _ = Mutation::delete_kb_info(&data.conn, model.kb_id).await.unwrap();
|
let _ = Mutation::delete_kb_info(&data.conn, model.kb_id).await?;
|
||||||
|
|
||||||
let json_response = JsonResponse {
|
let json_response = JsonResponse {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -55,5 +55,5 @@ async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) ->
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
@ -1,9 +1,10 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub(crate) mod tag;
|
pub(crate) mod tag_info;
|
||||||
pub(crate) mod kb_info;
|
pub(crate) mod kb_info;
|
||||||
pub(crate) mod dialog_info;
|
pub(crate) mod dialog_info;
|
||||||
pub(crate) mod doc_info;
|
pub(crate) mod doc_info;
|
||||||
|
pub(crate) mod user_info;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
struct JsonResponse<T> {
|
struct JsonResponse<T> {
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use actix_web::{get, HttpResponse, post, web};
|
use actix_web::{get, HttpResponse, post, web};
|
||||||
use actix_web::http::Error;
|
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||||
|
use crate::validator;
|
||||||
use crate::api::JsonResponse;
|
use crate::api::JsonResponse;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::entity::tag_info;
|
use crate::entity::tag_info;
|
||||||
|
use crate::errors::AppError;
|
||||||
use crate::service::tag_info::{Mutation, Query};
|
use crate::service::tag_info::{Mutation, Query};
|
||||||
|
|
||||||
#[post("/v1.0/create_tag")]
|
#[post("/v1.0/create_tag")]
|
||||||
async fn create(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn create(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let model = Mutation::create_tag(&data.conn, model.into_inner()).await.unwrap();
|
let model = Mutation::create_tag(&data.conn, model.into_inner()).await?;
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("tid", model.tid.unwrap());
|
result.insert("tid", model.tid.unwrap());
|
||||||
@ -21,12 +23,12 @@ async fn create(model: web::Json<tag_info::Model>, data: web::Data<AppState>) ->
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/v1.0/delete_tag")]
|
#[post("/v1.0/delete_tag")]
|
||||||
async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let _ = Mutation::delete_tag(&data.conn, model.tid).await.unwrap();
|
let _ = Mutation::delete_tag(&data.conn, model.tid).await?;
|
||||||
|
|
||||||
let json_response = JsonResponse {
|
let json_response = JsonResponse {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -36,12 +38,12 @@ async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) ->
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/v1.0/tags")]
|
#[get("/v1.0/tags", wrap = "HttpAuthentication::bearer(validator)")]
|
||||||
async fn list(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
async fn list(data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
|
||||||
let tags = Query::find_tag_infos(&data.conn).await.unwrap();
|
let tags = Query::find_tag_infos(&data.conn).await?;
|
||||||
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
result.insert("tags", tags);
|
result.insert("tags", tags);
|
||||||
@ -54,5 +56,5 @@ async fn list(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(serde_json::to_string(&json_response).unwrap()))
|
.body(serde_json::to_string(&json_response)?))
|
||||||
}
|
}
|
52
src/api/user_info.rs
Normal file
52
src/api/user_info.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use actix_identity::Identity;
|
||||||
|
use actix_web::{get, HttpResponse, post, web};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::api::JsonResponse;
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::entity::user_info::Model;
|
||||||
|
use crate::errors::{AppError, UserError};
|
||||||
|
use crate::service::user_info::Query;
|
||||||
|
|
||||||
|
pub(crate) fn create_auth_token(user: &Model) -> u64 {
|
||||||
|
use std::{
|
||||||
|
collections::hash_map::DefaultHasher,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
user.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub(crate) struct LoginParams {
|
||||||
|
pub(crate) email: String,
|
||||||
|
pub(crate) password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/v1.0/login")]
|
||||||
|
async fn login(
|
||||||
|
data: web::Data<AppState>,
|
||||||
|
identity: Identity,
|
||||||
|
input: web::Json<LoginParams>
|
||||||
|
) -> Result<HttpResponse, AppError> {
|
||||||
|
match Query::login(&data.conn, &input.email, &input.password).await? {
|
||||||
|
Some(user) => {
|
||||||
|
let token = create_auth_token(&user).to_string();
|
||||||
|
|
||||||
|
identity.remember(token.clone());
|
||||||
|
|
||||||
|
let json_response = JsonResponse {
|
||||||
|
code: 200,
|
||||||
|
err: "".to_owned(),
|
||||||
|
data: token.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("application/json")
|
||||||
|
.append_header(("X-Auth-Token", token))
|
||||||
|
.body(serde_json::to_string(&json_response)?))
|
||||||
|
}
|
||||||
|
None => Err(UserError::LoginFailed.into())
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,10 @@ pub struct Model {
|
|||||||
pub tid: i64,
|
pub tid: i64,
|
||||||
pub uid: i64,
|
pub uid: i64,
|
||||||
pub tag_name: String,
|
pub tag_name: String,
|
||||||
pub regx: String,
|
pub regx: Option<String>,
|
||||||
pub color: i64,
|
pub color: i64,
|
||||||
pub icon: i64,
|
pub icon: i64,
|
||||||
pub dir: String,
|
pub dir: Option<String>,
|
||||||
|
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
pub created_at: Date,
|
pub created_at: Date,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, DeriveEntityModel, Deserialize, Serialize)]
|
||||||
#[sea_orm(table_name = "user_info")]
|
#[sea_orm(table_name = "user_info")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
@ -9,10 +9,11 @@ pub struct Model {
|
|||||||
pub uid: i64,
|
pub uid: i64,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub nickname: String,
|
pub nickname: String,
|
||||||
pub avatar_url: String,
|
pub avatar_url: Option<String>,
|
||||||
pub color_schema: String,
|
pub color_schema: String,
|
||||||
pub list_style: String,
|
pub list_style: String,
|
||||||
pub language: String,
|
pub language: String,
|
||||||
|
pub password: String,
|
||||||
|
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
pub created_at: Date,
|
pub created_at: Date,
|
||||||
|
82
src/errors.rs
Normal file
82
src/errors.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use actix_web::{HttpResponse, ResponseError};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub(crate) enum AppError {
|
||||||
|
#[error("`{0}`")]
|
||||||
|
User(#[from] UserError),
|
||||||
|
|
||||||
|
#[error("`{0}`")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("`{0}`")]
|
||||||
|
Actix(#[from] actix_web::Error),
|
||||||
|
|
||||||
|
#[error("`{0}`")]
|
||||||
|
Db(#[from] sea_orm::DbErr),
|
||||||
|
|
||||||
|
#[error("`{0}`")]
|
||||||
|
Std(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub(crate) enum UserError {
|
||||||
|
#[error("`username` field of `User` cannot be empty!")]
|
||||||
|
EmptyUsername,
|
||||||
|
|
||||||
|
#[error("`username` field of `User` cannot contain whitespaces!")]
|
||||||
|
UsernameInvalidCharacter,
|
||||||
|
|
||||||
|
#[error("`password` field of `User` cannot be empty!")]
|
||||||
|
EmptyPassword,
|
||||||
|
|
||||||
|
#[error("`password` field of `User` cannot contain whitespaces!")]
|
||||||
|
PasswordInvalidCharacter,
|
||||||
|
|
||||||
|
#[error("Could not find any `User` for id: `{0}`!")]
|
||||||
|
NotFound(i64),
|
||||||
|
|
||||||
|
#[error("Failed to login user!")]
|
||||||
|
LoginFailed,
|
||||||
|
|
||||||
|
#[error("User is not logged in!")]
|
||||||
|
NotLoggedIn,
|
||||||
|
|
||||||
|
#[error("Invalid authorization token!")]
|
||||||
|
InvalidToken,
|
||||||
|
|
||||||
|
#[error("Could not find any `User`!")]
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for AppError {
|
||||||
|
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||||
|
match self {
|
||||||
|
AppError::User(user_error) => match user_error {
|
||||||
|
UserError::EmptyUsername => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
|
||||||
|
UserError::UsernameInvalidCharacter => {
|
||||||
|
actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
|
||||||
|
}
|
||||||
|
UserError::EmptyPassword => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
|
||||||
|
UserError::PasswordInvalidCharacter => {
|
||||||
|
actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
|
||||||
|
}
|
||||||
|
UserError::NotFound(_) => actix_web::http::StatusCode::NOT_FOUND,
|
||||||
|
UserError::NotLoggedIn => actix_web::http::StatusCode::UNAUTHORIZED,
|
||||||
|
UserError::Empty => actix_web::http::StatusCode::NOT_FOUND,
|
||||||
|
UserError::LoginFailed => actix_web::http::StatusCode::NOT_FOUND,
|
||||||
|
UserError::InvalidToken => actix_web::http::StatusCode::UNAUTHORIZED,
|
||||||
|
},
|
||||||
|
AppError::Json(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
AppError::Actix(fail) => fail.as_response_error().status_code(),
|
||||||
|
AppError::Db(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
AppError::Std(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
let status_code = self.status_code();
|
||||||
|
let response = HttpResponse::build(status_code).body(self.to_string());
|
||||||
|
response
|
||||||
|
}
|
||||||
|
}
|
59
src/main.rs
59
src/main.rs
@ -1,19 +1,41 @@
|
|||||||
mod api;
|
mod api;
|
||||||
mod entity;
|
mod entity;
|
||||||
mod service;
|
mod service;
|
||||||
|
mod errors;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
use actix_web::{web, App, HttpServer, middleware};
|
use actix_identity::{CookieIdentityPolicy, IdentityService, RequestIdentity};
|
||||||
|
use actix_session::CookieSession;
|
||||||
|
use actix_web::{web, App, HttpServer, middleware, Error};
|
||||||
|
use actix_web::cookie::time::Duration;
|
||||||
|
use actix_web::dev::ServiceRequest;
|
||||||
|
use actix_web::error::ErrorUnauthorized;
|
||||||
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use listenfd::ListenFd;
|
use listenfd::ListenFd;
|
||||||
use sea_orm::{Database, DatabaseConnection};
|
use sea_orm::{Database, DatabaseConnection};
|
||||||
use migration::{Migrator, MigratorTrait};
|
use migration::{Migrator, MigratorTrait};
|
||||||
|
use crate::errors::UserError;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
conn: DatabaseConnection,
|
conn: DatabaseConnection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn validator(
|
||||||
|
req: ServiceRequest,
|
||||||
|
credentials: BearerAuth,
|
||||||
|
) -> Result<ServiceRequest, Error> {
|
||||||
|
if let Some(token) = req.get_identity() {
|
||||||
|
println!("{}, {}",credentials.token(), token);
|
||||||
|
(credentials.token() == token)
|
||||||
|
.then(|| req)
|
||||||
|
.ok_or(ErrorUnauthorized(UserError::InvalidToken))
|
||||||
|
} else {
|
||||||
|
Err(ErrorUnauthorized(UserError::NotLoggedIn))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
std::env::set_var("RUST_LOG", "debug");
|
std::env::set_var("RUST_LOG", "debug");
|
||||||
@ -39,6 +61,19 @@ async fn main() -> std::io::Result<()> {
|
|||||||
App::new()
|
App::new()
|
||||||
.service(Files::new("/static", "./static"))
|
.service(Files::new("/static", "./static"))
|
||||||
.app_data(web::Data::new(state.clone()))
|
.app_data(web::Data::new(state.clone()))
|
||||||
|
.wrap(IdentityService::new(
|
||||||
|
CookieIdentityPolicy::new(&[0; 32])
|
||||||
|
.name("auth-cookie")
|
||||||
|
.login_deadline(Duration::seconds(120))
|
||||||
|
.secure(false),
|
||||||
|
))
|
||||||
|
.wrap(
|
||||||
|
CookieSession::signed(&[0; 32])
|
||||||
|
.name("session-cookie")
|
||||||
|
.secure(false)
|
||||||
|
// WARNING(alex): This uses the `time` crate, not `std::time`!
|
||||||
|
.expires_in_time(Duration::seconds(60)),
|
||||||
|
)
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.configure(init)
|
.configure(init)
|
||||||
});
|
});
|
||||||
@ -55,7 +90,23 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init(cfg: &mut web::ServiceConfig) {
|
fn init(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(api::tag::create);
|
cfg.service(api::tag_info::create);
|
||||||
cfg.service(api::tag::delete);
|
cfg.service(api::tag_info::delete);
|
||||||
cfg.service(api::tag::list);
|
cfg.service(api::tag_info::list);
|
||||||
|
|
||||||
|
cfg.service(api::kb_info::create);
|
||||||
|
cfg.service(api::kb_info::delete);
|
||||||
|
cfg.service(api::kb_info::list);
|
||||||
|
|
||||||
|
cfg.service(api::doc_info::list);
|
||||||
|
cfg.service(api::doc_info::delete);
|
||||||
|
cfg.service(api::doc_info::mv);
|
||||||
|
cfg.service(api::doc_info::upload);
|
||||||
|
|
||||||
|
cfg.service(api::dialog_info::list);
|
||||||
|
cfg.service(api::dialog_info::delete);
|
||||||
|
cfg.service(api::dialog_info::detail);
|
||||||
|
cfg.service(api::dialog_info::create);
|
||||||
|
|
||||||
|
cfg.service(api::user_info::login);
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, Pagina
|
|||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use sea_orm::QueryFilter;
|
use sea_orm::QueryFilter;
|
||||||
use sea_orm::ColumnTrait;
|
use sea_orm::ColumnTrait;
|
||||||
use crate::entity::{dialog_info, kb_info};
|
use crate::entity::dialog_info;
|
||||||
use crate::entity::dialog_info::Entity;
|
use crate::entity::dialog_info::Entity;
|
||||||
|
|
||||||
pub struct Query;
|
pub struct Query;
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use postgres::fallible_iterator::FallibleIterator;
|
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
|
use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use sea_orm::QueryFilter;
|
use sea_orm::QueryFilter;
|
||||||
use crate::api::doc_info::{FilterParams, Params};
|
use crate::api::doc_info::Params;
|
||||||
use crate::entity::{doc2_doc, doc_info, kb_info, tag_info};
|
use crate::entity::{doc2_doc, doc_info, kb_info, tag_info};
|
||||||
use crate::entity::doc_info::Entity;
|
use crate::entity::doc_info::Entity;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use chrono::{Local, NaiveDate};
|
use chrono::Local;
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
|
use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use crate::entity::kb_info;
|
use crate::entity::kb_info;
|
||||||
|
@ -2,3 +2,4 @@ pub(crate) mod dialog_info;
|
|||||||
pub(crate) mod tag_info;
|
pub(crate) mod tag_info;
|
||||||
pub(crate) mod kb_info;
|
pub(crate) mod kb_info;
|
||||||
pub(crate) mod doc_info;
|
pub(crate) mod doc_info;
|
||||||
|
pub(crate) mod user_info;
|
@ -1,4 +1,4 @@
|
|||||||
use chrono::{Local, NaiveDate};
|
use chrono::Local;
|
||||||
use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
|
use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use crate::entity::tag_info;
|
use crate::entity::tag_info;
|
||||||
|
105
src/service/user_info.rs
Normal file
105
src/service/user_info.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use chrono::Local;
|
||||||
|
use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use crate::entity::user_info;
|
||||||
|
use crate::entity::user_info::Entity;
|
||||||
|
|
||||||
|
pub struct Query;
|
||||||
|
|
||||||
|
impl Query {
|
||||||
|
pub async fn find_user_info_by_id(db: &DbConn, id: i64) -> Result<Option<user_info::Model>, DbErr> {
|
||||||
|
Entity::find_by_id(id).one(db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login(db: &DbConn, email: &str, password: &str) -> Result<Option<user_info::Model>, DbErr> {
|
||||||
|
Entity::find()
|
||||||
|
.filter(user_info::Column::Email.eq(email))
|
||||||
|
.filter(user_info::Column::Password.eq(password))
|
||||||
|
.one(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_user_infos(db: &DbConn) -> Result<Vec<user_info::Model>, DbErr> {
|
||||||
|
Entity::find().all(db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_user_infos_in_page(
|
||||||
|
db: &DbConn,
|
||||||
|
page: u64,
|
||||||
|
posts_per_page: u64,
|
||||||
|
) -> Result<(Vec<user_info::Model>, u64), DbErr> {
|
||||||
|
// Setup paginator
|
||||||
|
let paginator = Entity::find()
|
||||||
|
.order_by_asc(user_info::Column::Uid)
|
||||||
|
.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_user(
|
||||||
|
db: &DbConn,
|
||||||
|
form_data: user_info::Model,
|
||||||
|
) -> Result<user_info::ActiveModel, DbErr> {
|
||||||
|
user_info::ActiveModel {
|
||||||
|
uid: Default::default(),
|
||||||
|
email: Set(form_data.email.to_owned()),
|
||||||
|
nickname: Set(form_data.nickname.to_owned()),
|
||||||
|
avatar_url: Set(form_data.avatar_url.to_owned()),
|
||||||
|
color_schema: Set(form_data.color_schema.to_owned()),
|
||||||
|
list_style: Set(form_data.list_style.to_owned()),
|
||||||
|
language: Set(form_data.language.to_owned()),
|
||||||
|
password: Set(form_data.password.to_owned()),
|
||||||
|
created_at: Set(Local::now().date_naive()),
|
||||||
|
updated_at: Set(Local::now().date_naive()),
|
||||||
|
}
|
||||||
|
.save(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_tag_by_id(
|
||||||
|
db: &DbConn,
|
||||||
|
id: i64,
|
||||||
|
form_data: user_info::Model,
|
||||||
|
) -> Result<user_info::Model, DbErr> {
|
||||||
|
let user: user_info::ActiveModel = Entity::find_by_id(id)
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.ok_or(DbErr::Custom("Cannot find tag.".to_owned()))
|
||||||
|
.map(Into::into)?;
|
||||||
|
|
||||||
|
user_info::ActiveModel {
|
||||||
|
uid: user.uid,
|
||||||
|
email: Set(form_data.email.to_owned()),
|
||||||
|
nickname: Set(form_data.nickname.to_owned()),
|
||||||
|
avatar_url: Set(form_data.avatar_url.to_owned()),
|
||||||
|
color_schema: Set(form_data.color_schema.to_owned()),
|
||||||
|
list_style: Set(form_data.list_style.to_owned()),
|
||||||
|
language: Set(form_data.language.to_owned()),
|
||||||
|
password: Set(form_data.password.to_owned()),
|
||||||
|
created_at: Default::default(),
|
||||||
|
updated_at: Set(Local::now().date_naive()),
|
||||||
|
}
|
||||||
|
.update(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_tag(db: &DbConn, tid: i64) -> Result<DeleteResult, DbErr> {
|
||||||
|
let tag: user_info::ActiveModel = Entity::find_by_id(tid)
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.ok_or(DbErr::Custom("Cannot find tag.".to_owned()))
|
||||||
|
.map(Into::into)?;
|
||||||
|
|
||||||
|
tag.delete(db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_all_tags(db: &DbConn) -> Result<DeleteResult, DbErr> {
|
||||||
|
Entity::delete_many().exec(db).await
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user