From 26e666bae0f1272d73c60e8bb7252cb6f8312161 Mon Sep 17 00:00:00 2001 From: Mariano Riefolo Date: Sun, 11 Aug 2024 22:05:58 +0200 Subject: [PATCH] feat(api): add api for script management (list, create, rename, delete, fill with content) --- .gitignore | 1 + src/api/mod.rs | 231 ++++++++++++++++++++++++++++++++++++++++++++++++- src/db/mod.rs | 34 +++++--- 3 files changed, 251 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index f7b8bee..492e6f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target database.db .env +/scripts diff --git a/src/api/mod.rs b/src/api/mod.rs index 1a1ea23..a691138 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -4,6 +4,8 @@ use rusqlite::Connection; use serde::Deserialize; use serde::Serialize; use serde_json::{json, Value}; +use std::fs::File; +use std::io::Write; use validator::Validate; use crate::db; @@ -17,7 +19,12 @@ pub fn get_routes() -> Router { .route("/change_username", post(change_username)) .route("/change_email", post(change_email)) .route("/change_password", post(change_password)) - .route("/delete_account", post(delete_account)), + .route("/delete_account", post(delete_account)) + .route("/get_scripts", post(get_scripts)) + .route("/add_script", post(add_script)) + .route("/delete_script", post(delete_script)) + .route("/edit_script", post(edit_script)) + .route("/rename_script", post(rename_script)), ) } @@ -216,11 +223,11 @@ pub async fn change_password(Json(payload): Json) -> Json } #[derive(Deserialize)] -pub struct DeleteAccountPayload { +pub struct GenericTokenPayload { token: String, } -pub async fn delete_account(Json(payload): Json) -> Json { +pub async fn delete_account(Json(payload): Json) -> Json { let account_id = match get_account_id_from_jwt(&payload.token) { Ok(account_id) => account_id, Err(_) => return Json(json!({ "error": "Invalid token" })), @@ -236,3 +243,221 @@ pub async fn delete_account(Json(payload): Json) -> Json) -> Json { + let account_id = match get_account_id_from_jwt(&payload.token) { + Ok(account_id) => account_id, + Err(_) => return Json(json!({ "error": "Invalid token" })), + }; + let connection = Connection::open("database.db").expect("Failed to open database"); + let scripts = db::get_scripts(&connection, account_id); + connection.close().expect("Failed to close database"); + match scripts { + Ok(scripts) => { + let mut result = json!({}); + for (id, name, last_edit) in scripts { + result[id.to_string()] = json!({ "name": name, "last_edit": last_edit }); + } + Json(result) + } + Err(_) => Json(json!({ "error": "Failed to get scripts" })), + } +} + +#[derive(Deserialize)] +pub struct AddScriptPayload { + name: String, + token: String, +} + +pub async fn add_script(Json(payload): Json) -> Json { + let account_id = match get_account_id_from_jwt(&payload.token) { + Ok(account_id) => account_id, + Err(_) => return Json(json!({ "error": "Invalid token" })), + }; + let connection = Connection::open("database.db").expect("Failed to open database"); + match db::add_script(&connection, &payload.name, account_id) { + Ok(_) => { + let username = match db::get_account_username(&connection, account_id) + .expect("Failed to get account username") + { + Some(username) => username, + None => { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Failed to get account username" })); + } + }; + let filename = format!("scripts/{}/{}", username, &payload.name); + std::fs::create_dir_all(format!("scripts/{}", username)) + .expect("Failed to create directory"); + File::create(&filename).expect("Failed to create file"); + + connection.close().expect("Failed to close"); + Json(json!({ "success": "Script added with success" })) + } + Err(_) => { + connection.close().expect("Failed to close"); + Json(json!({ "error": "Failed to add script" })) + } + } +} + +#[derive(Deserialize)] +pub struct DeleteScriptPayload { + script_id: usize, + token: String, +} + +pub async fn delete_script(Json(payload): Json) -> Json { + let account_id = match get_account_id_from_jwt(&payload.token) { + Ok(account_id) => account_id, + Err(_) => return Json(json!({ "error": "Invalid token" })), + }; + let connection = Connection::open("database.db").expect("Failed to open database"); + if !db::script_is_owned_by(&connection, payload.script_id, account_id) + .expect("Failed to check if script is owned by user") + { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Script is not owned by user" })); + } + let script_name = match db::get_script_name(&connection, payload.script_id, account_id) + .expect("Failed to get script name") + { + Some(script_name) => script_name, + None => { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Failed to get script name" })); + } + }; + + match db::remove_script(&connection, payload.script_id) { + Ok(_) => { + let username = match db::get_account_username(&connection, account_id) + .expect("Failed to get account username") + { + Some(username) => username, + None => { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Failed to get account username" })); + } + }; + let filename = format!("scripts/{}/{}", username, script_name); + std::fs::remove_file(&filename).expect("Failed to remove file"); + + connection.close().expect("Failed to close"); + Json(json!({ "success": "Script deleted with success" })) + } + Err(_) => { + connection.close().expect("Failed to close"); + Json(json!({ "error": "Failed to delete script" })) + } + } +} + +#[derive(Deserialize)] +pub struct EditScriptPayload { + script_id: usize, + content: String, + token: String, +} + +pub async fn edit_script(Json(payload): Json) -> Json { + let account_id = match get_account_id_from_jwt(&payload.token) { + Ok(account_id) => account_id, + Err(_) => return Json(json!({ "error": "Invalid token" })), + }; + let connection = Connection::open("database.db").expect("Failed to open database"); + if !db::script_is_owned_by(&connection, payload.script_id, account_id) + .expect("Failed to check if script is owned by user") + { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Script is not owned by user" })); + } + match db::update_script_last_edit(&connection, payload.script_id) { + Ok(_) => { + let username = match db::get_account_username(&connection, account_id) + .expect("Failed to get account username") + { + Some(username) => username, + None => { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Failed to get account username" })); + } + }; + let script_name = match db::get_script_name(&connection, payload.script_id, account_id) + .expect("Failed to get script name") + { + Some(script_name) => script_name, + None => { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Failed to get script name" })); + } + }; + let filename = format!("scripts/{}/{}", username, script_name); + let mut file = File::create(&filename).expect("Failed to open file"); + file.write_all(payload.content.as_bytes()) + .expect("Failed to write to file"); + + connection.close().expect("Failed to close"); + Json(json!({ "success": "Script edited with success" })) + } + Err(_) => { + connection.close().expect("Failed to close"); + Json(json!({ "error": "Failed to edit script" })) + } + } +} + +#[derive(Deserialize)] +pub struct RenameScriptPayload { + script_id: usize, + new_script_name: String, + token: String, +} + +pub async fn rename_script(Json(payload): Json) -> Json { + let account_id = match get_account_id_from_jwt(&payload.token) { + Ok(account_id) => account_id, + Err(_) => return Json(json!({ "error": "Invalid token" })), + }; + let connection = Connection::open("database.db").expect("Failed to open database"); + if !db::script_is_owned_by(&connection, payload.script_id, account_id) + .expect("Failed to check if script is owned by user") + { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Script is not owned by user" })); + } + let old_script_name = match db::get_script_name(&connection, payload.script_id, account_id) + .expect("Failed to get script name") + { + Some(script_name) => script_name, + None => { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Failed to get script name" })); + } + }; + + match db::modify_script_name(&connection, payload.script_id, &payload.new_script_name) { + Ok(_) => { + let username = match db::get_account_username(&connection, account_id) + .expect("Failed to get account username") + { + Some(username) => username, + None => { + connection.close().expect("Failed to close"); + return Json(json!({ "error": "Failed to get account username" })); + } + }; + let old_filename = format!("scripts/{}/{}", username, old_script_name); + let new_filename = format!("scripts/{}/{}", username, &payload.new_script_name); + std::fs::rename(&old_filename, &new_filename).expect("Failed to rename file"); + + connection.close().expect("Failed to close"); + Json(json!({ "success": "Script renamed with success" })) + } + Err(_) => { + connection.close().expect("Failed to close"); + Json(json!({ "error": "Failed to rename script" })) + } + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 032bf1d..a85e659 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -122,7 +122,17 @@ pub fn get_account_id_from_email(conn: &Connection, email: &str) -> Result Result> { +pub fn get_account_username(conn: &Connection, id: usize) -> Result> { + let mut stmt = conn.prepare("SELECT username FROM accounts WHERE id = ?1")?; + let mut rows = stmt.query([id])?; + if let Some(row) = rows.next()? { + Ok(Some(row.get(0)?)) + } else { + Ok(None) + } +} + +pub fn get_scripts(conn: &Connection, account_id: usize) -> Result> { let mut stmt = conn.prepare("SELECT id, name, last_edit FROM scripts WHERE account_id = ?1")?; let mut rows = stmt.query([account_id])?; let mut scripts = Vec::new(); @@ -138,12 +148,7 @@ pub fn get_scripts(conn: &Connection, account_id: usize) -> Result Result<()> { +pub fn add_script(conn: &Connection, name: &str, account_id: usize) -> Result<()> { let mut stmt = conn.prepare("SELECT id FROM scripts WHERE name = ?1 AND account_id = ?2")?; let mut rows = stmt.query([&name as &dyn ToSql, &account_id as &dyn ToSql])?; if rows.next()?.is_some() { @@ -151,8 +156,8 @@ pub fn add_script( } conn.execute( - "INSERT INTO scripts (name, last_edit, account_id) VALUES (?1, ?2, ?3)", - (name, last_edit, account_id), + "INSERT INTO scripts (name, account_id) VALUES (?1, ?2)", + (name, account_id), )?; Ok(()) @@ -169,10 +174,15 @@ pub fn script_is_owned_by(conn: &Connection, script_id: usize, account_id: usize } } -pub fn get_script_id(conn: &Connection, name: &str, account_id: usize) -> Result> { - let mut stmt = conn.prepare("SELECT id FROM scripts WHERE name = ?1 AND account_id = ?2")?; - let mut rows = stmt.query([&name as &dyn ToSql, &account_id as &dyn ToSql])?; +pub fn get_script_name( + conn: &Connection, + script_id: usize, + account_id: usize, +) -> Result> { + let mut stmt = conn.prepare("SELECT name FROM scripts WHERE id = ?1 AND account_id = ?2")?; + let mut rows = stmt.query([&script_id as &dyn ToSql, &account_id as &dyn ToSql])?; if let Some(row) = rows.next()? { + println!("row: {:?}", row); Ok(Some(row.get(0)?)) } else { Ok(None)