working example with write and select

This commit is contained in:
2026-01-29 15:50:56 +01:00
parent 361bd7bc4a
commit 71b17a11bd
10 changed files with 150 additions and 99 deletions

21
Cargo.lock generated
View File

@@ -460,6 +460,7 @@ dependencies = [
"num-integer", "num-integer",
"num-traits", "num-traits",
"pq-sys", "pq-sys",
"r2d2",
"serde_json", "serde_json",
] ]
@@ -1088,6 +1089,17 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 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]] [[package]]
name = "rand" name = "rand"
version = "0.9.2" version = "0.9.2"
@@ -1176,6 +1188,15 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 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]] [[package]]
name = "schema" name = "schema"
version = "0.1.0" version = "0.1.0"

View File

@@ -8,6 +8,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
actix-web = "4.x.x" actix-web = "4.x.x"
bigdecimal = {version = "0.4.10", features = ["serde"] } 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" dotenvy = "0.15.7"
schema = "0.1.0" schema = "0.1.0"

View File

@@ -1,18 +1,18 @@
CREATE TYPE battery_status_enum AS ENUM ('unknown', 'unplugged', 'charging', 'full'); CREATE TYPE battery_status_enum AS ENUM ('unknown', 'unplugged', 'charging', 'full');
CREATE TABLE locations ( CREATE TABLE locations (
timestamp bigint PRIMARY KEY, tst bigint PRIMARY KEY,
latitude numeric(9,6) NOT NULL, lat numeric(9,6) NOT NULL,
longitude numeric(9,6) NOT NULL, lon numeric(9,6) NOT NULL,
accuracy numeric(7,2) NOT NULL, acc numeric(7,2) NOT NULL,
altitude numeric(7,2), alt numeric(7,2),
velocity numeric(7,2), vel numeric(7,2),
battery_level smallint NOT NULL, batt smallint NOT NULL,
bearing numeric(6,3), bear numeric(6,3),
battery_status battery_status_enum DEFAULT 'unknown' NOT NULL, bs battery_status_enum DEFAULT 'unknown' NOT NULL,
CONSTRAINT Location_battery_level_check CHECK ((battery_level >= 0) AND (battery_level <= 100)), CONSTRAINT Location_battery_level_check CHECK ((batt >= 0) AND (batt <= 100)),
CONSTRAINT Location_bearing_check CHECK ((bearing >= (0)::numeric) AND (bearing <= (360)::numeric)) CONSTRAINT Location_bearing_check CHECK ((bear >= (0)::numeric) AND (bear <= (360)::numeric))
) )
WITH (oids = false); WITH (oids = false);
CREATE INDEX locations_timestamp ON locations USING btree (timestamp); CREATE INDEX locations_timestamp ON locations USING btree (tst);

66
src/handlers.rs Normal file
View File

