diff --git a/Cargo.toml b/Cargo.toml index 80e2e9b..e9f910e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,13 @@ strip = "symbols" tokio = { version = "1.43.0", default-features = false,features = ["macros", "rt-multi-thread", "time"] } serde = { version = "1.0.217", default-features = false, features = ["derive"] } serde_json = { version = "1.0.135" , default-features = false} -axum = { version = "0.8.1", default-features = false, features = ["http2", "json", "tokio"]} +axum = { version = "0.8.1", default-features = false, features = ["http1", "json", "tokio"]} chrono = { version = "0.4.39", default-features = false, features = ["serde"] } jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] } lettre = { version = "0.11.11", default-features = false, features = ["builder", "serde", "smtp-transport", "tokio1-rustls-tls"] } sqlx = { version = "0.8.3", default-features = false, features = ["chrono", "macros", "migrate", "postgres", "runtime-tokio-rustls"] } tower = { version = "0.5.2", default-features = false, features = ["limit"] } -tower-http = { version = "0.6.2", default-features = false, features = ["cors"] } +tower-http = { version = "0.6.2", default-features = false, features = ["cors", "trace"] } +axum-macros = "0.5.0" +tracing-subscriber = "0.3.19" +tracing = "0.1.41" diff --git a/configs/database_config.toml b/configs/database_config.toml index 88aab43..9fdcb5a 100644 --- a/configs/database_config.toml +++ b/configs/database_config.toml @@ -4,4 +4,5 @@ username = "root" password = "root" database = "rust_forum" backend = "postgres" -connection_pool_size = "100" \ No newline at end of file +connection_pool_size = 100 +default_user_role_id = 10 diff --git a/mail_templates/one_time_password.toml b/mail_templates/one_time_password.toml index 6cdadf6..a52031c 100644 --- a/mail_templates/one_time_password.toml +++ b/mail_templates/one_time_password.toml @@ -1,3 +1,3 @@ [one_time_password] subject = "Your One Time Password" -body = "Dear *, \nHere is your One Time Password = * \n Best Wishes" \ No newline at end of file +body = "Dear *,

Here is your One Time Password = *

