initial commit :)

main
Charlotte Som 2022-01-14 07:02:31 +00:00
commit b82670fdb2
16 changed files with 1983 additions and 0 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
[*.rs]
indent_size = 4

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
/misc
/data.db
/data.db-*

1507
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "char_lt"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.133", features = ["derive"] }
sqlx = { version = "0.5.10", features = ["sqlite", "runtime-tokio-native-tls"] }
tokio = { version = "1.15.0", features = ["full"] }
warp = "0.3.2"

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# char.lt
- website (`static` folder)
- filedump (`misc` folder - gitignored)
- personal url shortener :)
- random gen prefix with `_` + mixed-case 5-char alphabetical
- custom links: anything goes (randoms take precedence though so maybe dont prefix `_`)
## setup
```shell
$ touch data.db
$ export DATABASE_URL="sqlite://data.db"
$
$ cd static/inter
static/inter $ # download inter.zip from GitHub and extract the contents of 'Inter Web'
static/inter $ cd ../../
$
$ cargo run --release
[...]
Listening at http://127.0.0.1:8000
```

5
build.rs Normal file
View File

@ -0,0 +1,5 @@
// generated by `sqlx migrate build-script`
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}

View File

@ -0,0 +1,7 @@
CREATE TABLE redirects (
path TEXT NOT NULL,
target_location TEXT NOT NULL,
redirect_code INT NOT NULL DEFAULT 302
) STRICT;
CREATE UNIQUE INDEX redirects_idx ON redirects(path);

42
src/main.rs Normal file
View File

@ -0,0 +1,42 @@
use std::{net::SocketAddr, path::PathBuf};
use warp::Filter;
mod redirects;
#[tokio::main]
async fn main() {
// TODO: Can we do some perfect hashing with the static dir?
let static_dir = std::env::var("STATIC_DIR").unwrap_or_else(|_| String::from("./static"));
let static_files = warp::fs::dir(static_dir.clone());
let misc_files_dir = std::env::var("MISC_DIR").unwrap_or_else(|_| String::from("misc"));
let misc_files = warp::fs::dir(misc_files_dir);
let redirect_routes = redirects::routes(&static_dir).await;
let not_found_route = {
let not_found_page = {
let mut path = PathBuf::from(&static_dir);
path.push("404.html");
path
};
warp::fs::file(not_found_page)
.map(|reply| warp::reply::with_status(reply, warp::hyper::StatusCode::NOT_FOUND))
};
let routes = static_files
.or(misc_files)
.or(redirect_routes)
.or(not_found_route);
let host = std::env::var("BIND_ADDR")
.map(|addr| {
addr.parse::<SocketAddr>()
.unwrap_or_else(|_| panic!("{addr} is not a valid socket address"))
})
.unwrap_or_else(|_| ([127, 0, 0, 1], 8080).into());
println!("Listening on http://{host} ...");
warp::serve(routes).bind(host).await;
}

115
src/redirects.rs Normal file
View File

@ -0,0 +1,115 @@
use std::path::PathBuf;
use sqlx::{Executor, SqlitePool};
use warp::{
filters::BoxedFilter,
hyper::{self},
reply::Response,
Filter, Rejection, Reply,
};
// if we're making a debug build, we don't want to expand the migrate! macro at all
#[inline]
#[cfg(debug_assertions)]
fn migrate(_db: &SqlitePool) {}
#[inline]
#[cfg(not(debug_assertions))]
async fn migrate(db: &SqlitePool) {
let _ = sqlx::migrate!().run(db).await;
}
struct Redirect {
path: String,
target_location: String,
redirect_code: i64,
}
impl Redirect {
fn status_code(&self) -> hyper::StatusCode {
hyper::StatusCode::from_u16(self.redirect_code as u16)
.expect("Status codes in database should always be valid")
}
}
#[derive(serde::Deserialize)]
struct AddRedirectForm {
path: Option<String>,
target: String,
status_code: Option<u16>,
}
async fn add_redirect(
db: SqlitePool,
add_redirect_form: AddRedirectForm,
) -> Result<impl Reply, Rejection> {
async fn create_redirect(db: SqlitePool, redir: Redirect) -> sqlx::Result<()> {
let mut conn = db.acquire().await?;
let query = sqlx::query!(
"INSERT INTO redirects VALUES (?, ?, ?)",
redir.path,
redir.target_location,
redir.redirect_code
);
let _ = conn.execute(query).await?;
Ok(())
}
Ok("hello")
}
async fn lookup_redirect(path: String, db: SqlitePool) -> Result<impl Reply, Rejection> {
async fn find_redirect(db: SqlitePool, path: &str) -> sqlx::Result<Redirect> {
let mut conn = db.acquire().await?;
let query = sqlx::query_as!(Redirect, "SELECT * FROM redirects WHERE path = ?", path);
query.fetch_one(&mut conn).await
}
match find_redirect(db, &path).await {
Ok(redir) => Ok(warp::reply::with_header(
redir.status_code(),
hyper::header::LOCATION,
redir.target_location,
)),
Err(_) => Err(warp::reject()),
}
}
pub async fn routes(static_dir: &str) -> BoxedFilter<(Response,)> {
let database_uri = std::env!("DATABASE_URL");
let db = SqlitePool::connect(database_uri)
.await
.unwrap_or_else(|_| panic!("Failed to open SQLite DB: {}", database_uri));
migrate(&db);
let shorten_html = {
let mut path = PathBuf::from(static_dir);
path.push("shorten.html");
path
};
let shorten_route = warp::path!("shorten")
.and(warp::get().and(warp::fs::file(shorten_html))) // TODO: Templating to list existing shortlinks?
.or({
let db = db.clone();
warp::post()
.map(move || db.clone())
.and(warp::body::form())
.and_then(add_redirect)
});
let lookup_route = warp::path::param()
.and(warp::path::end())
.and(warp::any().map(move || db.clone()))
.and_then(lookup_redirect);
shorten_route
.or(lookup_route)
.map(Reply::into_response)
.boxed()
}

