From 427926001d76e55ba5888f1c12a6b23d510cdc47 Mon Sep 17 00:00:00 2001 From: thevickypedia Date: Fri, 15 Mar 2024 14:21:42 -0500 Subject: [PATCH] Make context menu appear right next to the pointer Make custom error responses generic across the app --- src/routes/auth.rs | 4 +-- src/routes/basics.rs | 4 +-- src/routes/fileIO.rs | 13 +++++---- src/routes/media.rs | 58 +++++++++++++++++++++++--------------- src/squire/custom.rs | 60 ++++++++++++++++++++++++++++++++++++++++ src/squire/logger.rs | 29 ------------------- src/squire/mod.rs | 6 ++-- src/squire/responses.rs | 48 -------------------------------- src/templates/listing.rs | 31 +++++++++++++++------ version.sh | 51 ---------------------------------- 10 files changed, 131 insertions(+), 173 deletions(-) create mode 100644 src/squire/custom.rs delete mode 100644 src/squire/logger.rs delete mode 100644 src/squire/responses.rs delete mode 100644 version.sh diff --git a/src/routes/auth.rs b/src/routes/auth.rs index 43b1e91..a73f6d4 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -50,7 +50,7 @@ pub async fn login(request: HttpRequest, } let mapped = verified.unwrap(); - let (_host, _last_accessed) = squire::logger::log_connection(&request, &session); + let (_host, _last_accessed) = squire::custom::log_connection(&request, &session); let payload = serde_json::to_string(&mapped).unwrap(); let encrypted_payload = fernet.encrypt(payload.as_bytes()); @@ -166,7 +166,7 @@ pub async fn home(request: HttpRequest, if !auth_response.ok { return failed_auth(auth_response, &config); } - let (_host, _last_accessed) = squire::logger::log_connection(&request, &session); + let (_host, _last_accessed) = squire::custom::log_connection(&request, &session); log::debug!("{}", auth_response.detail); let listing_page = squire::content::get_all_stream_content(&config, &auth_response); diff --git a/src/routes/basics.rs b/src/routes/basics.rs index e466647..2addeee 100644 --- a/src/routes/basics.rs +++ b/src/routes/basics.rs @@ -38,7 +38,7 @@ pub async fn root(request: HttpRequest, session: web::Data>, metadata: web::Data>, template: web::Data>>) -> HttpResponse { - let (_host, _last_accessed) = squire::logger::log_connection(&request, &session); + let (_host, _last_accessed) = squire::custom::log_connection(&request, &session); let index = template.get_template("index").unwrap(); HttpResponse::build(StatusCode::OK) .content_type("text/html; charset=utf-8") @@ -70,7 +70,7 @@ pub async fn profile(request: HttpRequest, if !auth_response.ok { return routes::auth::failed_auth(auth_response, &config); } - let (_host, last_accessed) = squire::logger::log_connection(&request, &session); + let (_host, last_accessed) = squire::custom::log_connection(&request, &session); let index = template.get_template("profile").unwrap(); let mut access_map = HashMap::new(); if !last_accessed.is_empty() { diff --git a/src/routes/fileIO.rs b/src/routes/fileIO.rs index fb84a97..7b416a9 100644 --- a/src/routes/fileIO.rs +++ b/src/routes/fileIO.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::http::StatusCode; use fernet::Fernet; use serde::Deserialize; @@ -79,12 +80,10 @@ pub async fn edit(request: HttpRequest, if !auth_response.ok { return routes::auth::failed_auth(auth_response, &config); } - let (_host, _last_accessed) = squire::logger::log_connection(&request, &session); + let (_host, _last_accessed) = squire::custom::log_connection(&request, &session); log::debug!("{}", auth_response.detail); let extracted = extract_media_path(payload, &config.media_source); - // todo: pop up doesn't always occur next to the mouse - // styling of the pop up is very basic - // make custom error responses generic + // todo: styling of the pop up is very basic let media_path: PathBuf = match extracted { Ok(path) => { path @@ -94,10 +93,12 @@ pub async fn edit(request: HttpRequest, } }; if !squire::authenticator::verify_secure_index(&PathBuf::from(&media_path), &auth_response.username) { - return squire::responses::restricted( + return squire::custom::error( + "RESTRICTED SECTION", template.get_template("error").unwrap(), - &auth_response.username, &metadata.pkg_version, + format!("This content is not accessible, as it does not belong to the user profile '{}'", auth_response.username), + StatusCode::FORBIDDEN ); } if let Some(edit_action) = request.headers().get("edit-action") { diff --git a/src/routes/media.rs b/src/routes/media.rs index 9e0d63e..bd379f0 100644 --- a/src/routes/media.rs +++ b/src/routes/media.rs @@ -96,13 +96,15 @@ pub async fn track(request: HttpRequest, return routes::auth::failed_auth(auth_response, &config); } if !squire::authenticator::verify_secure_index(&PathBuf::from(&info.file), &auth_response.username) { - return squire::responses::restricted( + return squire::custom::error( + "RESTRICTED SECTION", template.get_template("error").unwrap(), - &auth_response.username, - &metadata.pkg_version + &metadata.pkg_version, + format!("This content is not accessible, as it does not belong to the user profile '{}'", auth_response.username), + StatusCode::FORBIDDEN ); } - let (_host, _last_accessed) = squire::logger::log_connection(&request, &session); + let (_host, _last_accessed) = squire::custom::log_connection(&request, &session); log::debug!("{}", auth_response.detail); log::debug!("Track requested: {}", &info.file); let filepath = Path::new(&config.media_source).join(&info.file); @@ -111,9 +113,13 @@ pub async fn track(request: HttpRequest, Ok(content) => HttpResponse::Ok() .content_type("text/plain") .body(content), - Err(_) => squire::responses::not_found(template.get_template("error").unwrap(), - &format!("'{}' was not found", &info.file), - &metadata.pkg_version) + Err(_) => squire::custom::error( + "CONTENT UNAVAILABLE", + template.get_template("error").unwrap(), + &metadata.pkg_version, + format!("'{}' was not found", &info.file), + StatusCode::NOT_FOUND + ) } } @@ -164,14 +170,16 @@ pub async fn stream(request: HttpRequest, if !auth_response.ok { return routes::auth::failed_auth(auth_response, &config); } - let (_host, _last_accessed) = squire::logger::log_connection(&request, &session); + let (_host, _last_accessed) = squire::custom::log_connection(&request, &session); log::debug!("{}", auth_response.detail); let filepath = media_path.to_string(); if !squire::authenticator::verify_secure_index(&PathBuf::from(&filepath), &auth_response.username) { - return squire::responses::restricted( + return squire::custom::error( + "RESTRICTED SECTION", template.get_template("error").unwrap(), - &auth_response.username, - &metadata.pkg_version + &metadata.pkg_version, + format!("This content is not accessible, as it does not belong to the user profile '{}'", auth_response.username), + StatusCode::FORBIDDEN ); } let secure_path = if filepath.contains(constant::SECURE_INDEX) { "true" } else { "false" }; @@ -179,11 +187,13 @@ pub async fn stream(request: HttpRequest, // True path of the media file let __target = config.media_source.join(&filepath); if !__target.exists() { - return squire::responses::not_found( + return squire::custom::error( + "CONTENT UNAVAILABLE", template.get_template("error").unwrap(), - &format!("'{}' was not found", filepath), - &metadata.pkg_version - ); + &metadata.pkg_version, + format!("'{}' was not found", filepath), + StatusCode::NOT_FOUND + ) } // True path of the media file as a String let __target_str = __target.to_string_lossy().to_string(); @@ -294,13 +304,15 @@ pub async fn streaming_endpoint(request: HttpRequest, } let media_path = config.media_source.join(&info.file); if !squire::authenticator::verify_secure_index(&media_path, &auth_response.username) { - return squire::responses::restricted( + return squire::custom::error( + "RESTRICTED SECTION", template.get_template("error").unwrap(), - &auth_response.username, - &metadata.pkg_version + &metadata.pkg_version, + format!("This content is not accessible, as it does not belong to the user profile '{}'", auth_response.username), + StatusCode::FORBIDDEN ); } - let (host, _last_accessed) = squire::logger::log_connection(&request, &session); + let (host, _last_accessed) = squire::custom::log_connection(&request, &session); if media_path.exists() { let file = actix_files::NamedFile::open_async(media_path).await.unwrap(); // Check if the host is making a continued connection streaming the same file @@ -313,9 +325,11 @@ pub async fn streaming_endpoint(request: HttpRequest, } let error = format!("File {:?} not found", media_path); log::error!("{}", error); - squire::responses::not_found( + squire::custom::error( + "CONTENT UNAVAILABLE", template.get_template("error").unwrap(), - &error, - &metadata.pkg_version + &metadata.pkg_version, + format!("'{}' was not found", &info.file), + StatusCode::NOT_FOUND ) } diff --git a/src/squire/custom.rs b/src/squire/custom.rs new file mode 100644 index 0000000..bb80e3b --- /dev/null +++ b/src/squire/custom.rs @@ -0,0 +1,60 @@ +use actix_web::{HttpRequest, HttpResponse}; +use actix_web::http::StatusCode; +use minijinja::Template; + +use crate::constant; + +/// Logs connection information for an incoming HTTP request. +/// +/// # Arguments +/// +/// * `request` - A reference to the Actix web `HttpRequest` object. +/// * `session` - Session struct that holds the `session_mapping` and `session_tracker` to handle sessions. +/// +/// This function logs the host and user agent information of the incoming connection. +/// +/// # Returns +/// +/// Returns a tuple of the host, and the last streamed file path. +pub fn log_connection(request: &HttpRequest, session: &constant::Session) -> (String, String) { + let host = request.connection_info().host().to_string(); + let mut tracker = session.tracker.lock().unwrap(); + if tracker.get(&host).is_none() { + tracker.insert(host.clone(), "".to_string()); + log::info!("Connection received from {}", host); + if let Some(user_agent) = request.headers().get("user-agent") { + log::info!("User agent: {}", user_agent.to_str().unwrap()) + } + } + return (host.clone(), tracker.get(&host).map_or("".to_string(), |s| s.to_string())); +} + +/// Frames a custom response into an error page. +/// +/// # Arguments +/// +/// * `title` - Title to be displayed in the error page. +/// * `error` - Jinja template for the error page. +/// * `version` - Application's version in the title tag of the webpage. +/// * `description` - Description to be displayed in the error page. +/// * `status_code` - Status code of the response. +/// +/// # Returns +/// +/// Returns an HTTPResponse with the appropriate status code formatted as HTML. +pub fn error(title: &str, + error: Template, + version: &String, + description: String, + status_code: StatusCode) -> HttpResponse { + HttpResponse::build(status_code) + .content_type("text/html; charset=utf-8") + .body(error.render(minijinja::context!( + version => version, + title => title, + description => description, + help => r"Lost your way?\n\nHit the HOME button to navigate back to home page.", + button_text => "HOME", button_link => "/home", + block_navigation => true + )).unwrap()) +} diff --git a/src/squire/logger.rs b/src/squire/logger.rs deleted file mode 100644 index aae19ca..0000000 --- a/src/squire/logger.rs +++ /dev/null @@ -1,29 +0,0 @@ -use actix_web::HttpRequest; - -use crate::constant; - - -/// Logs connection information for an incoming HTTP request. -/// -/// # Arguments -/// -/// * `request` - A reference to the Actix web `HttpRequest` object. -/// * `session` - Session struct that holds the `session_mapping` and `session_tracker` to handle sessions. -/// -/// This function logs the host and user agent information of the incoming connection. -/// -/// # Returns -/// -/// Returns a tuple of the host, and the last streamed file path. -pub fn log_connection(request: &HttpRequest, session: &constant::Session) -> (String, String) { - let host = request.connection_info().host().to_string(); - let mut tracker = session.tracker.lock().unwrap(); - if tracker.get(&host).is_none() { - tracker.insert(host.clone(), "".to_string()); - log::info!("Connection received from {}", host); - if let Some(user_agent) = request.headers().get("user-agent") { - log::info!("User agent: {}", user_agent.to_str().unwrap()) - } - } - return (host.clone(), tracker.get(&host).map_or("".to_string(), |s| s.to_string())) -} diff --git a/src/squire/mod.rs b/src/squire/mod.rs index 561210b..c5bbb93 100644 --- a/src/squire/mod.rs +++ b/src/squire/mod.rs @@ -4,8 +4,8 @@ pub mod settings; pub mod startup; /// Module for the functions that handle encryption/encoding and decryption/decoding. pub mod secure; -/// Module for the function that logs the incoming connection information. -pub mod logger; +/// Module for custom functions that logs connection information and builds custom error responses. +pub mod custom; /// Module for the functions that yield an ASCII art to print during startup. pub mod ascii_art; /// Module for the CORS middleware configuration. @@ -18,5 +18,3 @@ pub mod content; pub mod authenticator; /// Module that handles parsing command line arguments. pub mod parser; -/// Module that handles custom error responses to the user. -pub mod responses; diff --git a/src/squire/responses.rs b/src/squire/responses.rs deleted file mode 100644 index 9334f39..0000000 --- a/src/squire/responses.rs +++ /dev/null @@ -1,48 +0,0 @@ -use actix_web::http::StatusCode; -use actix_web::HttpResponse; -use minijinja::Template; - -/// Frames a response for Not Found [404] into an error page. -/// -/// # Arguments -/// -/// * `error` - Jinja template for the error page. -/// * `description` - Description to be rendered in the UI. -/// -/// # Returns -/// -/// Returns an HTTPResponse with 404 error code formatted as HTML. -pub fn not_found(error: Template, description: &String, version: &String) -> HttpResponse { - HttpResponse::build(StatusCode::NOT_FOUND) - .content_type("text/html; charset=utf-8") - .body(error.render(minijinja::context!( - version => version, - title => "CONTENT UNAVAILABLE", - description => description, - help => r"Lost your way?\n\nHit the HOME button to navigate back to home page.", - button_text => "HOME", button_link => "/home" - )).unwrap()) -} - -/// Frames a response for Forbidden [403] into an error page. -/// -/// # Arguments -/// -/// * `error` - Jinja template for the error page. -/// * `username` - Username whose access is forbidden. -/// -/// # Returns -/// -/// Returns an HTTPResponse with 403 error code formatted as HTML. -pub fn restricted(error: Template, username: &String, version: &String) -> HttpResponse { - HttpResponse::build(StatusCode::FORBIDDEN) - .content_type("text/html; charset=utf-8") - .body(error.render(minijinja::context!( - version => version, - title => "RESTRICTED SECTION", - description => format!("This content is not accessible, as it does not belong to the user profile '{}'", username), - help => r"Lost your way?\n\nHit the HOME button to navigate back to home page.", - button_text => "HOME", button_link => "/home", - block_navigation => true - )).unwrap()) -} diff --git a/src/templates/listing.rs b/src/templates/listing.rs index 1bf303b..5bf209c 100644 --- a/src/templates/listing.rs +++ b/src/templates/listing.rs @@ -253,10 +253,24 @@ pub fn get_content() -> String { // Set the global variable to the current file path currentPath = path; - // Position the context menu beneath the clicked icon + // Calculate the appropriate coordinates for the context menu + var mouseX = event.clientX; + var mouseY = event.clientY; + var windowWidth = window.innerWidth; + var windowHeight = window.innerHeight; + var contextMenuWidth = contextMenu.offsetWidth; + var contextMenuHeight = contextMenu.offsetHeight; + var scrollX = window.scrollX || window.pageXOffset; + var scrollY = window.scrollY || window.pageYOffset; + + // Adjust the coordinates considering the scroll position and moving 2 pixels away from the mouse pointer + var menuX = mouseX + scrollX + contextMenuWidth > windowWidth ? mouseX + scrollX - contextMenuWidth - 2 : mouseX + scrollX + 2; + var menuY = mouseY + scrollY + contextMenuHeight > windowHeight ? mouseY + scrollY - contextMenuHeight - 2 : mouseY + scrollY + 2; + + // Position the context menu at the calculated coordinates + contextMenu.style.left = menuX + 'px'; + contextMenu.style.top = menuY + 'px'; contextMenu.style.display = 'block'; - contextMenu.style.left = event.clientX + 'px'; - contextMenu.style.top = event.clientY + 'px'; } function editAction(action, trueURL, relativePath) { @@ -264,11 +278,6 @@ pub fn get_content() -> String { http.open('POST', window.location.origin + `/edit`, true); // asynchronous session http.setRequestHeader('Content-Type', 'application/json'); // Set content type to JSON http.setRequestHeader('edit-action', action); - let data = { - url_locator: trueURL, - path_locator: relativePath - }; - let jsonData = JSON.stringify(data); http.onreadystatechange = function() { if (http.readyState === XMLHttpRequest.DONE) { if (http.status === 200) { @@ -278,7 +287,11 @@ pub fn get_content() -> String { } } }; - http.send(jsonData); + let data = { + url_locator: trueURL, + path_locator: relativePath + }; + http.send(JSON.stringify(data)); } function getConfirmation(fileName, action) { diff --git a/version.sh b/version.sh deleted file mode 100644 index 21a666c..0000000 --- a/version.sh +++ /dev/null @@ -1,51 +0,0 @@ -current_version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') -versions=$(curl -s https://crates.io/api/v1/crates/RuStream | jq -r '.versions | map(.num)') -latest_version=$(echo "$versions" | jq -r '.[0]') -echo "Current Package Version: ${current_version}" -echo "Latest Package Version: $latest_version" -version_exists=false -for version in $(echo "$versions" | jq -r '.[]'); do - if [ "$version" == "$current_version" ]; then - version_exists=true - break - fi -done -if [ "$version_exists" = true ]; then - echo "Current version ['$current_version'] found in crates.io" - release="false"; -else - echo "Current version ['$current_version'] unavailable in crates.io" - release="true"; -fi - -if [ "$release" == "true" ]; then - echo "Creating PROD release" - release_tag="v$current_version" - cargo_prerelease=("alpha" "beta" "rc") - prerelease=false - for cargo_pre in "${cargo_prerelease[@]}"; do - if [[ "$current_version" == *"$cargo_pre"* ]]; then - prerelease=true - break - fi - done -else - echo "Creating TEST release" - epoch="$(date +%s)" - version_as_int=$((10#${current_version//./})) - ((version_as_int++)) - major=$((version_as_int / 100)) - minor=$((version_as_int % 100 / 10)) - patch=$((version_as_int % 10)) - new_version="$major.$minor.$patch" - echo "Bumped version to: $new_version" - release_tag="v${new_version}-prerelease-${epoch}" - prerelease=true -fi -#commit_msg="$(git log -1 --pretty=%B | sed ':a;N;$!ba;s/\n/\\n/g')" -#commit_msg=$(git log -1 --pretty=%B | tr '\n' '\\n' | awk '{$1=$1};1') -commit_msg=$(git log -1 --pretty=%B | tr -d '\n' | sed 's/^[ \t]*//;s/[ \t]*$//' | sed 's/$/\\n/') - -release_data="{\"tag_name\":\"$release_tag\",\"name\":\"$release_tag\",\"body\":\"$commit_msg\",\"draft\":false,\"prerelease\":$prerelease}" -echo "" -echo "$release_data"