From: Greg Burri Date: Thu, 14 Nov 2024 00:59:40 +0000 (+0100) Subject: Begining of a RON API to edit profile X-Git-Url: http://git.euphorik.ch/index.cgi?a=commitdiff_plain;h=405aa68526eee98b71185d86436a9ba6aa35455d;p=recipes.git Begining of a RON API to edit profile --- diff --git a/Cargo.lock b/Cargo.lock index 0a841ab..fc305e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -381,9 +381,9 @@ checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.1.37" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" dependencies = [ "shlex", ] @@ -420,9 +420,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -454,9 +454,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" @@ -542,9 +542,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -1640,9 +1640,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] @@ -1712,7 +1712,7 @@ dependencies = [ "ron", "serde", "sqlx", - "thiserror 2.0.1", + "thiserror 2.0.3", "tokio", "tower", "tower-http", @@ -1737,7 +1737,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -1752,9 +1752,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1828,9 +1828,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags", "errno", @@ -1900,18 +1900,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -2121,7 +2121,7 @@ dependencies = [ "sha2", "smallvec", "sqlformat", - "thiserror 1.0.68", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -2205,7 +2205,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.68", + "thiserror 1.0.69", "tracing", "whoami", ] @@ -2244,7 +2244,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.68", + "thiserror 1.0.69", "tracing", "whoami", ] @@ -2364,27 +2364,27 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.68", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c1e40dd48a282ae8edc36c732cbc219144b87fb6a4c7316d611c6b1f06ec0c" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl 2.0.1", + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -2393,9 +2393,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874aa7e446f1da8d9c3a5c95b1c5eb41d800045252121dc7f8e0ba370cee55f5" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", diff --git a/backend/src/data/db.rs b/backend/src/data/db.rs index 3e6b291..8fccd78 100644 --- a/backend/src/data/db.rs +++ b/backend/src/data/db.rs @@ -251,6 +251,15 @@ FROM [UserLoginToken] WHERE [token] = $1 .map_err(DBError::from) } + pub async fn set_user_name(&self, user_id: i64, name: &str) -> Result<()> { + sqlx::query("UPDATE [User] SET [name] = $2 WHERE [id] = $1") + .bind(user_id) + .bind(name) + .execute(&self.pool) + .await?; + Ok(()) + } + pub async fn sign_up(&self, email: &str, password: &str) -> Result { self.sign_up_with_given_time(email, password, Utc::now()) .await diff --git a/backend/src/main.rs b/backend/src/main.rs index 03e0977..2394670 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -4,7 +4,7 @@ use axum::{ extract::{ConnectInfo, FromRef, Request, State}, middleware::{self, Next}, response::{Response, Result}, - routing::get, + routing::{get, post, put}, Router, }; use axum_extra::extract::cookie::CookieJar; @@ -22,6 +22,7 @@ mod data; mod email; mod hash; mod model; +mod ron_extractor; mod services; mod utils; @@ -81,7 +82,6 @@ async fn main() { get(services::sign_up_get).post(services::sign_up_post), ) .route("/validation", get(services::sign_up_validation)) - .route("/recipe/view/:id", get(services::view_recipe)) .route( "/signin", get(services::sign_in_get).post(services::sign_in_post), @@ -95,6 +95,9 @@ async fn main() { "/reset_password", get(services::reset_password_get).post(services::reset_password_post), ) + .route("/recipe/view/:id", get(services::view_recipe)) + // RON API. + .route("/user/set_name", put(services::ron_api::set_user_name)) .layer(TraceLayer::new_for_http()) .route_layer(middleware::from_fn_with_state( state.clone(), diff --git a/backend/src/ron_extractor.rs b/backend/src/ron_extractor.rs new file mode 100644 index 0000000..8f52ec6 --- /dev/null +++ b/backend/src/ron_extractor.rs @@ -0,0 +1,102 @@ +use axum::{ + async_trait, + body::Bytes, + extract::{FromRequest, Request}, + http::{header, StatusCode}, + response::{IntoResponse, Response}, +}; +use ron::{ + de::from_bytes, + ser::{to_string_pretty, PrettyConfig}, +}; +use serde::{de::DeserializeOwned, Serialize}; + +const RON_CONTENT_TYPE: &'static str = "application/ron"; + +#[derive(Debug, Serialize, Clone)] +pub struct RonError { + pub error: String, +} + +impl axum::response::IntoResponse for RonError { + fn into_response(self) -> Response { + let ron_as_str = to_string_pretty(&self, PrettyConfig::new()).unwrap(); + ron_as_str.into_response() + } +} + +impl From for Response { + fn from(value: RonError) -> Self { + value.into_response() + } +} + +pub fn ron_error(status: StatusCode, message: &str) -> impl IntoResponse { + ( + status, + [(header::CONTENT_TYPE, RON_CONTENT_TYPE)], + RonError { + error: message.to_string(), + }, + ) +} + +pub fn ron_response(ron: T) -> impl IntoResponse +where + T: Serialize, +{ + let ron_as_str = to_string_pretty(&ron, PrettyConfig::new()).unwrap(); + ([(header::CONTENT_TYPE, RON_CONTENT_TYPE)], ron_as_str) +} + +fn parse_body(body: Bytes) -> Result +where + T: DeserializeOwned, +{ + match from_bytes::(&body) { + Ok(ron) => Ok(ron), + Err(error) => { + return Err(RonError { + error: format!("Ron parsing error: {}", error), + }); + } + } +} + +pub struct ExtractRon(pub T); + +#[async_trait] +impl FromRequest for ExtractRon +where + S: Send + Sync, + T: DeserializeOwned, +{ + type Rejection = Response; // axum::Error::ErrorResponse; + + async fn from_request(req: Request, state: &S) -> Result { + match req.headers().get(header::CONTENT_TYPE) { + Some(content_type) => { + if content_type != RON_CONTENT_TYPE { + return Err(ron_error( + StatusCode::BAD_REQUEST, + &format!("Invalid content type, must be {}", RON_CONTENT_TYPE), + ) + .into_response()); + } + } + None => { + return Err( + ron_error(StatusCode::BAD_REQUEST, "No content type given").into_response() + ) + } + } + + let body = Bytes::from_request(req, state) + .await + .map_err(IntoResponse::into_response)?; + + let ron = parse_body(body)?; + + Ok(Self(ron)) + } +} diff --git a/backend/src/services.rs b/backend/src/services.rs index 243dfe5..eee0117 100644 --- a/backend/src/services.rs +++ b/backend/src/services.rs @@ -4,7 +4,7 @@ use askama::Template; use axum::{ body::Body, debug_handler, - extract::{connect_info, ConnectInfo, Extension, Host, Path, Query, Request, State}, + extract::{ConnectInfo, Extension, Host, Path, Query, Request, State}, http::{HeaderMap, StatusCode}, response::{IntoResponse, Redirect, Response, Result}, Form, @@ -14,14 +14,9 @@ use chrono::Duration; use serde::Deserialize; use tracing::{event, Level}; -use crate::{ - config::Config, - consts::{self, VALIDATION_PASSWORD_RESET_TOKEN_DURATION}, - data::db, - email, model, utils, AppState, -}; +use crate::{config::Config, consts, data::db, email, model, utils, AppState}; -pub mod webapi; +pub mod ron_api; impl axum::response::IntoResponse for db::DBError { fn into_response(self) -> Response { @@ -623,8 +618,6 @@ pub async fn ask_reset_password_post( struct ResetPasswordTemplate { user: Option, reset_token: String, - password_1: String, - password_2: String, message: String, message_password: String, } @@ -638,8 +631,6 @@ pub async fn reset_password_get( Ok(ResetPasswordTemplate { user, reset_token: reset_token.to_string(), - password_1: String::new(), - password_2: String::new(), message: String::new(), message_password: String::new(), } @@ -681,8 +672,6 @@ pub async fn reset_password_post( Ok(ResetPasswordTemplate { user, reset_token: form_data.reset_token.clone(), - password_1: String::new(), - password_2: String::new(), message_password: match error { ResetPasswordError::PasswordsNotEqual => "Passwords don't match", ResetPasswordError::InvalidPassword => { @@ -731,6 +720,16 @@ pub async fn reset_password_post( } } +///// EDIT PROFILE ///// + +#[debug_handler] +pub async fn edit_user( + State(connection): State, + Extension(user): Extension>, +) -> Result { + Ok("todo") +} + ///// 404 ///// #[debug_handler] pub async fn not_found() -> Result { diff --git a/backend/src/services/ron_api.rs b/backend/src/services/ron_api.rs new file mode 100644 index 0000000..b92b943 --- /dev/null +++ b/backend/src/services/ron_api.rs @@ -0,0 +1,95 @@ +// use actix_web::{ +// http::{header, header::ContentType, StatusCode}, +// post, put, web, HttpMessage, HttpRequest, HttpResponse, Responder, +// }; +// use log::{debug, error, info, log_enabled, Level}; +// use ron::de::from_bytes; + +// use super::Result; +// use crate::data::db; + +// #[put("/ron-api/recipe/set-title")] +// pub async fn set_recipe_title( +// req: HttpRequest, +// body: web::Bytes, +// connection: web::Data, +// ) -> Result { +// let ron_req: common::ron_api::SetRecipeTitle = from_bytes(&body)?; +// connection +// .set_recipe_title_async(ron_req.recipe_id, &ron_req.title) +// .await?; +// Ok(HttpResponse::Ok().finish()) +// } + +// #[put("/ron-api/recipe/set-description")] +// pub async fn set_recipe_description( +// req: HttpRequest, +// body: web::Bytes, +// connection: web::Data, +// ) -> Result { +// let ron_req: common::ron_api::SetRecipeDescription = from_bytes(&body)?; +// connection +// .set_recipe_description_async(ron_req.recipe_id, &ron_req.description) +// .await?; +// Ok(HttpResponse::Ok().finish()) +// } + +// #[put("/ron-api/recipe/add-image)] +// #[put("/ron-api/recipe/rm-photo")] +// #[put("/ron-api/recipe/add-ingredient")] +// #[put("/ron-api/recipe/rm-ingredient")] +// #[put("/ron-api/recipe/set-ingredients-order")] +// #[put("/ron-api/recipe/add-group")] +// #[put("/ron-api/recipe/rm-group")] +// #[put("/ron-api/recipe/set-groups-order")] +// #[put("/ron-api/recipe/add-step")] +// #[put("/ron-api/recipe/rm-step")] +// #[put("/ron-api/recipe/set-steps-order")] + +use askama_axum::IntoResponse; +use axum::{ + debug_handler, + extract::{Extension, State}, + http::StatusCode, + response::ErrorResponse, + response::Result, +}; +use tracing::{event, Level}; + +use crate::{ + data::db, + model, + ron_extractor::{ron_error, ron_response, ExtractRon}, +}; + +#[debug_handler] +pub async fn set_user_name( + State(connection): State, + Extension(user): Extension>, + ExtractRon(ron): ExtractRon, +) -> Result { + if let Some(user) = user { + connection.set_user_name(user.id, &ron.name).await?; + } else { + return Err(ErrorResponse::from(ron_error( + StatusCode::UNAUTHORIZED, + "Action not authorized", + ))); + } + Ok(StatusCode::OK) +} + +/* Example with RON return value. +#[debug_handler] +pub async fn set_user_name( + State(connection): State, + Extension(user): Extension>, + ExtractRon(ron): ExtractRon, +) -> Result { + + + Ok(ron_response(common::ron_api::SetProfileName { + name: "abc".to_string(), + })) +} +*/ diff --git a/backend/src/services/webapi.rs b/backend/src/services/webapi.rs deleted file mode 100644 index 93836a2..0000000 --- a/backend/src/services/webapi.rs +++ /dev/null @@ -1,47 +0,0 @@ -// use actix_web::{ -// http::{header, header::ContentType, StatusCode}, -// post, put, web, HttpMessage, HttpRequest, HttpResponse, Responder, -// }; -// use log::{debug, error, info, log_enabled, Level}; -// use ron::de::from_bytes; - -// use super::Result; -// use crate::data::db; - -// #[put("/ron-api/recipe/set-title")] -// pub async fn set_recipe_title( -// req: HttpRequest, -// body: web::Bytes, -// connection: web::Data, -// ) -> Result { -// let ron_req: common::ron_api::SetRecipeTitle = from_bytes(&body)?; -// connection -// .set_recipe_title_async(ron_req.recipe_id, &ron_req.title) -// .await?; -// Ok(HttpResponse::Ok().finish()) -// } - -// #[put("/ron-api/recipe/set-description")] -// pub async fn set_recipe_description( -// req: HttpRequest, -// body: web::Bytes, -// connection: web::Data, -// ) -> Result { -// let ron_req: common::ron_api::SetRecipeDescription = from_bytes(&body)?; -// connection -// .set_recipe_description_async(ron_req.recipe_id, &ron_req.description) -// .await?; -// Ok(HttpResponse::Ok().finish()) -// } - -// #[put("/ron-api/recipe/add-image)] -// #[put("/ron-api/recipe/rm-photo")] -// #[put("/ron-api/recipe/add-ingredient")] -// #[put("/ron-api/recipe/rm-ingredient")] -// #[put("/ron-api/recipe/set-ingredients-order")] -// #[put("/ron-api/recipe/add-group")] -// #[put("/ron-api/recipe/rm-group")] -// #[put("/ron-api/recipe/set-groups-order")] -// #[put("/ron-api/recipe/add-step")] -// #[put("/ron-api/recipe/rm-step")] -// #[put("/ron-api/recipe/set-steps-order")] diff --git a/backend/templates/base_with_header.html b/backend/templates/base_with_header.html index 8dc71bd..f758547 100644 --- a/backend/templates/base_with_header.html +++ b/backend/templates/base_with_header.html @@ -7,7 +7,7 @@ {% match user %} {% when Some with (user) %} Create a new recipe - {{ user.email }} / Sign out + {{ user.email }} / Sign out {% when None %} Sign in/Sign up/Lost password diff --git a/backend/templates/edit_recipe.html b/backend/templates/edit_recipe.html deleted file mode 100644 index f999c41..0000000 --- a/backend/templates/edit_recipe.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base_with_list.html" %} - -{% block content %} - - - - - -{% endblock %} \ No newline at end of file diff --git a/backend/templates/profile.html b/backend/templates/profile.html index f999c41..9dc573c 100644 --- a/backend/templates/profile.html +++ b/backend/templates/profile.html @@ -1,22 +1,12 @@ {% extends "base_with_list.html" %} {% block content %} - + - - - diff --git a/backend/templates/recipe_edit.html b/backend/templates/recipe_edit.html new file mode 100644 index 0000000..f999c41 --- /dev/null +++ b/backend/templates/recipe_edit.html @@ -0,0 +1,23 @@ +{% extends "base_with_list.html" %} + +{% block content %} + + + + + +{% endblock %} \ No newline at end of file diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs index 1d50b12..ce7b2d1 100644 --- a/common/src/ron_api.rs +++ b/common/src/ron_api.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +///// RECIPE ///// + #[derive(Serialize, Deserialize, Clone)] pub struct SetRecipeTitle { pub recipe_id: i64, @@ -91,3 +93,10 @@ pub struct AddRecipeStepReply { pub struct RemoveRecipeStep { pub step_id: i64, } + +///// PROFILE ///// + +#[derive(Serialize, Deserialize, Clone)] +pub struct SetProfileName { + pub name: String, +} diff --git a/deploy.nu b/deploy.nu index 3cb718a..684ad0b 100644 --- a/deploy.nu +++ b/deploy.nu @@ -17,6 +17,7 @@ def main [host: string, destination: string, ssh_key: path] { scp ...$args } + cargo test cargo build --target $target --release invoke_ssh [sudo systemctl stop recipes] copy_ssh ./target/($target)/release/recipes $destination