Best Wishes" diff --git a/migrations/20241204225135_create_user_table.up.sql b/migrations/20241204225135_create_user_table.up.sql index e1bee62..a57a049 100644 --- a/migrations/20241204225135_create_user_table.up.sql +++ b/migrations/20241204225135_create_user_table.up.sql @@ -3,8 +3,8 @@ CREATE TABLE IF NOT EXISTS "user"( user_id BIGSERIAL PRIMARY KEY NOT NULL UNIQUE, name VARCHAR(256) NOT NULL, surname VARCHAR(256) NOT NULL, - gender boolean NOT NULL, + gender BOOLEAN NOT NULL, birth_date DATE NOT NULL, - role_id BIGSERIAL NOT NULL REFERENCES "role"(id), + role_id BIGINT NOT NULL REFERENCES "role" DEFAULT 10, creation_time TIMESTAMPTZ NOT NULL DEFAULT NOW() ); diff --git a/migrations/20241213232937_contact.up.sql b/migrations/20241213232937_contact.up.sql index cca60e8..cfc4961 100644 --- a/migrations/20241213232937_contact.up.sql +++ b/migrations/20241213232937_contact.up.sql @@ -2,4 +2,6 @@ CREATE TABLE IF NOT EXISTS "contact"( id BIGSERIAL PRIMARY KEY NOT NULL UNIQUE, name VARCHAR(32) NOT NULL UNIQUE -); \ No newline at end of file +); + +INSERT INTO "contact"(id, name) VALUES (0, 'Email') ON CONFLICT(id) DO UPDATE SET "name" = 'Email'; diff --git a/migrations/20241215002127_user_contact.up.sql b/migrations/20241215002127_user_contact.up.sql index 67440c1..f0f7f52 100644 --- a/migrations/20241215002127_user_contact.up.sql +++ b/migrations/20241215002127_user_contact.up.sql @@ -2,5 +2,6 @@ CREATE TABLE IF NOT EXISTS "user_contact"( user_id BIGSERIAL NOT NULL REFERENCES "user"(user_id), contact_id BIGSERIAL NOT NULL REFERENCES "contact"(id), + contact_value VARCHAR(256) NOT NULL, PRIMARY KEY (user_id, contact_id) ); diff --git a/src/database/user.rs b/src/database/user.rs index 0b95b9d..fb03f3d 100644 --- a/src/database/user.rs +++ b/src/database/user.rs @@ -13,15 +13,14 @@ pub async fn create( sqlx::query_as!( User, r#" - INSERT INTO "user"(name, surname, gender, birth_date, role_id) - VALUES ($1, $2, $3, $4, $5) + INSERT INTO "user"(name, surname, gender, birth_date) + VALUES ($1, $2, $3, $4) RETURNING * "#, name, surname, gender, birth_date, - 2 ) .fetch_one(&*DATABASE_CONNECTIONS) .await diff --git a/src/database/user_contact.rs b/src/database/user_contact.rs index e6d908f..4b09ddd 100644 --- a/src/database/user_contact.rs +++ b/src/database/user_contact.rs @@ -2,16 +2,21 @@ use crate::feature::user_contact::UserContact; use super::DATABASE_CONNECTIONS; -pub async fn create(user_id: &i64, contact_id: &i64) -> Result { +pub async fn create( + user_id: &i64, + contact_id: &i64, + contact_value: &String, +) -> Result { sqlx::query_as!( UserContact, r#" - INSERT INTO "user_contact"(user_id, contact_id) - VALUES ($1, $2) + INSERT INTO "user_contact"(user_id, contact_id, contact_value) + VALUES ($1, $2, $3) RETURNING * "#, user_id, contact_id, + contact_value, ) .fetch_one(&*DATABASE_CONNECTIONS) .await @@ -24,21 +29,25 @@ pub async fn read(user_id: &i64, contact_id: &i64) -> Result Result { +pub async fn update( + user_id: &i64, + contact_id: &i64, + contact_value: &String, +) -> Result { sqlx::query_as!( UserContact, r#" - UPDATE "user_contact" SET "contact_id" = $2 WHERE "user_id" = $1 - RETURNING * + UPDATE "user_contact" SET "contact_value" = $3 WHERE "user_id" = $1 AND "contact_id" = $2 RETURNING * "#, user_id, contact_id, + contact_value, ) .fetch_one(&*DATABASE_CONNECTIONS) .await diff --git a/src/feature/login.rs b/src/feature/login.rs index 069c930..9781a84 100644 --- a/src/feature/login.rs +++ b/src/feature/login.rs @@ -14,6 +14,11 @@ use super::user::User; static TOKEN_META: LazyLock = LazyLock::new(TokenMeta::init); +#[derive(Debug, Serialize, Deserialize)] +pub struct CustomClaim { + pub user_id: i64, +} + pub struct TokenMeta { token_key: HS256Key, token_verification_options: Option, @@ -33,9 +38,9 @@ impl TokenMeta { async fn create_token(user_id: &i64) -> Option { let key = &TOKEN_META.token_key; - + let custom_claim = CustomClaim { user_id: *user_id }; let claims = Claims::with_custom_claims( - *user_id, + custom_claim, jwt_simple::prelude::Duration::from_mins( SERVER_CONFIG.login_token_expiration_time_limit as u64, ), @@ -48,11 +53,10 @@ impl TokenMeta { } } - pub async fn verify_token(token: &String) -> Result, jwt_simple::Error> { - let token_meta = &TOKEN_META; - token_meta + pub async fn verify_token(token: &String) -> Result, jwt_simple::Error> { + TOKEN_META .token_key - .verify_token::(token, token_meta.token_verification_options.clone()) + .verify_token::(token, TOKEN_META.token_verification_options.clone()) } } diff --git a/src/feature/user_contact.rs b/src/feature/user_contact.rs index 13dcdc2..1b00f2a 100644 --- a/src/feature/user_contact.rs +++ b/src/feature/user_contact.rs @@ -6,19 +6,28 @@ use crate::database::user_contact; pub struct UserContact { pub user_id: i64, pub contact_id: i64, + pub contact_value: String, } impl UserContact { - pub async fn create(user_id: &i64, contact_id: &i64) -> Result { - user_contact::create(user_id, contact_id).await + pub async fn create( + user_id: &i64, + contact_id: &i64, + contact_value: &String, + ) -> Result { + user_contact::create(user_id, contact_id, contact_value).await } pub async fn read(user_id: &i64, contact_id: &i64) -> Result { user_contact::read(user_id, contact_id).await } - pub async fn update(user_id: &i64, contact_id: &i64) -> Result { - user_contact::update(user_id, contact_id).await + pub async fn update( + user_id: &i64, + contact_id: &i64, + contact_value: &String, + ) -> Result { + user_contact::update(user_id, contact_id, contact_value).await } pub async fn delete(user_id: &i64, contact_id: &i64) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 296bdd0..72f208a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,11 @@ impl Default for ServerConfig { fn default() -> Self { let (header, mut server_configs) = naive_toml_parser(SERVER_CONFIG_FILE_LOCATION); let value_or_max = |value: String| value.parse().map_or(usize::MAX, |value| value); + let value_or_semaphore_max = |value: String| { + value + .parse() + .map_or(tokio::sync::Semaphore::MAX_PERMITS, |value| value) + }; if header == "[server_config]" { Self { @@ -66,7 +71,7 @@ impl Default for ServerConfig { server_configs.pop_front().unwrap(), ), login_token_refresh_time_limit: value_or_max(server_configs.pop_front().unwrap()), - concurrency_limit: value_or_max(server_configs.pop_front().unwrap()), + concurrency_limit: value_or_semaphore_max(server_configs.pop_front().unwrap()), } } else { panic!("Server Config File Must Include [server_config] at the First Line") diff --git a/src/mail.rs b/src/mail.rs index 69f9980..1887180 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -66,7 +66,7 @@ impl MailTemplate { MailTemplate::OneTimePassword(mail_fields) => { let mut mail_template_from_file = naive_toml_parser(ONE_TIME_PASSWORD_MAIL_TEMPLATE_FILE_LOCATION); - if mail_template_from_file.0 == "one_time_password" { + if mail_template_from_file.0 == "[one_time_password]" { let subject = match mail_template_from_file.1.pop_front() { Some(subject) => subject, None => return Err(ForumMailError::TemplateLackOfParameter), @@ -106,7 +106,7 @@ async fn send_mail( ) .to(format!("<{}>", receiver).parse().unwrap()) .subject(subject) - .header(ContentType::TEXT_PLAIN) + .header(ContentType::TEXT_HTML) .body(body.to_owned()) .unwrap(); diff --git a/src/main.rs b/src/main.rs index d093ffb..ae033f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,11 @@ use rust_forum::server::start_server; +use tracing::Level; #[tokio::main] async fn main() { println!("Hello, world!"); - + tracing_subscriber::fmt() + .with_max_level(Level::TRACE) + .init(); start_server().await; } diff --git a/src/routing.rs b/src/routing.rs index c0fb739..8065596 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -12,13 +12,14 @@ pub mod user_contact; use axum::{http::StatusCode, response::IntoResponse, routing::get, Router}; use tower::limit::ConcurrencyLimitLayer; -use tower_http::cors::CorsLayer; +use tower_http::{cors::CorsLayer, trace::TraceLayer}; use crate::database; pub async fn route(concurrency_limit: &usize) -> Router { Router::new() .route("/", get(alive)) + .nest("/logins", login::route()) .nest("/roles", role::route()) .nest("/users", user::route()) .nest("/posts", post::route()) @@ -30,6 +31,7 @@ pub async fn route(concurrency_limit: &usize) -> Router { .nest("/user_contacts", user_contact::route()) .layer(CorsLayer::permissive()) .layer(ConcurrencyLimitLayer::new(*concurrency_limit)) + .layer(TraceLayer::new_for_http()) } pub async fn alive() -> impl IntoResponse { diff --git a/src/routing/comment.rs b/src/routing/comment.rs index 5bbb1b9..9ee9649 100644 --- a/src/routing/comment.rs +++ b/src/routing/comment.rs @@ -26,10 +26,10 @@ struct UpdateComment { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/:creation_time", get(read)) + .route("/{creation_time}", get(read)) .route("/", patch(update)) - .route("/:creation_time", delete(delete_)) - .route("/posts/:post_creation_time", get(read_all_for_post)) + .route("/{creation_time}", delete(delete_)) + .route("/posts/{post_creation_time}", get(read_all_for_post)) } async fn create(Json(create_comment): Json) -> impl IntoResponse { diff --git a/src/routing/comment_interaction.rs b/src/routing/comment_interaction.rs index b17eaf8..8fcbe3a 100644 --- a/src/routing/comment_interaction.rs +++ b/src/routing/comment_interaction.rs @@ -28,11 +28,11 @@ struct UpdateCommentInteraction { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/:interaction_time", get(read)) + .route("/{interaction_time}", get(read)) .route("/", patch(update)) - .route("/:interaction_time", delete(delete_)) + .route("/{interaction_time}", delete(delete_)) .route( - "/comments/:comment_creation_time", + "/comments/{comment_creation_time}", get(read_all_for_comment), ) } diff --git a/src/routing/contact.rs b/src/routing/contact.rs index d4d1f27..32b7fbe 100644 --- a/src/routing/contact.rs +++ b/src/routing/contact.rs @@ -23,9 +23,9 @@ struct UpdateContact { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/:id", get(read)) + .route("/{id}", get(read)) .route("/", patch(update)) - .route("/:id", delete(delete_)) + .route("/{id}", delete(delete_)) .route("/", get(read_all)) } diff --git a/src/routing/interaction.rs b/src/routing/interaction.rs index e098688..63e6b60 100644 --- a/src/routing/interaction.rs +++ b/src/routing/interaction.rs @@ -23,9 +23,9 @@ struct UpdateInteraction { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/:id", get(read)) + .route("/{id}", get(read)) .route("/", patch(update)) - .route("/:id", delete(delete_)) + .route("/{id}", delete(delete_)) .route("/", get(read_all)) } diff --git a/src/routing/login.rs b/src/routing/login.rs index c6d8aa3..6fd5e90 100644 --- a/src/routing/login.rs +++ b/src/routing/login.rs @@ -7,7 +7,14 @@ use axum::{ }; use serde::{Deserialize, Serialize}; -use crate::feature::{auth::OneTimePassword, login::Login}; +use crate::feature::{auth::OneTimePassword, login::Login, user::User, user_contact::UserContact}; + +const CONTACT_EMAIL_DEFAULT_ID: i64 = 0; + +#[derive(Debug, Serialize, Deserialize)] +struct CreateOneTimePassword { + pub user_id: i64, +} #[derive(Debug, Serialize, Deserialize)] struct CreateLogin { @@ -22,15 +29,34 @@ struct UpdateLogin { pub fn route() -> Router { Router::new() + .route("/one_time_password", post(create_one_time_password)) .route("/", post(create)) - .route("/users/:user_id/token/:token", get(read)) + .route("/users/{user_id}/tokens/{token}", get(read)) .route("/", patch(update)) - .route("/users/:user_id/token/:token", delete(delete_)) - .route("/users/:user_id", get(read_all_for_user)) - .route("/users/:user_id", delete(delete_all_for_user)) - .route("/count/users/:user_id", get(count_all_for_user)) + .route("/users/{user_id}/tokens/{token}", delete(delete_)) + .route("/users/{user_id}", get(read_all_for_user)) + .route("/users/{user_id}", delete(delete_all_for_user)) + .route("/count/users/{user_id}", get(count_all_for_user)) +} +async fn create_one_time_password( + Json(create_one_time_password): Json, +) -> impl IntoResponse { + //todo get user from middleware or something + let user = User::read(&create_one_time_password.user_id).await.unwrap(); + match UserContact::read(&user.user_id, &CONTACT_EMAIL_DEFAULT_ID).await { + Ok(user_email) => match OneTimePassword::new(&user, &user_email.contact_value).await { + Ok(_) => (StatusCode::CREATED, Json(serde_json::json!(""))), + Err(err_val) => ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!(err_val.to_string())), + ), + }, + Err(err_val) => ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!(err_val.to_string())), + ), + } } - async fn create(Json(create_login): Json) -> impl IntoResponse { match OneTimePassword::verify(&create_login.one_time_password).await { true => match Login::create(&create_login.one_time_password.user_id).await { @@ -59,8 +85,8 @@ async fn read(Path((user_id, token)): Path<(i64, String)>) -> impl IntoResponse } } -async fn update(Json(update_role): Json) -> impl IntoResponse { - match Login::update(&update_role.user_id, &update_role.token).await { +async fn update(Json(update_login): Json) -> impl IntoResponse { + match Login::update(&update_login.user_id, &update_login.token).await { Ok(login) => (StatusCode::ACCEPTED, Json(serde_json::json!(login))), Err(err_val) => ( StatusCode::BAD_REQUEST, diff --git a/src/routing/middleware.rs b/src/routing/middleware.rs index 3bf9546..d55a5fa 100644 --- a/src/routing/middleware.rs +++ b/src/routing/middleware.rs @@ -33,14 +33,17 @@ async fn user_extraction(request: Request) -> Option { if let Some(authorization_header) = request.headers().get(http::header::AUTHORIZATION) { if let Ok(authorization_header) = authorization_header.to_str() { if let Some((bearer, authorization_header)) = authorization_header.split_once(' ') { - if bearer == "bearer" { - if let Ok(claims) = - TokenMeta::verify_token(&authorization_header.to_string()).await - { - return Some(UserAndRequest { - user: User::read(&claims.custom).await.ok()?, - request, - }); + if bearer.to_lowercase() == "bearer" { + match TokenMeta::verify_token(&authorization_header.to_string()).await { + Ok(claims) => { + return Some(UserAndRequest { + user: User::read(&claims.custom.user_id).await.ok()?, + request, + }); + } + Err(err_val) => { + eprintln!("Verify Token | {}", err_val); + } } } } diff --git a/src/routing/post.rs b/src/routing/post.rs index 486df47..85e15e9 100644 --- a/src/routing/post.rs +++ b/src/routing/post.rs @@ -26,9 +26,9 @@ struct UpdatePost { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/:creation_time", get(read)) + .route("/{creation_time}", get(read)) .route("/", patch(update)) - .route("/:creation_time", delete(delete_)) + .route("/{creation_time}", delete(delete_)) .route("/", get(read_all)) } diff --git a/src/routing/post_interaction.rs b/src/routing/post_interaction.rs index 5c7c581..a81d3ea 100644 --- a/src/routing/post_interaction.rs +++ b/src/routing/post_interaction.rs @@ -28,10 +28,10 @@ struct UpdatePostInteraction { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/:interaction_time", get(read)) + .route("/{interaction_time}", get(read)) .route("/", patch(update)) - .route("/:interaction_time", delete(delete_)) - .route("/posts/:post_creation_time", get(read_all_for_post)) + .route("/{interaction_time}", delete(delete_)) + .route("/posts/{post_creation_time}", get(read_all_for_post)) } async fn create(Json(create_post_interaction): Json) -> impl IntoResponse { diff --git a/src/routing/role.rs b/src/routing/role.rs index 840d158..6f8bf1f 100644 --- a/src/routing/role.rs +++ b/src/routing/role.rs @@ -9,8 +9,6 @@ use serde::{Deserialize, Serialize}; use crate::feature::role::Role; -use super::middleware; - #[derive(Debug, Serialize, Deserialize)] struct CreateRole { name: String, @@ -25,15 +23,10 @@ struct UpdateRole { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route_layer(axum::middleware::from_fn(middleware::pass_builder_or_admin)) - .route("/:id", get(read)) - .route_layer(axum::middleware::from_fn(middleware::pass)) + .route("/{id}", get(read)) .route("/", patch(update)) - .route_layer(axum::middleware::from_fn(middleware::pass_builder_or_admin)) - .route("/:id", delete(delete_)) - .route_layer(axum::middleware::from_fn(middleware::pass_builder_or_admin)) + .route("/{id}", delete(delete_)) .route("/", get(read_all)) - .route_layer(axum::middleware::from_fn(middleware::pass)) } async fn create(Json(create_role): Json) -> impl IntoResponse { diff --git a/src/routing/user.rs b/src/routing/user.rs index 765d25b..f197914 100644 --- a/src/routing/user.rs +++ b/src/routing/user.rs @@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize}; use crate::feature::user::User; +use super::middleware; + #[derive(Debug, Serialize, Deserialize)] struct CreateUser { name: String, @@ -31,33 +33,45 @@ struct UpdateUser { pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/:id", get(read)) - .route("/", patch(update)) - .route("/:id", delete(delete_)) - .route("/", get(read_all)) - .route("/names/:name", get(read_all_for_name)) - .route("/surnames/:surname", get(read_all_for_surname)) - .route("/birth_dates/:birth_date", get(read_all_for_birth_date)) - .route("/roles/:role", get(read_all_for_role)) - .route("/genders/:gender", get(read_all_for_gender)) - .route("/ids", get(read_all_id)) - .route("/ids/names/:name", get(read_all_id_for_name)) - .route("/ids/surnames/:surname", get(read_all_id_for_surname)) .route( - "/ids/birth_dates/:birth_date", + "/{id}", + get(read).route_layer(axum::middleware::from_fn(middleware::pass)), + ) + .route( + "/", + patch(update).route_layer(axum::middleware::from_fn(middleware::pass_higher_or_self)), + ) + .route( + "/{id}", + delete(delete_).route_layer(axum::middleware::from_fn(middleware::pass_higher_or_self)), + ) + .route( + "/", + get(read_all).route_layer(axum::middleware::from_fn(middleware::pass_builder_or_admin)), + ) + .route("/names/{name}", get(read_all_for_name)) + .route("/surnames/{surname}", get(read_all_for_surname)) + .route("/birth_dates/{birth_date}", get(read_all_for_birth_date)) + .route("/roles/{role}", get(read_all_for_role)) + .route("/genders/{gender}", get(read_all_for_gender)) + .route("/ids", get(read_all_id)) + .route("/ids/names/{name}", get(read_all_id_for_name)) + .route("/ids/surnames/{surname}", get(read_all_id_for_surname)) + .route( + "/ids/birth_dates/{birth_date}", get(read_all_id_for_birth_date), ) - .route("/ids/roles/:role", get(read_all_id_for_role)) - .route("/ids/genders/:gender", get(read_all_id_for_gender)) + .route("/ids/roles/{role}", get(read_all_id_for_role)) + .route("/ids/genders/{gender}", get(read_all_id_for_gender)) .route("/count", get(count_all)) - .route("/count/names/:name", get(count_all_for_name)) - .route("/count/surnames/:surname", get(count_all_for_surname)) + .route("/count/names/{name}", get(count_all_for_name)) + .route("/count/surnames/{surname}", get(count_all_for_surname)) .route( - "/count/birth_dates/:birth_date", + "/count/birth_dates/{birth_date}", get(count_all_for_birth_date), ) - .route("/count/roles/:role", get(count_all_for_role)) - .route("/count/genders/:gender", get(count_all_for_gender)) + .route("/count/roles/{role}", get(count_all_for_role)) + .route("/count/genders/{gender}", get(count_all_for_gender)) } async fn create(Json(create_user): Json) -> impl IntoResponse { diff --git a/src/routing/user_contact.rs b/src/routing/user_contact.rs index 4478c27..046aef6 100644 --- a/src/routing/user_contact.rs +++ b/src/routing/user_contact.rs @@ -13,28 +13,31 @@ use crate::feature::user_contact::UserContact; struct CreateUserContact { pub user_id: i64, pub contact_id: i64, + pub contact_value: String, } #[derive(Debug, Serialize, Deserialize)] struct UpdateUserContact { pub user_id: i64, pub contact_id: i64, + pub contact_value: String, } pub fn route() -> Router { Router::new() .route("/", post(create)) - .route("/roles/:user_id/contacts/:contact_id", get(read)) + .route("/roles/{user_id}/contacts/{contact_id}", get(read)) .route("/", patch(update)) - .route("/roles/:user_id/contacts/:contact_id", delete(delete_)) - .route("/users/:user_id", get(read_all_for_user)) - .route("/users/:user_id", delete(delete_all_for_user)) + .route("/roles/{user_id}/contacts/{contact_id}", delete(delete_)) + .route("/users/{user_id}", get(read_all_for_user)) + .route("/users/{user_id}", delete(delete_all_for_user)) } async fn create(Json(create_user_contact): Json) -> impl IntoResponse { match UserContact::create( &create_user_contact.user_id, &create_user_contact.contact_id, + &create_user_contact.contact_value, ) .await { @@ -56,8 +59,14 @@ async fn read(Path((user_id, contact_id)): Path<(i64, i64)>) -> impl IntoRespons } } -async fn update(Json(update_role): Json) -> impl IntoResponse { - match UserContact::update(&update_role.user_id, &update_role.contact_id).await { +async fn update(Json(update_user_contact): Json) -> impl IntoResponse { + match UserContact::update( + &update_user_contact.user_id, + &update_user_contact.contact_id, + &update_user_contact.contact_value, + ) + .await + { Ok(user_contact) => (StatusCode::ACCEPTED, Json(serde_json::json!(user_contact))), Err(err_val) => ( StatusCode::BAD_REQUEST,