From 805ff8cb1d1ba77e9e722fd8d792657bf74afdba Mon Sep 17 00:00:00 2001 From: yaqubroli Date: Thu, 22 Dec 2022 22:46:14 -0800 Subject: [PATCH] Rebuilt templating engine, rebuilt how counts are --- html/index.html | 26 ++++++++------------------ html/url.html | 2 +- src/database.rs | 48 +++++++++++++++++++++++++++++++++++++++++++----- src/endpoints.rs | 77 ++++++++++++++++++++++++++++++++--------------------------------------------- src/templating.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- target/.rustc_info.json | 2 +- html/static/7800.svg | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ html/static/HKGroteskWide-SemiBold.otf | 0 html/static/paste.html | 20 ++++++++++++++++++++ html/static/style.css | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ html/static/url.html | 20 ++++++++++++++++++++ target/debug/url_shortener | 0 12 files changed, 420 insertions(+), 89 deletions(-) diff --git a/html/index.html b/html/index.html index e7fd072..6b6e323 100644 --- a/html/index.html +++ a/html/index.html @@ -1,25 +1,15 @@ - URL submission form - - + +7800.io -

URL shortener

-
- - - - -
-
-

Pastebin

-
- - - - -
+

7800.io

+
+ +

7800.io is the future host of services that provide dynamically generated content.

+ In the meantime, use this domain as a URL shortener or as a pastebin. +
diff --git a/html/url.html b/html/url.html index 61ed301..8e26b47 100644 --- a/html/url.html +++ a/html/url.html @@ -7,6 +7,6 @@

URL shortened successfully

-

Your url is accessible at {{domain}}/{{shortened}}

+

Your url is accessible at {domain}/{shortened}

