feat: Implement authentication (#10)

Co-authored-by: kould <2435992353@qq.com>
This commit is contained in:
Kould 2023-12-18 15:52:52 +08:00 committed by GitHub
parent c64dcb929b
commit d94c6df4a4
18 changed files with 364 additions and 67 deletions

View File

@ -10,6 +10,10 @@ actix-web = "4.3.1"
actix-rt = "2.8.0"
actix-files = "0.6.2"
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"
sea-orm = {version = "0.12.9", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"]}
serde = { version = "1", features = ["derive"] }

View File

@ -24,6 +24,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(UserInfo::ColorSchema).string().default("dark"))
.col(ColumnDef::new(UserInfo::ListStyle).string().default("list"))
.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::UpdatedAt).date().not_null())
.col(ColumnDef::new(UserInfo::IsDeleted).boolean().default(false))
@ -215,6 +216,7 @@ enum UserInfo {
ColorSchema,
ListStyle,
Language,
Password,
CreatedAt,
UpdatedAt,
IsDeleted,

View File

@ -1,15 +1,15 @@
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::errors::AppError;
use crate::service::dialog_info::Query;
use crate::service::dialog_info::Mutation;
#[get("/v1.0/dialogs")]
async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let dialogs = Query::find_dialog_infos_by_uid(&data.conn, model.uid).await.unwrap();
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?;
let mut result = HashMap::new();
result.insert("dialogs", dialogs);
@ -22,12 +22,12 @@ async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[get("/v1.0/dialog")]
async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let dialogs = Query::find_dialog_info_by_id(&data.conn, model.dialog_id).await.unwrap();
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?;
let mut result = HashMap::new();
result.insert("dialogs", dialogs);
@ -40,12 +40,12 @@ async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>)
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[post("/v1.0/delete_dialog")]
async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let _ = Mutation::delete_dialog_info(&data.conn, model.dialog_id).await.unwrap();
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?;
let json_response = JsonResponse {
code: 200,
@ -55,12 +55,12 @@ async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>)
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[post("/v1.0/create_kb")]
async fn create(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let model = Mutation::create_dialog_info(&data.conn, model.into_inner()).await.unwrap();
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?;
let mut result = HashMap::new();
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()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}

View File

