Compare commits
No commits in common. "a8d8e9a13166c47e1e87d13e42e362ff60d49f5e" and "5815c9fe10eb68c273b6fff16faba269945610c0" have entirely different histories.
a8d8e9a131
...
5815c9fe10
4 changed files with 96 additions and 70 deletions
|
@ -6,16 +6,13 @@ use crate::db::{Database, DbItem};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
use crate::models::item::Item;
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ItemRequest {
|
pub struct ItemRequest {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub item: Item,
|
pub item: DbItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
|
@ -95,7 +92,7 @@ pub async fn get_items_by_url(
|
||||||
pub async fn create_item_by_url(
|
pub async fn create_item_by_url(
|
||||||
db: web::Data<Arc<Mutex<Database>>>,
|
db: web::Data<Arc<Mutex<Database>>>,
|
||||||
url: web::Path<String>,
|
url: web::Path<String>,
|
||||||
item: web::Json<Item>,
|
item: web::Json<DbItem>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let db = db.lock().await;
|
let db = db.lock().await;
|
||||||
match db.insert_item_by_url(&url, &item.into_inner()).await {
|
match db.insert_item_by_url(&url, &item.into_inner()).await {
|
||||||
|
|
|
@ -38,22 +38,33 @@ pub async fn load_items_from_db(current_url: &str) -> Result<Vec<Item>, String>
|
||||||
// Deserialize into Vec<DbItem>
|
// Deserialize into Vec<DbItem>
|
||||||
log!("Loading items from DB...");
|
log!("Loading items from DB...");
|
||||||
let db_items = response
|
let db_items = response
|
||||||
.json::<Vec<Item>>()
|
.json::<Vec<DbItem>>()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format!("Failed to parse items: {:?}", err))?;
|
.map_err(|err| format!("Failed to parse items: {:?}", err))?;
|
||||||
|
|
||||||
// log!("Deserialized DB items: {:?}", db_items);
|
// log!("Deserialized DB items: {:?}", db_items);
|
||||||
|
|
||||||
// Convert DbItem to Item
|
// Convert DbItem to Item
|
||||||
let items = db_items.into_iter().map(|db_item| {
|
let items = db_items
|
||||||
Item {
|
.into_iter()
|
||||||
id: db_item.id,
|
.map(|db_item| {
|
||||||
name: db_item.name,
|
// Deserialize `custom_properties` from a JSON string to a HashMap
|
||||||
description: db_item.description,
|
let custom_properties: HashMap<String, String> =
|
||||||
wikidata_id: db_item.wikidata_id,
|
serde_json::from_str(&db_item.custom_properties)
|
||||||
custom_properties: HashMap::new() // Now populated from joins
|
.unwrap_or_default(); // Fallback to an empty HashMap if deserialization fails
|
||||||
}
|
|
||||||
}).collect();
|
log!("Loaded item: {:?}", db_item.id);
|
||||||
|
log!("Custom properties: {:?}", custom_properties);
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: db_item.id,
|
||||||
|
name: db_item.name,
|
||||||
|
description: db_item.description,
|
||||||
|
wikidata_id: db_item.wikidata_id,
|
||||||
|
custom_properties,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
// log!("Converted items: {:?}", items);
|
// log!("Converted items: {:?}", items);
|
||||||
Ok(items)
|
Ok(items)
|
||||||
} else {
|
} else {
|
||||||
|
|
124
src/db.rs
124
src/db.rs
|
@ -7,7 +7,6 @@ mod db_impl {
|
||||||
use leptos::logging;
|
use leptos::logging;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::models::item::Item;
|
|
||||||
|
|
||||||
// Define a struct to represent a database connection
|
// Define a struct to represent a database connection
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -84,6 +83,31 @@ mod db_impl {
|
||||||
Ok(url_id)
|
Ok(url_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert a new item into the database
|
||||||
|
pub async fn insert_item(&self, url_id: i64, item: &DbItem) -> Result<(), Error> {
|
||||||
|
let conn = self.conn.lock().await;
|
||||||
|
let wikidata_id = item.wikidata_id.as_ref().map(|s| s.as_str()).unwrap_or("");
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO items (id, name, description, wikidata_id, custom_properties, url_id)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
description = excluded.description,
|
||||||
|
wikidata_id = excluded.wikidata_id,
|
||||||
|
custom_properties = excluded.custom_properties;",
|
||||||
|
&[
|
||||||
|
&item.id,
|
||||||
|
&item.name,
|
||||||
|
&item.description,
|
||||||
|
&wikidata_id.to_string(),
|
||||||
|
&item.custom_properties,
|
||||||
|
&url_id.to_string(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
logging::log!("Item inserted: {}", item.id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete_item(&self, item_id: &str) -> Result<(), Error> {
|
pub async fn delete_item(&self, item_id: &str) -> Result<(), Error> {
|
||||||
let conn = self.conn.lock().await;
|
let conn = self.conn.lock().await;
|
||||||
conn.execute("DELETE FROM items WHERE id = ?", &[item_id])?;
|
conn.execute("DELETE FROM items WHERE id = ?", &[item_id])?;
|
||||||
|
@ -109,6 +133,7 @@ mod db_impl {
|
||||||
name: row.get(1)?,
|
name: row.get(1)?,
|
||||||
description: row.get(2)?,
|
description: row.get(2)?,
|
||||||
wikidata_id: row.get(3)?,
|
wikidata_id: row.get(3)?,
|
||||||
|
custom_properties: row.get(4)?,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
@ -120,46 +145,25 @@ mod db_impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve all items from the database for a specific URL
|
// Retrieve all items from the database for a specific URL
|
||||||
pub async fn get_items_by_url(&self, url: &str) -> Result<Vec<Item>, Error> {
|
pub async fn get_items_by_url(&self, url: &str) -> Result<Vec<DbItem>, Error> {
|
||||||
let conn = self.conn.lock().await;
|
let conn = self.conn.lock().await;
|
||||||
let url_id: i64 = conn.query_row("SELECT id FROM urls WHERE url = ?", &[url], |row| row.get(0))?;
|
let url_id: i64 = conn.query_row("SELECT id FROM urls WHERE url = ?", &[url], |row| row.get(0))?;
|
||||||
let mut stmt = conn.prepare(
|
let mut stmt = conn.prepare("SELECT * FROM items WHERE url_id = ?")?;
|
||||||
"SELECT i.id, i.name, i.description, i.wikidata_id,
|
let items = stmt.query_map(&[&url_id], |row| {
|
||||||
p.name AS prop_name, ip.value
|
Ok(DbItem {
|
||||||
FROM items i
|
id: row.get(0)?,
|
||||||
LEFT JOIN item_properties ip ON i.id = ip.item_id
|
name: row.get(1)?,
|
||||||
LEFT JOIN properties p ON ip.property_id = p.id
|
description: row.get(2)?,
|
||||||
WHERE i.url_id = ?"
|
wikidata_id: row.get(3)?,
|
||||||
)?;
|
custom_properties: row.get(4)?,
|
||||||
let mut items: HashMap<String, Item> = HashMap::new();
|
})
|
||||||
|
|
||||||
let rows = stmt.query_map([url_id], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, String>(0)?, // id
|
|
||||||
row.get::<_, String>(1)?, // name
|
|
||||||
row.get::<_, String>(2)?, // description
|
|
||||||
row.get::<_, Option<String>>(3)?, // wikidata_id
|
|
||||||
row.get::<_, Option<String>>(4)?, // prop_name
|
|
||||||
row.get::<_, Option<String>>(5)?, // value
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
let mut result = Vec::new();
|
||||||
for row in rows {
|
for item in items {
|
||||||
let (id, name, desc, wd_id, prop, val) = row?;
|
result.push(item?);
|
||||||
let item = items.entry(id.clone()).or_insert(Item {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
description: desc,
|
|
||||||
wikidata_id: wd_id,
|
|
||||||
custom_properties: HashMap::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if let (Some(p), Some(v)) = (prop, val) {
|
|
||||||
item.custom_properties.insert(p, v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
logging::log!("Fetched {} items from the database for URL: {}", result.len(), url);
|
||||||
Ok(items.into_values().collect())
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_url_id(&self, url: &str) -> Result<Option<i64>, Error> {
|
async fn get_url_id(&self, url: &str) -> Result<Option<i64>, Error> {
|
||||||
|
@ -196,7 +200,7 @@ mod db_impl {
|
||||||
pub async fn insert_item_by_url(
|
pub async fn insert_item_by_url(
|
||||||
&self,
|
&self,
|
||||||
url: &str,
|
url: &str,
|
||||||
item: &Item
|
item: &DbItem
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let conn = self.conn.lock().await;
|
let conn = self.conn.lock().await;
|
||||||
// Get or create URL record
|
// Get or create URL record
|
||||||
|
@ -213,8 +217,11 @@ mod db_impl {
|
||||||
&item.description, &item.wikidata_id.as_ref().unwrap_or(&String::new())],
|
&item.description, &item.wikidata_id.as_ref().unwrap_or(&String::new())],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let custom_props: HashMap<String, String> = serde_json::from_str(&item.custom_properties)
|
||||||
|
.map_err(|e| Error::ToSqlConversionFailure(e.into()))?;
|
||||||
|
|
||||||
// Handle properties through junction table
|
// Handle properties through junction table
|
||||||
for (prop, value) in &item.custom_properties {
|
for (prop, value) in custom_props {
|
||||||
let prop_id = self.get_or_create_property(&prop).await?;
|
let prop_id = self.get_or_create_property(&prop).await?;
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO item_properties (item_id, property_id, value)
|
"INSERT INTO item_properties (item_id, property_id, value)
|
||||||
|
@ -238,18 +245,8 @@ mod db_impl {
|
||||||
pub async fn delete_property_by_url(&self, url: &str, property: &str) -> Result<(), Error> {
|
pub async fn delete_property_by_url(&self, url: &str, property: &str) -> Result<(), Error> {
|
||||||
let conn = self.conn.lock().await;
|
let conn = self.conn.lock().await;
|
||||||
let url_id: i64 = conn.query_row("SELECT id FROM urls WHERE url = ?", &[url], |row| row.get(0))?;
|
let url_id: i64 = conn.query_row("SELECT id FROM urls WHERE url = ?", &[url], |row| row.get(0))?;
|
||||||
|
let query = format!("UPDATE items SET custom_properties = json_remove(custom_properties, '$.{}') WHERE url_id = ?", property);
|
||||||
// Delete from junction table instead of JSON
|
conn.execute(&query, &[&url_id.to_string()])?;
|
||||||
conn.execute(
|
|
||||||
"DELETE FROM item_properties
|
|
||||||
WHERE property_id IN (
|
|
||||||
SELECT id FROM properties WHERE name = ?
|
|
||||||
) AND item_id IN (
|
|
||||||
SELECT id FROM items WHERE url_id = ?
|
|
||||||
)",
|
|
||||||
&[property, &url_id.to_string()],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
logging::log!("Property deleted from the database for URL: {}", url);
|
logging::log!("Property deleted from the database for URL: {}", url);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -262,8 +259,31 @@ mod db_impl {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub wikidata_id: Option<String>,
|
pub wikidata_id: Option<String>,
|
||||||
|
pub custom_properties: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement conversion from DbItem to a JSON-friendly format
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct ItemResponse {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub wikidata_id: Option<String>,
|
||||||
|
pub custom_properties: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DbItem> for ItemResponse {
|
||||||
|
fn from(item: DbItem) -> Self {
|
||||||
|
ItemResponse {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
description: item.description,
|
||||||
|
wikidata_id: item.wikidata_id,
|
||||||
|
custom_properties: item.custom_properties,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub use db_impl::{Database, DbItem};
|
pub use db_impl::{Database, DbItem, ItemResponse};
|
|
@ -4,7 +4,6 @@ use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use compareware::db::{Database, DbItem};
|
use compareware::db::{Database, DbItem};
|
||||||
use compareware::api::{ItemRequest,create_item, get_items, delete_item_by_url};
|
use compareware::api::{ItemRequest,create_item, get_items, delete_item_by_url};
|
||||||
use compareware::models::item::Item;
|
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
|
@ -17,7 +16,6 @@ async fn main() -> std::io::Result<()> {
|
||||||
use compareware::api::{get_items, create_item, delete_item, delete_property, delete_item_by_url, delete_property_by_url, create_item_by_url, get_items_by_url}; // Import API handlers
|
use compareware::api::{get_items, create_item, delete_item, delete_property, delete_item_by_url, delete_property_by_url, create_item_by_url, get_items_by_url}; // Import API handlers
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
let conf = get_configuration(None).await.unwrap();
|
let conf = get_configuration(None).await.unwrap();
|
||||||
|
@ -88,7 +86,7 @@ async fn get_items_handler(
|
||||||
async fn create_item_handler(
|
async fn create_item_handler(
|
||||||
db: web::Data<Arc<Mutex<Database>>>,
|
db: web::Data<Arc<Mutex<Database>>>,
|
||||||
path: web::Path<String>,
|
path: web::Path<String>,
|
||||||
item: web::Json<Item>,
|
item: web::Json<DbItem>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let url = path.into_inner();
|
let url = path.into_inner();
|
||||||
let request = ItemRequest {
|
let request = ItemRequest {
|
||||||
|
|
Loading…
Add table
Reference in a new issue