summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSanto Cariotti <santo@dcariotti.me>2021-03-16 14:19:42 +0100
committerGitHub <noreply@github.com>2021-03-16 14:19:42 +0100
commit896ab2a9fb4a34d82c70792a1114ac1a0f4ad6c0 (patch)
tree6884cfe0cb85ffee94095d1e9db3864476c24de4
parentb950072a3109d2c13881611a3950baa191caf097 (diff)
parentfc28f445d4cfbfcd597438ab2cdd137fa2021dbc (diff)
Merge pull request #13 from gico-net/feat/add-emails
Manage emails and their hashes for Gravatar
-rw-r--r--Cargo.lock19
-rw-r--r--Cargo.toml2
-rw-r--r--extra/insomnia.yml114
-rw-r--r--src/email/mod.rs2
-rw-r--r--src/email/models.rs111
-rw-r--r--src/email/routes.rs75
-rw-r--r--src/main.rs2
-rw-r--r--src/repository/models.rs7
-rw-r--r--src/repository/routes.rs1
9 files changed, 326 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ad4fcfa..bcdd80e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -913,6 +913,8 @@ dependencies = [
"config",
"deadpool-postgres",
"dotenv",
+ "hex",
+ "md-5",
"regex",
"serde 1.0.124",
"slog",
@@ -975,6 +977,12 @@ dependencies = [
]
[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
name = "hmac"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1172,6 +1180,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
+name = "md-5"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
+dependencies = [
+ "block-buffer",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index ae7576f..8502fcb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,3 +25,5 @@ serde = { version = "1.0.104", features = ["derive"] }
chrono = { version = "0.4.19", features = ["serde"] }
uuid = { version = "0.8.2", features = ["serde", "v4"] }
regex = "1"
+md-5 = "0.9.1"
+hex = "0.4.3"
diff --git a/extra/insomnia.yml b/extra/insomnia.yml
index a682c95..0e4395f 100644
--- a/extra/insomnia.yml
+++ b/extra/insomnia.yml
@@ -1,6 +1,6 @@
_type: export
__export_format: 4
-__export_date: 2021-03-14T21:23:45.742Z
+__export_date: 2021-03-16T13:18:44.060Z
__export_source: insomnia.desktop.app:v2020.4.2
resources:
- _id: req_d42fbef765e149648399036fbb069ea3
@@ -42,6 +42,35 @@ resources:
description: ""
scope: null
_type: workspace
+ - _id: req_4a674beeeee048f9a804d3b5f4ce5abf
+ parentId: fld_b5ba3b39b5394b64b0fea47812192c27
+ modified: 1615899152584
+ created: 1615883844898
+ url: "{{API}}/repo/"
+ name: /repo/
+ description: ""
+ method: POST
+ body:
+ mimeType: application/json
+ text: |-
+ {
+ "url": "https://github.com/gico-net/server.git"
+ }
+ parameters: []
+ headers:
+ - name: Content-Type
+ value: application/json
+ id: pair_0c0c295ef0e741bb8f3a5b4b9b7924c9
+ authentication: {}
+ metaSortKey: -1615752333836
+ isPrivate: false
+ settingStoreCookies: true
+ settingSendCookies: true
+ settingDisableRenderRequestBody: false
+ settingEncodeUrl: true
+ settingRebuildPath: true
+ settingFollowRedirects: global
+ _type: request
- _id: req_ad2a16371ab44200a3f580000ece7dec
parentId: fld_b5ba3b39b5394b64b0fea47812192c27
modified: 1615752395118
@@ -65,7 +94,7 @@ resources:
_type: request
- _id: req_0a203eacc036463a8e3c1a3a900b8109
parentId: fld_b5ba3b39b5394b64b0fea47812192c27
- modified: 1615756966728
+ modified: 1615841682839
created: 1615752431937
url: "{{API}}/repo/dbc980b2-b4dc-400d-9110-4ea6a45c730a/"
name: /repo/<uid>/
@@ -88,6 +117,87 @@ resources:
settingRebuildPath: true
settingFollowRedirects: global
_type: request
+ - _id: req_43bdccbddd5645ec84ae1458a171bdc1
+ parentId: fld_587740fc7d084792a897ae554ff46254
+ modified: 1615900193869
+ created: 1615896001344
+ url: "{{ API }}/email/"
+ name: /email/
+ description: ""
+ method: GET
+ body: {}
+ parameters: []
+ headers: []
+ authentication: {}
+ metaSortKey: -1615752333661
+ isPrivate: false
+ settingStoreCookies: true
+ settingSendCookies: true
+ settingDisableRenderRequestBody: false
+ settingEncodeUrl: true
+ settingRebuildPath: true
+ settingFollowRedirects: global
+ _type: request
+ - _id: fld_587740fc7d084792a897ae554ff46254
+ parentId: wrk_83d42d64ffc54233ba18b02611268a92
+ modified: 1615899143962
+ created: 1615895986599
+ name: Email
+ description: ""
+ environment: {}
+ environmentPropertyOrder: null
+ metaSortKey: -1615752305094
+ _type: request_group
+ - _id: req_10d1257a2fca47cfa04dc271450707b3
+ parentId: fld_587740fc7d084792a897ae554ff46254
+ modified: 1615900289005
+ created: 1615900198335
+ url: "{{ API }}/email/search/?q=santo@dcariotti.me"
+ name: /email/search/
+ description: ""
+ method: GET
+ body: {}
+ parameters: []
+ headers: []
+ authentication: {}
+ metaSortKey: -1615547149670.5
+ isPrivate: false
+ settingStoreCookies: true
+ settingSendCookies: true
+ settingDisableRenderRequestBody: false
+ settingEncodeUrl: true
+ settingRebuildPath: true
+ settingFollowRedirects: global
+ _type: request
+ - _id: req_daa30eedb3fb48a0848c73fc555898df
+ parentId: fld_587740fc7d084792a897ae554ff46254
+ modified: 1615898990554
+ created: 1615897787425
+ url: "{{ API }}/email/"
+ name: /email/
+ description: ""
+ method: POST
+ body:
+ mimeType: application/json
+ text: |-
+ {
+ "email": "dcariotti24@gmail.com"
+ }
+ parameters: []
+ headers:
+ - name: Content-Type
+ value: application/json
+ id: pair_d73066cd3e2245618aab96e8fe1e9327
+ authentication: {}
+ metaSortKey: -1615341965680
+ isPrivate: false
+ settingStoreCookies: true
+ settingSendCookies: true
+ settingDisableRenderRequestBody: false
+ settingEncodeUrl: true
+ settingRebuildPath: true
+ settingFollowRedirects: global
+ _type: request
- _id: env_fae256ef6cd347e85cc3938afa45748d6a2ec5a2
parentId: wrk_83d42d64ffc54233ba18b02611268a92
modified: 1615752324694
diff --git a/src/email/mod.rs b/src/email/mod.rs
new file mode 100644
index 0000000..a0e1883
--- /dev/null
+++ b/src/email/mod.rs
@@ -0,0 +1,2 @@
+pub mod models;
+pub mod routes;
diff --git a/src/email/models.rs b/src/email/models.rs
new file mode 100644
index 0000000..ed46026
--- /dev/null
+++ b/src/email/models.rs
@@ -0,0 +1,111 @@
+use crate::db::get_client;
+use crate::errors::{AppError, AppErrorType};
+
+use deadpool_postgres::Pool;
+use serde::{Deserialize, Serialize};
+use tokio_pg_mapper::FromTokioPostgresRow;
+use tokio_pg_mapper_derive::PostgresMapper;
+
+use hex;
+use md5::{Digest, Md5};
+
+#[derive(Serialize, Deserialize, PostgresMapper)]
+#[pg_mapper(table = "email")]
+/// Emails model
+pub struct Email {
+ pub email: String,
+ pub hash_md5: String,
+}
+
+// Struct used to creare a new email
+#[derive(Serialize, Deserialize)]
+pub struct EmailData {
+ pub email: String,
+}
+
+impl Email {
+ /// Find all emails, returns email and its MD5 hash
+ pub async fn find_all(pool: Pool) -> Result<Vec<Email>, AppError> {
+ let client = get_client(pool.clone()).await.unwrap();
+ let statement = client.prepare("SELECT * FROM email").await?;
+
+ let emails = client
+ .query(&statement, &[])
+ .await?
+ .iter()
+ .map(|row| Email::from_row_ref(row).unwrap())
+ .collect::<Vec<Email>>();
+
+ Ok(emails)
+ }
+
+ /// Search an email
+ pub async fn search(
+ pool: Pool,
+ email: &String,
+ ) -> Result<Email, AppError> {
+ let client = get_client(pool.clone()).await.unwrap();
+
+ let statement =
+ client.prepare("SELECT * FROM email WHERE email=$1").await?;
+
+ let email = client
+ .query_opt(&statement, &[&email])
+ .await?
+ .map(|row| Email::from_row_ref(&row).unwrap());
+
+ match email {
+ Some(email) => Ok(email),
+ None => Err(AppError {
+ error_type: AppErrorType::NotFoundError,
+ cause: None,
+ message: Some("Email not found".to_string()),
+ }),
+ }
+ }
+
+ /// Create new email
+ pub async fn create(
+ pool: Pool,
+ data: &EmailData,
+ ) -> Result<Email, AppError> {
+ // Search an email that matches with that string, because if it's
+ // exists, the server cannot create a clone
+ match Email::search(pool.clone(), &data.email).await {
+ Ok(_) => {
+ return Err(AppError {
+ message: Some("Email already exists".to_string()),
+ cause: Some("".to_string()),
+ error_type: AppErrorType::AuthorizationError,
+ });
+ }
+ Err(_) => {}
+ };
+
+ let client = get_client(pool.clone()).await.unwrap();
+
+ let mut hasher = Md5::new();
+ hasher.update(&data.email.as_bytes());
+ let hash_final = hasher.finalize();
+
+ let digest = hex::encode(&hash_final.as_slice());
+
+ let statement = client
+ .prepare("INSERT INTO email VALUES ($1, $2) RETURNING *")
+ .await?;
+
+ let email = client
+ .query_opt(&statement, &[&data.email, &digest])
+ .await?
+ .map(|row| Email::from_row_ref(&row).unwrap());
+
+ match email {
+ Some(email) => Ok(email),
+ None => Err(AppError {
+ message: Some("Error creating a new email".to_string()),
+ cause: Some("Unknown error".to_string()),
+ error_type: AppErrorType::DbError,
+ }),
+ }
+ }
+}
diff --git a/src/email/routes.rs b/src/email/routes.rs
new file mode 100644
index 0000000..14299eb
--- /dev/null
+++ b/src/email/routes.rs
@@ -0,0 +1,75 @@
+use std::collections::HashMap;
+
+use crate::config::AppState;
+use crate::email::models::{Email, EmailData};
+use crate::errors::AppErrorResponse;
+use actix_web::{web, HttpRequest, HttpResponse, Responder};
+use slog::info;
+
+/// Endpoint used for retrieve all emails
+async fn index(state: web::Data<AppState>) -> impl Responder {
+ let result = Email::find_all(state.pool.clone()).await;
+ info!(state.log, "GET /email/");
+
+ match result {
+ Ok(emails) => HttpResponse::Ok().json(emails),
+ _ => HttpResponse::BadRequest().json(AppErrorResponse {
+ detail: "Error trying to read all emails from database"
+ .to_string(),
+ }),
+ }
+}
+
+// Endpoint used for create new email
+async fn create_email(
+ payload: web::Json<EmailData>,
+ state: web::Data<AppState>,
+) -> impl Responder {
+ info!(state.log, "POST /email/");
+ let result = Email::create(state.pool.clone(), &payload).await;
+
+ result
+ .map(|email| HttpResponse::Created().json(email))
+ .map_err(|e| e)
+}
+
+// Endpoint used for email search
+async fn search_email(
+ req: HttpRequest,
+ state: web::Data<AppState>,
+) -> impl Responder {
+ let query =
+ web::Query::<HashMap<String, String>>::from_query(req.query_string())
+ .unwrap();
+ let email = match query.get("q") {
+ Some(x) => x,
+ None => {
+ return HttpResponse::NotFound().json(AppErrorResponse {
+ detail: "No email found".to_string(),
+ });
+ }
+ };
+ let result = Email::search(state.pool.clone(), email).await;
+ info!(state.log, "GET /email/search?q={}", email);
+
+ match result {
+ Ok(email) => HttpResponse::Ok().json(email),
+ _ => HttpResponse::NotFound().json(AppErrorResponse {
+ detail: "No email found".to_string(),
+ }),
+ }
+}
+pub fn config(cfg: &mut web::ServiceConfig) {
+ cfg.service(
+ web::scope("/email")
+ .service(
+ web::resource("{_:/?}")
+ .route(web::get().to(index))
+ .route(web::post().to(create_email)),
+ )
+ .service(
+ web::resource("/search{_:/?}")
+ .route(web::get().to(search_email)),
+ ),
+ );
+}
diff --git a/src/main.rs b/src/main.rs
index 2c5532a..63f48fa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@ mod db;
mod errors;
mod helpers;
+mod email;
mod repository;
use actix_web::{middleware, App, HttpServer};
@@ -35,6 +36,7 @@ async fn main() -> std::io::Result<()> {
})
.wrap(middleware::Logger::default())
.configure(repository::routes::config)
+ .configure(email::routes::config)
})
.bind(format!("{}:{}", config.server.host, config.server.port))?
.run()
diff --git a/src/repository/models.rs b/src/repository/models.rs
index 1cbf3bb..e3d8456 100644
--- a/src/repository/models.rs
+++ b/src/repository/models.rs
@@ -180,12 +180,9 @@ impl Repository {
};
let repo = client
- .query(&statement, &[&uuid, &repo_name, &user_ip])
+ .query_opt(&statement, &[&uuid, &repo_name, &user_ip])
.await?
- .iter()
- .map(|row| Repository::from_row_ref(row).unwrap())
- .collect::<Vec<Repository>>()
- .pop();
+ .map(|row| Repository::from_row_ref(&row).unwrap());
match repo {
Some(repo) => Ok(repo),
diff --git a/src/repository/routes.rs b/src/repository/routes.rs
index a0f4db5..abf77b6 100644
--- a/src/repository/routes.rs
+++ b/src/repository/routes.rs
@@ -80,6 +80,7 @@ async fn delete_repo(
.map_err(|e| e)
}
+/// Endpoint used for create new repository
async fn create_repo(
req: HttpRequest,
payload: web::Json<RepositoryData>,