From fdf335b346c1d2d91fa499c1a2a8c399985a51d7 Mon Sep 17 00:00:00 2001 From: Nico Date: Sat, 1 Nov 2025 20:47:04 +0100 Subject: [PATCH] added database models and models for continous use. Also changed get all paths according to that. Nothing is testet yet! --- devenv.lock | 12 +- src-tauri/migrations/0001_path_table.down.sql | 4 +- src-tauri/migrations/0001_path_table.up.sql | 12 +- src-tauri/src/lib.rs | 1 - src-tauri/src/models/db_models/exercise_db.rs | 7 + src-tauri/src/models/db_models/mod.rs | 3 + src-tauri/src/models/db_models/node_db.rs | 7 + src-tauri/src/models/db_models/path_db.rs | 17 ++ src-tauri/src/models/exercise.rs | 9 +- src-tauri/src/models/mod.rs | 2 + src-tauri/src/models/node.rs | 15 +- src-tauri/src/models/path.rs | 24 +- src-tauri/src/repositories/path_repository.rs | 218 ++++++++++++++++-- 13 files changed, 280 insertions(+), 51 deletions(-) create mode 100644 src-tauri/src/models/db_models/exercise_db.rs create mode 100644 src-tauri/src/models/db_models/mod.rs create mode 100644 src-tauri/src/models/db_models/node_db.rs create mode 100644 src-tauri/src/models/db_models/path_db.rs diff --git a/devenv.lock b/devenv.lock index 6e2114e..6a4523d 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,10 +3,10 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1761343822, + "lastModified": 1761922975, "owner": "cachix", "repo": "devenv", - "rev": "679d2951cee2d09da3c732d00b320ce752d21ee0", + "rev": "c9f0b47815a4895fadac87812de8a4de27e0ace1", "type": "github" }, "original": { @@ -19,10 +19,10 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1747046372, + "lastModified": 1761588595, "owner": "edolstra", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", "type": "github" }, "original": { @@ -74,10 +74,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1758532697, + "lastModified": 1761313199, "owner": "cachix", "repo": "devenv-nixpkgs", - "rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f", + "rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff", "type": "github" }, "original": { diff --git a/src-tauri/migrations/0001_path_table.down.sql b/src-tauri/migrations/0001_path_table.down.sql index 4f01373..60ae5ae 100644 --- a/src-tauri/migrations/0001_path_table.down.sql +++ b/src-tauri/migrations/0001_path_table.down.sql @@ -1,2 +1,2 @@ -DROP TABLE pathVersions; -DROP TABLE path; \ No newline at end of file +DROP TABLE pathMetadata; +DROP TABLE path; diff --git a/src-tauri/migrations/0001_path_table.up.sql b/src-tauri/migrations/0001_path_table.up.sql index 0be4f62..cdd6588 100644 --- a/src-tauri/migrations/0001_path_table.up.sql +++ b/src-tauri/migrations/0001_path_table.up.sql @@ -1,12 +1,14 @@ CREATE TABLE path ( - id integer primary key, + id text primary key, title text, description text, versions text ); -CREATE TABLE pathVersions ( - pathId integer references path(id), - versionNumber string, +CREATE TABLE pathMetadata ( + pathId text references path(id), + versionNumber text, + createdAt text, + updatedAt text, primary key (pathId, versionNumber) -); \ No newline at end of file +); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index b3297bf..d48233d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,3 @@ -// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ use sqlx::{migrate::MigrateDatabase, sqlite::SqlitePoolOptions, Pool, Sqlite}; use tauri::{App, Manager}; diff --git a/src-tauri/src/models/db_models/exercise_db.rs b/src-tauri/src/models/db_models/exercise_db.rs new file mode 100644 index 0000000..4979333 --- /dev/null +++ b/src-tauri/src/models/db_models/exercise_db.rs @@ -0,0 +1,7 @@ +#[derive(Debug, sqlx::FromRow)] +pub struct ExerciseDb { + pub id: u16, + pub node_id: u32, + pub ex_type: String, + pub content: String, +} diff --git a/src-tauri/src/models/db_models/mod.rs b/src-tauri/src/models/db_models/mod.rs new file mode 100644 index 0000000..bf1438f --- /dev/null +++ b/src-tauri/src/models/db_models/mod.rs @@ -0,0 +1,3 @@ +pub mod path_db; +pub mod node_db; +pub mod exercise_db; diff --git a/src-tauri/src/models/db_models/node_db.rs b/src-tauri/src/models/db_models/node_db.rs new file mode 100644 index 0000000..ac7c388 --- /dev/null +++ b/src-tauri/src/models/db_models/node_db.rs @@ -0,0 +1,7 @@ +#[derive(Debug, sqlx::FromRow)] +pub struct NodeDb { + pub id: u32, + pub title: String, + pub description: String, + pub path_id: String +} diff --git a/src-tauri/src/models/db_models/path_db.rs b/src-tauri/src/models/db_models/path_db.rs new file mode 100644 index 0000000..9a335c4 --- /dev/null +++ b/src-tauri/src/models/db_models/path_db.rs @@ -0,0 +1,17 @@ +use chrono::{DateTime, Utc}; + + +#[derive(sqlx::FromRow, Debug)] +pub struct PathDb { + pub id: String, + pub title: String, + pub description: String, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct MetadataDb { + pub path_id : String, + pub version: String, + pub created_at: String, + pub updated_at: String, +} diff --git a/src-tauri/src/models/exercise.rs b/src-tauri/src/models/exercise.rs index 0d55b6f..511ae24 100644 --- a/src-tauri/src/models/exercise.rs +++ b/src-tauri/src/models/exercise.rs @@ -1,6 +1,7 @@ +#[derive(Debug, Clone)] pub struct Exercise { - id: u16, - ex_type: String, - content: String, - node_id: u32 + pub id: u16, + pub ex_type: String, + pub content: String, + pub node_id: u32, } diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index c1e36d6..3a6d0c4 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -1,3 +1,5 @@ pub mod path; pub mod node; pub mod exercise; + +pub mod db_models; diff --git a/src-tauri/src/models/node.rs b/src-tauri/src/models/node.rs index 580d426..749f74b 100644 --- a/src-tauri/src/models/node.rs +++ b/src-tauri/src/models/node.rs @@ -1,7 +1,10 @@ -#[derive(Debug, sqlx::FromRow)] -pub struct Node { - id: u32, - title: String, - description: String, - path_id: String +use crate::models::exercise::Exercise; + +#[derive(Debug)] +pub struct Node{ + pub id: u32, + pub title: String, + pub description: String, + pub path_id: String, + pub exercises: Vec, } diff --git a/src-tauri/src/models/path.rs b/src-tauri/src/models/path.rs index 9e8ca81..64d6137 100644 --- a/src-tauri/src/models/path.rs +++ b/src-tauri/src/models/path.rs @@ -1,21 +1,21 @@ use chrono::{DateTime, Utc}; -use sqlx::sqlite::SqliteRow; -use sqlx::Row; use crate::models::node::Node; -#[derive(sqlx::FromRow, Debug)] + +#[derive(Debug)] pub struct Path { - id: String, - title: String, - description: String, - nodes: Vec, - metadata: Metadata, + pub id: String, + pub title: String, + pub description: String, + pub metadata: Vec, + pub nodes: Vec, } -#[derive(Debug, sqlx::FromRow)] +#[derive(Debug)] pub struct Metadata { - versions: Vec, - created_at: DateTime, - updated_at: DateTime, + pub path_id : String, + pub version: String, + pub created_at: DateTime, + pub updated_at: DateTime, } diff --git a/src-tauri/src/repositories/path_repository.rs b/src-tauri/src/repositories/path_repository.rs index c0fe09b..74c3274 100644 --- a/src-tauri/src/repositories/path_repository.rs +++ b/src-tauri/src/repositories/path_repository.rs @@ -1,6 +1,19 @@ -use sqlx::{sqlite::SqlitePool, FromRow}; +use sqlx::{ + sqlite::{SqlitePool, SqliteRow}, + FromRow, +}; +use std::collections::HashMap; -use crate::models::path::Path; +use crate::models::{ + db_models::{ + exercise_db::ExerciseDb, + node_db::NodeDb, + path_db::{MetadataDb, PathDb}, + }, + exercise::Exercise, + node::Node, + path::{Metadata, Path}, +}; pub struct PathRepository<'a> { pub pool: &'a SqlitePool, @@ -8,36 +21,211 @@ pub struct PathRepository<'a> { impl<'a> PathRepository<'a> { pub async fn get_path_by_id(&self, id: i32) -> Result { - let result = sqlx::query("SELECT * FROM path WHERE id = ?") + // Get Path + let path_result = sqlx::query("SELECT * FROM path WHERE id = ?") .bind(id) .fetch_all(self.pool) - .await - .map_err(|e| format!("ERROR: Failed to query Path db: {} ", e)) - .unwrap(); + .await; - if result.len() > 1 { + let path_result: Vec = match path_result { + Ok(r) => r, + Err(e) => { + return Err(format!("ERROR: Failed to query Path db: {} ", e)); + } + }; + + if path_result.len() > 1 { return Err(format!("ERROR: Multiple paths for ID {} found", id)); - } else if result.is_empty(){ + } else if path_result.is_empty() { return Err(format!("ERROR: No Path with ID {} found", id)); } - let path = match result.first() { - Some(p) => match Path::from_row(p) { - Ok(p) => {p}, - Err(e) => {return Err(format!("ERROR: Could not parse Path: {}", e));}, - }, + let path_result = match path_result.first() { + Some(p) => match PathDb::from_row(p) { + Ok(p) => p, + Err(e) => { + return Err(format!("ERROR: Could not parse Path: {}", e)); + } + }, None => return Err(format!("ERROR: No path for ID {} found", id)), }; + // Get Metadata for path + let metadata_result = sqlx::query("SELECT * From pathMetadata where pathId = ?") + .bind(path_result.id.clone()) + .fetch_all(self.pool) + .await; + + let metadata_result = match metadata_result { + Ok(r) => r, + Err(e) => { + return Err(format!("ERROR: Failed to query Metadata db: {}", e)); + } + }; + + if metadata_result.is_empty() { + return Err(format!( + "ERROR: No metadata for path [{:?}] found", + path_result + )); + } + + let metadata_result: Result, String> = metadata_result + .iter() + .map(|row| { + MetadataDb::from_row(row) + .map_err(|e| format!("ERROR: Could not parse Metadata struct: {}", e)) + }) + .collect(); + + let metadata_result = match metadata_result { + Ok(r) => r, + Err(e) => return Err(e), + }; + + // Get nodes for path + let node_result = sqlx::query("SELECT * From node where pathId = ?") + .bind(path_result.id.clone()) + .fetch_all(self.pool) + .await; + + let node_result = match node_result { + Ok(r) => r, + Err(e) => { + return Err(format!("ERROR: Failed to query Node db: {}", e)); + } + }; + + if node_result.is_empty() { + return Err(format!( + "ERROR: No Nodes for path [{:?}] found", + path_result + )); + } + + let node_result: Result, String> = node_result + .iter() + .map(|row| { + NodeDb::from_row(row) + .map_err(|e| format!("ERROR: Could not parse Node struct: {}", e)) + }) + .collect(); + + let node_result = match node_result { + Ok(r) => r, + Err(e) => return Err(e), + }; + + // Get exercises for path + let exercise_result = sqlx::query("SELECT * From exercise where pathId = ?") + .bind(path_result.id.clone()) + .fetch_all(self.pool) + .await; + + let exercise_result = match exercise_result { + Ok(r) => r, + Err(e) => { + return Err(format!("ERROR: Failed to query Exercise db: {}", e)); + } + }; + + if exercise_result.is_empty() { + return Err(format!( + "ERROR: No Exercise for path [{:?}] found", + path_result + )); + } + + let exercise_result: Result, String> = exercise_result + .iter() + .map(|row| { + ExerciseDb::from_row(row) + .map_err(|e| format!("ERROR: Could not parse Exercise struct: {}", e)) + }) + .collect(); + + let exercise_result = match exercise_result { + Ok(r) => r, + Err(e) => return Err(e), + }; + + // Convert metadata + let metadata: Vec = metadata_result + .iter() + .map(|m| Metadata { + path_id: m.path_id.clone(), + version: m.version.clone(), + created_at: m.created_at.parse().unwrap(), + updated_at: m.updated_at.parse().unwrap(), + }) + .collect(); + + // Group exercises by node_id + let mut exercises_by_node: HashMap> = HashMap::new(); + for exercise_db in exercise_result { + let exercise = Exercise { + id: exercise_db.id, + ex_type: exercise_db.ex_type, + content: exercise_db.content, + node_id: exercise_db.node_id, + }; + + exercises_by_node + .entry(exercise_db.node_id) + .or_insert_with(Vec::new) + .push(exercise); + } + + // Create nodes with their respective exercises + let nodes: Vec = node_result + .iter() + .map(|node_db| Node { + id: node_db.id, + title: node_db.title.clone(), + description: node_db.description.clone(), + path_id: node_db.path_id.clone(), + exercises: exercises_by_node + .get(&node_db.id) + .cloned() + .unwrap_or_else(Vec::new), + }) + .collect(); + + let path = Path { + id: path_result.id, + title: path_result.title, + description: path_result.description, + metadata, + nodes, + }; + Ok(path) } pub async fn get_all_paths(&self) -> Result, String> { - let rows = sqlx::query_as::<_, Path>("SELECT * FROM paths") + let rows = sqlx::query_as::<_, PathDb>("SELECT * FROM path") .fetch_all(self.pool) .await .map_err(|e| e.to_string())?; - Ok(rows) + let mut paths = Vec::new(); + + for path_db in rows { + match self.get_path_by_id(path_db.id.parse().unwrap_or(0)).await { + Ok(path) => paths.push(path), + Err(e) => { + eprintln!("Warning: Failed to load path {}: {}", path_db.id, e); + // Continue with other paths instead of failing completely + continue; + } + } + } + + Ok(paths) + } + + pub async fn save_path(&self, _path: Path) -> Result<(), String> { + // TODO: Implement path saving logic + todo!("Implement save_path functionality") } }