Initial version

This commit is contained in:
Jani Pulkkinen
2025-04-16 15:03:30 +03:00
commit 27a51916a3
24 changed files with 2551 additions and 0 deletions

12
src/connection.rs Normal file
View File

@@ -0,0 +1,12 @@
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}

1
src/constants.rs Normal file
View File

@@ -0,0 +1 @@
pub const APPLICATION_JSON: &str = "application/json";

193
src/loota.rs Normal file
View File

@@ -0,0 +1,193 @@
use actix_web::{get, post, web, HttpResponse, Responder};
use chrono::{NaiveDateTime};
use diesel::prelude::*;
use diesel::result::Error;
use uuid::Uuid;
use crate::constants::APPLICATION_JSON;
use crate::connection::establish_connection;
use crate::models::{Customer, Box, Order, LootaResponse, LootaBoxResponse, LootaRequest};
use crate::schema::loota_box::dsl::loota_box;
use crate::schema::loota_box::{delivery_date, id, pickup_date};
use crate::schema::loota_customer::dsl::loota_customer;
use crate::schema::loota_customer::identifier;
fn find_boxes(_identifier: String, _order: &Order, conn: &mut PgConnection) -> LootaResponse {
let boxes = Box::belonging_to(_order)
.select(Box::as_select())
.order_by(delivery_date.asc())
.load(conn);
match boxes {
Ok(boxes) => {
let _box_responses = boxes.iter().map(|b| {
LootaBoxResponse {
id: b.id.to_string(),
delivery_date: b.delivery_date,
pickup_date: b.pickup_date,
}
});
LootaResponse {
identifier: _identifier.clone(),
location: _order.location.clone(),
boxes: _box_responses.collect(),
}
}
Err(err) => {
println!("Error: {:?}", err);
LootaResponse {
identifier: "".to_string(),
location: "".to_string(),
boxes: vec![],
}
}
}
}
fn find_order(_customer: &Customer, conn: &mut PgConnection) -> Result<LootaResponse, Error> {
let _identifier = _customer.identifier.clone();
let order = Order::belonging_to(_customer)
.select(Order::as_select())
.limit(1)
.load(conn);
match order {
Ok(order) => match order.first() {
Some(order) => Ok(find_boxes(_identifier, order, conn)),
_ => Err(Error::NotFound)
}
Err(err) => {
println!("Error: {:?}", err);
Err(err)
}
}
}
fn find_customer(_identifier: String) -> Result<LootaResponse, Error> {
let conn = &mut establish_connection();
let customer = loota_customer
.filter(identifier.eq(_identifier))
.select(Customer::as_select())
.load(conn);
match customer {
Ok(customer) => match customer.first() {
Some(customer) => Ok(find_order(customer, conn)?),
_ => Err(Error::NotFound)
}
Err(err) => {
println!("Error: {:?}", err);
Err(err)
}
}
}
fn update_box(_id: Uuid, _pickup_date: NaiveDateTime) -> Result<LootaBoxResponse, Error> {
let conn = &mut establish_connection();
let loota = diesel::update(loota_box.filter(id.eq(&_id)))
.set(pickup_date.eq(_pickup_date))
.returning(Box::as_returning())
.get_result(conn);
match loota {
Ok(loota) => {
Ok(LootaBoxResponse {
id: loota.id.to_string(),
delivery_date: loota.delivery_date,
pickup_date: loota.pickup_date,
})
}
Err(err) => {
println!("Error: {:?}", err);
Err(err)
}
}
}
fn parse_uuid(_id: String) -> Result<Uuid, uuid::Error> {
Uuid::parse_str(&_id)
}
fn parse_date(_date: String) -> Result<NaiveDateTime, chrono::ParseError> {
NaiveDateTime::parse_from_str(&_date, "%Y-%m-%dT%H:%M:%S")
}
#[get("/loota/{id}")]
async fn get(path: web::Path<String>) -> impl Responder {
let identifer = path.into_inner();
println!("id: {:?}", identifer);
let response = web::block(move || find_customer(identifer)).await.unwrap();
match response {
Ok(response) => {
HttpResponse::Ok()
.content_type(APPLICATION_JSON).json(response)
}
_ => HttpResponse::NotFound()
.content_type(APPLICATION_JSON)
.await
.unwrap(),
}
}
#[post("/loota/")]
async fn update(req: web::Json<LootaRequest>) -> impl Responder {
let box_id = parse_uuid(req.id.clone());
match box_id {
Ok(box_id) => {
let date = parse_date(req.pickup_date.clone());
match date {
Ok(date) => {
let updated_box = update_box(box_id, date);
match updated_box {
Ok(updated_box) => {
HttpResponse::Ok()
.content_type(APPLICATION_JSON)
.json(updated_box)
}
Err(err) => {
println!("Error: {:?}", err);
HttpResponse::BadRequest()
.content_type(APPLICATION_JSON)
.finish()
}
}
}
Err(err) => {
println!("Error: {:?}", err);
HttpResponse::BadRequest()
.content_type(APPLICATION_JSON)
.finish()
}
}
}
Err(err) => {
println!("Error: {:?}", err);
HttpResponse::BadRequest()
.content_type(APPLICATION_JSON)
.finish()
}
}
}

