Browse Source

wip state (building)

xenua 2 years ago
parent
commit
09b60656da
  1. 3
      .gitignore
  2. 5
      Cargo.lock
  3. 1
      Cargo.toml
  4. 8
      src/admin.rs
  5. 116
      src/data.rs
  6. 73
      src/down.rs
  7. 79
      src/main.rs
  8. 5
      src/up.rs
  9. 7
      src/user.rs
  10. 4
      templates/base.html
  11. 5
      templates/file.html
  12. 7
      templates/text.html

3
.gitignore vendored

@ -1,2 +1,5 @@
/target /target
.idea/ .idea/
test_data/
.env

5
Cargo.lock generated

@ -663,9 +663,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.16.0" version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
@ -1328,6 +1328,7 @@ dependencies = [
"dashmap", "dashmap",
"lazy_static", "lazy_static",
"nanoid", "nanoid",
"once_cell",
"parking_lot", "parking_lot",
"serde", "serde",
"tera", "tera",

1
Cargo.toml

@ -12,6 +12,7 @@ axum = "0.6.1"
dashmap = "5.4.0" dashmap = "5.4.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
nanoid = "0.4.0" nanoid = "0.4.0"
once_cell = "1.17.0"
parking_lot = "0.12.1" parking_lot = "0.12.1"
serde = { version = "1.0.152", features = ["derive", "serde_derive"] } serde = { version = "1.0.152", features = ["derive", "serde_derive"] }
tera = "1.17.1" tera = "1.17.1"

8
src/admin.rs

@ -0,0 +1,8 @@
use axum::{routing::{post, get}, Router};
pub(crate) fn routes() -> Router {
Router::new()
.route("/", get(todo!()))
.route("/user", get(todo!()).post(todo!()))
.route("/user/:id", get(todo!()).post(todo!()))
}

116
src/data.rs

@ -1,16 +1,18 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::{read_to_string, remove_file, DirBuilder, File}, fs::{read_to_string, remove_file, DirBuilder, File},
io::{self, BufRead}, os::unix::fs::DirBuilderExt,
path::PathBuf, path::PathBuf,
}; };
use std::os::unix::fs::DirBuilderExt;
use argon2::{ use argon2::{
password_hash::{rand_core::OsRng, SaltString}, password_hash::{rand_core::OsRng, SaltString},
Argon2, PasswordHash, PasswordHasher, PasswordVerifier, Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
}; };
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use dashmap::DashMap; use dashmap::DashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -43,6 +45,18 @@ pub enum Error {
BadPassword, BadPassword,
} }
impl IntoResponse for Error {
fn into_response(self) -> Response {
match self {
Error::ShitMissing(thing) => {
(StatusCode::NOT_FOUND, format!("404 not found: {}", thing)).into_response()
}
_ => (StatusCode::INTERNAL_SERVER_ERROR, "shit went wrong sorry").into_response(),
}
}
}
fn meta_str_to_map(from: &str) -> Result<HashMap<&str, String>, Error> { fn meta_str_to_map(from: &str) -> Result<HashMap<&str, String>, Error> {
let map = from let map = from
.lines() .lines()
@ -67,12 +81,14 @@ fn hash_password(password: String) -> Result<String, Error> {
Ok(hash) Ok(hash)
} }
struct TextPaste { #[derive(Clone, Debug)]
pub struct TextPaste {
pub text: String, pub text: String,
pub meta: TextMeta, pub meta: TextMeta,
} }
struct TextMeta { #[derive(Clone, Debug)]
pub struct TextMeta {
language: Option<String>, language: Option<String>,
} }
@ -95,8 +111,10 @@ impl std::fmt::Display for TextMeta {
} }
} }
struct FileMeta { #[derive(Clone, Debug)]
file_name: String, pub struct FileMeta {
pub file_name: String,
pub size: u64,
} }
impl FileMeta { impl FileMeta {
@ -104,17 +122,22 @@ impl FileMeta {
let mut map = meta_str_to_map(from)?; let mut map = meta_str_to_map(from)?;
let file_name = map.remove("file_name").ok_or(Error::Metadata)?; let file_name = map.remove("file_name").ok_or(Error::Metadata)?;
Ok(Self { file_name }) let size = map
.remove("size")
.and_then(|string| string.parse::<u64>().ok())
.ok_or(Error::Metadata)?;
Ok(Self { file_name, size })
} }
} }
impl std::fmt::Display for FileMeta { impl std::fmt::Display for FileMeta {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "file_name: {}\n", self.file_name) write!(f, "file_name: {}\nsize: {}\n", self.file_name, self.size)
} }
} }
struct UserData { #[derive(Clone, Debug)]
pub struct UserData {
pub username: String, pub username: String,
pub pw_hash: String, pub pw_hash: String,
pub is_admin: bool, pub is_admin: bool,
@ -146,7 +169,7 @@ impl UserData {
pub fn set_password_checked(&mut self, old: String, new: String) -> Result<(), Error> { pub fn set_password_checked(&mut self, old: String, new: String) -> Result<(), Error> {
self.validate_password(old)?; self.validate_password(old)?;
self.set_password(new); self.set_password(new)?;
Ok(()) Ok(())
} }
@ -187,15 +210,40 @@ impl DataStorage {
} }
pub fn init(&mut self) -> Result<(), Error> { pub fn init(&mut self) -> Result<(), Error> {
println!("1");
self.ensure_folder_structure()?; self.ensure_folder_structure()?;
println!("2");
self.load_redirects()?; self.load_redirects()?;
println!("3");
self.load_text()?; self.load_text()?;
println!("4");
self.load_files()?; self.load_files()?;
println!("lets go");
self.load_users()?; self.load_users()?;
self.print_init_message();
Ok(()) Ok(())
} }
fn print_init_message(&self) {
println!(
"data storage finished initializing. data loaded from {}:\n- {} redirects\n- {} users\n- {} text pastes\n- {} file/media pastes",
self.base_dir.display(),
self.redirects.len(),
self.users.len(),
self.text.len(),
self.files.len(),
);
}
pub fn get_text(&self, id: String) -> Result<TextPaste, Error> {
self.text
.get(&id)
.map(|thing| thing.value().clone())
.ok_or(Error::ShitMissing(id))
}
pub fn create_text(&mut self, id: String, text: String, meta: TextMeta) -> Result<(), Error> { pub fn create_text(&mut self, id: String, text: String, meta: TextMeta) -> Result<(), Error> {
if self.text.contains_key(&id) { if self.text.contains_key(&id) {
return Err(Error::IDCollision); return Err(Error::IDCollision);
@ -232,6 +280,14 @@ impl DataStorage {
Ok(old_text) Ok(old_text)
} }
pub fn get_file_meta(&self, id: String) -> Result<FileMeta, Error> {
Ok(self
.files
.get(&id)
.map(|item| item.value().clone())
.ok_or(Error::ShitMissing(id))?)
}
pub fn create_file(&mut self, id: String, file_meta: FileMeta) -> Result<(), Error> { pub fn create_file(&mut self, id: String, file_meta: FileMeta) -> Result<(), Error> {
if self.files.contains_key(&id) { if self.files.contains_key(&id) {
return Err(Error::IDCollision); return Err(Error::IDCollision);
@ -239,16 +295,14 @@ impl DataStorage {
self.files.insert(id.clone(), file_meta); self.files.insert(id.clone(), file_meta);
self.write_file_meta(id); self.write_file_meta(id)?;
Ok(()) Ok(())
} }
pub fn delete_file(&mut self, id: String) -> Result<(), Error> { pub fn delete_file(&mut self, id: String) -> Result<(), Error> {
let file_meta = self self.files
.files
.remove(&id) .remove(&id)
.ok_or(Error::ShitMissing(id.clone()))? .ok_or(Error::ShitMissing(id.clone()))?;
.1;
remove_file(self.base_dir.join(FILE_DIR.as_str()).join(id.clone()))?; remove_file(self.base_dir.join(FILE_DIR.as_str()).join(id.clone()))?;
remove_file(self.base_dir.join(FILE_DIR.as_str()).join(id + ".meta"))?; remove_file(self.base_dir.join(FILE_DIR.as_str()).join(id + ".meta"))?;
@ -256,6 +310,13 @@ impl DataStorage {
Ok(()) Ok(())
} }
pub fn get_redirect(&self, id: String) -> Result<String, Error> {
self.redirects
.get(&id)
.map(|meow| meow.value().to_string())
.ok_or(Error::ShitMissing(id))
}
pub fn create_redirect(&mut self, id: String, target: String) -> Result<(), Error> { pub fn create_redirect(&mut self, id: String, target: String) -> Result<(), Error> {
if self.redirects.contains_key(&id) { if self.redirects.contains_key(&id) {
return Err(Error::IDCollision); return Err(Error::IDCollision);
@ -335,18 +396,23 @@ impl DataStorage {
dir_builder.create(self.base_dir.as_path())?; dir_builder.create(self.base_dir.as_path())?;
dir_builder.create(self.base_dir.join(FILE_DIR.as_str()))?; dir_builder.create(self.base_dir.join(FILE_DIR.as_str()))?;
dir_builder.create(self.base_dir.join(TEXT_DIR.as_str()))?; dir_builder.create(self.base_dir.join(TEXT_DIR.as_str()))?;
let redir_file = self.base_dir.join(REDIRECTS_FILE.as_str());
if !redir_file.exists() {
File::create(redir_file)?;
}
let users_file = self.base_dir.join(USERS_FILE.as_str());
if !users_file.exists() {
File::create(users_file)?;
}
Ok(()) Ok(())
} }
fn load_redirects(&mut self) -> Result<(), Error> { fn load_redirects(&mut self) -> Result<(), Error> {
let f = File::options() let contents = read_to_string(self.base_dir.join(REDIRECTS_FILE.as_str()))?;
.create(true)
.read(true)
.open(self.base_dir.join(REDIRECTS_FILE.as_str()))?;
for line in io::BufReader::new(f).lines() { for line in contents.lines() {
let (id, url) = line? let (id, url) = line
.split_once(": ") .split_once(": ")
.map(|(id, url)| (id.to_string(), url.to_string())) .map(|(id, url)| (id.to_string(), url.to_string()))
.ok_or(Error::Metadata)?; .ok_or(Error::Metadata)?;
@ -411,7 +477,7 @@ impl DataStorage {
for line in read_to_string(self.base_dir.join(USERS_FILE.as_str()))?.lines() { for line in read_to_string(self.base_dir.join(USERS_FILE.as_str()))?.lines() {
if line.starts_with("---") { if line.starts_with("---") {
is_admin = false; is_admin = false;
break; continue;
} }
let user = UserData::parse(line, is_admin)?; let user = UserData::parse(line, is_admin)?;
self.users.insert(user.username.clone(), user); self.users.insert(user.username.clone(), user);
@ -420,7 +486,7 @@ impl DataStorage {
} }
fn write_redirects(&self) -> Result<(), Error> { fn write_redirects(&self) -> Result<(), Error> {
let mut fuckery: String = self let fuckery: String = self
.redirects .redirects
.iter() .iter()
.map(|meow| format!("{}: {}\n", meow.key(), meow.value())) .map(|meow| format!("{}: {}\n", meow.key(), meow.value()))
@ -432,7 +498,7 @@ impl DataStorage {
fn write_users(&self) -> Result<(), Error> { fn write_users(&self) -> Result<(), Error> {
let mut fuckery = String::from("---"); let mut fuckery = String::from("---");
self.users.iter().map(|item| { self.users.iter().for_each(|item| {
if item.value().is_admin { if item.value().is_admin {
fuckery = format!("{}\n{}", item.value().to_string(), fuckery) fuckery = format!("{}\n{}", item.value().to_string(), fuckery)
} else { } else {

73
src/down.rs

@ -1,11 +1,64 @@
use axum::{routing::get, Router}; use axum::{
extract::{Path, State},
pub(crate) fn routes() -> Router { http::{header, StatusCode},
Router::new() response::{Html, IntoResponse},
.route("/t/:id", get(todo!())) routing::get,
.route("/f/:id", get(todo!())) Router,
.route("/l/:id", get(todo!())) };
.route("/text/:id", get(todo!()))
.route("/file/:id", get(todo!())) use crate::{AppState, Error};
.route("/link/:id", get(todo!()))
pub(crate) fn routes(router: Router<AppState>) -> Router<AppState> {
router
.route("/t/:id", get(get_text_page))
.route("/t/:id/raw", get(get_text_raw))
.route("/f/:id", get(get_file_page))
.route("/f/:id/dl", get(|| async { "Hello world" }))
.route("/l/:id", get(get_redirect))
.route("/text/:id", get(get_text_page))
.route("/text/:id/raw", get(get_text_raw))
.route("/file/:id", get(get_file_page))
.route("/file/:id/download", get(|| async { "Hello world" }))
.route("/link/:id", get(get_redirect))
}
async fn get_redirect(State(state): State<AppState>, Path(id): Path<String>) -> impl IntoResponse {
match state.cache.get_redirect(id) {
Ok(target) => (StatusCode::FOUND, [(header::LOCATION, target)]).into_response(),
Err(e) => e.into_response(),
}
}
async fn get_text_raw(State(state): State<AppState>, Path(id): Path<String>) -> impl IntoResponse {
match state.cache.get_text(id) {
Ok(text) => text.text.into_response(),
Err(e) => e.into_response(),
}
}
async fn get_text_page(
State(state): State<AppState>,
Path(id): Path<String>,
) -> Result<Html<String>, Error> {
let text = state.cache.get_text(id)?;
let mut ctx = tera::Context::new();
ctx.insert("text", &text.text);
return Ok(Html(state.tera.render("text.html", &ctx)?));
}
async fn get_file_page(
State(state): State<AppState>,
Path(id): Path<String>,
) -> Result<Html<String>, Error> {
let file_meta = state.cache.get_file_meta(id)?;
let mut ctx = tera::Context::new();
ctx.insert("size", &file_meta.size);
ctx.insert("file_name", &file_meta.file_name);
return Ok(Html(state.tera.render("file.html", &ctx)?));
} }

79
src/main.rs

@ -1,71 +1,94 @@
#![allow(dead_code)]
mod data; mod data;
mod down; mod down;
mod up; //mod up;
//mod user;
//mod admin;
use std::{net::SocketAddr, path::PathBuf}; use std::{net::SocketAddr, path::PathBuf, sync::Arc};
use axum::{routing::get, Router}; use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::get, Router};
use lazy_static::lazy_static;
use tera::Tera; use tera::Tera;
use data::DataStorage; use data::DataStorage;
lazy_static! {
pub static ref TEMPLATES: Tera = {
let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*.html"))
.expect("Parsing error(s)");
tera
};
}
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum Error {
#[error("DataStorage error")] #[error("DataStorage error")]
DataStorage(#[from] data::Error), DataStorage(#[from] data::Error),
#[error("invalid env var")] #[error("invalid or missing env var: {0}")]
EnvVar, EnvVar(String),
#[error("tera error")]
Tera(#[from] tera::Error),
}
impl IntoResponse for Error {
fn into_response(self) -> axum::response::Response {
match self {
Error::DataStorage(ds_error) => ds_error.into_response(),
_ => (StatusCode::INTERNAL_SERVER_ERROR, "shit went wrong sorry").into_response(),
}
}
} }
#[derive(Clone)]
struct AppState { struct AppState {
alphabet: Vec<char>, alphabet: Vec<char>,
id_length: usize, id_length: usize,
cache: DataStorage, cache: Arc<DataStorage>,
tera: Tera,
} }
impl AppState { impl AppState {
pub fn new() -> Result<Self, Error> { pub fn new() -> Result<Self, Error> {
let base_dir_string = std::env::var("V_BASE_DIR").or(Err(Error::EnvVar))?; let base_dir_string =
std::env::var("V_BASE_DIR").or(Err(Error::EnvVar("V_BASE_DIR".to_string())))?;
let id_length: usize = std::env::var("V_ID_LENGTH") let id_length: usize = std::env::var("V_ID_LENGTH")
.unwrap_or("6".to_string()) .unwrap_or("6".to_string())
.parse() .parse()
.or(Err(Error::EnvVar))?; .or(Err(Error::EnvVar("V_ID_LENGTH".to_string())))?;
let alphabet = std::env::var("V_ID_ALPHABET") let alphabet = std::env::var("V_ID_ALPHABET")
.unwrap_or("abcdefghijkmnpqrstuwxyz123467890".to_string()) .unwrap_or("abcdefghijkmnpqrstuwxyz123467890".to_string())
.chars() .chars()
.collect(); .collect();
let base_dir = PathBuf::new(); let mut base_dir = PathBuf::new();
base_dir.push(base_dir_string); base_dir.push(base_dir_string);
let mut cache = DataStorage::new(base_dir); let mut cache = DataStorage::new(base_dir);
cache.init()?; cache.init()?;
let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*.html"))
.expect("Parsing error(s)");
Ok(Self { Ok(Self {
alphabet, alphabet,
id_length, id_length,
cache, cache: Arc::new(cache),
tera,
}) })
} }
} }
fn routes() -> Router { fn routes() -> Result<Router, Error> {
let up = up::routes(); let state = AppState::new()?;
let down = down::routes(); // let up = up::routes();
Router::new() // let down = down::routes();
.route("/", get(|| async { "Hello, World!" })) // let user = user::routes();
.merge(down) // let admin = admin::routes();
.nest("/up", up) let router = Router::new().route("/extrac", get(test_extrac));
// .merge(down)
// .nest("/up", up)
// .nest("/user", user)
// .nest("/admin", admin)
let meow = down::routes(router);
Ok(meow.with_state(state))
}
async fn test_extrac(State(state): State<AppState>) -> String {
state.id_length.to_string()
} }
#[tokio::main] #[tokio::main]
@ -75,7 +98,7 @@ async fn main() -> anyhow::Result<()> {
.parse() .parse()
.expect("LISTEN_ADDR parse error"); .expect("LISTEN_ADDR parse error");
let app = routes(); let app = routes()?;
axum::Server::bind(&addr) axum::Server::bind(&addr)
.serve(app.into_make_service()) .serve(app.into_make_service())

5
src/up.rs

@ -1,8 +1,11 @@
use axum::{routing::post, Router}; use axum::{routing::{post, get}, Router};
pub(crate) fn routes() -> Router { pub(crate) fn routes() -> Router {
Router::new() Router::new()
.route("/text", post(todo!())) .route("/text", post(todo!()))
.route("/file", post(todo!())) .route("/file", post(todo!()))
.route("/link", post(todo!())) .route("/link", post(todo!()))
.route("/text/:id", get(todo!()).post(todo!()))
.route("/file/:id", get(todo!()).post(todo!()))
.route("/link/:id", get(todo!()).post(todo!()))
} }

7
src/user.rs

@ -0,0 +1,7 @@
use axum::{Router, routing::post};
pub(crate) fn routes() -> Router {
Router::new()
.route("/password", post(todo!()))
.route("/login", post(todo!()))
}

4
templates/base.html

@ -10,13 +10,13 @@
<link rel="stylesheet" href="static/main.css" type="text/css" /> <link rel="stylesheet" href="static/main.css" type="text/css" />
{% endblock head %} {% endblock head %}
</head> </head>
<body {% block bodytags %}{% endblock bodytags %}> <body>
{% block body %} {% block body %}
<header> <header>
{% block header %}{% endblock header %} {% block header %}{% endblock header %}
</header> </header>
<main> <main>
{% block main %}{% enblock main %} {% block main %}{% endblock main %}
</main> </main>
<footer> <footer>
{% block footer %}{% endblock footer %} {% block footer %}{% endblock footer %}

5
templates/file.html

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block main %}
file {{ file_name }} ({{ size | filesizeformat }})
{% endblock main %}

7
templates/text.html

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block main %}
{{ text }}
{% endblock main %}
Loading…
Cancel
Save