diff --git a/actix/src/database.rs b/actix/src/database.rs index 87ddec9..c694c49 100644 --- a/actix/src/database.rs +++ b/actix/src/database.rs @@ -77,6 +77,15 @@ pub fn delete_link(shortlink: String, db: &Connection) -> bool { } } +// Edit Existing Long link. +pub fn edit_link(shortlink: String, longlink: String, db: &Connection) -> bool { + db.execute( + "UPDATE urls SET long_url = ?1 WHERE short_url = ?2;", + [longlink, shortlink], + ) + .is_ok() +} + // Open the DB, and create schema if missing pub fn open_db(path: String) -> Connection { let db = Connection::open(path).expect("Unable to open database!"); diff --git a/actix/src/main.rs b/actix/src/main.rs index 70d069e..27f9ac5 100644 --- a/actix/src/main.rs +++ b/actix/src/main.rs @@ -63,6 +63,7 @@ async fn main() -> Result<()> { .service(services::siteurl) .service(services::version) .service(services::add_link) + .service(services::edit_link) .service(services::delete_link) .service(services::login) .service(services::logout) diff --git a/actix/src/services.rs b/actix/src/services.rs index b01516f..e435cd0 100644 --- a/actix/src/services.rs +++ b/actix/src/services.rs @@ -4,11 +4,7 @@ use actix_files::NamedFile; use actix_session::Session; use actix_web::{ - delete, get, - http::StatusCode, - post, - web::{self, Redirect}, - Either, HttpResponse, Responder, + delete, get, http::StatusCode, post, put, web::{self, Redirect}, Either, HttpResponse, Responder }; use std::env; @@ -128,6 +124,26 @@ pub async fn logout(session: Session) -> HttpResponse { } } +// Edit link +#[put("/api/edit/{shortlink}")] +pub async fn edit_link( + req: String, + shortlink: web::Path, + data: web::Data, + session: Session, +) -> HttpResponse { + if env::var("public_mode") == Ok(String::from("Enable")) || auth::validate(session) { + let out = utils::edit_link(req, shortlink.to_string(), &data.db); + if out.0 { + HttpResponse::Created().body(out.1) + } else { + HttpResponse::Conflict().body(out.1) + } + } else { + HttpResponse::Unauthorized().body("Not logged in!") + } +} + // Delete a given shortlink #[delete("/api/del/{shortlink}")] pub async fn delete_link( @@ -144,4 +160,4 @@ pub async fn delete_link( } else { HttpResponse::Unauthorized().body("Not logged in!") } -} +} \ No newline at end of file diff --git a/actix/src/utils.rs b/actix/src/utils.rs index a72f36e..43b2ff9 100644 --- a/actix/src/utils.rs +++ b/actix/src/utils.rs @@ -17,6 +17,11 @@ struct URLPair { longlink: String, } +#[derive(Deserialize)] +struct EditLinkJson { + longlink: String, +} + // Request the DB for searching an URL pub fn get_longurl(shortlink: String, db: &Connection) -> Option { if validate_link(&shortlink) { @@ -76,6 +81,46 @@ pub fn add_link(req: String, db: &Connection) -> (bool, String) { } } +// Make Check then edit the longurl link +pub fn edit_link(req: String, shortlink: String, db: &Connection) -> (bool, String) { + let chunks: EditLinkJson; + if let Ok(json) = serde_json::from_str(&req) { + chunks = json; + } else { + // shorturl should always be supplied, even if empty + return (false, String::from("Invalid request!")); + } + + if shortlink.is_empty() { + (false, String::from("Invaild edit parameter received.")); + } + + if longurl_compares(shortlink.clone(), chunks.longlink.clone(), db) + { + ( + database::edit_link(shortlink.clone(), chunks.longlink, db), + shortlink, + ) + } else { + ( + false, + String::from("Long/Short URL not valid or already in use!"), + ) + } +} + +// Doing Longurl check(Type None or existed?) +pub fn longurl_compares(shorturl: String, longurl:String, db: &Connection) -> bool { + if get_longurl(shorturl.clone(), db).is_none() { + return false; + } + + if get_longurl(shorturl.clone(), db).unwrap() == longurl { + return false; + } + return true; +} + // Check if link, and request DB to delete it if exists pub fn delete_link(shortlink: String, db: &Connection) -> bool { if validate_link(shortlink.as_str()) { diff --git a/resources/index.html b/resources/index.html index 88c5c8d..7b37db2 100644 --- a/resources/index.html +++ b/resources/index.html @@ -35,10 +35,10 @@ -
+
+ pattern="[a-z0-9\-_]+" title="Only a-z, 0-9, - and _ are allowed" />
@@ -56,6 +56,7 @@ Short URL (click to copy) Long URL Hits + Edit × diff --git a/resources/static/script.js b/resources/static/script.js index 1edb5ac..fb3f898 100644 --- a/resources/static/script.js +++ b/resources/static/script.js @@ -105,19 +105,20 @@ const TR = (row, site) => { var shortTD = null; if (window.isSecureContext) { shortTD = TD(A_SHORT(row["shortlink"], site), "Short URL"); - } - else { + } else { shortTD = TD(A_SHORT_INSECURE(row["shortlink"], site), "Short URL"); } let hitsTD = TD(row["hits"]); hitsTD.setAttribute("label", "Hits"); hitsTD.setAttribute("name", "hitsColumn"); - const btn = deleteButton(row["shortlink"]); + const deleteBtn = deleteButton(row["shortlink"]); + const editBtn = editButton(row["shortlink"]); tr.appendChild(shortTD); tr.appendChild(longTD); tr.appendChild(hitsTD); - tr.appendChild(btn); + tr.appendChild(editBtn); + tr.appendChild(deleteBtn); return tr; } @@ -178,6 +179,53 @@ const deleteButton = (shortUrl) => { return td; } +const editButton = (shortUrl) => { + const td = document.createElement("td"); + const btn = document.createElement("button"); + btn.innerHTML = "Edit"; + + btn.onclick = async (e) => { + e.preventDefault(); + const newUrl = prompt("Enter the new long URL:"); + + if (newUrl) { + if (!isValidUrl(newUrl)) { + showAlert("Invalid URL format. Please enter a valid URL.", "red"); + return; + } + + const response = await fetch(prepSubdir(`/api/edit/${shortUrl}`), { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ longlink: newUrl }), + }); + + if (response.ok) { + showAlert(`Successfully updated ${shortUrl}.`, "green"); + refreshData(); + } else { + const errorMsg = await response.text(); + showAlert(`Error: ${errorMsg}`, "red"); + } + } + }; + + td.setAttribute("name", "editBtn"); + td.appendChild(btn); + return td; +} + +const isValidUrl = (urlString) => { + try { + new URL(urlString); + return true; + } catch (_) { + return false; + } +} + const TD = (s, u) => { const td = document.createElement("td"); const div = document.createElement("div");