35
src/main.rs Normal file
View File

@@ -0,0 +1,35 @@
extern crate actix_web;
extern crate diesel;
use std::{io};
use actix_web::{middleware, App, HttpServer};
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use diesel::PgConnection;
mod constants;
mod loota;
mod models;
mod schema;
mod connection;
pub type DBPool = Pool<ConnectionManager<PgConnection>>;
pub type DBPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
#[actix_rt::main]
async fn main() -> io::Result<()> {
env_logger::init();
HttpServer::new(|| {
App::new()
// enable logger - always register actix-web Logger middleware last
.wrap(middleware::Logger::default())
// register HTTP requests handlers
.service(loota::get)
.service(loota::update)
})
.bind("0.0.0.0:9090")?
.run()
.await
}

55
src/models.rs Normal file
View File

@@ -0,0 +1,55 @@
use uuid::Uuid;
use diesel::prelude::*;
use chrono::{NaiveDateTime};
use serde::{Serialize, Deserialize};
use crate::schema::{loota_customer, loota_order, loota_box};
#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)]
#[diesel(table_name = loota_customer)]
pub struct Customer {
pub id: Uuid,
pub identifier: String
}
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
#[diesel(belongs_to(Customer))]
#[diesel(table_name = loota_order)]
pub struct Order {
pub id: Uuid,
pub location: String,
pub customer_id: Uuid,
}
#[derive(Queryable, Selectable, Identifiable, Associations, Debug, PartialEq)]
#[diesel(belongs_to(Order))]
#[diesel(table_name = loota_box)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Box {
pub id: Uuid,
pub delivery_date: Option<NaiveDateTime>,
pub pickup_date: Option<NaiveDateTime>,
pub order_id: Uuid
}
#[derive(Debug, Serialize)]
pub struct LootaResponse {
pub identifier: String,
pub location: String,
pub boxes: Vec<LootaBoxResponse>
}
#[derive(Debug, Serialize)]
pub struct LootaBoxResponse {
pub id: String,
pub delivery_date: Option<NaiveDateTime>,
pub pickup_date: Option<NaiveDateTime>
}
#[derive(Debug, Deserialize)]
pub struct LootaRequest {
pub id: String,
pub pickup_date: String
}

37
src/schema.rs Normal file
View File

@@ -0,0 +1,37 @@
// @generated automatically by Diesel CLI.
diesel::table! {
loota_box (id) {
id -> Uuid,
order_id -> Uuid,
delivery_date -> Nullable<Timestamp>,
pickup_date -> Nullable<Timestamp>,
created_time -> Timestamp,
}
}
diesel::table! {
loota_customer (id) {
id -> Uuid,
identifier -> Varchar,
created_time -> Timestamp,
}
}
diesel::table! {
loota_order (id) {
id -> Uuid,
customer_id -> Uuid,
location -> Varchar,
created_time -> Timestamp,
}
}
diesel::joinable!(loota_box -> loota_order (order_id));
diesel::joinable!(loota_order -> loota_customer (customer_id));
diesel::allow_tables_to_appear_in_same_query!(
loota_box,
loota_customer,
loota_order,
);