Get Product API working fine
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
.idea/
|
||||||
|
.env
|
||||||
2017
Cargo.lock
generated
Normal file
2017
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "actixweb_diesel"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Pratik Tripathy <mail@pratik.live>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "3.3.0"
|
||||||
|
actix-rt = "1.1.1"
|
||||||
|
listenfd = "0.3.3"
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
log = "0.4.11"
|
||||||
|
env_logger = "0.8.2"
|
||||||
|
serde = "1.0.117"
|
||||||
|
serde_json = "1.0.59"
|
||||||
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
diesel = { version = "1.4.5", features = ["postgres", "chrono", "r2d2"] }
|
||||||
|
diesel_migrations = "1.4.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
r2d2 = "0.8.9"
|
||||||
5
diesel.toml
Normal file
5
diesel.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
||||||
0
migrations/.gitkeep
Normal file
0
migrations/.gitkeep
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||||
|
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
||||||
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Sets up a trigger for the given table to automatically set a column called
|
||||||
|
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||||
|
-- in the modified columns)
|
||||||
|
--
|
||||||
|
-- # Example
|
||||||
|
--
|
||||||
|
-- ```sql
|
||||||
|
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||||
|
--
|
||||||
|
-- SELECT diesel_manage_updated_at('users');
|
||||||
|
-- ```
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||||
|
BEGIN
|
||||||
|
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
IF (
|
||||||
|
NEW IS DISTINCT FROM OLD AND
|
||||||
|
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||||
|
) THEN
|
||||||
|
NEW.updated_at := current_timestamp;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
2
migrations/2020-11-29-201712_create_products/down.sql
Normal file
2
migrations/2020-11-29-201712_create_products/down.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
drop table products;
|
||||||
8
migrations/2020-11-29-201712_create_products/up.sql
Normal file
8
migrations/2020-11-29-201712_create_products/up.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
create table if not exists products
|
||||||
|
(
|
||||||
|
productid integer not null
|
||||||
|
constraint pk_products
|
||||||
|
primary key,
|
||||||
|
productname text not null,
|
||||||
|
created_at timestamp not null default current_timestamp
|
||||||
|
);
|
||||||
2
migrations/2020-11-29-201716_create_orders/down.sql
Normal file
2
migrations/2020-11-29-201716_create_orders/down.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
drop table orders;
|
||||||
8
migrations/2020-11-29-201716_create_orders/up.sql
Normal file
8
migrations/2020-11-29-201716_create_orders/up.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
create table if not exists orders
|
||||||
|
(
|
||||||
|
orderid integer not null
|
||||||
|
constraint pk_orders
|
||||||
|
primary key,
|
||||||
|
customer_email text not null,
|
||||||
|
created_at timestamp not null default current_timestamp
|
||||||
|
);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
drop table order_details;
|
||||||
12
migrations/2020-11-29-201720_create_order_details/up.sql
Normal file
12
migrations/2020-11-29-201720_create_order_details/up.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
create table if not exists order_details
|
||||||
|
(
|
||||||
|
orderid integer not null
|
||||||
|
constraint fk_order_details_orders
|
||||||
|
references orders (orderid),
|
||||||
|
productid integer not null
|
||||||
|
constraint fk_order_details_products
|
||||||
|
references products(productid),
|
||||||
|
constraint pk_order_details
|
||||||
|
primary key (orderid, productid),
|
||||||
|
quantity integer not null
|
||||||
|
);
|
||||||
54
src/api_error.rs
Normal file
54
src/api_error.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::{HttpResponse, ResponseError};
|
||||||
|
use diesel::result::Error as DieselError;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ApiError {
|
||||||
|
pub status_code: u16,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiError {
|
||||||
|
pub fn new(status_code: u16, message: String) -> ApiError {
|
||||||
|
ApiError { status_code, message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ApiError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_str(self.message.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DieselError> for ApiError {
|
||||||
|
fn from(error: DieselError) -> ApiError {
|
||||||
|
match error {
|
||||||
|
DieselError::DatabaseError(_, err) => ApiError::new(409, err.message().to_string()),
|
||||||
|
DieselError::NotFound => ApiError::new(404, "Record not found".to_string()),
|
||||||
|
err => ApiError::new(500, format!("Diesel error: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for ApiError {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
let status_code = match StatusCode::from_u16(self.status_code) {
|
||||||
|
Ok(status_code) => status_code,
|
||||||
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = match status_code.as_u16() < 500 {
|
||||||
|
true => self.message.clone(),
|
||||||
|
false => {
|
||||||
|
error!("{}", self.message);
|
||||||
|
"Internal server error".to_string()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse::build(status_code)
|
||||||
|
.json(json!({ "message": message }))
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/db.rs
Normal file
31
src/db.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use crate::api_error::ApiError;
|
||||||
|
use diesel::pg::PgConnection;
|
||||||
|
use diesel::r2d2::ConnectionManager;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use r2d2;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
||||||
|
pub type DbConnection = r2d2::PooledConnection<ConnectionManager<PgConnection>>;
|
||||||
|
|
||||||
|
embed_migrations!();
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref POOL: Pool = {
|
||||||
|
let db_url = env::var("DATABASE_URL").expect("Database url not set");
|
||||||
|
let manager = ConnectionManager::<PgConnection>::new(db_url);
|
||||||
|
Pool::new(manager).expect("Failed to create db pool")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
info!("Initializing DB");
|
||||||
|
lazy_static::initialize(&POOL);
|
||||||
|
let conn = connection().expect("Failed to get db connection");
|
||||||
|
embedded_migrations::run(&conn).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connection() -> Result<DbConnection, ApiError> {
|
||||||
|
POOL.get()
|
||||||
|
.map_err(|e| ApiError::new(500, format!("Failed getting db connection: {}", e)))
|
||||||
|
}
|
||||||
42
src/main.rs
Normal file
42
src/main.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel_migrations;
|
||||||
|
|
||||||
|
use actix_web::{App, HttpServer};
|
||||||
|
use dotenv::dotenv;
|
||||||
|
use listenfd::ListenFd;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
mod db;
|
||||||
|
mod product;
|
||||||
|
mod schema;
|
||||||
|
mod api_error;
|
||||||
|
|
||||||
|
#[actix_rt::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
dotenv().ok();
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
db::init();
|
||||||
|
|
||||||
|
let mut listenfd = ListenFd::from_env();
|
||||||
|
let mut server = HttpServer::new(||
|
||||||
|
App::new()
|
||||||
|
.configure(product::init_routes)
|
||||||
|
);
|
||||||
|
|
||||||
|
server = match listenfd.take_tcp_listener(0)? {
|
||||||
|
Some(listener) => server.listen(listener)?,
|
||||||
|
None => {
|
||||||
|
let host = env::var("HOST").expect("Host not set in .env file");
|
||||||
|
let port = env::var("PORT").expect("Port not set in .env file");
|
||||||
|
server.bind(format!("{}:{}", host, port))?
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Starting Server");
|
||||||
|
server.run().await
|
||||||
|
}
|
||||||
5
src/product/mod.rs
Normal file
5
src/product/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod model;
|
||||||
|
mod routes;
|
||||||
|
|
||||||
|
pub use model::Product;
|
||||||
|
pub use routes::init_routes;
|
||||||
52
src/product/model.rs
Normal file
52
src/product/model.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use crate::api_error::ApiError;
|
||||||
|
use crate::db;
|
||||||
|
use crate::schema::products;
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, AsChangeset)]
|
||||||
|
#[table_name = "products"]
|
||||||
|
pub struct ProductMessage {
|
||||||
|
pub productid: i32,
|
||||||
|
pub productname: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Queryable, Insertable)]
|
||||||
|
#[table_name = "products"]
|
||||||
|
pub struct Product {
|
||||||
|
pub productid: i32,
|
||||||
|
pub productname: String,
|
||||||
|
pub created_at: NaiveDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Product {
|
||||||
|
pub fn find_all() -> Result<Vec<Self>, ApiError> {
|
||||||
|
let conn = db::connection()?;
|
||||||
|
|
||||||
|
let products = products::table
|
||||||
|
.load::<Product>(&conn)?;
|
||||||
|
|
||||||
|
Ok(products)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find(id: i32) -> Result<Self, ApiError> {
|
||||||
|
let conn = db::connection()?;
|
||||||
|
|
||||||
|
let product = products::table
|
||||||
|
.filter(products::productid.eq(id))
|
||||||
|
.first(&conn)?;
|
||||||
|
|
||||||
|
Ok(product)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProductMessage> for Product {
|
||||||
|
fn from(product: ProductMessage) -> Self {
|
||||||
|
Product {
|
||||||
|
productid: product.productid,
|
||||||
|
productname: product.productname,
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/product/routes.rs
Normal file
20
src/product/routes.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// src/product/routes.rs
|
||||||
|
use crate::product::Product;
|
||||||
|
use actix_web::{get, post, web, HttpResponse, Responder};
|
||||||
|
|
||||||
|
#[get("/products")]
|
||||||
|
async fn find_all() -> impl Responder {
|
||||||
|
let products = Product::find_all().expect("Error fetching all Products");
|
||||||
|
HttpResponse::Ok().json(products)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/product/{id}")]
|
||||||
|
async fn find(id: web::Path<i32>) -> impl Responder {
|
||||||
|
let product = Product::find(id.into_inner()).expect("Error fetching Product");
|
||||||
|
HttpResponse::Ok().json(product)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_routes(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(find_all);
|
||||||
|
cfg.service(find);
|
||||||
|
}
|
||||||
32
src/schema.rs
Normal file
32
src/schema.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
table! {
|
||||||
|
order_details (orderid, productid) {
|
||||||
|
orderid -> Int4,
|
||||||
|
productid -> Int4,
|
||||||
|
quantity -> Int4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
orders (orderid) {
|
||||||
|
customer_email -> Text,
|
||||||
|
orderid -> Int4,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
products (productid) {
|
||||||
|
productid -> Int4,
|
||||||
|
productname -> Text,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
joinable!(order_details -> orders (orderid));
|
||||||
|
joinable!(order_details -> products (productid));
|
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!(
|
||||||
|
order_details,
|
||||||
|
orders,
|
||||||
|
products,
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user