diff --git a/src/database.rs b/src/database.rs index 7b63c96..50fe4ef 100644 --- a/src/database.rs +++ a/src/database.rs @@ -1,6 +1,6 @@ use actix_settings::BasicSettings; use serde::{Serialize, Deserialize}; -use crate::{settings, shortener}; +use crate::{settings, shortener, url}; use mysql::prelude::*; use mysql::*; @@ -35,6 +35,27 @@ } } +#[derive(Debug, Clone)] +pub struct Count { + pub count: u64, + pub content_type: ContentType +} + +impl From for u64 { + fn from(item: Count) -> Self { + item.count + } +} + +impl From for Count { + fn from(item: u64) -> Self { + Count { + count: item, + content_type: ContentType::All + } + } +} + #[derive(Debug)] pub struct RetrievedUrl { pub url: String, @@ -120,20 +141,26 @@ } // Description: This function takes in a connection and a ContentType, and counts the number of entries with that content_type -pub async fn count_entries (connection: &mut PooledConn, content_type: ContentType) -> u64 { +pub async fn count_entries (connection: &mut PooledConn, content_type: ContentType) -> Count { // if content_type is ContentType::All, then we don't need to filter by content_type if content_type == ContentType::All { let mut result = connection.exec_iter("SELECT COUNT(*) FROM entries", ()).unwrap(); let row = result.next().unwrap(); let count = row.unwrap().get::("COUNT(*)").unwrap(); - return count; + return Count { + count, + content_type + }; } let mut result = connection.exec_iter("SELECT COUNT(*) FROM entries WHERE content_type = :content_type", params! { - "content_type" => content_type as u8 + "content_type" => content_type.clone() as u8 }).unwrap(); let row = result.next().unwrap(); let count = row.unwrap().get::("COUNT(*)").unwrap(); - count + Count { + count, + content_type + } } // Description: This function takes in a connection and a shortened url and returns a RetrievedEntry struct, where `content` is the content column and `success` is true if the shortened url exists in the database @@ -141,7 +168,7 @@ let mut result = connection.exec_iter("SELECT content, content_type FROM entries WHERE shortened = :shortened", params! { "shortened" => shortened }).unwrap(); - let row = result.next().unwrap(); + let row = result.next().expect("No entry found"); let content = row.as_ref().unwrap().get::("content").unwrap(); let content_type = ContentType::from(row.as_ref().unwrap().get::("content_type").unwrap()); RetrievedEntry { @@ -153,7 +180,7 @@ // Description: This function takes in a connection, a `content` string, and a content_type u8 and returns a SubmittedEntry struct, where `shortened` is the shortened url column and `success` is true if the shortened url does not exist in the database. pub async fn submit_entry (connection: &mut PooledConn, content: &str, content_type: &ContentType) -> SubmittedEntry { - let shortened = shortener::base64(count_entries(connection, ContentType::All).await); + let shortened = shortener::base64(count_entries(connection, ContentType::All).await.count); let row = connection.exec_iter("SELECT shortened FROM entries WHERE shortened = :shortened", params! { "shortened" => shortened.clone() }).unwrap().next(); @@ -164,7 +191,12 @@ } } else { connection.exec_drop("INSERT INTO entries (content, shortened, content_type) VALUES (:content, :shortened, :content_type)", params! { - "content" => content, + "content" => + if content_type == &ContentType::Url { + url::format_url(content.to_string()) + } else { + content.to_string() + }, "shortened" => shortened.clone(), "content_type" => u8::from(content_type.clone()) }).unwrap(); diff --git a/src/endpoints.rs b/src/endpoints.rs index 452959c..6a807e4 100644 --- a/src/endpoints.rs +++ a/src/endpoints.rs @@ -1,9 +1,8 @@ use std::path::PathBuf; -use actix_files::NamedFile; -use actix_web::{web, HttpResponse, Responder}; +use actix_web::{web, HttpResponse, Responder, HttpRequest}; use serde::{Deserialize, Serialize}; -use crate::{database, shortener, url, templating, full_path}; +use crate::{database, templating, full_path}; use crate::database::ContentType; #[derive(Deserialize, Serialize, Debug)] @@ -12,27 +11,21 @@ content_type: ContentType } -pub async fn static_file(path: web::Path, app_data: web::Data) -> impl Responder { +pub async fn static_file(path: web::Path, req: HttpRequest, app_data: web::Data) -> impl Responder { let path_string = path.into_inner(); println!("Accessing file {:?}", path_string); - let body = if app_data.config.application.html.template_static && path_string.ends_with(".html") { - templating::read_and_apply_templates( - full_path::get_full_path(&app_data, &path_string, true), - templating::TemplateSchema { - content: "".to_string(), - shortened: "".to_string(), - domain: app_data.config.application.html.domain.clone(), - count: if app_data.config.application.html.count { - database::count_entries(&mut app_data.database.get_conn().unwrap(), ContentType::All).await.to_string() - } else { - "".to_string() - } - } + if app_data.config.application.html.template_static && path_string.ends_with(".html") { + HttpResponse::Ok().body( + templating::read_and_apply_templates( + full_path::get_full_path(&app_data, &path_string, true), + app_data.clone(), + &mut templating::TemplateSchema::create_null_schema() + ).await ) } else { - std::fs::read_to_string(full_path::get_full_path(&app_data, &path_string, true)).unwrap() - }; - HttpResponse::Ok().body(body) + // get the file we want to access as stream + actix_files::NamedFile::open_async(full_path::get_full_path(&app_data, &path_string, true)).await.unwrap().into_response(&req) + } } pub async fn index(app_data: web::Data) -> impl Responder { @@ -45,17 +38,9 @@ "index.html", false ), - templating::TemplateSchema { - content: "".to_string(), - shortened: "".to_string(), - domain: app_data.config.application.html.domain.clone(), - count: if app_data.config.application.html.count { - database::count_entries(&mut app_data.database.get_conn().unwrap(), ContentType::All).await.to_string() - } else { - "".to_string() - } - } - ) + app_data, + &mut templating::TemplateSchema::create_null_schema() + ).await ) } else { HttpResponse::Ok().body( @@ -84,13 +69,15 @@ "paste.html" }, false), - templating::TemplateSchema { - content: form.content.clone(), - shortened: submitted_entry.shortened.clone(), - domain: app_data.config.application.html.domain.clone(), - count: count.to_string() - } - ) + app_data.clone(), + &mut templating::TemplateSchema::new( + ( + form.content.clone(), + submitted_entry.shortened.clone(), + count + ) + ) + ).await ); } else { // If the content type is a URL, tell them the shortened URL, otherwise tell them the paste ID @@ -124,17 +111,9 @@ if app_data.config.application.html.template { templating::read_and_apply_templates( full_path::get_full_path(&app_data, "404.html", false), - templating::TemplateSchema { - content: "".to_string(), - shortened: "".to_string(), - domain: app_data.config.application.html.domain.clone(), - count: if app_data.config.application.html.count { - database::count_entries(&mut app_data.database.get_conn().unwrap(), ContentType::All).await.to_string() - } else { - "".to_string() - } - } - ) + app_data.clone(), + &mut templating::TemplateSchema::create_null_schema() + ).await } else { std::fs::read_to_string(PathBuf::from(format!("static/404.html"))).unwrap() } diff --git a/src/templating.rs b/src/templating.rs index 9536232..96610bd 100644 --- a/src/templating.rs +++ a/src/templating.rs @@ -1,22 +1,127 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; +use actix_web::web; +use crate::{AppData, database}; +use crate::database::Count; + +#[derive(Debug, Clone)] pub struct TemplateSchema { pub content: String, pub shortened: String, - pub domain: String, - pub count: String + pub count: Vec +} + +pub trait IntoTemplateSchema { + fn into(self) -> TemplateSchema; +} + +impl IntoTemplateSchema for TemplateSchema { + fn into(self) -> TemplateSchema { + self + } +} + +impl IntoTemplateSchema for (String, String) { + fn into(self) -> TemplateSchema { + TemplateSchema { + content: self.0, + shortened: self.1, + count: Vec::new() + } + } +} + +impl IntoTemplateSchema for (String, String, Vec) { + fn into(self) -> TemplateSchema { + TemplateSchema { + content: self.0, + shortened: self.1, + count: self.2 + } + } } -pub fn read_and_apply_templates(path: PathBuf, schema: TemplateSchema) -> String { +impl IntoTemplateSchema for (String, String, Count) { + fn into(self) -> TemplateSchema { + TemplateSchema { + content: self.0, + shortened: self.1, + count: vec![self.2] + } + } +} + +impl TemplateSchema { + pub fn new(to_schema: T) -> TemplateSchema { + to_schema.into() + } + pub fn create_null_schema() -> TemplateSchema { + TemplateSchema { + content: "".to_string(), + shortened: "".to_string(), + count: Vec::new() + } + } +} + +async fn get_count_and_update_schema (schema: &mut TemplateSchema, app_data: &web::Data) { + let mut connection = app_data.database.get_conn().unwrap(); + let count = database::count_entries(&mut connection, database::ContentType::All).await; + schema.count.push(count); +} + +pub async fn get_necessary_value(key: String, key_value: u8, app_data: web::Data, schema: &mut TemplateSchema) -> String { + // create a vector of (ContentType, i64) tuples + match key.as_str() { + "content" => + schema.content.as_str().to_string(), + "shortened" => + schema.shortened.as_str().to_string(), + "domain" => + app_data.config.application.html.domain.clone(), + "count" => + match schema.count.iter().find(|&x| x.content_type == key_value.into()) { + Some(count) => count.count.to_string(), + None => { + get_count_and_update_schema(schema, &app_data).await; + schema.count.iter().find(|&x| x.content_type == key_value.into()).unwrap().count.to_string() + } + }, + _ => panic!("Invalid key") + } +} + +pub async fn read_and_apply_templates(path: PathBuf, app_data: web::Data, schema: &mut TemplateSchema) -> String { + // read the file into a string let mut file = File::open(path).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); - // Hardcoded templates, will change this if/when the amount of templates increases - contents - .replace("{{content}}", &schema.content) - .replace("{{shortened}}", &schema.shortened) - .replace("{{domain}}", &schema.domain) - .replace("{{count}}", &schema.count) -}+ // replace all instances of {key} with the value, and return the string + let mut new_contents = String::new(); + let mut key = String::new(); + let mut key_value = 0; + let mut in_key = false; + for c in contents.chars() { + if c == '{' { + in_key = true; + } else if c == '}' { + in_key = false; + let value = get_necessary_value(key.clone(), key_value, app_data.clone(), schema).await; + new_contents.push_str(value.as_str()); + key = String::new(); + key_value = 0; + } else if in_key { + if c == ':' { + key_value = key.parse::().unwrap(); + key = String::new(); + } else { + key.push(c); + } + } else { + new_contents.push(c); + } + } + new_contents +} diff --git a/target/.rustc_info.json b/target/.rustc_info.json index 7928b5a..84d1174 100644 --- a/target/.rustc_info.json +++ a/target/.rustc_info.json @@ -1,1 +1,1 @@ -{"rustc_fingerprint":13098977256995595289,"outputs":{"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.65.0\nbinary: rustc\ncommit-hash: unknown\ncommit-date: unknown\nhost: aarch64-apple-darwin\nrelease: 1.65.0\nLLVM version: 15.0.0\n","stderr":""},"10376369925670944939":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/opt/homebrew/Cellar/rust/1.65.0\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"15697416045686424142":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""}},"successes":{}}+{"rustc_fingerprint":13098977256995595289,"outputs":{"10376369925670944939":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/opt/homebrew/Cellar/rust/1.65.0\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_arch=\"aarch64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"aes\"\ntarget_feature=\"crc\"\ntarget_feature=\"dit\"\ntarget_feature=\"dotprod\"\ntarget_feature=\"dpb\"\ntarget_feature=\"dpb2\"\ntarget_feature=\"fcma\"\ntarget_feature=\"fhm\"\ntarget_feature=\"flagm\"\ntarget_feature=\"fp16\"\ntarget_feature=\"frintts\"\ntarget_feature=\"jsconv\"\ntarget_feature=\"lor\"\ntarget_feature=\"lse\"\ntarget_feature=\"neon\"\ntarget_feature=\"paca\"\ntarget_feature=\"pacg\"\ntarget_feature=\"pan\"\ntarget_feature=\"pmuv3\"\ntarget_feature=\"ras\"\ntarget_feature=\"rcpc\"\ntarget_feature=\"rcpc2\"\ntarget_feature=\"rdm\"\ntarget_feature=\"sb\"\ntarget_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"ssbs\"\ntarget_feature=\"vh\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.65.0\nbinary: rustc\ncommit-hash: unknown\ncommit-date: unknown\nhost: aarch64-apple-darwin\nrelease: 1.65.0\nLLVM version: 15.0.0\n","stderr":""},"15697416045686424142":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""}},"successes":{}}diff --git a/html/static/7800.svg b/html/static/7800.svg new file mode 100644 index 0000000..354c087 100644 --- /dev/null +++ a/html/static/7800.svg @@ -1,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/html/static/HKGroteskWide-SemiBold.otf b/html/static/HKGroteskWide-SemiBold.otf new file mode 100644 index 0000000000000000000000000000000000000000..8a96f8caa67a95d8f3fbc8258275aa40c7b79ff5 100644 Binary files /dev/null and a/html/static/HKGroteskWide-SemiBold.otf differ diff --git a/html/static/paste.html b/html/static/paste.html new file mode 100644 index 0000000..a77a42e 100644 --- /dev/null +++ a/html/static/paste.html @@ -1,0 +1,20 @@ + + + + +7800.io: Pastebin + + +

