diff --git a/Cargo.toml b/Cargo.toml index 6ccca4cc..d4cd561b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index 928a1e61..3c0859db 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -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, diff --git a/src/api/dialog_info.rs b/src/api/dialog_info.rs index da81dedd..e45dc9bb 100644 --- a/src/api/dialog_info.rs +++ b/src/api/dialog_info.rs @@ -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, data: web::Data) -> Result { - let dialogs = Query::find_dialog_infos_by_uid(&data.conn, model.uid).await.unwrap(); +async fn list(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) - 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, data: web::Data) -> Result { - let dialogs = Query::find_dialog_info_by_id(&data.conn, model.dialog_id).await.unwrap(); +async fn detail(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) 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, data: web::Data) -> Result { - let _ = Mutation::delete_dialog_info(&data.conn, model.dialog_id).await.unwrap(); +async fn delete(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) 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, data: web::Data) -> Result { - let model = Mutation::create_dialog_info(&data.conn, model.into_inner()).await.unwrap(); +async fn create(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) Ok(HttpResponse::Ok() .content_type("application/json") - .body(serde_json::to_string(&json_response).unwrap())) + .body(serde_json::to_string(&json_response)?)) } \ No newline at end of file diff --git a/src/api/doc_info.rs b/src/api/doc_info.rs index da6079c8..d9589c02 100644 --- a/src/api/doc_info.rs +++ b/src/api/doc_info.rs @@ -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, data: web::Data) -> Result { +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(); + .await?; let mut result = HashMap::new(); result.insert("docs", docs); @@ -51,11 +50,11 @@ async fn list(params: web::Json, data: web::Data) -> Result, did: web::Data, uid: web::Data, data: web::Data) -> Result { +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 { @@ -65,16 +64,14 @@ async fn upload(mut payload: Multipart, filename: web::Data, 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, 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>, data: web::Data) -> Result { +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 _ = Mutation::delete_doc_info(&data.conn, *doc_id).await?; } let json_response = JsonResponse { @@ -108,12 +105,12 @@ async fn delete(doc_ids: web::Json>, data: web::Data) -> 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, data: web::Data) -> Result { - Mutation::mv_doc_info(&data.conn, params.dest_did, ¶ms.dids).await.unwrap(); +async fn mv(params: web::Json, data: web::Data) -> Result { + Mutation::mv_doc_info(&data.conn, params.dest_did, ¶ms.dids).await?; let json_response = JsonResponse { code: 200, @@ -123,5 +120,5 @@ async fn mv(params: web::Json, data: web::Data) -> Result, data: web::Data) -> Result { - let model = Mutation::create_kb_info(&data.conn, model.into_inner()).await.unwrap(); +async fn create(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) -> 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, data: web::Data) -> Result { - let kbs = Query::find_kb_infos_by_uid(&data.conn, model.uid).await.unwrap(); +async fn list(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) -> 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, data: web::Data) -> Result { - let _ = Mutation::delete_kb_info(&data.conn, model.kb_id).await.unwrap(); +async fn delete(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) -> Ok(HttpResponse::Ok() .content_type("application/json") - .body(serde_json::to_string(&json_response).unwrap())) + .body(serde_json::to_string(&json_response)?)) } \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index 65f6a22d..d072819c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -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 { diff --git a/src/api/tag.rs b/src/api/tag_info.rs similarity index 67% rename from src/api/tag.rs rename to src/api/tag_info.rs index b902f3d3..28f09b19 100644 --- a/src/api/tag.rs +++ b/src/api/tag_info.rs @@ -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, data: web::Data) -> Result { - let model = Mutation::create_tag(&data.conn, model.into_inner()).await.unwrap(); +async fn create(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) -> 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, data: web::Data) -> Result { - let _ = Mutation::delete_tag(&data.conn, model.tid).await.unwrap(); +async fn delete(model: web::Json, data: web::Data) -> Result { + 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, data: web::Data) -> 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) -> Result { - let tags = Query::find_tag_infos(&data.conn).await.unwrap(); +#[get("/v1.0/tags", wrap = "HttpAuthentication::bearer(validator)")] +async fn list(data: web::Data) -> Result { + 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) -> Result { Ok(HttpResponse::Ok() .content_type("application/json") - .body(serde_json::to_string(&json_response).unwrap())) + .body(serde_json::to_string(&json_response)?)) } \ No newline at end of file diff --git a/src/api/user_info.rs b/src/api/user_info.rs new file mode 100644 index 00000000..625a2437 --- /dev/null +++ b/src/api/user_info.rs @@ -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, + identity: Identity, + input: web::Json +) -> Result { + 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()) + } +} \ No newline at end of file diff --git a/src/entity/tag_info.rs b/src/entity/tag_info.rs index f2a8efe3..34d7e597 100644 --- a/src/entity/tag_info.rs +++ b/src/entity/tag_info.rs @@ -9,10 +9,10 @@ pub struct Model { pub tid: i64, pub uid: i64, pub tag_name: String, - pub regx: String, + pub regx: Option, pub color: i64, pub icon: i64, - pub dir: String, + pub dir: Option, #[serde(skip_deserializing)] pub created_at: Date, diff --git a/src/entity/user_info.rs b/src/entity/user_info.rs index c706e353..b9eb759d 100644 --- a/src/entity/user_info.rs +++ b/src/entity/user_info.rs @@ -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, pub color_schema: String, pub list_style: String, pub language: String, + pub password: String, #[serde(skip_deserializing)] pub created_at: Date, diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 00000000..6b6ba9c4 --- /dev/null +++ b/src/errors.rs @@ -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 + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 25757443..9a990735 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 { + 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); } \ No newline at end of file diff --git a/src/service/dialog_info.rs b/src/service/dialog_info.rs index bdd1d6db..c4bddc97 100644 --- a/src/service/dialog_info.rs +++ b/src/service/dialog_info.rs @@ -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; diff --git a/src/service/doc_info.rs b/src/service/doc_info.rs index f05685e3..ed5e99ca 100644 --- a/src/service/doc_info.rs +++ b/src/service/doc_info.rs @@ -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; diff --git a/src/service/kb_info.rs b/src/service/kb_info.rs index 99933545..7580596e 100644 --- a/src/service/kb_info.rs +++ b/src/service/kb_info.rs @@ -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; diff --git a/src/service/mod.rs b/src/service/mod.rs index 333b9f18..24bd859a 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod dialog_info; pub(crate) mod tag_info; pub(crate) mod kb_info; -pub(crate) mod doc_info; \ No newline at end of file +pub(crate) mod doc_info; +pub(crate) mod user_info; \ No newline at end of file diff --git a/src/service/tag_info.rs b/src/service/tag_info.rs index f7d3f5ba..42c9397b 100644 --- a/src/service/tag_info.rs +++ b/src/service/tag_info.rs @@ -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; diff --git a/src/service/user_info.rs b/src/service/user_info.rs new file mode 100644 index 00000000..bfb998f4 --- /dev/null +++ b/src/service/user_info.rs @@ -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, DbErr> { + Entity::find_by_id(id).one(db).await + } + + pub async fn login(db: &DbConn, email: &str, password: &str) -> Result, 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, DbErr> { + Entity::find().all(db).await + } + + pub async fn find_user_infos_in_page( + db: &DbConn, + page: u64, + posts_per_page: u64, + ) -> Result<(Vec, 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 { + 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 { + 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 { + 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 { + Entity::delete_many().exec(db).await + } +} \ No newline at end of file