@ -1,14 +1,14 @@
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 futures_util::StreamExt;
use serde::Deserialize;
use std::io::Write;
use crate::api::JsonResponse;
use crate::AppState;
use crate::entity::doc_info::Model;
use crate::errors::AppError;
use crate::service::doc_info::{Mutation, Query};
#[derive(Debug, Deserialize)]
@ -35,10 +35,9 @@ pub struct MvParams {
}
#[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())
.await
.unwrap();
.await?;
let mut result = HashMap::new();
result.insert("docs", docs);
@ -51,11 +50,11 @@ async fn list(params: web::Json<Params>, data: web::Data<AppState>) -> Result<Ht
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[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;
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))
.await
.unwrap()
.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();
.unwrap()?;
}
}
@ -89,15 +86,15 @@ async fn upload(mut payload: Multipart, filename: web::Data<String>, did: web::D
r#type: "".to_string(),
created_at: Local::now().date_naive(),
updated_at: Local::now().date_naive(),
}).await.unwrap();
}).await?;
Ok(HttpResponse::Ok().body("File uploaded successfully"))
}
#[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() {
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 {
@ -108,12 +105,12 @@ async fn delete(doc_ids: web::Json<Vec<i64>>, data: web::Data<AppState>) -> Resu
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[post("/v1.0/mv_docs")]
async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
Mutation::mv_doc_info(&data.conn, params.dest_did, &params.dids).await.unwrap();
async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
Mutation::mv_doc_info(&data.conn, params.dest_did, &params.dids).await?;
let json_response = JsonResponse {
code: 200,
@ -123,5 +120,5 @@ async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<Ht
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}

View File

@ -1,15 +1,15 @@
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::errors::AppError;
use crate::service::kb_info::Mutation;
use crate::service::kb_info::Query;
#[post("/v1.0/create_kb")]
async fn create(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let model = Mutation::create_kb_info(&data.conn, model.into_inner()).await.unwrap();
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?;
let mut result = HashMap::new();
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()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[get("/v1.0/kbs")]
async fn list(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let kbs = Query::find_kb_infos_by_uid(&data.conn, model.uid).await.unwrap();
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?;
let mut result = HashMap::new();
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()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[post("/v1.0/delete_kb")]
async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let _ = Mutation::delete_kb_info(&data.conn, model.kb_id).await.unwrap();
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?;
let json_response = JsonResponse {
code: 200,
@ -55,5 +55,5 @@ async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) ->
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}

View File

@ -1,9 +1,10 @@
use serde::{Deserialize, Serialize};
pub(crate) mod tag;
pub(crate) mod tag_info;
pub(crate) mod kb_info;
pub(crate) mod dialog_info;
pub(crate) mod doc_info;
pub(crate) mod user_info;
#[derive(Debug, Deserialize, Serialize)]
struct JsonResponse<T> {

View File

@ -1,14 +1,16 @@
use std::collections::HashMap;
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::AppState;
use crate::entity::tag_info;
use crate::errors::AppError;
use crate::service::tag_info::{Mutation, Query};
#[post("/v1.0/create_tag")]
async fn create(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let model = Mutation::create_tag(&data.conn, model.into_inner()).await.unwrap();
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?;
let mut result = HashMap::new();
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()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[post("/v1.0/delete_tag")]
async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let _ = Mutation::delete_tag(&data.conn, model.tid).await.unwrap();
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?;
let json_response = JsonResponse {
code: 200,
@ -36,12 +38,12 @@ async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) ->
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&json_response).unwrap()))
.body(serde_json::to_string(&json_response)?))
}
#[get("/v1.0/tags")]
async fn list(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
let tags = Query::find_tag_infos(&data.conn).await.unwrap();
#[get("/v1.0/tags", wrap = "HttpAuthentication::bearer(validator)")]
async fn list(data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
let tags = Query::find_tag_infos(&data.conn).await?;
let mut result = HashMap::new();
result.insert("tags", tags);
@ -54,5 +56,5 @@ async fn list(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
Ok(HttpResponse::Ok()
.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
View 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())
}
}

View File

@ -9,10 +9,10 @@ pub struct Model {
pub tid: i64,
pub uid: i64,
pub tag_name: String,
pub regx: String,
pub regx: Option<String>,
pub color: i64,
pub icon: i64,
pub dir: String,
pub dir: Option<String>,
#[serde(skip_deserializing)]
pub created_at: Date,

View File

@ -1,7 +1,7 @@
use sea_orm::entity::prelude::*;
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")]
pub struct Model {
#[sea_orm(primary_key)]
@ -9,10 +9,11 @@ pub struct Model {
pub uid: i64,
pub email: String,
pub nickname: String,
pub avatar_url: String,
pub avatar_url: Option<String>,
pub color_schema: String,
pub list_style: String,
pub language: String,
pub password: String,
#[serde(skip_deserializing)]
pub created_at: Date,

82
src/errors.rs Normal file
View 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
}
}

View File

@ -1,19 +1,41 @@
mod api;
mod entity;
mod service;
mod errors;
use std::env;
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 sea_orm::{Database, DatabaseConnection};
use migration::{Migrator, MigratorTrait};
use crate::errors::UserError;
#[derive(Debug, Clone)]
struct AppState {
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]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
@ -39,6 +61,19 @@ async fn main() -> std::io::Result<()> {
App::new()
.service(Files::new("/static", "./static"))
.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())
.configure(init)
});
@ -55,7 +90,23 @@ async fn main() -> std::io::Result<()> {
}
fn init(cfg: &mut web::ServiceConfig) {
cfg.service(api::tag::create);
cfg.service(api::tag::delete);
cfg.service(api::tag::list);
cfg.service(api::tag_info::create);
cfg.service(api::tag_info::delete);
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);
}

View File

@ -3,7 +3,7 @@ use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, Pagina
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;
use crate::entity::dialog_info::Entity;
pub struct Query;

View File

@ -1,9 +1,8 @@
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::api::doc_info::Params;
use crate::entity::{doc2_doc, doc_info, kb_info, tag_info};
use crate::entity::doc_info::Entity;

View File

@ -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::ActiveValue::Set;
use crate::entity::kb_info;

View File

@ -2,3 +2,4 @@ pub(crate) mod dialog_info;
pub(crate) mod tag_info;
pub(crate) mod kb_info;
pub(crate) mod doc_info;
pub(crate) mod user_info;

View File

@ -1,4 +1,4 @@
use chrono::{Local, NaiveDate};
use chrono::Local;
use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
use sea_orm::ActiveValue::Set;
use crate::entity::tag_info;

105
src/service/user_info.rs Normal file
View 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
}
}