Kould kould commited on
Commit
d94c6df
·
1 Parent(s): c64dcb9

feat: Implement authentication (#10)

Browse files

Co-authored-by: kould <[email protected]>

Cargo.toml CHANGED
@@ -10,6 +10,10 @@ actix-web = "4.3.1"
10
  actix-rt = "2.8.0"
11
  actix-files = "0.6.2"
12
  actix-multipart = "0.4"
 
 
 
 
13
  postgres = "0.19.7"
14
  sea-orm = {version = "0.12.9", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"]}
15
  serde = { version = "1", features = ["derive"] }
 
10
  actix-rt = "2.8.0"
11
  actix-files = "0.6.2"
12
  actix-multipart = "0.4"
13
+ actix-session = { version = "0.5" }
14
+ actix-identity = { version = "0.4" }
15
+ actix-web-httpauth = { version = "0.6" }
16
+ thiserror = "1.0"
17
  postgres = "0.19.7"
18
  sea-orm = {version = "0.12.9", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"]}
19
  serde = { version = "1", features = ["derive"] }
migration/src/m20220101_000001_create_table.rs CHANGED
@@ -24,6 +24,7 @@ impl MigrationTrait for Migration {
24
  .col(ColumnDef::new(UserInfo::ColorSchema).string().default("dark"))
25
  .col(ColumnDef::new(UserInfo::ListStyle).string().default("list"))
26
  .col(ColumnDef::new(UserInfo::Language).string().default("chinese"))
 
27
  .col(ColumnDef::new(UserInfo::CreatedAt).date().not_null())
28
  .col(ColumnDef::new(UserInfo::UpdatedAt).date().not_null())
29
  .col(ColumnDef::new(UserInfo::IsDeleted).boolean().default(false))
@@ -215,6 +216,7 @@ enum UserInfo {
215
  ColorSchema,
216
  ListStyle,
217
  Language,
 
218
  CreatedAt,
219
  UpdatedAt,
220
  IsDeleted,
 
24
  .col(ColumnDef::new(UserInfo::ColorSchema).string().default("dark"))
25
  .col(ColumnDef::new(UserInfo::ListStyle).string().default("list"))
26
  .col(ColumnDef::new(UserInfo::Language).string().default("chinese"))
27
+ .col(ColumnDef::new(UserInfo::Password).string().not_null())
28
  .col(ColumnDef::new(UserInfo::CreatedAt).date().not_null())
29
  .col(ColumnDef::new(UserInfo::UpdatedAt).date().not_null())
30
  .col(ColumnDef::new(UserInfo::IsDeleted).boolean().default(false))
 
216
  ColorSchema,
217
  ListStyle,
218
  Language,
219
+ Password,
220
  CreatedAt,
221
  UpdatedAt,
222
  IsDeleted,
src/api/dialog_info.rs CHANGED
@@ -1,15 +1,15 @@
1
  use std::collections::HashMap;
2
  use actix_web::{get, HttpResponse, post, web};
3
- use actix_web::http::Error;
4
  use crate::api::JsonResponse;
5
  use crate::AppState;
6
  use crate::entity::dialog_info;
 
7
  use crate::service::dialog_info::Query;
8
  use crate::service::dialog_info::Mutation;
9
 
10
  #[get("/v1.0/dialogs")]
11
- async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
12
- let dialogs = Query::find_dialog_infos_by_uid(&data.conn, model.uid).await.unwrap();
13
 
14
  let mut result = HashMap::new();
15
  result.insert("dialogs", dialogs);
@@ -22,12 +22,12 @@ async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -
22
 
23
  Ok(HttpResponse::Ok()
24
  .content_type("application/json")
25
- .body(serde_json::to_string(&json_response).unwrap()))
26
  }
27
 
28
  #[get("/v1.0/dialog")]
29
- async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
30
- let dialogs = Query::find_dialog_info_by_id(&data.conn, model.dialog_id).await.unwrap();
31
 
32
  let mut result = HashMap::new();
33
  result.insert("dialogs", dialogs);
@@ -40,12 +40,12 @@ async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>)
40
 
41
  Ok(HttpResponse::Ok()
42
  .content_type("application/json")
43
- .body(serde_json::to_string(&json_response).unwrap()))
44
  }
45
 
46
  #[post("/v1.0/delete_dialog")]
47
- async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
48
- let _ = Mutation::delete_dialog_info(&data.conn, model.dialog_id).await.unwrap();
49
 
50
  let json_response = JsonResponse {
51
  code: 200,
@@ -55,12 +55,12 @@ async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>)
55
 
56
  Ok(HttpResponse::Ok()
57
  .content_type("application/json")
58
- .body(serde_json::to_string(&json_response).unwrap()))
59
  }
60
 
61
  #[post("/v1.0/create_kb")]
62
- async fn create(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
63
- let model = Mutation::create_dialog_info(&data.conn, model.into_inner()).await.unwrap();
64
 
65
  let mut result = HashMap::new();
66
  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>)
73
 
74
  Ok(HttpResponse::Ok()
75
  .content_type("application/json")
76
- .body(serde_json::to_string(&json_response).unwrap()))
77
  }
 
1
  use std::collections::HashMap;
