Rebuilt templating engine, rebuilt how counts are
Diff
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(-)
@@ -1,25 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>URL submission form</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="static/style.css" />
<link rel="stylesheet" href="static/style.css">
<title>7800.io</title>
</head>
<body>
<h1>URL shortener</h1>
<form action="/" method="post">
<input type="hidden" name="content_type" value="Url" />
<label for="content">URL:</label>
<input type="text" name="content" id="url" />
<input type="submit" value="Shorten" />
</form>
<hr>
<h1>Pastebin</h1>
<form action="/" method="post">
<input type="hidden" name="content_type" value="Pastebin" />
<label for="content">Paste:</label>
<textarea name="content" id="paste" rows="10" cols="50"></textarea>
<input type="submit" value="Paste" />
</form>
<h1>7800.io</h1>
<main>
<img src="static/7800.svg" alt="7800.io" id="logo">
<p>7800.io is the future host of services that provide dynamically generated content.</p>
<small>In the meantime, use this domain as a <a href="static/url.html">URL shortener</a> or as a <a>pastebin</a>.</small>
</main>
</body>
</html>
@@ -7,6 +7,6 @@
</head>
<body>
<h1>URL shortened successfully</h1>
<p>Your url is accessible at {{domain}}/{{shortened}}</p>
<p>Your url is accessible at {domain}/{shortened}</p>
</body>
</html>
@@ -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<Count> for u64 {
fn from(item: Count) -> Self {
item.count
}
}
impl From<u64> 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 @@
}
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 == ContentType::All {
let mut result = connection.exec_iter("SELECT COUNT(*) FROM entries", ()).unwrap();
let row = result.next().unwrap();
let count = row.unwrap().get::<u64, _>("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::<u64, _>("COUNT(*)").unwrap();
count
Count {
count,
content_type
}
}
@@ -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::<String, _>("content").unwrap();
let content_type = ContentType::from(row.as_ref().unwrap().get::<u8, _>("content_type").unwrap());
RetrievedEntry {
@@ -153,7 +180,7 @@
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();
@@ -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<String>, app_data: web::Data<crate::AppData>) -> impl Responder {
pub async fn static_file(path: web::Path<String>, req: HttpRequest, app_data: web::Data<crate::AppData>) -> 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)
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<crate::AppData>) -> 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 {
@@ -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()
}
@@ -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<Count>
}
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<Count>) {
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<T: IntoTemplateSchema>(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<AppData>) {
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<AppData>, schema: &mut TemplateSchema) -> String {
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<AppData>, schema: &mut TemplateSchema) -> String {
let mut file = File::open(path).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
contents
.replace("{{content}}", &schema.content)
.replace("{{shortened}}", &schema.shortened)
.replace("{{domain}}", &schema.domain)
.replace("{{count}}", &schema.count)
}
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::<u8>().unwrap();
key = String::new();
} else {
key.push(c);
}
} else {
new_contents.push(c);
}
}
new_contents
}
@@ -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":{}}
@@ -1,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="3415.6"
height="776.4"
version="1.1"
viewBox="0 0 3415.6 776.4"
xml:space="preserve"
id="svg31"
sodipodi:docname="7800.svg"
inkscape:version="1.2 (dc2aeda, 2022-05-15)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs35" /><sodipodi:namedview
id="namedview33"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
showguides="false"
inkscape:zoom="0.24645277"
inkscape:cx="1091.487"
inkscape:cy="355.0376"
inkscape:window-width="1728"
inkscape:window-height="1051"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="svg31" />
<style
type="text/css"
id="style9">
.st0{fill:#FFFFFF;}
</style>
<g
transform="matrix(0.99999988,0,0,0.9994848,-441.33547,-3.2881357)"
id="g29">
<path
class="st0"
d="m 2221.4356,129.18983 c -59.1,-82.299999 -146,-125.8999994 -251.1,-125.8999994 -105.2,0 -192,43.5000004 -251.1,125.8999994 -49.7,69.3 -77.1,162.4 -77.1,262.2 0,193.2 101.5,388.2 328.2,388.2 226.7,0 328.2,-195 328.2,-388.2 0,-99.8 -27.3,-192.9 -77.1,-262.2 z m -251.1,420.6 c -79.1,0 -87.7,-107.4 -87.7,-159.2 0,-51.3 8.7,-157.4 87.7,-157.4 79,0 87.7,106.1 87.7,157.4 -0.1,51.8 -8.7,159.2 -87.7,159.2 z"
id="path11" />
<path
class="st0"
d="m 2877.8356,129.18983 c -59.1,-82.299999 -145.9,-125.8999994 -251.1,-125.8999994 -105.2,0 -192,43.5000004 -251.1,125.8999994 -49.7,69.3 -77.1,162.4 -77.1,262.2 0,193.2 101.5,388.2 328.2,388.2 226.7,0 328.2,-195 328.2,-388.2 0.1,-99.8 -27.2,-192.9 -77.1,-262.2 0.1,0 0,0 0,0 z m -251.1,420.6 c -79.1,0 -87.7,-107.4 -87.7,-159.2 0,-51.3 8.7,-157.4 87.7,-157.4 79,0 87.7,106.1 87.7,157.4 0,51.8 -8.7,159.2 -87.7,159.2 z"
id="path13" />
<polygon
class="st0"
points="1663.6,760.3 1988.3,14 1326.3,14 1326.3,252.5 1633.4,252.5 1387.7,760.3 "
id="polygon15"
transform="translate(-884.96443,3.2898306)" />
<path
class="st0"
d="m 1496.8356,372.28983 c 93.1,-34.3 155.8,-97.9 155.8,-170.9 0,-109.399999 -140.4,-197.9999994 -313.7,-197.9999994 -173.3,0 -313.7,88.7000004 -313.7,197.9999994 0,73 62.7,136.6 155.8,170.9 -102.5,36.8 -172.1,108.3 -172.1,190.5 0,119.8 147.8,216.9 330,216.9 182.3,0 330,-97.1 330,-216.9 0,-82.3 -69.6,-153.7 -172.1,-190.5 z m -157.9,-208.7 c 35.4,0 64,28.7 64,64 0,35.3 -28.7,64 -64,64 -35.4,0 -64,-28.7 -64,-64 0,-35.3 28.6,-64 64,-64 z m 0,439.2 c -42.3,0 -76.6,-34.3 -76.6,-76.6 0,-42.3 34.3,-76.6 76.6,-76.6 42.3,0 76.6,34.3 76.6,76.6 0,42.3 -34.3,76.6 -76.6,76.6 z"
id="path17" />
</g>
<g
aria-label=".io"
id="text345"
style="font-size:1024px;line-height:1.25;letter-spacing:-60px;stroke-width:5.33333;fill:#ffffff"><path
d="m 2485.2292,708 q 0,-28 20.5,-48.5 20.5,-20.5 49,-20.5 28.5,0 49,20.5 20.5,20.5 20.5,49 0,29 -20.5,49.5 -20,20 -49,20 -29.5,0 -49.5,-20 -20,-20 -20,-50 z"
style="font-family:Futura;-inkscape-font-specification:Futura;fill:#ffffff"
id="path499" /><path
d="m 2836.2292,277 v 487 h -112.5 V 277 Z m -129.5,-202.5 q 0,-29.5 21.5,-51 21.5,-21.5 51.5,-21.5 30.5,0 52,21.5 21.5,21 21.5,51.5 0,30.5 -21.5,52 -21,21.5 -51.5,21.5 -30.5,0 -52,-21.5 -21.5,-21.5 -21.5,-52.5 z"
style="font-family:Futura;-inkscape-font-specification:Futura;fill:#ffffff"
id="path501" /><path
d="m 2895.7292,517 q 0,-105.5 75.5,-179.5 75.5,-74 184,-74 109,0 185,74.5 75,74.5 75,183 0,109.5 -75.5,183.5 -76,73.5 -186.5,73.5 -109.5,0 -183.5,-75 -74,-74 -74,-186 z m 115,2 q 0,73 39,115.5 40,43 105.5,43 66,0 105.5,-42.5 39.5,-42.5 39.5,-113.5 0,-71 -39.5,-113.5 -40,-43 -105.5,-43 -64.5,0 -104.5,43 -40,43 -40,111 z"
style="font-family:Futura;-inkscape-font-specification:Futura;fill:#ffffff"
id="path503" /></g></svg>
Binary files /dev/null and a/html/static/HKGroteskWide-SemiBold.otf differ
@@ -1,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/style.css">
<title>7800.io: Pastebin</title>
</head>
<body>
<h1>Pastebin</h1>
<main>
<div id="ui">
<p>New paste</p>
<form action="/" method="post">
<input type="hidden" name="content_type" value="Paste">
<textarea name="content" placeholder="Paste" class="text"></textarea>
<input type="submit" value="Paste" class="button">
</form>
</div>
</main>
</body>
</html>
@@ -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;
}
@media screen and (max-width: 375px) {
#logo {
max-width: 75%;
min-width: 10em;
}
}
a {
color: white;
text-decoration: none;
border-bottom: 2px solid white;
}
#ui {
display: inline-block;
padding: 1em 2em;
background: black;
border-radius: 1em;
}
.text {
display: block;
font-family: monospace;
color: white;
background: black;
border: 2px dashed white;
padding: 0.5em 1em;
margin: 1em 0;
}
.button {
font-family: "HK Grotesk";
text-transform: uppercase;
text-align: center;
color: black;
background: white;
border: none;
padding: 0.5em 1em;
border-radius: 1em;
}
textarea {
width: 20em;
height: 10em;
}
@@ -1,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/style.css">
<title>7800.io: URL Shortener</title>
</head>
<body>
<h1>URL Shortener</h1>
<main>
<div id="ui">
<p>Shorten a URL</p>
<form action="/" method="post">
<input type="hidden" name="content_type" value="Url">
<input type="text" name="content" placeholder="URL" class="text">
<input type="submit" value="Shorten" class="button">
</form>
</div>
</main>
</body>
</html>
Binary files a/target/debug/url_shortener and a/target/debug/url_shortener differ