diff --git a/README.md b/README.md index 3fc90ee..bacd838 100644 --- a/README.md +++ b/README.md @@ -162,31 +162,6 @@ 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. -### 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 -curl -X POST -d "" -c cookie.txt http://localhost:4567/api/login -``` -You should receive "Correct password!" if the provided password was correct. For any subsequent -request, please add `-b cookie.txt` to provide authentication. - -To add a link, do -```bash -curl -X POST -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 as `json`, do -```bash -curl http://localhost:4567/api/all -``` - -To delete a link, do -```bash -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** @@ -201,6 +176,12 @@ curl -X POST -H "X-API-Key: " -d '{"shortlink":"", "lon ``` 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 @@ -215,6 +196,31 @@ Where `` is name of the shortened link you would like to delete. For 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 +curl -X POST -d "" -c cookie.txt http://localhost:4567/api/login +``` +You should receive "Correct password!" if the provided password was correct. For any subsequent +request, please add `-b cookie.txt` to provide authentication. + +To add a link, do +```bash +curl -X POST -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 as `json`, do +```bash +curl http://localhost:4567/api/all +``` + +To delete a link, do +```bash +curl -X DELETE http://localhost:4567/api/del/ +``` +The server will send a confirmation. + ## Disable authentication If you do not define a password environment variable when starting the docker image, authentication will be disabled. diff --git a/actix/src/database.rs b/actix/src/database.rs index ab0277a..7dbebca 100644 --- a/actix/src/database.rs +++ b/actix/src/database.rs @@ -13,14 +13,21 @@ pub struct DBRow { } // Find a single URL -pub fn find_url(shortlink: &str, db: &Connection) -> 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 FROM urls WHERE short_url = ?1") + .prepare_cached(query) .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..dfd44e9 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, true); + 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, 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 c548907..7bbb412 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, 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, 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, false).0.is_none() { ( database::add_link(chunks.shortlink.clone(), chunks.longlink, db),