feat(api): add api for script management (list, create, rename, delete, fill with content)

This commit is contained in:
Mariano Riefolo 2024-08-11 22:05:58 +02:00
parent aa6a48a09b
commit 26e666bae0
3 changed files with 251 additions and 15 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target /target
database.db database.db
.env .env
/scripts

View File

@ -4,6 +4,8 @@ use rusqlite::Connection;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::fs::File;
use std::io::Write;
use validator::Validate; use validator::Validate;
use crate::db; use crate::db;
@ -17,7 +19,12 @@ pub fn get_routes() -> Router {
.route("/change_username", post(change_username)) .route("/change_username", post(change_username))
.route("/change_email", post(change_email)) .route("/change_email", post(change_email))
.route("/change_password", post(change_password)) .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<ChangePasswordPayload>) -> Json
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct DeleteAccountPayload { pub struct GenericTokenPayload {
token: String, token: String,
} }
pub async fn delete_account(Json(payload): Json<DeleteAccountPayload>) -> Json<Value> { pub async fn delete_account(Json(payload): Json<GenericTokenPayload>) -> Json<Value> {
let account_id = match get_account_id_from_jwt(&payload.token) { let account_id = match get_account_id_from_jwt(&payload.token) {
Ok(account_id) => account_id, Ok(account_id) => account_id,
Err(_) => return Json(json!({ "error": "Invalid token" })), Err(_) => return Json(json!({ "error": "Invalid token" })),
@ -236,3 +243,221 @@ pub async fn delete_account(Json(payload): Json<DeleteAccountPayload>) -> Json<V
connection.close().expect("Failed to close"); connection.close().expect("Failed to close");
Json(json!({ "success": "Account deleted with success" })) Json(json!({ "success": "Account deleted with success" }))
} }
pub async fn get_scripts(Json(payload): Json<GenericTokenPayload>) -> Json<Value> {
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<AddScriptPayload>) -> Json<Value> {
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<DeleteScriptPayload>) -> Json<Value> {
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<EditScriptPayload>) -> Json<Value> {
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<RenameScriptPayload>) -> Json<Value> {
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" }))
}
}
}

View File

@ -122,7 +122,17 @@ pub fn get_account_id_from_email(conn: &Connection, email: &str) -> Result<Optio
} }
} }
pub fn get_scripts(conn: &Connection, account_id: usize) -> Result<Vec<(usize, String, usize)>> { pub fn get_account_username(conn: &Connection, id: usize) -> Result<Option<String>> {
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<Vec<(usize, String, String)>> {
let mut stmt = conn.prepare("SELECT id, name, last_edit FROM scripts WHERE account_id = ?1")?; 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 rows = stmt.query([account_id])?;
let mut scripts = Vec::new(); let mut scripts = Vec::new();
@ -138,12 +148,7 @@ pub fn get_scripts(conn: &Connection, account_id: usize) -> Result<Vec<(usize, S
Ok(scripts) Ok(scripts)
} }
pub fn add_script( pub fn add_script(conn: &Connection, name: &str, account_id: usize) -> Result<()> {
conn: &Connection,
name: &str,
last_edit: usize,
account_id: usize,
) -> Result<()> {
let mut stmt = conn.prepare("SELECT id FROM scripts WHERE name = ?1 AND account_id = ?2")?; 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])?; let mut rows = stmt.query([&name as &dyn ToSql, &account_id as &dyn ToSql])?;
if rows.next()?.is_some() { if rows.next()?.is_some() {
@ -151,8 +156,8 @@ pub fn add_script(
} }
conn.execute( conn.execute(
"INSERT INTO scripts (name, last_edit, account_id) VALUES (?1, ?2, ?3)", "INSERT INTO scripts (name, account_id) VALUES (?1, ?2)",
(name, last_edit, account_id), (name, account_id),
)?; )?;
Ok(()) 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<Option<usize>> { pub fn get_script_name(
let mut stmt = conn.prepare("SELECT id FROM scripts WHERE name = ?1 AND account_id = ?2")?; conn: &Connection,
let mut rows = stmt.query([&name as &dyn ToSql, &account_id as &dyn ToSql])?; script_id: usize,
account_id: usize,
) -> Result<Option<String>> {
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()? { if let Some(row) = rows.next()? {
println!("row: {:?}", row);
Ok(Some(row.get(0)?)) Ok(Some(row.get(0)?))
} else { } else {
Ok(None) Ok(None)