Pastebin

+
+
+

New paste

+
+ + + +
+
+
+ +diff --git a/html/static/style.css b/html/static/style.css index 359f792..5665cf2 100644 --- a/html/static/style.css +++ a/html/static/style.css @@ -1,5 +1,101 @@ +@keyframes fadeInDownward { + 0% { + opacity: 0; + transform: translateY(-30px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +@font-face { + font-family: "HK Grotesk"; + src: url("/static/HKGroteskWide-SemiBold.otf"); +} + +html { + height: 100%; + background: linear-gradient(#650b0b, #ab1616); +} + +h1 { + display: none; +} + +main { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +main * { + animation-name: fadeInDownward; + animation-duration: 1s; +} + body { - font-family: sans-serif; - background-color: #f0f0f0; + font-family: "HK Grotesk"; + font-size: 1.5em; + text-transform: uppercase; text-align: center; + color: white; +} + +#logo { + width: 25em; + margin-bottom: 2.5em; +} + +/* if viewport is iphone sized */ +@media screen and (max-width: 375px) { + #logo { + max-width: 75%; + min-width: 10em; + } +} + +a { + color: white; + text-decoration: none; + border-bottom: 2px solid white; +} + +/* make #ui a rounded black rectangle with lots of padding */ +#ui { + display: inline-block; + padding: 1em 2em; + background: black; + border-radius: 1em; +} + +/* make .text, which is an input text field, use the same font as the rest of the website. make it a black box with a thick white outline */ +.text { + display: block; + font-family: monospace; + color: white; + background: black; + border: 2px dashed white; + padding: 0.5em 1em; + margin: 1em 0; +} + +/* make .button a black button with thick white outline that uses the same font as the rest of the website */ +.button { + font-family: "HK Grotesk"; + text-transform: uppercase; + text-align: center; + color: black; + background: white; + border: none; + padding: 0.5em 1em; + /* make the button a rounded rectangle */ + border-radius: 1em; +} + +/* make textarea a reasonable size by default */ +textarea { + width: 20em; + height: 10em; }diff --git a/html/static/url.html b/html/static/url.html new file mode 100644 index 0000000..4f9c413 100644 --- /dev/null +++ a/html/static/url.html @@ -1,0 +1,20 @@ + + + + +7800.io: URL Shortener + + +

URL Shortener

+
+
+

Shorten a URL

+
+ + + +
+
+
+ +diff --git a/target/debug/url_shortener b/target/debug/url_shortener index 810b445b86457400f02a7c03324665984f33364c..93b43235902d088f703d90c27b6673e0f2da9704 100755 Binary files a/target/debug/url_shortener and a/target/debug/url_shortener differ -- rgit 0.1.5