@@ -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<HttpResponse, Error> {
Ok(HttpResponse::Ok().json("Connection Successful"))
}
pub async fn create_location(
pool: web::Data<DbPool>,
body: web::Json<Location>,
) -> Result<HttpResponse, Error> {
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<DbPool>) -> Result<HttpResponse, Error> {
let locations = web::block(move || {
let mut conn = get_connection(pool);
locations::table
.select(Location::as_select())
.load::<Location>(&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<DbPool>) -> PooledConnection<ConnectionManager<PgConnection>> {
pool.get().expect("Failed to get DB connection from pool")
}

View File

@@ -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<Location> = vec![];
// Json(locations)
// }
// async fn create_location(Json(location): Json<Location>) -> impl Responder {
// Json(location)
// }
// async fn update_location(Json(location): Json<Location>) -> impl Responder {
// Json(location)
// }
// async fn delete_location(timestamp: i64) -> impl Responder {
// format!("Deleted location with timestamp: {}", timestamp)
// }

View File

@@ -1,25 +1,23 @@
use diesel::prelude::*; use diesel::prelude::*;
use dotenvy::dotenv; use dotenvy::dotenv;
use std::{env}; use std::{env};
use std::io::{stdout, Write}; use diesel::r2d2::{ConnectionManager, Pool};
pub mod models; pub mod models;
pub mod schema; pub mod schema;
pub mod routes;
pub mod handlers;
pub fn establish_connection() -> PgConnection { pub type DbPool = Pool<ConnectionManager<PgConnection>>;
pub fn create_pool() -> DbPool {
dotenv().ok(); 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"); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
println!("{}", database_url); let manager = ConnectionManager::<PgConnection>::new(database_url);
PgConnection::establish(&database_url) Pool::builder()
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url)) .build(manager)
.expect("Failed to create DB pool")
} }

View File

@@ -1,42 +1,19 @@
use actix_web::{web, App, HttpRequest, HttpServer, Responder}; use actix_web::{web, App, HttpServer};
use colota_backend::establish_connection; use colota_backend::{create_pool, DbPool};
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;
pub use colota_backend::schema; pub use colota_backend::schema;
pub use colota_backend::models; pub use colota_backend::models;
pub use colota_backend::routes;
include!("handlers/locations.rs");
async fn index() -> impl Responder { "Connection successful" }
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
use self::schema::locations::dsl::*; let pool: DbPool = create_pool();
HttpServer::new(move || {
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(|| {
App::new() App::new()
.route("/", web::get().to(index)) .app_data(web::Data::new(pool.clone()))
.service( .configure(routes::config_routes)
web::scope("/api/v1")
// .service(web::resource("/locations").to(|| {
// // get_locations()
// }))
// .service(web::resource("/locations").route(web::post().to(create_location)))
)
}) })
.bind("127.0.0.1:8080")? .bind(("0.0.0.0", 8080))?
.run() .run()
.await .await
} }

View File

@@ -3,29 +3,30 @@ use diesel::deserialize::{self, FromSql, FromSqlRow};
use diesel::pg::{Pg, PgValue}; use diesel::pg::{Pg, PgValue};
use diesel::serialize::{IsNull, Output, ToSql}; use diesel::serialize::{IsNull, Output, ToSql};
use diesel::*; use diesel::*;
use serde::{Deserialize, Serialize};
use std::io::Write; use std::io::Write;
use diesel::expression::AsExpression; use diesel::expression::AsExpression;
use crate::schema::sql_types::BatteryStatusEnum; use crate::schema::sql_types::BatteryStatusEnum;
#[derive(Queryable, Selectable)] #[derive(Queryable, Selectable, Insertable, Serialize, Deserialize, Debug)]
#[diesel(table_name = crate::schema::locations)] #[diesel(table_name = crate::schema::locations)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
struct Location { pub struct Location {
latitude: BigDecimal, lat: BigDecimal,
longitude: BigDecimal, lon: BigDecimal,
accuracy: BigDecimal, acc: BigDecimal,
altitude: Option<BigDecimal>, alt: Option<BigDecimal>,
velocity: Option<BigDecimal>, vel: Option<BigDecimal>,
battery_level: i16, batt: i16,
battery_status: BatteryStatus, bs: BatteryStatus,
timestamp: i64, tst: i64,
bearing: Option<BigDecimal>, bear: Option<BigDecimal>,
} }
#[derive(Debug, FromSqlRow, AsExpression)] #[derive(Debug, FromSqlRow, AsExpression, Serialize, Deserialize)]
#[diesel(sql_type = BatteryStatusEnum)] #[diesel(sql_type = BatteryStatusEnum)]
enum BatteryStatus { pub enum BatteryStatus {
Unknown, Unknown,
Unplugged, Unplugged,
Charging, Charging,

8
src/routes.rs Normal file
View File

@@ -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)));
}

View File

@@ -10,15 +10,15 @@ diesel::table! {
use diesel::sql_types::*; use diesel::sql_types::*;
use super::sql_types::BatteryStatusEnum; use super::sql_types::BatteryStatusEnum;
locations (timestamp) { locations (tst) {
timestamp -> Int8, tst -> Int8,
latitude -> Numeric, lat -> Numeric,
longitude -> Numeric, lon -> Numeric,
accuracy -> Numeric, acc -> Numeric,
altitude -> Nullable<Numeric>, alt -> Nullable<Numeric>,
velocity -> Nullable<Numeric>, vel -> Nullable<Numeric>,
battery_level -> Int2, batt -> Int2,
bearing -> Nullable<Numeric>, bear -> Nullable<Numeric>,
battery_status -> BatteryStatusEnum, bs -> BatteryStatusEnum,
} }
} }