2
  use actix_web::{get, HttpResponse, post, web};
 
3
  use crate::api::JsonResponse;
4
  use crate::AppState;
5
  use crate::entity::dialog_info;
6
+ use crate::errors::AppError;
7
  use crate::service::dialog_info::Query;
8
  use crate::service::dialog_info::Mutation;
9
 
10
  #[get("/v1.0/dialogs")]
11
+ async fn list(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
12
+ let dialogs = Query::find_dialog_infos_by_uid(&data.conn, model.uid).await?;
13
 
14
  let mut result = HashMap::new();
15
  result.insert("dialogs", dialogs);
 
22
 
23
  Ok(HttpResponse::Ok()
24
  .content_type("application/json")
25
+ .body(serde_json::to_string(&json_response)?))
26
  }
27
 
28
  #[get("/v1.0/dialog")]
29
+ async fn detail(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
30
+ let dialogs = Query::find_dialog_info_by_id(&data.conn, model.dialog_id).await?;
31
 
32
  let mut result = HashMap::new();
33
  result.insert("dialogs", dialogs);
 
40
 
41
  Ok(HttpResponse::Ok()
42
  .content_type("application/json")
43
+ .body(serde_json::to_string(&json_response)?))
44
  }
45
 
46
  #[post("/v1.0/delete_dialog")]
47
+ async fn delete(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
48
+ let _ = Mutation::delete_dialog_info(&data.conn, model.dialog_id).await?;
49
 
50
  let json_response = JsonResponse {
51
  code: 200,
 
55
 
56
  Ok(HttpResponse::Ok()
57
  .content_type("application/json")
58
+ .body(serde_json::to_string(&json_response)?))
59
  }
60
 
61
  #[post("/v1.0/create_kb")]
62
+ async fn create(model: web::Json<dialog_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
63
+ let model = Mutation::create_dialog_info(&data.conn, model.into_inner()).await?;
64
 
65
  let mut result = HashMap::new();
66
  result.insert("dialog_id", model.dialog_id.unwrap());
 
73
 
74
  Ok(HttpResponse::Ok()
75
  .content_type("application/json")
76
+ .body(serde_json::to_string(&json_response)?))
77
  }
src/api/doc_info.rs CHANGED
@@ -1,14 +1,14 @@
1
  use std::collections::HashMap;
2
  use actix_multipart::Multipart;
3
  use actix_web::{get, HttpResponse, post, web};
4
- use actix_web::http::Error;
5
  use chrono::Local;
6
- use futures_util::{AsyncWriteExt, StreamExt};
7
  use serde::Deserialize;
8
  use std::io::Write;
9
  use crate::api::JsonResponse;
10
  use crate::AppState;
11
  use crate::entity::doc_info::Model;
 
12
  use crate::service::doc_info::{Mutation, Query};
13
 
14
  #[derive(Debug, Deserialize)]
@@ -35,10 +35,9 @@ pub struct MvParams {
35
  }
36
 
37
  #[get("/v1.0/docs")]
38
- async fn list(params: web::Json<Params>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
39
  let docs = Query::find_doc_infos_by_params(&data.conn, params.into_inner())
40
- .await
41
- .unwrap();
42
 
43
  let mut result = HashMap::new();
44
  result.insert("docs", docs);
@@ -51,11 +50,11 @@ async fn list(params: web::Json<Params>, data: web::Data<AppState>) -> Result<Ht
51
 
52
  Ok(HttpResponse::Ok()
53
  .content_type("application/json")
54
- .body(serde_json::to_string(&json_response).unwrap()))
55
  }
56
 
57
  #[post("/v1.0/upload")]
58
- 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> {
59
  let mut size = 0;
60
 
61
  while let Some(item) = payload.next().await {
@@ -65,16 +64,14 @@ async fn upload(mut payload: Multipart, filename: web::Data<String>, did: web::D
65
 
66
  let mut file = web::block(|| std::fs::File::create(filepath))
67
  .await
68
- .unwrap()
69
- .unwrap();
70
 
71
  while let Some(chunk) = field.next().await {
72
  let data = chunk.unwrap();
73
  size += data.len() as u64;
74
  file = web::block(move || file.write_all(&data).map(|_| file))
75
  .await
76
- .unwrap()
77
- .unwrap();
78
  }
79
  }
80
 
@@ -89,15 +86,15 @@ async fn upload(mut payload: Multipart, filename: web::Data<String>, did: web::D
89
  r#type: "".to_string(),
90
  created_at: Local::now().date_naive(),
91
  updated_at: Local::now().date_naive(),
92
- }).await.unwrap();
93
 
94
  Ok(HttpResponse::Ok().body("File uploaded successfully"))
95
  }
96
 
97
  #[post("/v1.0/delete_docs")]
98
- async fn delete(doc_ids: web::Json<Vec<i64>>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
99
  for doc_id in doc_ids.iter() {
100
- let _ = Mutation::delete_doc_info(&data.conn, *doc_id).await.unwrap();
101
  }
102
 
103
  let json_response = JsonResponse {
@@ -108,12 +105,12 @@ async fn delete(doc_ids: web::Json<Vec<i64>>, data: web::Data<AppState>) -> Resu
108
 
109
  Ok(HttpResponse::Ok()
110
  .content_type("application/json")
111
- .body(serde_json::to_string(&json_response).unwrap()))
112
  }
113
 
114
  #[post("/v1.0/mv_docs")]
115
- async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
116
- Mutation::mv_doc_info(&data.conn, params.dest_did, &params.dids).await.unwrap();
117
 
118
  let json_response = JsonResponse {
119
  code: 200,
@@ -123,5 +120,5 @@ async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<Ht
123
 
124
  Ok(HttpResponse::Ok()
125
  .content_type("application/json")
126
- .body(serde_json::to_string(&json_response).unwrap()))
127
  }
 
1
  use std::collections::HashMap;
2
  use actix_multipart::Multipart;
3
  use actix_web::{get, HttpResponse, post, web};
 
4
  use chrono::Local;
5
+ use futures_util::StreamExt;
6
  use serde::Deserialize;
7
  use std::io::Write;
8
  use crate::api::JsonResponse;
9
  use crate::AppState;
10
  use crate::entity::doc_info::Model;
11
+ use crate::errors::AppError;
12
  use crate::service::doc_info::{Mutation, Query};
13
 
14
  #[derive(Debug, Deserialize)]
 
35
  }
36
 
37
  #[get("/v1.0/docs")]
38
+ async fn list(params: web::Json<Params>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
39
  let docs = Query::find_doc_infos_by_params(&data.conn, params.into_inner())
40
+ .await?;
 
41
 
42
  let mut result = HashMap::new();
43
  result.insert("docs", docs);
 
50
 
51
  Ok(HttpResponse::Ok()
52
  .content_type("application/json")
53
+ .body(serde_json::to_string(&json_response)?))
54
  }
55
 
56
  #[post("/v1.0/upload")]
57
+ 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> {
58
  let mut size = 0;
59
 
60
  while let Some(item) = payload.next().await {
 
64
 
65
  let mut file = web::block(|| std::fs::File::create(filepath))
66
  .await
67
+ .unwrap()?;
 
68
 
69
  while let Some(chunk) = field.next().await {
70
  let data = chunk.unwrap();
71
  size += data.len() as u64;
72
  file = web::block(move || file.write_all(&data).map(|_| file))
73
  .await
74
+ .unwrap()?;
 
75
  }
76
  }
77
 
 
86
  r#type: "".to_string(),
87
  created_at: Local::now().date_naive(),
88
  updated_at: Local::now().date_naive(),
89
+ }).await?;
90
 
