use sqlx::{sqlite::SqlitePool, FromRow, Row}; use crate::models::{db_models::exercise_db::ExerciseDb, exercise::Exercise}; pub struct ExerciseRepository<'a> { pub pool: &'a SqlitePool, } impl<'a> ExerciseRepository<'a> { pub fn new(pool: &'a SqlitePool) -> Self { Self { pool } } pub async fn get_exercises_by_node_id(&self, node_id: u32) -> Result, String> { let exercise_rows = sqlx::query("SELECT * FROM exercise WHERE nodeId = ?") .bind(node_id) .fetch_all(self.pool) .await .map_err(|e| format!("ERROR: Failed to query Exercise db: {}", e))?; let exercises = self.parse_exercise_rows(exercise_rows)?; Ok(exercises) } pub async fn get_exercises_by_path_id(&self, path_id: &str) -> Result, String> { let exercise_rows = sqlx::query("SELECT * FROM exercise WHERE pathId = ?") .bind(path_id) .fetch_all(self.pool) .await .map_err(|e| format!("ERROR: Failed to query Exercise db: {}", e))?; if exercise_rows.is_empty() { return Err(format!( "ERROR: No Exercise for path with ID {} found", path_id )); } let exercises = self.parse_exercise_rows(exercise_rows)?; Ok(exercises) } pub async fn get_exercise_by_id(&self, exercise_id: u32) -> Result { let exercise_row = sqlx::query("SELECT * FROM exercise WHERE id = ?") .bind(exercise_id) .fetch_optional(self.pool) .await .map_err(|e| format!("ERROR: Failed to query Exercise db: {}", e))?; let exercise_row = exercise_row .ok_or_else(|| format!("ERROR: No Exercise with ID {} found", exercise_id))?; let exercise_db = ExerciseDb::from_row(&exercise_row) .map_err(|e| format!("ERROR: Could not parse Exercise struct: {}", e))?; let exercise = self.convert_exercise_db_to_model(exercise_db); Ok(exercise) } pub async fn get_exercises_by_type( &self, ex_type: &str, path_id: Option<&str>, ) -> Result, String> { let exercise_rows = if let Some(path_id) = path_id { sqlx::query("SELECT * FROM exercise WHERE ex_type = ? AND pathId = ?") .bind(ex_type) .bind(path_id) .fetch_all(self.pool) .await .map_err(|e| format!("ERROR: Failed to query Exercise db: {}", e))? } else { sqlx::query("SELECT * FROM exercise WHERE ex_type = ?") .bind(ex_type) .fetch_all(self.pool) .await .map_err(|e| format!("ERROR: Failed to query Exercise db: {}", e))? }; let exercises = self.parse_exercise_rows(exercise_rows)?; Ok(exercises) } fn parse_exercise_rows( &self, exercise_rows: Vec, ) -> Result, String> { exercise_rows .iter() .map(|row| { let exercise_db = ExerciseDb::from_row(row) .map_err(|e| format!("ERROR: Could not parse Exercise struct: {}", e))?; Ok(self.convert_exercise_db_to_model(exercise_db)) }) .collect() } fn convert_exercise_db_to_model(&self, exercise_db: ExerciseDb) -> Exercise { Exercise { id: exercise_db.id as u32, ex_type: exercise_db.ex_type, content: exercise_db.content, node_id: exercise_db.node_id as u32, } } pub async fn save_exercise(&self, exercise: &Exercise) -> Result { let query = "INSERT INTO exercise (ex_type, content, nodeId, pathId) VALUES (?, ?, ?, (SELECT pathId FROM node WHERE id = ?)) RETURNING id"; let row = sqlx::query(query) .bind(&exercise.ex_type) .bind(&exercise.content) .bind(exercise.node_id) .bind(exercise.node_id) .fetch_one(self.pool) .await .map_err(|e| format!("ERROR: Failed to save exercise: {}", e))?; let exercise_id: i64 = row .try_get("id") .map_err(|e| format!("ERROR: Failed to get exercise ID: {}", e))?; Ok(exercise_id as u32) } pub async fn save_multiple_exercises( &self, exercises: &[Exercise], ) -> Result, String> { if exercises.is_empty() { return Ok(Vec::new()); } let mut transaction = self .pool .begin() .await .map_err(|e| format!("ERROR: Failed to begin transaction: {}", e))?; let mut exercise_ids = Vec::new(); for exercise in exercises { let row = sqlx::query("INSERT INTO exercise (ex_type, content, nodeId, pathId) VALUES (?, ?, ?, (SELECT pathId FROM node WHERE id = ?)) RETURNING id") .bind(&exercise.ex_type) .bind(&exercise.content) .bind(exercise.node_id) .bind(exercise.node_id) .fetch_one(&mut *transaction) .await .map_err(|e| format!("ERROR: Failed to save exercise in transaction: {}", e))?; let exercise_id: i64 = row .try_get("id") .map_err(|e| format!("ERROR: Failed to get exercise ID: {}", e))?; exercise_ids.push(exercise_id as u32); } transaction .commit() .await .map_err(|e| format!("ERROR: Failed to commit exercise transaction: {}", e))?; Ok(exercise_ids) } pub async fn update_exercise(&self, exercise: &Exercise) -> Result<(), String> { let query = "UPDATE exercise SET ex_type = ?, content = ? WHERE id = ?"; let result = sqlx::query(query) .bind(&exercise.ex_type) .bind(&exercise.content) .bind(exercise.id) .execute(self.pool) .await .map_err(|e| format!("ERROR: Failed to update exercise: {}", e))?; if result.rows_affected() == 0 { return Err(format!("ERROR: No exercise found with ID {}", exercise.id)); } Ok(()) } pub async fn delete_exercise(&self, exercise_id: u32) -> Result<(), String> { let query = "DELETE FROM exercise WHERE id = ?"; let result = sqlx::query(query) .bind(exercise_id) .execute(self.pool) .await .map_err(|e| format!("ERROR: Failed to delete exercise: {}", e))?; if result.rows_affected() == 0 { return Err(format!("ERROR: No exercise found with ID {}", exercise_id)); } Ok(()) } pub async fn delete_exercises_by_node_id(&self, node_id: u32) -> Result { let query = "DELETE FROM exercise WHERE nodeId = ?"; let result = sqlx::query(query) .bind(node_id) .execute(self.pool) .await .map_err(|e| format!("ERROR: Failed to delete exercises by node ID: {}", e))?; Ok(result.rows_affected()) } pub async fn delete_exercises_by_path_id(&self, path_id: &str) -> Result { let query = "DELETE FROM exercise WHERE pathId = ?"; let result = sqlx::query(query) .bind(path_id) .execute(self.pool) .await .map_err(|e| format!("ERROR: Failed to delete exercises by path ID: {}", e))?; Ok(result.rows_affected()) } pub async fn update_exercises_for_node( &self, node_id: u32, exercises: &[Exercise], ) -> Result<(), String> { let mut transaction = self .pool .begin() .await .map_err(|e| format!("ERROR: Failed to begin transaction: {}", e))?; // Delete existing exercises for the node sqlx::query("DELETE FROM exercise WHERE nodeId = ?") .bind(node_id) .execute(&mut *transaction) .await .map_err(|e| format!("ERROR: Failed to delete existing exercises: {}", e))?; // Insert new exercises for exercise in exercises { sqlx::query("INSERT INTO exercise (ex_type, content, nodeId, pathId) VALUES (?, ?, ?, (SELECT pathId FROM node WHERE id = ?))") .bind(&exercise.ex_type) .bind(&exercise.content) .bind(node_id) .bind(node_id) .execute(&mut *transaction) .await .map_err(|e| format!("ERROR: Failed to insert exercise in transaction: {}", e))?; } transaction .commit() .await .map_err(|e| format!("ERROR: Failed to commit exercise update transaction: {}", e))?; Ok(()) } }