From 2b9fafe440a8045717305e7db71abdc63f2cc19e Mon Sep 17 00:00:00 2001 From: SinTan1729 Date: Wed, 8 Jan 2025 20:09:24 +0530 Subject: [PATCH 1/3] new: Got the expand API path working It replies with info for a single shortlink. May be useful for applications using json interface. --- actix/src/database.rs | 10 ++++++---- actix/src/main.rs | 1 + actix/src/services.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- actix/src/utils.rs | 6 +++--- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/actix/src/database.rs b/actix/src/database.rs index ab0277a..09145f2 100644 --- a/actix/src/database.rs +++ b/actix/src/database.rs @@ -13,14 +13,16 @@ pub struct DBRow { } // Find a single URL -pub fn find_url(shortlink: &str, db: &Connection) -> Option { +pub fn find_url(shortlink: &str, db: &Connection) -> (Option, Option) { let mut statement = db - .prepare_cached("SELECT long_url FROM urls WHERE short_url = ?1") + .prepare_cached("SELECT long_url,hits FROM urls WHERE short_url = ?1") .expect("Error preparing SQL statement for find_url."); - statement + let longlink = statement .query_row([shortlink], |row| row.get("long_url")) - .ok() + .ok(); + let hits = statement.query_row([shortlink], |row| row.get("hits")).ok(); + (longlink, hits) } // Get all URLs in DB diff --git a/actix/src/main.rs b/actix/src/main.rs index 825ab4a..f9dd507 100644 --- a/actix/src/main.rs +++ b/actix/src/main.rs @@ -82,6 +82,7 @@ async fn main() -> Result<()> { .service(services::delete_link) .service(services::login) .service(services::logout) + .service(services::expand) .service(Files::new("/", "./resources/").index_file("index.html")) .default_service(actix_web::web::get().to(services::error404)) }) diff --git a/actix/src/services.rs b/actix/src/services.rs index a5c80db..4c99d81 100644 --- a/actix/src/services.rs +++ b/actix/src/services.rs @@ -31,7 +31,7 @@ struct Response { reason: String, } -// Needs to return the short URL to make it easier for programs leveraging the API +// Needed to return the short URL to make it easier for programs leveraging the API #[derive(Serialize)] struct CreatedURL { success: bool, @@ -39,6 +39,15 @@ struct CreatedURL { shorturl: String, } +// Struct for returning information about a shortlink +#[derive(Serialize)] +struct LinkInfo { + success: bool, + error: bool, + longurl: String, + hits: i64, +} + // Define the routes // Add new links @@ -123,6 +132,35 @@ pub async fn getall( } } +// Get information about a single shortlink +#[post("/api/expand")] +pub async fn expand(req: String, data: web::Data, http: HttpRequest) -> HttpResponse { + let result = utils::is_api_ok(http); + if result.success { + let linkinfo = utils::get_longurl(req, &data.db); + if let Some(longlink) = linkinfo.0 { + let body = LinkInfo { + success: true, + error: false, + longurl: longlink, + hits: linkinfo + .1 + .expect("Error getting hit count for existing shortlink."), + }; + HttpResponse::Ok().json(body) + } else { + let body = Response { + success: false, + error: true, + reason: "The shortlink does not exist on the server.".to_string(), + }; + HttpResponse::Unauthorized().json(body) + } + } else { + HttpResponse::Unauthorized().json(result) + } +} + // Get the site URL #[get("/api/siteurl")] pub async fn siteurl() -> HttpResponse { @@ -154,7 +192,7 @@ pub async fn link_handler( data: web::Data, ) -> impl Responder { let shortlink_str = shortlink.to_string(); - if let Some(longlink) = utils::get_longurl(shortlink_str, &data.db) { + if let Some(longlink) = utils::get_longurl(shortlink_str, &data.db).0 { let redirect_method = env::var("redirect_method").unwrap_or(String::from("PERMANENT")); database::add_hit(shortlink.as_str(), &data.db); if redirect_method == "TEMPORARY" { diff --git a/actix/src/utils.rs b/actix/src/utils.rs index c548907..2534b3f 100644 --- a/actix/src/utils.rs +++ b/actix/src/utils.rs @@ -80,11 +80,11 @@ pub fn is_api_ok(http: HttpRequest) -> Response { } // Request the DB for searching an URL -pub fn get_longurl(shortlink: String, db: &Connection) -> Option { +pub fn get_longurl(shortlink: String, db: &Connection) -> (Option, Option) { if validate_link(&shortlink) { database::find_url(shortlink.as_str(), db) } else { - None + (None, None) } } @@ -124,7 +124,7 @@ pub fn add_link(req: String, db: &Connection) -> (bool, String) { } if validate_link(chunks.shortlink.as_str()) - && get_longurl(chunks.shortlink.clone(), db).is_none() + && get_longurl(chunks.shortlink.clone(), db).0.is_none() { ( database::add_link(chunks.shortlink.clone(), chunks.longlink, db), From a60853fd21722b4cd941ccc87231322e1da47699 Mon Sep 17 00:00:00 2001 From: SinTan1729 Date: Thu, 9 Jan 2025 00:21:05 +0530 Subject: [PATCH 2/3] fix: Only pull hits when needed --- actix/src/database.rs | 9 +++++++-- actix/src/services.rs | 4 ++-- actix/src/utils.rs | 6 +++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/actix/src/database.rs b/actix/src/database.rs index 09145f2..7dbebca 100644 --- a/actix/src/database.rs +++ b/actix/src/database.rs @@ -13,9 +13,14 @@ pub struct DBRow { } // Find a single URL -pub fn find_url(shortlink: &str, db: &Connection) -> (Option, Option) { +pub fn find_url(shortlink: &str, db: &Connection, needhits: bool) -> (Option, Option) { + let query = if needhits { + "SELECT long_url,hits FROM urls WHERE short_url = ?1" + } else { + "SELECT long_url FROM urls WHERE short_url = ?1" + }; let mut statement = db - .prepare_cached("SELECT long_url,hits FROM urls WHERE short_url = ?1") + .prepare_cached(query) .expect("Error preparing SQL statement for find_url."); let longlink = statement diff --git a/actix/src/services.rs b/actix/src/services.rs index 4c99d81..dfd44e9 100644 --- a/actix/src/services.rs +++ b/actix/src/services.rs @@ -137,7 +137,7 @@ pub async fn getall( pub async fn expand(req: String, data: web::Data, http: HttpRequest) -> HttpResponse { let result = utils::is_api_ok(http); if result.success { - let linkinfo = utils::get_longurl(req, &data.db); + let linkinfo = utils::get_longurl(req, &data.db, true); if let Some(longlink) = linkinfo.0 { let body = LinkInfo { success: true, @@ -192,7 +192,7 @@ pub async fn link_handler( data: web::Data, ) -> impl Responder { let shortlink_str = shortlink.to_string(); - if let Some(longlink) = utils::get_longurl(shortlink_str, &data.db).0 { + if let Some(longlink) = utils::get_longurl(shortlink_str, &data.db, false).0 { let redirect_method = env::var("redirect_method").unwrap_or(String::from("PERMANENT")); database::add_hit(shortlink.as_str(), &data.db); if redirect_method == "TEMPORARY" { diff --git a/actix/src/utils.rs b/actix/src/utils.rs index 2534b3f..7bbb412 100644 --- a/actix/src/utils.rs +++ b/actix/src/utils.rs @@ -80,9 +80,9 @@ pub fn is_api_ok(http: HttpRequest) -> Response { } // Request the DB for searching an URL -pub fn get_longurl(shortlink: String, db: &Connection) -> (Option, Option) { +pub fn get_longurl(shortlink: String, db: &Connection, needhits: bool) -> (Option, Option) { if validate_link(&shortlink) { - database::find_url(shortlink.as_str(), db) + database::find_url(shortlink.as_str(), db, needhits) } else { (None, None) } @@ -124,7 +124,7 @@ pub fn add_link(req: String, db: &Connection) -> (bool, String) { } if validate_link(chunks.shortlink.as_str()) - && get_longurl(chunks.shortlink.clone(), db).0.is_none() + && get_longurl(chunks.shortlink.clone(), db, false).0.is_none() { ( database::add_link(chunks.shortlink.clone(), chunks.longlink, db), From 1be89db43bd81e0390bb5d1ebbcd07aedbe44854 Mon Sep 17 00:00:00 2001 From: SinTan1729 Date: Thu, 9 Jan 2025 00:27:05 +0530 Subject: [PATCH 3/3] docs: Add info about expand route, and put API as preferred method --- README.md | 62 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3fc90ee..bacd838 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,40 @@ below, replace `http://localhost:4567` with where your instance of `chhoto-url` You can get the version of `chhoto-url` the server is running using `curl http://localhost:4567/api/version` and get the siteurl using `curl http://localhost:4567/api/siteurl`. These routes are accessible without any authentication. +### API key validation +**This is required for programs that rely on a JSON response from Chhoto URL** + +In order to use API key validation, set the `api_key` environment variable. If this is not set, the API will default to cookie +validation (see section above). If the API key is insecure, a warning will be outputted along with a generated API key which may be used. + +Example Linux command for generating a secure API key: `tr -dc A-Za-z0-9 " -d '{"shortlink":"", "longlink":""}' http://localhost:4567/api/new +``` +Send an empty `` if you want it to be auto-generated. The server will reply with the generated shortlink. + +To get information about a single shortlink: +``` bash +curl -H "X-API-Key: " -d '' http://localhost:4567/api/expand +``` +(This route is not accessible using cookie validation.) + +To get a list of all the currently available links: +``` bash +curl -H "X-API-Key: " http://localhost:4567/api/all +``` + +To delete a link: +``` bash +curl -X DELETE -H "X-API-Key: " http://localhost:4567/api/del/ +``` +Where `` is name of the shortened link you would like to delete. For example, if the shortened link is +`http://localhost:4567/example`, `` would be `example`. + +The server will output when the instance is accessed over API, when an incorrect API key is received, etc. + ### Cookie validation If you have set up a password, first do the following to get an authentication cookie and store it in a file. ```bash @@ -187,34 +221,6 @@ curl -X DELETE http://localhost:4567/api/del/ ``` The server will send a confirmation. -### API key validation -**This is required for programs that rely on a JSON response from Chhoto URL** - -In order to use API key validation, set the `api_key` environment variable. If this is not set, the API will default to cookie -validation (see section above). If the API key is insecure, a warning will be outputted along with a generated API key which may be used. - -Example Linux command for generating a secure API key: `tr -dc A-Za-z0-9 " -d '{"shortlink":"", "longlink":""}' http://localhost:4567/api/new -``` -Send an empty `` if you want it to be auto-generated. The server will reply with the generated shortlink. - -To get a list of all the currently available links: -``` bash -curl -H "X-API-Key: " http://localhost:4567/api/all -``` - -To delete a link: -``` bash -curl -X DELETE -H "X-API-Key: " http://localhost:4567/api/del/ -``` -Where `` is name of the shortened link you would like to delete. For example, if the shortened link is -`http://localhost:4567/example`, `` would be `example`. - -The server will output when the instance is accessed over API, when an incorrect API key is received, etc. - ## Disable authentication If you do not define a password environment variable when starting the docker image, authentication will be disabled.