initial commit :)
This commit is contained in:
commit
b82670fdb2
16 changed files with 1983 additions and 0 deletions
12
.editorconfig
Normal file
12
.editorconfig
Normal 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
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/target
|
||||||
|
|
||||||
|
/misc
|
||||||
|
|
||||||
|
/data.db
|
||||||
|
/data.db-*
|
||||||
1507
Cargo.lock
generated
Normal file
1507
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal 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
22
README.md
Normal 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
5
build.rs
Normal 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");
|
||||||
|
}
|
||||||
7
migrations/20220114060531_create_redirects_table.sql
Normal file
7
migrations/20220114060531_create_redirects_table.sql
Normal 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
42
src/main.rs
Normal 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
115
src/redirects.rs
Normal 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
25
static/404.html
Normal 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
70
static/css/layout.css
Normal 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
43
static/css/styles.css
Normal 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
114
static/index.html
Normal 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 <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
2
static/inter/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
BIN
static/pfp.jpg
Normal file
BIN
static/pfp.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 310 KiB |
3
static/shorten.html
Normal file
3
static/shorten.html
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<p>TODO! sorry :(</p>
|
||||||
Loading…
Reference in a new issue