diff options
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | migrations/20220903093057_add-users-table.sql | 2 | ||||
| -rw-r--r-- | migrations/20220906164354_add-models-table.sql | 13 | ||||
| -rw-r--r-- | src/main.rs | 3 | ||||
| -rw-r--r-- | src/models/auth.rs | 2 | ||||
| -rw-r--r-- | src/models/mod.rs | 1 | ||||
| -rw-r--r-- | src/models/model.rs | 105 | ||||
| -rw-r--r-- | src/routes/mod.rs | 1 | ||||
| -rw-r--r-- | src/routes/model.rs | 39 |
10 files changed, 167 insertions, 5 deletions
@@ -231,6 +231,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits", + "serde", "time 0.1.44", "wasm-bindgen", "winapi", @@ -1299,6 +1300,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", + "chrono", "crc", "crossbeam-queue", "dirs", @@ -16,7 +16,7 @@ tokio = { version = "1.20", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tower-http = { version = "0.3.4", features = ["trace", "compression-br", "propagate-header", "sensitive-headers"] } -sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres" ] } +sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "chrono" ] } sha256 = "1.0.3" validator = { version = "0.16.0", features = ["derive"] } -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } diff --git a/migrations/20220903093057_add-users-table.sql b/migrations/20220903093057_add-users-table.sql index b55eae6..73c2396 100644 --- a/migrations/20220903093057_add-users-table.sql +++ b/migrations/20220903093057_add-users-table.sql @@ -1,5 +1,5 @@ CREATE TABLE users ( - id SERIAL UNIQUE PRIMARY KEY, + id SERIAL PRIMARY KEY, email VARCHAR(100) UNIQUE NOT NULL, username VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, diff --git a/migrations/20220906164354_add-models-table.sql b/migrations/20220906164354_add-models-table.sql new file mode 100644 index 0000000..84425ba --- /dev/null +++ b/migrations/20220906164354_add-models-table.sql @@ -0,0 +1,13 @@ +CREATE TABLE models ( + id SERIAL PRIMARY KEY, + name VARCHAR NOT NULL, + description TEXT, + duration INTEGER NOT NULL, + height INTEGER NOT NULL, + weight INTEGER NOT NULL, + printer VARCHAR, + material VARCHAR, + author_id INTEGER REFERENCES users(id) NOT NULL, + created TIMESTAMP NOT NULL, + updated TIMESTAMP NOT NULL +); diff --git a/src/main.rs b/src/main.rs index 4a211fd..1c9d63a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,8 @@ async fn create_app() -> Router { let api_routes = Router::new() .nest("/users", routes::user::create_route()) - .nest("/auth", routes::auth::create_route()); + .nest("/auth", routes::auth::create_route()) + .nest("/models", routes::model::create_route()); Router::new() // Map all routes to `/v1/*` namespace diff --git a/src/models/auth.rs b/src/models/auth.rs index 7f9ae00..4453bc7 100644 --- a/src/models/auth.rs +++ b/src/models/auth.rs @@ -18,7 +18,7 @@ struct Keys { #[derive(Serialize, Deserialize)] pub struct Claims { /// ID from the user model - user_id: i32, + pub user_id: i32, /// Expiration timestamp exp: usize, } diff --git a/src/models/mod.rs b/src/models/mod.rs index f9bae3d..7e61662 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,2 +1,3 @@ pub mod auth; +pub mod model; pub mod user; diff --git a/src/models/model.rs b/src/models/model.rs new file mode 100644 index 0000000..1fe9b61 --- /dev/null +++ b/src/models/model.rs @@ -0,0 +1,105 @@ +use crate::db::get_client; +use crate::errors::AppError; + +use chrono::{Local, NaiveDateTime}; +use serde::{Deserialize, Serialize}; +use validator::Validate; + +/// Model for models. +#[derive(Deserialize, Serialize, Validate)] +pub struct Model { + id: i32, + #[validate(length(min = 2, message = "Can not be empty"))] + name: String, + description: Option<String>, + duration: i32, + height: i32, + weight: i32, + printer: Option<String>, + material: Option<String>, + author_id: i32, + created: NaiveDateTime, + updated: NaiveDateTime, +} + +/// Payload used for model creation +#[derive(Deserialize)] +pub struct ModelCreate { + pub name: String, + pub description: Option<String>, + pub duration: i32, + pub height: i32, + pub weight: i32, + pub printer: Option<String>, + pub material: Option<String>, +} + +impl Model { + pub fn new( + name: String, + description: Option<String>, + duration: i32, + height: i32, + weight: i32, + printer: Option<String>, + material: Option<String>, + author_id: i32, + ) -> Self { + let now = Local::now().naive_utc(); + Self { + id: 0, + name, + description, + duration, + height, + weight, + printer, + material, + author_id, + created: now, + updated: now, + } + } + + /// Create a new model + pub async fn create(model: Model) -> Result<Model, AppError> { + let pool = unsafe { get_client() }; + + model + .validate() + .map_err(|error| AppError::BadRequest(error.to_string()))?; + + let rec = sqlx::query_as!( + Model, + r#" + INSERT INTO models (name, description, duration, height, weight, printer, material, author_id, created, updated) + VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + RETURNING * + "#, + model.name, + model.description, + model.duration, + model.height, + model.weight, + model.printer, + model.material, + model.author_id, + model.created, + model.updated, + ) + .fetch_one(pool) + .await?; + + Ok(rec) + } + + /// List all models + pub async fn list() -> Result<Vec<Model>, AppError> { + let pool = unsafe { get_client() }; + let rows = sqlx::query_as!(Model, r#"SELECT * FROM models"#) + .fetch_all(pool) + .await?; + + Ok(rows) + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index f9bae3d..7e61662 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,2 +1,3 @@ pub mod auth; +pub mod model; pub mod user; diff --git a/src/routes/model.rs b/src/routes/model.rs new file mode 100644 index 0000000..b1d4cc2 --- /dev/null +++ b/src/routes/model.rs @@ -0,0 +1,39 @@ +use crate::errors::AppError; +use crate::models::{ + auth::Claims, + model::{Model, ModelCreate}, +}; +use axum::{routing::get, Json, Router}; + +/// Create routes for `/v1/models/` namespace +pub fn create_route() -> Router { + Router::new().route("/", get(list_models).post(create_model)) +} + +/// List models. +async fn list_models() -> Result<Json<Vec<Model>>, AppError> { + let models = Model::list().await?; + + Ok(Json(models)) +} + +/// Create a model. Checks Authorization token +async fn create_model( + Json(payload): Json<ModelCreate>, + claims: Claims, +) -> Result<Json<Model>, AppError> { + let model = Model::new( + payload.name, + payload.description, + payload.duration, + payload.height, + payload.weight, + payload.printer, + payload.material, + claims.user_id, + ); + + let model_new = Model::create(model).await?; + + Ok(Json(model_new)) +} |
