From 5042f009cb9c1d0039ddd00e7102e26a7ab17654 Mon Sep 17 00:00:00 2001 From: Ivan Bushchik Date: Mon, 5 Jun 2023 12:04:27 +0300 Subject: [PATCH] 0.3.0: Add more api requests --- Cargo.toml | 2 +- README.md | 80 ++++++++++++++++++++++++++++-- src/main.rs | 98 ++++++++++-------------------------- src/post.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 75 deletions(-) create mode 100644 src/post.rs diff --git a/Cargo.toml b/Cargo.toml index b913610..13be0de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aliurl" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" repository = "https://github.com/ivabus/aliurl" diff --git a/README.md b/README.md index a6ad6e0..cd4ed2a 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,14 @@ Edit `Rocket.toml` to set port and ip. cargo run -r ``` -## Usage +## API ### Create new alias #### Request ```http request -POST /post HTTP/1.1 +POST /api/create_alias HTTP/1.1 ``` #### Request body @@ -45,8 +45,82 @@ POST /post HTTP/1.1 } ``` -### Use alias +### Get all aliases +#### Request + +```http request +POST /api/create_alias HTTP/1.1 +``` + +#### Request body + +```json +{ + "access_key": "" // May not be provided, if no ./access_keys file +} +``` + +#### Response body + +```json +[ + { + "alias": "alias_without_ad", + "url": "https://example.com" + }, + { + "alias": "alias_with_ad", + "redirect_with_ad": true, + "url": "https://example.com" + } +] +``` + +### Remove alias + +Removes all alias with provided name. + +#### Request + +```http request +POST /api/remove_alias HTTP/1.1 +``` + +#### Request body + +```json +{ + "alias": "", + "access_key": "" // May not be provided, if no ./access_keys file +} +``` + +#### Response + +##### Alias(es) removed + +```json +[ + { + "alias": "alias", + "url": "https://example.com" + }, + { + "alias": "alias", + "redirect_with_ad": true, + "url": "https://another.com" + } +] +``` + +##### No aliases found + +```json +[] +``` + +### Use alias #### Request diff --git a/src/main.rs b/src/main.rs index 9aacaa3..9c9dc06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,19 @@ +mod post; + #[macro_use] extern crate rocket; use std::io::prelude::*; use std::io::BufReader; -use rocket::http::RawStr; use rocket::http::Status; use rocket::response::content::RawHtml; use rocket::response::Redirect; use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize)] -struct CreateAliasRequest { - url: String, - redirect_with_ad: Option, - access_key: Option, - alias: Option, -} - static mut ACCESS_KEY_REQUIRED: bool = true; const INDEX_REDIRECT: &'static str = "https://ivabus.dev"; +const INDEX_WITH_AD: bool = true; #[derive(Deserialize, Serialize, Clone)] struct Alias { @@ -29,7 +23,7 @@ struct Alias { redirect_with_ad: Option, } -fn read_alias() -> Vec { +fn read_aliases() -> Vec { if !std::path::Path::new("./alias.json").exists() { let mut file = std::fs::File::create("./alias.json").unwrap(); file.write_all(b"[]").unwrap(); @@ -44,63 +38,8 @@ fn read_alias() -> Vec { let mut buf_reader = BufReader::new(file); let mut contents = String::new(); buf_reader.read_to_string(&mut contents).unwrap(); - let alias_list: Vec = serde_json::from_str(&contents).unwrap(); - alias_list -} - -#[post("/post", data = "")] -fn create_alias(data: &RawStr) -> (Status, String) { - let data: CreateAliasRequest = match serde_json::from_str(&data.to_string()) { - Ok(req) => req, - Err(e) => return (Status::BadRequest, format!("Error: {e}")), - }; - let mut file = std::fs::File::open("./access_keys").unwrap(); - let mut buffer: String = String::new(); - file.read_to_string(&mut buffer).unwrap(); - let access_keys: Vec<&str> = buffer.split("\n").collect(); - if let Some(key) = data.access_key { - if !access_keys.contains(&key.as_str()) { - return (Status::Forbidden, "Access key is invalid".to_string()); - } - } else { - unsafe { - if ACCESS_KEY_REQUIRED { - return (Status::Forbidden, "Access key needs to be provided".to_string()); - } - } - }; - - let mut alias_list = read_alias(); - let mut file = std::fs::File::options().write(true).open("./alias.json").unwrap(); - let alias = match data.alias { - None => uuid::Uuid::new_v4().to_string(), - Some(alias) => alias, - }; - if alias.contains("?") { - return (Status::BadRequest, format!("Error: alias should not contain '?'")); - } - if let Some(s) = data.redirect_with_ad { - if s.to_lowercase() == "true" { - alias_list.push(Alias { - url: data.url.clone(), - alias: alias.clone(), - redirect_with_ad: Some(true), - }); - } - } else { - alias_list.push(Alias { - url: data.url.clone(), - alias: alias.clone(), - redirect_with_ad: None, - }); - } - - alias_list.dedup_by(|a, b| a.alias == b.alias); - - file.write_all(serde_json::to_string(&alias_list).unwrap().as_bytes()).unwrap(); - - file.sync_all().unwrap(); - return (Status::Ok, format!("Created {} at {}", data.url, alias)); + let aliases_list: Vec = serde_json::from_str(&contents).unwrap(); + aliases_list } #[get("/404")] @@ -112,7 +51,7 @@ fn not_found() -> Status { async fn get_page(page: String) -> Result> { let mut decoded_page = String::new(); url_escape::decode_to_string(page, &mut decoded_page); - let alias_list = read_alias(); + let alias_list = read_aliases(); for i in alias_list { if i.alias == decoded_page { if let Some(red) = i.redirect_with_ad { @@ -130,8 +69,15 @@ async fn get_page(page: String) -> Result> { } #[get("/")] -async fn get_index() -> Redirect { - Redirect::to(INDEX_REDIRECT) +async fn get_index() -> Result> { + if INDEX_WITH_AD { + let mut redirect = String::new(); + let mut file = std::fs::File::open("./redirect.html").unwrap(); + file.read_to_string(&mut redirect).unwrap(); + Err(RawHtml(redirect.replace("#REDIRECT#", INDEX_REDIRECT))) + } else { + Ok(Redirect::to(INDEX_REDIRECT)) + } } #[rocket::main] @@ -155,7 +101,17 @@ async fn main() -> Result<(), rocket::Error> { } let _rocket = rocket::build() - .mount("/", routes![not_found, create_alias, get_page, get_index]) + .mount( + "/", + routes![ + not_found, + post::create_alias, + post::get_aliases, + post::remove_alias, + get_page, + get_index + ], + ) .launch() .await?; diff --git a/src/post.rs b/src/post.rs new file mode 100644 index 0000000..7e206b6 --- /dev/null +++ b/src/post.rs @@ -0,0 +1,140 @@ +use crate::*; +use rocket::http::{RawStr, Status}; +use rocket::response::content::RawJson; +use serde_json::json; + +#[derive(Debug, Deserialize)] +struct CreateAliasRequest { + url: String, + redirect_with_ad: Option, + access_key: Option, + alias: Option, +} + +#[derive(Debug, Deserialize)] +struct GetAliasesRequest { + access_key: Option, +} + +#[derive(Debug, Deserialize)] +struct RemoveAliasRequest { + alias: String, + access_key: Option, +} + +fn check_access_key(key: Option) -> Result)> { + let mut file = std::fs::File::open("./access_keys").unwrap(); + let mut buffer: String = String::new(); + file.read_to_string(&mut buffer).unwrap(); + let access_keys: Vec<&str> = buffer.split("\n").collect(); + return if let Some(key) = key { + if !access_keys.contains(&key.as_str()) { + Err((Status::Forbidden, RawJson(json!({"Error": "Invalid access key"}).to_string()))) + } else { + Ok(key) + } + } else { + unsafe { + if ACCESS_KEY_REQUIRED { + Err((Status::Forbidden, RawJson(json!({"Error": "No access key"}).to_string()))) + } else { + Ok("".to_string()) + } + } + }; +} + +#[post("/api/create_alias", data = "")] +pub fn create_alias(data: &RawStr) -> (Status, RawJson) { + let data: CreateAliasRequest = match serde_json::from_str(&data.to_string()) { + Ok(req) => req, + Err(e) => { + return (Status::BadRequest, RawJson(json!({"Error": e.to_string()}).to_string())) + } + }; + + if let Err(e) = check_access_key(data.access_key) { + return e; + } + + let mut aliases_list = read_aliases(); + let mut file = std::fs::File::options().write(true).open("./alias.json").unwrap(); + let alias = match data.alias { + None => uuid::Uuid::new_v4().to_string(), + Some(alias) => alias, + }; + if alias.contains("?") { + return (Status::BadRequest, RawJson(json!({"Error": "No access key"}).to_string())); + } + let alia = if let Some(s) = data.redirect_with_ad { + if s.to_lowercase() == "true" { + Alias { + url: data.url.clone(), + alias: alias.clone(), + redirect_with_ad: Some(true), + } + } else { + Alias { + url: data.url.clone(), + alias: alias.clone(), + redirect_with_ad: None, + } + } + } else { + Alias { + url: data.url.clone(), + alias: alias.clone(), + redirect_with_ad: None, + } + }; + aliases_list.push(alia.clone()); + aliases_list.dedup_by(|a, b| a.alias == b.alias); + + file.write_all(serde_json::to_string(&aliases_list).unwrap().as_bytes()).unwrap(); + + file.sync_all().unwrap(); + return (Status::Ok, RawJson(serde_json::to_string(&alia).unwrap())); +} + +#[post("/api/get_aliases", data = "")] +pub fn get_aliases(data: &RawStr) -> (Status, RawJson) { + let data: GetAliasesRequest = match serde_json::from_str(&data.to_string()) { + Ok(req) => req, + Err(e) => { + return (Status::BadRequest, RawJson(json!({"Error": format!("{e}")}).to_string())) + } + }; + if let Err(e) = check_access_key(data.access_key) { + return e; + } + + return (Status::Ok, RawJson(serde_json::to_string(&read_aliases()).unwrap())); +} + +#[post("/api/remove_alias", data = "")] +pub fn remove_alias(data: &RawStr) -> (Status, RawJson) { + let data: RemoveAliasRequest = match serde_json::from_str(&data.to_string()) { + Ok(req) => req, + Err(e) => { + return (Status::BadRequest, RawJson(json!({"Error": format!("{e}")}).to_string())) + } + }; + if let Err(e) = check_access_key(data.access_key) { + return e; + } + let mut aliases_list = read_aliases(); + let mut removed_aliases: Vec = vec![]; + let mut file = std::fs::File::options().write(true).open("./alias.json").unwrap(); + + for i in (0..aliases_list.len()).rev() { + if aliases_list[i].alias == data.alias { + removed_aliases.push(aliases_list.remove(i)); + } + } + let aliases_list = serde_json::to_string(&aliases_list).unwrap(); + file.write_all(&aliases_list.as_bytes()).unwrap(); + file.set_len(aliases_list.as_bytes().len() as u64).unwrap(); + file.sync_all().unwrap(); + + return (Status::Ok, RawJson(serde_json::to_string(&removed_aliases).unwrap())); +}