91
  Ok(HttpResponse::Ok().body("File uploaded successfully"))
92
  }
93
 
94
  #[post("/v1.0/delete_docs")]
95
+ async fn delete(doc_ids: web::Json<Vec<i64>>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
96
  for doc_id in doc_ids.iter() {
97
+ let _ = Mutation::delete_doc_info(&data.conn, *doc_id).await?;
98
  }
99
 
100
  let json_response = JsonResponse {
 
105
 
106
  Ok(HttpResponse::Ok()
107
  .content_type("application/json")
108
+ .body(serde_json::to_string(&json_response)?))
109
  }
110
 
111
  #[post("/v1.0/mv_docs")]
112
+ async fn mv(params: web::Json<MvParams>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
113
+ Mutation::mv_doc_info(&data.conn, params.dest_did, &params.dids).await?;
114
 
115
  let json_response = JsonResponse {
116
  code: 200,
 
120
 
121
  Ok(HttpResponse::Ok()
122
  .content_type("application/json")
123
+ .body(serde_json::to_string(&json_response)?))
124
  }
src/api/kb_info.rs CHANGED
@@ -1,15 +1,15 @@
1
  use std::collections::HashMap;
2
  use actix_web::{get, HttpResponse, post, web};
3
- use actix_web::http::Error;
4
  use crate::api::JsonResponse;
5
  use crate::AppState;
6
  use crate::entity::kb_info;
 
7
  use crate::service::kb_info::Mutation;
8
  use crate::service::kb_info::Query;
9
 
10
  #[post("/v1.0/create_kb")]
11
- async fn create(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
12
- let model = Mutation::create_kb_info(&data.conn, model.into_inner()).await.unwrap();
13
 
14
  let mut result = HashMap::new();
15
  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>) ->
22
 
23
  Ok(HttpResponse::Ok()
24
  .content_type("application/json")
25
- .body(serde_json::to_string(&json_response).unwrap()))
26
  }
27
 
28
  #[get("/v1.0/kbs")]
29
- async fn list(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
30
- let kbs = Query::find_kb_infos_by_uid(&data.conn, model.uid).await.unwrap();
31
 
32
  let mut result = HashMap::new();
33
  result.insert("kbs", kbs);
@@ -40,12 +40,12 @@ async fn list(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Re
40
 
41
  Ok(HttpResponse::Ok()
42
  .content_type("application/json")
43
- .body(serde_json::to_string(&json_response).unwrap()))
44
  }
45
 
46
  #[post("/v1.0/delete_kb")]
47
- async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
48
- let _ = Mutation::delete_kb_info(&data.conn, model.kb_id).await.unwrap();
49
 
50
  let json_response = JsonResponse {
51
  code: 200,
@@ -55,5 +55,5 @@ async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) ->
55
 
56
  Ok(HttpResponse::Ok()
57
  .content_type("application/json")
58
- .body(serde_json::to_string(&json_response).unwrap()))
59
  }
 
1
  use std::collections::HashMap;
2
  use actix_web::{get, HttpResponse, post, web};
 
3
  use crate::api::JsonResponse;
4
  use crate::AppState;
5
  use crate::entity::kb_info;
6
+ use crate::errors::AppError;
7
  use crate::service::kb_info::Mutation;
8
  use crate::service::kb_info::Query;
9
 
10
  #[post("/v1.0/create_kb")]
11
+ async fn create(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
12
+ let model = Mutation::create_kb_info(&data.conn, model.into_inner()).await?;
13
 
14
  let mut result = HashMap::new();
15
  result.insert("kb_id", model.kb_id.unwrap());
 
22
 
23
  Ok(HttpResponse::Ok()
24
  .content_type("application/json")
25
+ .body(serde_json::to_string(&json_response)?))
26
  }
27
 
28
  #[get("/v1.0/kbs")]
29
+ async fn list(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
30
+ let kbs = Query::find_kb_infos_by_uid(&data.conn, model.uid).await?;
31
 
32
  let mut result = HashMap::new();
33
  result.insert("kbs", kbs);
 
40
 
41
  Ok(HttpResponse::Ok()
42
  .content_type("application/json")
43
+ .body(serde_json::to_string(&json_response)?))
44
  }
45
 
46
  #[post("/v1.0/delete_kb")]
47
+ async fn delete(model: web::Json<kb_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
48
+ let _ = Mutation::delete_kb_info(&data.conn, model.kb_id).await?;
49
 
50
  let json_response = JsonResponse {
51
  code: 200,
 
55
 
56
  Ok(HttpResponse::Ok()
57
  .content_type("application/json")
58
+ .body(serde_json::to_string(&json_response)?))
59
  }
src/api/mod.rs CHANGED
@@ -1,9 +1,10 @@
1
  use serde::{Deserialize, Serialize};
2
 
3
- pub(crate) mod tag;
4
  pub(crate) mod kb_info;
5
  pub(crate) mod dialog_info;
6
  pub(crate) mod doc_info;
 
7
 
8
  #[derive(Debug, Deserialize, Serialize)]
9
  struct JsonResponse<T> {
 
1
  use serde::{Deserialize, Serialize};
2
 
3
+ pub(crate) mod tag_info;
4
  pub(crate) mod kb_info;
5
  pub(crate) mod dialog_info;
6
  pub(crate) mod doc_info;
7
+ pub(crate) mod user_info;
8
 
9
  #[derive(Debug, Deserialize, Serialize)]
10
  struct JsonResponse<T> {
src/api/{tag.rs → tag_info.rs} RENAMED
@@ -1,14 +1,16 @@
1
  use std::collections::HashMap;
2
  use actix_web::{get, HttpResponse, post, web};
3
- use actix_web::http::Error;
 
4
  use crate::api::JsonResponse;
5
  use crate::AppState;
6
  use crate::entity::tag_info;
 
7
  use crate::service::tag_info::{Mutation, Query};
8
 
9
  #[post("/v1.0/create_tag")]
10
- async fn create(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
11
- let model = Mutation::create_tag(&data.conn, model.into_inner()).await.unwrap();
12
 
13
  let mut result = HashMap::new();
14
  result.insert("tid", model.tid.unwrap());
@@ -21,12 +23,12 @@ async fn create(model: web::Json<tag_info::Model>, data: web::Data<AppState>) ->
21
 
22
  Ok(HttpResponse::Ok()
23
  .content_type("application/json")
24
- .body(serde_json::to_string(&json_response).unwrap()))
25
  }
26
 
27
  #[post("/v1.0/delete_tag")]
28
- async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, Error> {
29
- let _ = Mutation::delete_tag(&data.conn, model.tid).await.unwrap();
30
 
31
  let json_response = JsonResponse {
32
  code: 200,
@@ -36,12 +38,12 @@ async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) ->
36
 
37
  Ok(HttpResponse::Ok()
38
  .content_type("application/json")
39
- .body(serde_json::to_string(&json_response).unwrap()))
40
  }
41
 
42
- #[get("/v1.0/tags")]
43
- async fn list(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
44
- let tags = Query::find_tag_infos(&data.conn).await.unwrap();
45
 
46
  let mut result = HashMap::new();
47
  result.insert("tags", tags);
@@ -54,5 +56,5 @@ async fn list(data: web::Data<AppState>) -> Result<HttpResponse, Error> {
54
 
55
  Ok(HttpResponse::Ok()
56
  .content_type("application/json")
57
- .body(serde_json::to_string(&json_response).unwrap()))
58
  }
 
1
  use std::collections::HashMap;
2
  use actix_web::{get, HttpResponse, post, web};
3
+ use actix_web_httpauth::middleware::HttpAuthentication;
4
+ use crate::validator;
5
  use crate::api::JsonResponse;
6
  use crate::AppState;
7
  use crate::entity::tag_info;
8
+ use crate::errors::AppError;
9
  use crate::service::tag_info::{Mutation, Query};
10
 
11
  #[post("/v1.0/create_tag")]
12
+ async fn create(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
13
+ let model = Mutation::create_tag(&data.conn, model.into_inner()).await?;
14
 
15
  let mut result = HashMap::new();
16
  result.insert("tid", model.tid.unwrap());
 
23
 
24
  Ok(HttpResponse::Ok()
25
  .content_type("application/json")
26
+ .body(serde_json::to_string(&json_response)?))
27
  }
28
 
29
  #[post("/v1.0/delete_tag")]
30
+ async fn delete(model: web::Json<tag_info::Model>, data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
31
+ let _ = Mutation::delete_tag(&data.conn, model.tid).await?;
32
 
33
  let json_response = JsonResponse {
34
  code: 200,
 
38
 
39
  Ok(HttpResponse::Ok()
40
  .content_type("application/json")
41
+ .body(serde_json::to_string(&json_response)?))
42
  }
43
 
44
+ #[get("/v1.0/tags", wrap = "HttpAuthentication::bearer(validator)")]
45
+ async fn list(data: web::Data<AppState>) -> Result<HttpResponse, AppError> {
46
+ let tags = Query::find_tag_infos(&data.conn).await?;
47
 
48
  let mut result = HashMap::new();
49
  result.insert("tags", tags);
 
56
 
57
  Ok(HttpResponse::Ok()
58
  .content_type("application/json")
59
+ .body(serde_json::to_string(&json_response)?))
60
  }
src/api/user_info.rs ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use actix_identity::Identity;
2
+ use actix_web::{get, HttpResponse, post, web};
3
+ use serde::{Deserialize, Serialize};
4
+ use crate::api::JsonResponse;
5
+ use crate::AppState;
6
+ use crate::entity::user_info::Model;
7
+ use crate::errors::{AppError, UserError};
8
+ use crate::service::user_info::Query;
9
+
10
+ pub(crate) fn create_auth_token(user: &Model) -> u64 {
11
+ use std::{
12
+ collections::hash_map::DefaultHasher,
13
+ hash::{Hash, Hasher},
14
+ };
15
+
16
+ let mut hasher = DefaultHasher::new();
17
+ user.hash(&mut hasher);
18
+ hasher.finish()
19
+ }
20
+
21
+ #[derive(Clone, Debug, Serialize, Deserialize)]
22
+ pub(crate) struct LoginParams {
23
+ pub(crate) email: String,
24
+ pub(crate) password: String,
25
+ }
26
+
27
+ #[post("/v1.0/login")]
28
+ async fn login(
29
+ data: web::Data<AppState>,
30
+ identity: Identity,
31
+ input: web::Json<LoginParams>
32
+ ) -> Result<HttpResponse, AppError> {
33
+ match Query::login(&data.conn, &input.email, &input.password).await? {
34
+ Some(user) => {
35
+ let token = create_auth_token(&user).to_string();
36
+
37
+ identity.remember(token.clone());
38
+
39
+ let json_response = JsonResponse {
40
+ code: 200,
41
+ err: "".to_owned(),
42
+ data: token.clone(),
43
+ };
44
+
45
+ Ok(HttpResponse::Ok()
46
+ .content_type("application/json")
47
+ .append_header(("X-Auth-Token", token))
48
+ .body(serde_json::to_string(&json_response)?))
49
+ }
50
+ None => Err(UserError::LoginFailed.into())
51
+ }
52
+ }
src/entity/tag_info.rs CHANGED
@@ -9,10 +9,10 @@ pub struct Model {
9
  pub tid: i64,
10
  pub uid: i64,
11
  pub tag_name: String,
12
- pub regx: String,
13
  pub color: i64,
14
  pub icon: i64,
15
- pub dir: String,
16
 
17
  #[serde(skip_deserializing)]
18
  pub created_at: Date,
 
9
  pub tid: i64,
10
  pub uid: i64,
11
  pub tag_name: String,
12
+ pub regx: Option<String>,
13
  pub color: i64,
14
  pub icon: i64,
15
+ pub dir: Option<String>,
16
 
17
  #[serde(skip_deserializing)]
18
  pub created_at: Date,
src/entity/user_info.rs CHANGED
@@ -1,7 +1,7 @@
1
  use sea_orm::entity::prelude::*;
2
  use serde::{Deserialize, Serialize};
3
 
4
- #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)]
5
  #[sea_orm(table_name = "user_info")]
6
  pub struct Model {
7
  #[sea_orm(primary_key)]
@@ -9,10 +9,11 @@ pub struct Model {
9
  pub uid: i64,
10
  pub email: String,
11
  pub nickname: String,
12
- pub avatar_url: String,
13
  pub color_schema: String,
14
  pub list_style: String,
15
  pub language: String,
 
16
 
17
  #[serde(skip_deserializing)]
18
  pub created_at: Date,
 
1
  use sea_orm::entity::prelude::*;
2
  use serde::{Deserialize, Serialize};
3
 
4
+ #[derive(Clone, Debug, PartialEq, Eq, Hash, DeriveEntityModel, Deserialize, Serialize)]
5
  #[sea_orm(table_name = "user_info")]
6
  pub struct Model {
7
  #[sea_orm(primary_key)]
 
9
  pub uid: i64,
10
  pub email: String,
11
  pub nickname: String,
12
+ pub avatar_url: Option<String>,
13
  pub color_schema: String,
14
  pub list_style: String,
15
  pub language: String,
16
+ pub password: String,
17
 
18
  #[serde(skip_deserializing)]
19
  pub created_at: Date,
src/errors.rs ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use actix_web::{HttpResponse, ResponseError};
2
+ use thiserror::Error;
3
+
4
+ #[derive(Debug, Error)]
5
+ pub(crate) enum AppError {
6
+ #[error("`{0}`")]
7
+ User(#[from] UserError),
8
+
9
+ #[error("`{0}`")]
10
+ Json(#[from] serde_json::Error),
11
+
12
+ #[error("`{0}`")]
13
+ Actix(#[from] actix_web::Error),
14
+
15
+ #[error("`{0}`")]
16
+ Db(#[from] sea_orm::DbErr),
17
+
18
+ #[error("`{0}`")]
19
+ Std(#[from] std::io::Error),
20
+ }
21
+
22
+ #[derive(Debug, Error)]
23
+ pub(crate) enum UserError {
24
+ #[error("`username` field of `User` cannot be empty!")]
25
+ EmptyUsername,
26
+
27
+ #[error("`username` field of `User` cannot contain whitespaces!")]
28
+ UsernameInvalidCharacter,
29
+
30
+ #[error("`password` field of `User` cannot be empty!")]
31
+ EmptyPassword,
32
+
33
+ #[error("`password` field of `User` cannot contain whitespaces!")]
34
+ PasswordInvalidCharacter,
35
+
36
+ #[error("Could not find any `User` for id: `{0}`!")]
37
+ NotFound(i64),
38
+
39
+ #[error("Failed to login user!")]
40
+ LoginFailed,
41
+
42
+ #[error("User is not logged in!")]
43
+ NotLoggedIn,
44
+
45
+ #[error("Invalid authorization token!")]
46
+ InvalidToken,
47
+
48
+ #[error("Could not find any `User`!")]
49
+ Empty,
50
+ }
51
+
52
+ impl ResponseError for AppError {
53
+ fn status_code(&self) -> actix_web::http::StatusCode {
54
+ match self {
55
+ AppError::User(user_error) => match user_error {
56
+ UserError::EmptyUsername => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
57
+ UserError::UsernameInvalidCharacter => {
58
+ actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
59
+ }
60
+ UserError::EmptyPassword => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
61
+ UserError::PasswordInvalidCharacter => {
62
+ actix_web::http::StatusCode::UNPROCESSABLE_ENTITY
63
+ }
64
+ UserError::NotFound(_) => actix_web::http::StatusCode::NOT_FOUND,
65
+ UserError::NotLoggedIn => actix_web::http::StatusCode::UNAUTHORIZED,
66
+ UserError::Empty => actix_web::http::StatusCode::NOT_FOUND,
67
+ UserError::LoginFailed => actix_web::http::StatusCode::NOT_FOUND,
68
+ UserError::InvalidToken => actix_web::http::StatusCode::UNAUTHORIZED,
69
+ },
70
+ AppError::Json(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
71
+ AppError::Actix(fail) => fail.as_response_error().status_code(),
72
+ AppError::Db(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
73
+ AppError::Std(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
74
+ }
75
+ }
76
+
77
+ fn error_response(&self) -> HttpResponse {
78
+ let status_code = self.status_code();
79
+ let response = HttpResponse::build(status_code).body(self.to_string());
80
+ response
81
+ }
82
+ }
src/main.rs CHANGED
@@ -1,19 +1,41 @@
1
  mod api;
2
  mod entity;
3
  mod service;
 
4
 
5
  use std::env;
6
  use actix_files::Files;
7
- use actix_web::{web, App, HttpServer, middleware};
 
 
 
 
 
 
8
  use listenfd::ListenFd;
9
  use sea_orm::{Database, DatabaseConnection};
10
  use migration::{Migrator, MigratorTrait};
 
11
 
12
  #[derive(Debug, Clone)]
13
  struct AppState {
14
  conn: DatabaseConnection,
15
  }
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  #[actix_web::main]
18
  async fn main() -> std::io::Result<()> {
19
  std::env::set_var("RUST_LOG", "debug");
@@ -39,6 +61,19 @@ async fn main() -> std::io::Result<()> {
39
  App::new()
40
  .service(Files::new("/static", "./static"))
41
  .app_data(web::Data::new(state.clone()))
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  .wrap(middleware::Logger::default())
43
  .configure(init)
44
  });
@@ -55,7 +90,23 @@ async fn main() -> std::io::Result<()> {
55
  }
56
 
57
  fn init(cfg: &mut web::ServiceConfig) {
58
- cfg.service(api::tag::create);
59
- cfg.service(api::tag::delete);
60
- cfg.service(api::tag::list);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
 
1
  mod api;
2
  mod entity;
3
  mod service;
4
+ mod errors;
5
 
6
  use std::env;
7
  use actix_files::Files;
8
+ use actix_identity::{CookieIdentityPolicy, IdentityService, RequestIdentity};
9
+ use actix_session::CookieSession;
10
+ use actix_web::{web, App, HttpServer, middleware, Error};
11
+ use actix_web::cookie::time::Duration;
12
+ use actix_web::dev::ServiceRequest;
13
+ use actix_web::error::ErrorUnauthorized;
14
+ use actix_web_httpauth::extractors::bearer::BearerAuth;
15
  use listenfd::ListenFd;
16
  use sea_orm::{Database, DatabaseConnection};
17
  use migration::{Migrator, MigratorTrait};
18
+ use crate::errors::UserError;
19
 
20
  #[derive(Debug, Clone)]
21
  struct AppState {
22
  conn: DatabaseConnection,
23
  }
24
 
25
+ pub(crate) async fn validator(
26
+ req: ServiceRequest,
27
+ credentials: BearerAuth,
28
+ ) -> Result<ServiceRequest, Error> {
29
+ if let Some(token) = req.get_identity() {
30
+ println!("{}, {}",credentials.token(), token);
31
+ (credentials.token() == token)
32
+ .then(|| req)
33
+ .ok_or(ErrorUnauthorized(UserError::InvalidToken))
34
+ } else {
35
+ Err(ErrorUnauthorized(UserError::NotLoggedIn))
36
+ }
37
+ }
38
+
39
  #[actix_web::main]
40
  async fn main() -> std::io::Result<()> {
41
  std::env::set_var("RUST_LOG", "debug");
 
61
  App::new()
62
  .service(Files::new("/static", "./static"))
63
  .app_data(web::Data::new(state.clone()))
64
+ .wrap(IdentityService::new(
65
+ CookieIdentityPolicy::new(&[0; 32])
66
+ .name("auth-cookie")
67
+ .login_deadline(Duration::seconds(120))
68
+ .secure(false),
69
+ ))
70
+ .wrap(
71
+ CookieSession::signed(&[0; 32])
72
+ .name("session-cookie")
73
+ .secure(false)
74
+ // WARNING(alex): This uses the `time` crate, not `std::time`!
75
+ .expires_in_time(Duration::seconds(60)),
76
+ )
77
  .wrap(middleware::Logger::default())
78
  .configure(init)
79
  });
 
90
  }
91
 
92
  fn init(cfg: &mut web::ServiceConfig) {
93
+ cfg.service(api::tag_info::create);
94
+ cfg.service(api::tag_info::delete);
95
+ cfg.service(api::tag_info::list);
96
+
97
+ cfg.service(api::kb_info::create);
98
+ cfg.service(api::kb_info::delete);
99
+ cfg.service(api::kb_info::list);
100
+
101
+ cfg.service(api::doc_info::list);
102
+ cfg.service(api::doc_info::delete);
103
+ cfg.service(api::doc_info::mv);
104
+ cfg.service(api::doc_info::upload);
105
+
106
+ cfg.service(api::dialog_info::list);
107
+ cfg.service(api::dialog_info::delete);
108
+ cfg.service(api::dialog_info::detail);
109
+ cfg.service(api::dialog_info::create);
110
+
111
+ cfg.service(api::user_info::login);
112
  }
src/service/dialog_info.rs CHANGED
@@ -3,7 +3,7 @@ use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, Pagina
3
  use sea_orm::ActiveValue::Set;
4
  use sea_orm::QueryFilter;
5
  use sea_orm::ColumnTrait;
6
- use crate::entity::{dialog_info, kb_info};
7
  use crate::entity::dialog_info::Entity;
8
 
9
  pub struct Query;
 
3
  use sea_orm::ActiveValue::Set;
4
  use sea_orm::QueryFilter;
5
  use sea_orm::ColumnTrait;
6
+ use crate::entity::dialog_info;
7
  use crate::entity::dialog_info::Entity;
8
 
9
  pub struct Query;
src/service/doc_info.rs CHANGED
@@ -1,9 +1,8 @@
1
  use chrono::Local;
2
- use postgres::fallible_iterator::FallibleIterator;
3
  use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
4
  use sea_orm::ActiveValue::Set;
5
  use sea_orm::QueryFilter;
6
- use crate::api::doc_info::{FilterParams, Params};
7
  use crate::entity::{doc2_doc, doc_info, kb_info, tag_info};
8
  use crate::entity::doc_info::Entity;
9
 
 
1
  use chrono::Local;
 
2
  use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
3
  use sea_orm::ActiveValue::Set;
4
  use sea_orm::QueryFilter;
5
+ use crate::api::doc_info::Params;
6
  use crate::entity::{doc2_doc, doc_info, kb_info, tag_info};
7
  use crate::entity::doc_info::Entity;
8
 
src/service/kb_info.rs CHANGED
@@ -1,4 +1,4 @@
1
- use chrono::{Local, NaiveDate};
2
  use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
3
  use sea_orm::ActiveValue::Set;
4
  use crate::entity::kb_info;
 
1
+ use chrono::Local;
2
  use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
3
  use sea_orm::ActiveValue::Set;
4
  use crate::entity::kb_info;
src/service/mod.rs CHANGED
@@ -1,4 +1,5 @@
1
  pub(crate) mod dialog_info;
2
  pub(crate) mod tag_info;
3
  pub(crate) mod kb_info;
4
- pub(crate) mod doc_info;
 
 
1
  pub(crate) mod dialog_info;
2
  pub(crate) mod tag_info;
3
  pub(crate) mod kb_info;
4
+ pub(crate) mod doc_info;
5
+ pub(crate) mod user_info;
src/service/tag_info.rs CHANGED
@@ -1,4 +1,4 @@
1
- use chrono::{Local, NaiveDate};
2
  use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
3
  use sea_orm::ActiveValue::Set;
4
  use crate::entity::tag_info;
 
1
+ use chrono::Local;
2
  use sea_orm::{ActiveModelTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryOrder};
3
  use sea_orm::ActiveValue::Set;
4
  use crate::entity::tag_info;
src/service/user_info.rs ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use chrono::Local;
2
+ use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, DbErr, DeleteResult, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
3
+ use sea_orm::ActiveValue::Set;
4
+ use crate::entity::user_info;
5
+ use crate::entity::user_info::Entity;
6
+
7
+ pub struct Query;
8
+
9
+ impl Query {
10
+ pub async fn find_user_info_by_id(db: &DbConn, id: i64) -> Result<Option<user_info::Model>, DbErr> {
11
+ Entity::find_by_id(id).one(db).await
12
+ }
13
+
14
+ pub async fn login(db: &DbConn, email: &str, password: &str) -> Result<Option<user_info::Model>, DbErr> {
15
+ Entity::find()
16
+ .filter(user_info::Column::Email.eq(email))
17
+ .filter(user_info::Column::Password.eq(password))
18
+ .one(db)
19
+ .await
20
+ }
21
+
22
+ pub async fn find_user_infos(db: &DbConn) -> Result<Vec<user_info::Model>, DbErr> {
23
+ Entity::find().all(db).await
24
+ }
25
+
26
+ pub async fn find_user_infos_in_page(
27
+ db: &DbConn,
28
+ page: u64,
29
+ posts_per_page: u64,
30
+ ) -> Result<(Vec<user_info::Model>, u64), DbErr> {
31
+ // Setup paginator
32
+ let paginator = Entity::find()
33
+ .order_by_asc(user_info::Column::Uid)
34
+ .paginate(db, posts_per_page);
35
+ let num_pages = paginator.num_pages().await?;
36
+
37
+ // Fetch paginated posts
38
+ paginator.fetch_page(page - 1).await.map(|p| (p, num_pages))
39
+ }
40
+ }
41
+
42
+ pub struct Mutation;
43
+
44
+ impl Mutation {
45
+ pub async fn create_user(
46
+ db: &DbConn,
47
+ form_data: user_info::Model,
48
+ ) -> Result<user_info::ActiveModel, DbErr> {
49
+ user_info::ActiveModel {
50
+ uid: Default::default(),
51
+ email: Set(form_data.email.to_owned()),
52
+ nickname: Set(form_data.nickname.to_owned()),
53
+ avatar_url: Set(form_data.avatar_url.to_owned()),
54
+ color_schema: Set(form_data.color_schema.to_owned()),
55
+ list_style: Set(form_data.list_style.to_owned()),
56
+ language: Set(form_data.language.to_owned()),
57
+ password: Set(form_data.password.to_owned()),
58
+ created_at: Set(Local::now().date_naive()),
59
+ updated_at: Set(Local::now().date_naive()),
60
+ }
61
+ .save(db)
62
+ .await
63
+ }
64
+
65
+ pub async fn update_tag_by_id(
66
+ db: &DbConn,
67
+ id: i64,
68
+ form_data: user_info::Model,
69
+ ) -> Result<user_info::Model, DbErr> {
70
+ let user: user_info::ActiveModel = Entity::find_by_id(id)
71
+ .one(db)
72
+ .await?
73
+ .ok_or(DbErr::Custom("Cannot find tag.".to_owned()))
74
+ .map(Into::into)?;
75
+
76
+ user_info::ActiveModel {
77
+ uid: user.uid,
78
+ email: Set(form_data.email.to_owned()),
79
+ nickname: Set(form_data.nickname.to_owned()),
80
+ avatar_url: Set(form_data.avatar_url.to_owned()),
81
+ color_schema: Set(form_data.color_schema.to_owned()),
82
+ list_style: Set(form_data.list_style.to_owned()),
83
+ language: Set(form_data.language.to_owned()),
84
+ password: Set(form_data.password.to_owned()),
85
+ created_at: Default::default(),
86
+ updated_at: Set(Local::now().date_naive()),
87
+ }
88
+ .update(db)
89
+ .await
90
+ }
91
+
92
+ pub async fn delete_tag(db: &DbConn, tid: i64) -> Result<DeleteResult, DbErr> {
93
+ let tag: user_info::ActiveModel = Entity::find_by_id(tid)
94
+ .one(db)
95
+ .await?
96
+ .ok_or(DbErr::Custom("Cannot find tag.".to_owned()))
97
+ .map(Into::into)?;
98
+
99
+ tag.delete(db).await
100
+ }
101
+
102
+ pub async fn delete_all_tags(db: &DbConn) -> Result<DeleteResult, DbErr> {
103
+ Entity::delete_many().exec(db).await
104
+ }
105
+ }