|
|
|
@ -1,8 +1,17 @@
@@ -1,8 +1,17 @@
|
|
|
|
|
use std::{collections::HashMap, net::SocketAddr, path::PathBuf}; |
|
|
|
|
use std::{ |
|
|
|
|
collections::HashMap, |
|
|
|
|
ffi::OsString, |
|
|
|
|
fs::{DirBuilder, File}, |
|
|
|
|
io::{self, BufRead}, |
|
|
|
|
net::SocketAddr, |
|
|
|
|
path::PathBuf, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
#[cfg(target_family = "unix")] |
|
|
|
|
use std::os::unix::fs::DirBuilderExt; |
|
|
|
|
|
|
|
|
|
use axum::{routing::get, Router}; |
|
|
|
|
use dashmap::DashMap; |
|
|
|
|
use parking_lot::RwLock; |
|
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
|
|
|
|
|
|
use lazy_static::lazy_static; |
|
|
|
@ -11,15 +20,31 @@ use tera::Tera;
@@ -11,15 +20,31 @@ use tera::Tera;
|
|
|
|
|
mod down; |
|
|
|
|
mod up; |
|
|
|
|
|
|
|
|
|
const TEXT_DIR: String = "text".to_string(); |
|
|
|
|
const FILE_DIR: String = "files".to_string(); |
|
|
|
|
const REDIRECTS_FILE: String = "redirects".to_string(); |
|
|
|
|
const STATS_FILE: String = "stats".to_string(); |
|
|
|
|
const USERS_FILE: String = "users".to_string(); |
|
|
|
|
|
|
|
|
|
lazy_static! { |
|
|
|
|
pub static ref TEMPLATES: Tera = { |
|
|
|
|
let tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*.html")) |
|
|
|
|
.expect("Parsing error(s):"); |
|
|
|
|
.expect("Parsing error(s)"); |
|
|
|
|
|
|
|
|
|
tera |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug)] |
|
|
|
|
pub enum Error { |
|
|
|
|
#[error("io error")] |
|
|
|
|
IO(#[from] std::io::Error), |
|
|
|
|
#[error("metadata error")] |
|
|
|
|
Metadata, |
|
|
|
|
#[error("some shit missing: {0}")] |
|
|
|
|
ShitMissing(String), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct TextPaste { |
|
|
|
|
pub text: String, |
|
|
|
|
pub meta: HashMap<String, String>, |
|
|
|
@ -29,22 +54,134 @@ struct FileMeta {
@@ -29,22 +54,134 @@ struct FileMeta {
|
|
|
|
|
file_name: String, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct UserData { |
|
|
|
|
username: String, |
|
|
|
|
pw_hash: String, |
|
|
|
|
is_admin: bool, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct DataStorage { |
|
|
|
|
links: DashMap<String, String>, |
|
|
|
|
base_dir: PathBuf, |
|
|
|
|
redirects: DashMap<String, String>, |
|
|
|
|
text: DashMap<String, TextPaste>, |
|
|
|
|
files: DashMap<String, FileMeta>, |
|
|
|
|
users: DashMap<String, UserData>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl DataStorage { |
|
|
|
|
pub fn new(base_dir: PathBuf) -> Self { |
|
|
|
|
let redirects = DashMap::new(); |
|
|
|
|
let text = DashMap::new(); |
|
|
|
|
let files = DashMap::new(); |
|
|
|
|
let users = DashMap::new(); |
|
|
|
|
DataStorage { |
|
|
|
|
base_dir, |
|
|
|
|
redirects, |
|
|
|
|
text, |
|
|
|
|
files, |
|
|
|
|
users, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn init(&mut self) -> Result<(), Error> { |
|
|
|
|
self.ensure_folder_structure()?; |
|
|
|
|
self.load_redirects()?; |
|
|
|
|
self.load_text()?; |
|
|
|
|
self.load_files()?; |
|
|
|
|
self.load_users()?; |
|
|
|
|
|
|
|
|
|
todo!() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn ensure_folder_structure(&self) -> Result<(), Error> { |
|
|
|
|
let dir_builder = DirBuilder::new().recursive(true); |
|
|
|
|
if cfg!(target_family = "unix") { |
|
|
|
|
dir_builder = dir_builder.mode(0o775); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
dir_builder.create(self.base_dir)?; |
|
|
|
|
dir_builder.create(self.base_dir.join(FILE_DIR))?; |
|
|
|
|
dir_builder.create(self.base_dir.join(TEXT_DIR))?; |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn load_redirects(&mut self) -> Result<(), Error> { |
|
|
|
|
let f = File::options() |
|
|
|
|
.create(true) |
|
|
|
|
.read(true) |
|
|
|
|
.open(self.base_dir.join(REDIRECTS_FILE))?; |
|
|
|
|
|
|
|
|
|
for line in io::BufReader::new(f).lines() { |
|
|
|
|
line?; |
|
|
|
|
let (id, url) = line?.split_once(": ").expect("invalid redirect format"); |
|
|
|
|
self.redirects |
|
|
|
|
.insert(id.trim().to_string(), url.trim().to_string()); |
|
|
|
|
// if there's a better way to do this let me know
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn load_text(&mut self) -> Result<(), Error> { |
|
|
|
|
for thing in std::fs::read_dir(self.base_dir.join(TEXT_DIR)).expect("fs error") { |
|
|
|
|
let dir_entry = thing?; // i'm so good at naming things
|
|
|
|
|
let f = File::open(dir_entry.path())?; |
|
|
|
|
let reader = io::BufReader::new(f); |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn load_files(&mut self) -> Result<(), Error> { |
|
|
|
|
for thing in std::fs::read_dir(self.base_dir.join(FILE_DIR)).expect("fs error") { |
|
|
|
|
let dir_entry = thing?; // i'm so good at naming things
|
|
|
|
|
let filename = dir_entry.file_name().into_string().expect("fucked up OS string"); |
|
|
|
|
if filename |
|
|
|
|
.ends_with(".meta") |
|
|
|
|
{ |
|
|
|
|
let id = filename.trim_end_matches(".meta").to_string(); |
|
|
|
|
let f = File::open(dir_entry.path())?; |
|
|
|
|
let reader = io::BufReader::new(f); |
|
|
|
|
let raw_metadata = HashMap::new(); |
|
|
|
|
for line in reader.lines() { |
|
|
|
|
let (k, v) = line?.split_once(": ").expect("invalid metadata"); |
|
|
|
|
raw_metadata.insert(k.trim().to_string(), v.trim().to_string()); |
|
|
|
|
} |
|
|
|
|
let metadata = Self::parse_file_meta(raw_metadata)?; |
|
|
|
|
self.files.insert(id, metadata); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
self.assert_all_files_present()?; |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn assert_all_files_present(&self) -> Result<(), Error> { |
|
|
|
|
let files_folder = self.base_dir.join(FILE_DIR); |
|
|
|
|
for (id, _) in self.files.into_iter() { |
|
|
|
|
files_folder.join(id).try_exists().map_err(|_| Error::ShitMissing(id)); |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn parse_file_meta(raw: HashMap<String, String>) -> Result<FileMeta, Error> { |
|
|
|
|
let file_name = raw.get("file_name").ok_or(Error::Metadata)?.to_owned(); |
|
|
|
|
Ok(FileMeta { file_name }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn load_users(&mut self) -> Result<(), Error> { |
|
|
|
|
todo!() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct AppState { |
|
|
|
|
alphabet: Box<[char; 32]>, |
|
|
|
|
alphabet: Vec<char>, |
|
|
|
|
id_length: usize, |
|
|
|
|
cache: DataStorage, |
|
|
|
|
base_dir: PathBuf, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl AppState { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
impl AppState {} |
|
|
|
|
|
|
|
|
|
fn routes() -> Router { |
|
|
|
|
let up = up::routes(); |
|
|
|
@ -60,7 +197,7 @@ async fn main() -> anyhow::Result<()> {
@@ -60,7 +197,7 @@ async fn main() -> anyhow::Result<()> {
|
|
|
|
|
let addr: SocketAddr = std::env::var("LISTEN_ADDR") |
|
|
|
|
.unwrap_or("127.0.0.1:8080".to_string()) |
|
|
|
|
.parse() |
|
|
|
|
.expect("parse error"); |
|
|
|
|
.expect("LISTEN_ADDR parse error"); |
|
|
|
|
|
|
|
|
|
let app = routes(); |
|
|
|
|
|
|
|
|
|