Skip to content

Commit

Permalink
Clean up database code
Browse files Browse the repository at this point in the history
  • Loading branch information
umonkey committed Jan 9, 2025
1 parent cf284ca commit 5de5fe8
Show file tree
Hide file tree
Showing 23 changed files with 243 additions and 490 deletions.
173 changes: 145 additions & 28 deletions backend/src/common/database/repositories/tree_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::services::*;
use crate::types::*;
use log::error;
use rusqlite::types::Value;
use std::collections::HashMap;
use std::sync::Arc;

const TABLE: &str = "trees";
Expand All @@ -15,51 +16,144 @@ pub struct TreeRepository {

impl TreeRepository {
pub async fn all(&self) -> Result<Vec<TreeRecord>> {
let query = SelectQuery {
self.query_multiple(SelectQuery {
table_name: TABLE.to_string(),
..Default::default()
};

let records = self.db.get_records(query).await?;

Ok(records
.iter()
.map(|props| TreeRecord::from_attributes(props).unwrap())
.collect())
})
.await
}

pub async fn get(&self, id: u64) -> Result<TreeRecord> {
let query = SelectQuery {
pub async fn get(&self, id: u64) -> Result<Option<TreeRecord>> {
self.query_single(SelectQuery {
table_name: TABLE.to_string(),
conditions: Attributes::from(&[("id".to_string(), Value::from(id as i64))]),
..Default::default()
};
})
.await
}

match self.db.get_record(query).await {
Ok(Some(props)) => TreeRecord::from_attributes(&props),
Ok(None) => Err(Error::TreeNotFound),
Err(err) => {
error!("Error reading a tree: {}", err);
Err(err)
pub async fn get_multiple(&self, ids: &[u64]) -> Result<Vec<TreeRecord>> {
let mut trees: Vec<TreeRecord> = Vec::new();

for id in ids {
if let Some(tree) = self.get(*id).await? {
trees.push(tree);
}
}

Ok(trees)
}

pub async fn get_last_by_user(&self, user_id: u64) -> Result<Option<TreeRecord>> {
self.query_single(SelectQuery {
table_name: TABLE.to_string(),
conditions: Attributes::from(&[("added_by".to_string(), Value::from(user_id as i64))]),
order: HashMap::from([("created_at".to_string(), "DESC".to_string())]),
limit: Some(1),
..Default::default()
})
.await
}

pub async fn get_by_osm_id(&self, id: u64) -> Result<Option<TreeRecord>> {
let query = SelectQuery {
self.query_single(SelectQuery {
table_name: TABLE.to_string(),
conditions: Attributes::from(&[("osm_id".to_string(), Value::from(id as i64))]),
..Default::default()
})
.await
}

// FIXME: use a proper query
pub async fn get_by_bounds(&self, bounds: Bounds) -> Result<Vec<TreeRecord>> {
let trees = self
.all()
.await?
.into_iter()
.filter(|tree| {
tree.lat <= bounds.n
&& tree.lat >= bounds.s
&& tree.lon <= bounds.e
&& tree.lon >= bounds.w
})
.collect();

Ok(trees)
}

pub async fn get_recently_created(&self, count: u64, skip: u64) -> Result<Vec<TreeRecord>> {
self.query_multiple(SelectQuery {
table_name: TABLE.to_string(),
order: HashMap::from([("created_at".to_string(), "DESC".to_string())]),
limit: Some(count as i64),
offset: Some(skip as i64),
..Default::default()
})
.await
}

pub async fn get_recently_updated(&self, count: u64, skip: u64) -> Result<Vec<TreeRecord>> {
self.query_multiple(SelectQuery {
table_name: TABLE.to_string(),
order: HashMap::from([("updated_at".to_string(), "DESC".to_string())]),
limit: Some(count as i64),
offset: Some(skip as i64),
..Default::default()
})
.await
}

pub async fn get_close(&self, lat: f64, lon: f64, distance: f64) -> Result<Vec<TreeRecord>> {
let delta = distance / 111_111.0; // meters per degree

let bounds = Bounds {
n: lat + delta,
s: lat - delta,
e: lon + delta,
w: lon - delta,
};

match self.db.get_record(query).await {
Ok(Some(props)) => Ok(Some(TreeRecord::from_attributes(&props)?)),
Ok(None) => Ok(None),
Err(err) => {
error!("Error reading a tree: {}", err);
Err(err)
}
}
self.get_by_bounds(bounds).await
}

pub async fn get_with_no_address(&self) -> Result<Vec<TreeRecord>> {
self.query_multiple(SelectQuery {
table_name: TABLE.to_string(),
order: HashMap::from([("updated_at".to_string(), "DESC".to_string())]),
conditions: Attributes::from(&[("address".to_string(), Value::Null)]),
..Default::default()
})
.await
}

pub async fn get_top_height(&self, count: u64) -> Result<Vec<TreeRecord>> {
self.query_multiple(SelectQuery {
table_name: TABLE.to_string(),
order: HashMap::from([("height".to_string(), "DESC".to_string())]),
limit: Some(count as i64),
..Default::default()
})
.await
}

pub async fn get_top_circumference(&self, count: u64) -> Result<Vec<TreeRecord>> {
self.query_multiple(SelectQuery {
table_name: TABLE.to_string(),
order: HashMap::from([("circumference".to_string(), "DESC".to_string())]),
limit: Some(count as i64),
..Default::default()
})
.await
}

pub async fn get_top_diameter(&self, count: u64) -> Result<Vec<TreeRecord>> {
self.query_multiple(SelectQuery {
table_name: TABLE.to_string(),
order: HashMap::from([("diameter".to_string(), "DESC".to_string())]),
limit: Some(count as i64),
..Default::default()
})
.await
}

pub async fn add(&self, tree: &TreeRecord) -> Result<()> {
Expand All @@ -75,7 +169,10 @@ impl TreeRepository {
}

pub async fn update(&self, tree: &TreeRecord, user_id: u64) -> Result<()> {
let old = self.get(tree.id).await?;
let old = self.get(tree.id).await?.ok_or_else(|| {
error!("Error updating a tree: tree not found");
Error::TreeNotFound
})?;

let query = UpdateQuery {
table_name: TABLE.to_string(),
Expand Down Expand Up @@ -130,6 +227,26 @@ impl TreeRepository {
Ok(())
}

async fn query_single(&self, query: SelectQuery) -> Result<Option<TreeRecord>> {
match self.db.get_record(query).await {
Ok(Some(props)) => Ok(Some(TreeRecord::from_attributes(&props)?)),
Ok(None) => Ok(None),
Err(err) => {
error!("Error reading a tree: {}", err);
Err(err)
}
}
}

async fn query_multiple(&self, query: SelectQuery) -> Result<Vec<TreeRecord>> {
let records = self.db.get_records(query).await?;

Ok(records
.iter()
.map(|props| TreeRecord::from_attributes(props).unwrap())
.collect())
}

async fn log_changes(&self, old: &TreeRecord, new: &TreeRecord, user_id: u64) -> Result<()> {
if old.species != new.species {
self.add_tree_prop(new.id, "species", &new.species, user_id)
Expand Down
10 changes: 7 additions & 3 deletions backend/src/handlers/get_new_comments_handler.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::common::database::repositories::*;
use crate::services::*;
use crate::types::*;
use std::sync::Arc;

pub struct GetNewCommentsHandler {
db: Arc<dyn DatabaseInterface>,
trees: Arc<TreeRepository>,
}

impl GetNewCommentsHandler {
Expand All @@ -14,15 +16,17 @@ impl GetNewCommentsHandler {
let users = self.db.get_users(&user_ids).await?;

let tree_ids: Vec<u64> = comments.iter().map(|r| r.tree_id).collect();
let trees = self.db.get_trees_by_ids(&tree_ids).await?;
let trees = self.trees.get_multiple(&tree_ids).await?;

Ok(CommentList::from_records(&comments, &users, &trees))
}
}

impl Locatable for GetNewCommentsHandler {
fn create(locator: &Locator) -> Result<Self> {
let db = locator.get::<PreferredDatabase>()?.driver();
Ok(Self { db })
Ok(Self {
db: locator.get::<PreferredDatabase>()?.driver(),
trees: locator.get::<TreeRepository>()?,
})
}
}
12 changes: 7 additions & 5 deletions backend/src/handlers/get_new_trees_handler.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
use crate::common::database::repositories::*;
use crate::services::*;
use crate::types::*;
use std::sync::Arc;

pub struct GetNewTreesHandler {
db: Arc<dyn DatabaseInterface>,
loader: Arc<TreeListLoader>,
trees: Arc<TreeRepository>,
}

impl GetNewTreesHandler {
pub async fn handle(&self, count: u64, skip: u64) -> Result<TreeList> {
let trees = self.db.get_new_trees(count, skip).await?;
let trees = self.trees.get_recently_created(count, skip).await?;
self.loader.load(&trees).await
}
}

impl Locatable for GetNewTreesHandler {
fn create(locator: &Locator) -> Result<Self> {
let db = locator.get::<PreferredDatabase>()?.driver();
let loader = locator.get::<TreeListLoader>()?;
Ok(Self { db, loader })
Ok(Self {
loader: locator.get::<TreeListLoader>()?,
trees: locator.get::<TreeRepository>()?,
})
}
}
12 changes: 7 additions & 5 deletions backend/src/handlers/get_top_circumference_handler.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
use crate::common::database::repositories::*;
use crate::services::*;
use crate::types::*;
use std::sync::Arc;

pub struct GetTopCircumferenceHandler {
db: Arc<dyn DatabaseInterface>,
loader: Arc<TreeListLoader>,
trees: Arc<TreeRepository>,
}

impl GetTopCircumferenceHandler {
pub async fn handle(&self) -> Result<TreeList> {
let trees = self.db.get_top_circumference(100).await?;
let trees = self.trees.get_top_circumference(100).await?;
self.loader.load(&trees).await
}
}

impl Locatable for GetTopCircumferenceHandler {
fn create(locator: &Locator) -> Result<Self> {
let db = locator.get::<PreferredDatabase>()?.driver();
let loader = locator.get::<TreeListLoader>()?;
Ok(Self { db, loader })
Ok(Self {
loader: locator.get::<TreeListLoader>()?,
trees: locator.get::<TreeRepository>()?,
})
}
}
12 changes: 7 additions & 5 deletions backend/src/handlers/get_top_diameter_handler.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
use crate::common::database::repositories::*;
use crate::services::*;
use crate::types::*;
use std::sync::Arc;

pub struct GetTopDiameterHandler {
db: Arc<dyn DatabaseInterface>,
loader: Arc<TreeListLoader>,
trees: Arc<TreeRepository>,
}

impl GetTopDiameterHandler {
pub async fn handle(&self) -> Result<TreeList> {
let trees = self.db.get_top_diameter(100).await?;
let trees = self.trees.get_top_diameter(100).await?;
self.loader.load(&trees).await
}
}

impl Locatable for GetTopDiameterHandler {
fn create(locator: &Locator) -> Result<Self> {
let db = locator.get::<PreferredDatabase>()?.driver();
let loader = locator.get::<TreeListLoader>()?;
Ok(Self { db, loader })
Ok(Self {
loader: locator.get::<TreeListLoader>()?,
trees: locator.get::<TreeRepository>()?,
})
}
}
12 changes: 7 additions & 5 deletions backend/src/handlers/get_top_height_handler.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
use crate::common::database::repositories::*;
use crate::services::*;
use crate::types::*;
use std::sync::Arc;

pub struct GetTopHeightHandler {
db: Arc<dyn DatabaseInterface>,
loader: Arc<TreeListLoader>,
trees: Arc<TreeRepository>,
}

impl GetTopHeightHandler {
pub async fn handle(&self) -> Result<TreeList> {
let trees = self.db.get_top_height(100).await?;
let trees = self.trees.get_top_height(100).await?;
self.loader.load(&trees).await
}
}

impl Locatable for GetTopHeightHandler {
fn create(locator: &Locator) -> Result<Self> {
let db = locator.get::<PreferredDatabase>()?.driver();
let loader = locator.get::<TreeListLoader>()?;
Ok(Self { db, loader })
Ok(Self {
loader: locator.get::<TreeListLoader>()?,
trees: locator.get::<TreeRepository>()?,
})
}
}
Loading

0 comments on commit 5de5fe8

Please sign in to comment.