diff --git a/Cargo.lock b/Cargo.lock index 0ba2fb5..4cef642 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,6 +460,7 @@ dependencies = [ "num-integer", "num-traits", "pq-sys", + "r2d2", "serde_json", ] @@ -1088,6 +1089,17 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "rand" version = "0.9.2" @@ -1176,6 +1188,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "schema" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ba03a7a..b8564fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" actix-web = "4.x.x" bigdecimal = {version = "0.4.10", features = ["serde"] } -diesel = { version = "2.3.6", default-features = false, features = ["postgres", "serde_json", "numeric"] } +diesel = { version = "2.3.6", default-features = false, features = ["postgres", "serde_json", "numeric", "r2d2"] } dotenvy = "0.15.7" schema = "0.1.0" diff --git a/migrations/2026-01-28-134347-0000_locations/up.sql b/migrations/2026-01-28-134347-0000_locations/up.sql index 45cc879..f7cc06a 100644 --- a/migrations/2026-01-28-134347-0000_locations/up.sql +++ b/migrations/2026-01-28-134347-0000_locations/up.sql @@ -1,18 +1,18 @@ CREATE TYPE battery_status_enum AS ENUM ('unknown', 'unplugged', 'charging', 'full'); CREATE TABLE locations ( - timestamp bigint PRIMARY KEY, - latitude numeric(9,6) NOT NULL, - longitude numeric(9,6) NOT NULL, - accuracy numeric(7,2) NOT NULL, - altitude numeric(7,2), - velocity numeric(7,2), - battery_level smallint NOT NULL, - bearing numeric(6,3), - battery_status battery_status_enum DEFAULT 'unknown' NOT NULL, - CONSTRAINT Location_battery_level_check CHECK ((battery_level >= 0) AND (battery_level <= 100)), - CONSTRAINT Location_bearing_check CHECK ((bearing >= (0)::numeric) AND (bearing <= (360)::numeric)) + tst bigint PRIMARY KEY, + lat numeric(9,6) NOT NULL, + lon numeric(9,6) NOT NULL, + acc numeric(7,2) NOT NULL, + alt numeric(7,2), + vel numeric(7,2), + batt smallint NOT NULL, + bear numeric(6,3), + bs battery_status_enum DEFAULT 'unknown' NOT NULL, + CONSTRAINT Location_battery_level_check CHECK ((batt >= 0) AND (batt <= 100)), + CONSTRAINT Location_bearing_check CHECK ((bear >= (0)::numeric) AND (bear <= (360)::numeric)) ) WITH (oids = false); -CREATE INDEX locations_timestamp ON locations USING btree (timestamp); \ No newline at end of file +CREATE INDEX locations_timestamp ON locations USING btree (tst); \ No newline at end of file diff --git a/src/handlers.rs b/src/handlers.rs new file mode 100644 index 0000000..ff873d8 --- /dev/null +++ b/src/handlers.rs @@ -0,0 +1,66 @@ +use crate::DbPool; +use crate::{models::Location, schema::locations}; +use actix_web::{Error, HttpResponse, web}; +use diesel::pg::PgConnection; +use diesel::prelude::*; +use diesel::r2d2::{ConnectionManager, PooledConnection}; +use diesel::result::Error as DieselError; + +pub async fn index() -> Result { + Ok(HttpResponse::Ok().json("Connection Successful")) +} + +pub async fn create_location( + pool: web::Data, + body: web::Json, +) -> Result { + let location = body.into_inner(); + + web::block(move || { + let mut conn = get_connection(pool); + + diesel::insert_into(locations::table) + .values(&location) + .execute(&mut conn) + }) + .await + .map_err(|e| { + eprintln!("Block error: {:?}", e); + actix_web::error::ErrorInternalServerError("Block error") + })? + .map_err(|e: DieselError| { + eprintln!("Diesel error: {:?}", e); + if let DieselError::DatabaseError(kind, info) = &e { + eprintln!("DB Error Kind: {:?}", kind); + eprintln!("DB Error Info: {}", info.message()); + if let Some(constraint) = info.constraint_name() { + eprintln!("Constraint: {}", constraint); + } + } + actix_web::error::ErrorInternalServerError(format!("DB error: {}", e)) + })?; + + Ok(HttpResponse::Created().json("Location created successfully")) +} + +pub async fn select_locations(pool: web::Data) -> Result { + + let locations = web::block(move || { + let mut conn = get_connection(pool); + locations::table + .select(Location::as_select()) + .load::(&mut conn) + }) + .await + .map_err(|_| actix_web::error::ErrorInternalServerError("Block error"))? + .map_err(|e: DieselError| { + eprintln!("Diesel SELECT error: {:?}", e); + actix_web::error::ErrorInternalServerError("DB query failed") + })?; + + Ok(HttpResponse::Ok().json(&locations)) +} + +fn get_connection(pool: web::Data) -> PooledConnection> { + pool.get().expect("Failed to get DB connection from pool") +} diff --git a/src/handlers/locations.rs b/src/handlers/locations.rs deleted file mode 100644 index 7a43003..0000000 --- a/src/handlers/locations.rs +++ /dev/null @@ -1,20 +0,0 @@ -use actix_web::web::Json; -use serde::{Deserialize, Serialize}; -include!("../models.rs"); - -// async fn get_locations() -> impl Responder { -// let locations: Vec = vec![]; -// Json(locations) -// } - -// async fn create_location(Json(location): Json) -> impl Responder { -// Json(location) -// } - -// async fn update_location(Json(location): Json) -> impl Responder { -// Json(location) -// } - -// async fn delete_location(timestamp: i64) -> impl Responder { -// format!("Deleted location with timestamp: {}", timestamp) -// } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index aa507c6..aeb3ca3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,23 @@ use diesel::prelude::*; use dotenvy::dotenv; use std::{env}; -use std::io::{stdout, Write}; +use diesel::r2d2::{ConnectionManager, Pool}; pub mod models; pub mod schema; +pub mod routes; +pub mod handlers; -pub fn establish_connection() -> PgConnection { +pub type DbPool = Pool>; + +pub fn create_pool() -> DbPool { dotenv().ok(); - // let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - // let database_name = env::var("DATABASE_NAME").expect("DATABASE_NAME must be set"); - // let database_user = env::var("DATABASE_USER").expect("DATABASE_USER must be set"); - // let database_password = env::var("DATABASE_PASSWORD").expect("DATABASE_PASSWORD must be set"); - - // let connection_url= format!("postgres://{}:{}@{}/{}", database_user, database_password, database_url, database_name); - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - println!("{}", database_url); + let manager = ConnectionManager::::new(database_url); - PgConnection::establish(&database_url) - .unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) + Pool::builder() + .build(manager) + .expect("Failed to create DB pool") } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e82fda0..033bd29 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,42 +1,19 @@ -use actix_web::{web, App, HttpRequest, HttpServer, Responder}; -use colota_backend::establish_connection; -use diesel::prelude::*; -use dotenvy::dotenv; -use std::env; - +use actix_web::{web, App, HttpServer}; +use colota_backend::{create_pool, DbPool}; pub use colota_backend::schema; pub use colota_backend::models; - -include!("handlers/locations.rs"); - -async fn index() -> impl Responder { "Connection successful" } +pub use colota_backend::routes; #[actix_web::main] async fn main() -> std::io::Result<()> { - use self::schema::locations::dsl::*; + let pool: DbPool = create_pool(); - - let connection = &mut establish_connection(); - let results = locations - .select(Location::as_select()) - .load(connection) - .expect("Error loading locations"); - - println!("Displaying {} locations", results.len()); - - - HttpServer::new(|| { + HttpServer::new(move || { App::new() - .route("/", web::get().to(index)) - .service( - web::scope("/api/v1") - // .service(web::resource("/locations").to(|| { - // // get_locations() - // })) - // .service(web::resource("/locations").route(web::post().to(create_location))) - ) + .app_data(web::Data::new(pool.clone())) + .configure(routes::config_routes) }) - .bind("127.0.0.1:8080")? + .bind(("0.0.0.0", 8080))? .run() .await } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index bf36dba..d93385a 100644 --- a/src/models.rs +++ b/src/models.rs @@ -3,29 +3,30 @@ use diesel::deserialize::{self, FromSql, FromSqlRow}; use diesel::pg::{Pg, PgValue}; use diesel::serialize::{IsNull, Output, ToSql}; use diesel::*; +use serde::{Deserialize, Serialize}; use std::io::Write; use diesel::expression::AsExpression; use crate::schema::sql_types::BatteryStatusEnum; -#[derive(Queryable, Selectable)] +#[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)] #[diesel(table_name = crate::schema::locations)] #[diesel(check_for_backend(diesel::pg::Pg))] -struct Location { - latitude: BigDecimal, - longitude: BigDecimal, - accuracy: BigDecimal, - altitude: Option, - velocity: Option, - battery_level: i16, - battery_status: BatteryStatus, - timestamp: i64, - bearing: Option, +pub struct Location { + lat: BigDecimal, + lon: BigDecimal, + acc: BigDecimal, + alt: Option, + vel: Option, + batt: i16, + bs: BatteryStatus, + tst: i64, + bear: Option, } -#[derive(Debug, FromSqlRow, AsExpression)] +#[derive(Debug, FromSqlRow, AsExpression, Serialize, Deserialize)] #[diesel(sql_type = BatteryStatusEnum)] -enum BatteryStatus { +pub enum BatteryStatus { Unknown, Unplugged, Charging, diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 0000000..bf35bb8 --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,8 @@ +use crate::handlers; +use actix_web::web; + +pub fn config_routes(cfg: &mut web::ServiceConfig) { + cfg.service(web::resource("/").route(web::get().to(handlers::index))) + .service(web::resource("/api/location").route(web::post().to(handlers::create_location))) + .service(web::resource("/api/locations").route(web::get().to(handlers::select_locations))); +} diff --git a/src/schema.rs b/src/schema.rs index b27902a..85693b4 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -10,15 +10,15 @@ diesel::table! { use diesel::sql_types::*; use super::sql_types::BatteryStatusEnum; - locations (timestamp) { - timestamp -> Int8, - latitude -> Numeric, - longitude -> Numeric, - accuracy -> Numeric, - altitude -> Nullable, - velocity -> Nullable, - battery_level -> Int2, - bearing -> Nullable, - battery_status -> BatteryStatusEnum, + locations (tst) { + tst -> Int8, + lat -> Numeric, + lon -> Numeric, + acc -> Numeric, + alt -> Nullable, + vel -> Nullable, + batt -> Int2, + bear -> Nullable, + bs -> BatteryStatusEnum, } }