25
static/404.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>404 - char.lt</title>
<link rel="stylesheet" href="/css/layout.css" />
<link rel="stylesheet" href="/css/styles.css" />
<link rel="stylesheet" href="/inter/inter.css" />
</head>
<body>
<div class="page-wrapper">
<main>
<h1>not found :(</h1>
<p>
sorry, this link doesn't work! why not learn
<a href="/">about me</a> instead?
</p>
</main>
</div>
</body>
</html>

70
static/css/layout.css Normal file
View File

@ -0,0 +1,70 @@
html,
body {
margin: 0;
padding: 0;
}
body {
display: flex;
min-height: 100vh;
align-items: center;
}
.page-wrapper {
display: flex;
flex-direction: column;
max-width: 60ch;
width: 60ch;
margin: 0 auto;
padding: 1em;
border: 2px solid white;
border-radius: 1em;
}
header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
header aside {
display: flex;
flex-direction: column;
max-width: 50%;
}
main {
flex: 1;
}
footer {
margin-top: 1em;
text-align: center;
}
.hero-image {
height: 10rem;
width: 10rem;
border-radius: 6px;
}
section + section {
margin-top: 0.5em;
}
h1,
h2,
h3 {
margin: 0;
margin-bottom: 1em;
}
p {
margin: 0.5em 0;
}

43
static/css/styles.css Normal file
View File

@ -0,0 +1,43 @@
html {
background-color: #121212;
color: white;
}
a {
color: rgb(255, 167, 248);
}
body {
font-family: "Inter", sans-serif;
font-size: 1.125em;
}
code {
font-family: "Courier New", Courier, monospace;
}
.inline-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.inline-list li {
display: block;
padding-inline-end: 0.5ch;
}
.inline-list li + li {
border-inline-start: 1px solid white;
padding-inline-start: 0.5ch;
}
abbr {
text-decoration: none;
}

114
static/index.html Normal file
View File

@ -0,0 +1,114 @@
<!--
hey! you might be interested in the source code for this site
(both front- + back-end) at https://git.lavender.software/charlotte/char.lt
<3
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>charlotte ✨</title>
<link rel="stylesheet" href="/css/layout.css" />
<link rel="stylesheet" href="/css/styles.css" />
<link rel="stylesheet" href="/inter/inter.css" />
</head>
<body>
<div class="page-wrapper">
<header>
<aside>
<h1><abbr title="charlotte athena som">charlotte ✨</abbr></h1>
<ul id="traits" class="inline-list">
<li id="age">21</li>
<li>trans</li>
<li>catgirl bunnygirl enby</li>
<li>lesbian</li>
<li>anarchist</li>
<li>communist</li>
</ul>
</aside>
<img
class="hero-image"
alt="charlotte's profile picture"
src="/pfp.jpg"
/>
</header>
<main>
<p>welcome to <code>char.lt</code>: the domain, is, like, my name!!</p>
<p>
hey, i'm charlotte! i like music and videogames and programming and
being hot and i speak multiple languages
</p>
<p>
i like breaking the rules
<small>(usually the ones that protect power)</small>, so i enjoy
software piracy, videogame cheat development
<small>(death to microtransactions!)</small>, other cursed computer
things, and being obnoxiously queer everywhere i go &lt;3
</p>
<section>
my pronouns are:
<ul id="pronouns" class="inline-list">
<li><a href="https://pronoun.is/she">she/her</a></li>
<li><a href="https://pronoun.is/they">they/them</a></li>
<li><a href="https://pronoun.is/fae">fae/faer</a></li>
<li>
<a href="https://pronoun.is/bun/bun/buns/buns/bunself">
bun/buns
</a>
</li>
</ul>
</section>
<section>
talk to me in:
<ul id="languages" class="inline-list">
<li>english</li>
<li>french</li>
<li>spanish</li>
<li>korean</li>
<li>toki pona</li>
</ul>
</section>
<section>
<ul id="links" class="inline-list">
<li>
<a rel="me" href="https://som.codes">som.codes</a>
</li>
<li>
<a rel="me" href="https://hackery.site">hackery.site</a>
</li>
<li>
<a rel="me" href="https://trans.enby.town/charlotte">
trans.enby.town
</a>
</li>
<li>
<a rel="me" href="https://github.com/videogame-hacker">github</a>
</li>
<li>
<a rel="me" href="https://twitter.com/half_kh_hacker">twitter</a>
</li>
<li>
<a rel="me" href="https://last.fm/user/half-kh-hacker">last.fm</a>
</li>
</ul>
</section>
</main>
<footer>a site by <a href="https://som.codes">charlotte som</a></footer>
</div>
</body>
</html>

2
static/inter/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

BIN
static/pfp.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

3
static/shorten.html Normal file
View File

@ -0,0 +1,3 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<p>TODO! sorry :(</p>