From dd29d83933734aac2cece43d0d8509c550942da5 Mon Sep 17 00:00:00 2001 From: Jijo Bose Date: Sun, 7 Jan 2024 23:49:45 +0530 Subject: [PATCH] Feature: Implement Item API (#20) --- Cargo.lock | 5 ++ Cargo.toml | 3 +- README.md | 4 ++ .../2024-01-07-051817_create_items/down.sql | 2 + .../2024-01-07-051817_create_items/up.sql | 11 ++++ src/app/actions/item.rs | 53 +++++++++++++++++++ src/app/actions/mod.rs | 1 + src/app/api/item.rs | 39 ++++++++++++++ src/app/api/mod.rs | 1 + src/app/mod.rs | 1 + src/app/models/item.rs | 49 +++++++++++++++++ src/main.rs | 3 ++ src/schema.rs | 15 ++++++ 13 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 migrations/2024-01-07-051817_create_items/down.sql create mode 100644 migrations/2024-01-07-051817_create_items/up.sql create mode 100644 src/app/actions/item.rs create mode 100644 src/app/api/item.rs create mode 100644 src/app/models/item.rs diff --git a/Cargo.lock b/Cargo.lock index 0426b1a..fc7b848 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,8 +460,10 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.48.5", ] @@ -628,10 +630,12 @@ checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8" dependencies = [ "bitflags 2.4.1", "byteorder", + "chrono", "diesel_derives", "itoa", "pq-sys", "r2d2", + "uuid", ] [[package]] @@ -940,6 +944,7 @@ name = "home-inventory-api" version = "0.1.0" dependencies = [ "actix-web", + "chrono", "diesel", "diesel_migrations", "dotenv", diff --git a/Cargo.toml b/Cargo.toml index 5600328..f2b2f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" [dependencies] actix-web = "4.4.0" -diesel = { version = "2.1.4", features = ["postgres", "r2d2"] } +chrono = "0.4.31" +diesel = { version = "2.1.4", features = ["postgres", "r2d2", "chrono", "uuid"] } diesel_migrations = "2.1.0" dotenvy = "0.15.7" dotenv = "0.15.0" diff --git a/README.md b/README.md index ebbe390..d2a9b9d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ Run Migration ``diesel migration run`` +Undo Migration + +``diesel migration redo`` + Run Cargo ``cargo run`` diff --git a/migrations/2024-01-07-051817_create_items/down.sql b/migrations/2024-01-07-051817_create_items/down.sql new file mode 100644 index 0000000..8e1121a --- /dev/null +++ b/migrations/2024-01-07-051817_create_items/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP table items diff --git a/migrations/2024-01-07-051817_create_items/up.sql b/migrations/2024-01-07-051817_create_items/up.sql new file mode 100644 index 0000000..d8f8c15 --- /dev/null +++ b/migrations/2024-01-07-051817_create_items/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here +CREATE TABLE items ( + id VARCHAR NOT NULL PRIMARY KEY, + room_id VARCHAR NOT NULL REFERENCES rooms(id) on DELETE CASCADE, + name VARCHAR NOT NULL, + description VARCHAR, + category VARCHAR NOT NULL, + purchase_date Timestamp NOT NULL, + expiry_date Timestamp, + value DOUBLE PRECISION NOT NULL +) diff --git a/src/app/actions/item.rs b/src/app/actions/item.rs new file mode 100644 index 0000000..08ee5df --- /dev/null +++ b/src/app/actions/item.rs @@ -0,0 +1,53 @@ +use actix_web::web::Json; +use diesel::prelude::*; +use uuid::Uuid; + +use crate::app::models::item; + +type DbError = Box; + +pub fn list_items(conn: &mut PgConnection, uid: Uuid) -> Result, DbError> { + use crate::schema::items::dsl::*; + + let item_list = items.filter(room_id.eq(uid.to_string())).load(conn)?; + + Ok(item_list) +} + +pub fn insert_new_item( + conn: &mut PgConnection, + form: &Json, +) -> Result { + use crate::schema::items::dsl::*; + + match form.validate() { + Ok(_) => { + let new_item = item::Item { + id: Uuid::new_v4().to_string(), + room_id: form.room_id.to_owned(), + name: form.name.to_owned(), + description: form.description.to_owned(), + category: form.category.to_owned(), + purchase_date: form.purchase_date.to_owned(), + expiry_date: form.expiry_date.to_owned(), + value: form.value.to_owned(), + }; + + diesel::insert_into(items).values(&new_item).execute(conn)?; + + Ok(new_item) + } + Err(error) => Err(DbError::from(error)), + } +} + +pub fn delete_item(conn: &mut PgConnection, uid: Uuid) -> Result { + use crate::schema::items::dsl::*; + + let result = diesel::delete(items.filter(id.eq(uid.to_string()))).execute(conn); + + match result { + Ok(_) => Ok("Success".to_string()), + Err(e) => Err(DbError::from(e)), + } +} diff --git a/src/app/actions/mod.rs b/src/app/actions/mod.rs index 89e3268..4b8f587 100644 --- a/src/app/actions/mod.rs +++ b/src/app/actions/mod.rs @@ -1,2 +1,3 @@ pub mod home; pub mod room; +pub mod item; diff --git a/src/app/api/item.rs b/src/app/api/item.rs new file mode 100644 index 0000000..90426ff --- /dev/null +++ b/src/app/api/item.rs @@ -0,0 +1,39 @@ +use actix_web::{error, get, post, web, HttpResponse, Responder, Result}; +use diesel::{r2d2, PgConnection}; + +use crate::app::actions; +use crate::app::models; + +type DbPool = r2d2::Pool>; + +#[post("/item")] +async fn add_item( + pool: web::Data, + form: web::Json, +) -> Result { + let response = web::block(move || { + let mut conn = pool.get()?; + actions::item::insert_new_item(&mut conn, &form) + }) + .await? + .map_err(error::ErrorBadRequest)?; + + Ok(HttpResponse::Created().json(response)) +} + +#[get("/item")] +async fn get_items( + pool: web::Data, + query: web::Query, +) -> Result { + let room_uid = query.room_id; + + let response = web::block(move || { + let mut conn = pool.get()?; + actions::item::list_items(&mut conn, room_uid) + }) + .await? + .map_err(error::ErrorBadRequest)?; + + Ok(HttpResponse::Ok().json(response)) +} diff --git a/src/app/api/mod.rs b/src/app/api/mod.rs index 89e3268..4b8f587 100644 --- a/src/app/api/mod.rs +++ b/src/app/api/mod.rs @@ -1,2 +1,3 @@ pub mod home; pub mod room; +pub mod item; diff --git a/src/app/mod.rs b/src/app/mod.rs index c2469df..e40689e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -5,4 +5,5 @@ pub mod db; pub mod models { pub mod home; pub mod room; + pub mod item; } diff --git a/src/app/models/item.rs b/src/app/models/item.rs new file mode 100644 index 0000000..f3acea2 --- /dev/null +++ b/src/app/models/item.rs @@ -0,0 +1,49 @@ +use diesel::prelude::*; +use diesel::{prelude::Insertable, Queryable}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::schema::items; +use crate::app::models::room::Room; + +/// Item details. +#[derive(Queryable, Serialize, Selectable, Identifiable, Associations, Debug, PartialEq, Insertable)] +#[diesel(belongs_to(Room))] +#[diesel(table_name = items)] +pub struct Item { + pub id: String, + pub name: String, + pub room_id: String, + pub description: Option, + pub category: String, + pub purchase_date: String, + pub expiry_date: Option, + pub value: f64 +} + +/// New Item. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NewItem { + pub name: String, + pub room_id: String, + pub description: Option, + pub category: String, + pub purchase_date: String, + pub expiry_date: Option, + pub value: f64 +} + +#[derive(Deserialize)] +pub struct ItemQuery { + pub room_id: Uuid, +} + +// validations +impl NewItem { + pub fn validate(&self) -> Result<(), String> { + if self.name.trim().is_empty() { + return Err("Name is empty".to_string()); + } + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index a9ed99b..b4b3d4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use app::api::home::{ }; use app::api::room::{add_room, get_room}; +use app::api::item::{add_item, get_items}; use app::db::{ initialize_db_pool, @@ -41,6 +42,8 @@ async fn main() -> std::io::Result<()> { .service(delete_home) .service(add_room) .service(get_room) + .service(get_items) + .service(add_item) }) .bind(("127.0.0.1", 8080))? .run() diff --git a/src/schema.rs b/src/schema.rs index 3095627..038dcf1 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -8,6 +8,19 @@ diesel::table! { } } +diesel::table! { + items (id) { + id -> Varchar, + room_id -> Varchar, + name -> Varchar, + description -> Nullable, + category -> Varchar, + purchase_date -> Varchar, + expiry_date -> Nullable, + value -> Float8, + } +} + diesel::table! { rooms (id) { id -> Varchar, @@ -16,9 +29,11 @@ diesel::table! { } } +diesel::joinable!(items -> rooms (room_id)); diesel::joinable!(rooms -> homes (home_id)); diesel::allow_tables_to_appear_in_same_query!( homes, + items, rooms, );