|
|
|
@ -1,16 +1,18 @@
@@ -1,16 +1,18 @@
|
|
|
|
|
use std::{ |
|
|
|
|
collections::HashMap, |
|
|
|
|
fs::{read_to_string, remove_file, DirBuilder, File}, |
|
|
|
|
io::{self, BufRead}, |
|
|
|
|
os::unix::fs::DirBuilderExt, |
|
|
|
|
path::PathBuf, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
use std::os::unix::fs::DirBuilderExt; |
|
|
|
|
|
|
|
|
|
use argon2::{ |
|
|
|
|
password_hash::{rand_core::OsRng, SaltString}, |
|
|
|
|
Argon2, PasswordHash, PasswordHasher, PasswordVerifier, |
|
|
|
|
}; |
|
|
|
|
use axum::{ |
|
|
|
|
http::StatusCode, |
|
|
|
|
response::{IntoResponse, Response}, |
|
|
|
|
}; |
|
|
|
|
use dashmap::DashMap; |
|
|
|
|
use lazy_static::lazy_static; |
|
|
|
|
|
|
|
|
@ -43,6 +45,18 @@ pub enum Error {
@@ -43,6 +45,18 @@ pub enum Error {
|
|
|
|
|
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> { |
|
|
|
|
let map = from |
|
|
|
|
.lines() |
|
|
|
@ -67,12 +81,14 @@ fn hash_password(password: String) -> Result<String, Error> {
@@ -67,12 +81,14 @@ fn hash_password(password: String) -> Result<String, Error> {
|
|
|
|
|
Ok(hash) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct TextPaste { |
|
|
|
|
#[derive(Clone, Debug)] |
|
|
|
|
pub struct TextPaste { |
|
|
|
|
pub text: String, |
|
|
|
|
pub meta: TextMeta, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct TextMeta { |
|
|
|
|
#[derive(Clone, Debug)] |
|
|
|
|
pub struct TextMeta { |
|
|
|
|
language: Option<String>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -95,8 +111,10 @@ impl std::fmt::Display for TextMeta {
@@ -95,8 +111,10 @@ impl std::fmt::Display for TextMeta {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct FileMeta { |
|
|
|
|
file_name: String, |
|
|
|
|
#[derive(Clone, Debug)] |
|
|
|
|
pub struct FileMeta { |
|
|
|
|
pub file_name: String, |
|
|
|
|
pub size: u64, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl FileMeta { |
|
|
|
@ -104,17 +122,22 @@ impl FileMeta {
@@ -104,17 +122,22 @@ impl FileMeta {
|
|
|
|
|
let mut map = meta_str_to_map(from)?; |
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
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 pw_hash: String, |
|
|
|
|
pub is_admin: bool, |
|
|
|
@ -146,7 +169,7 @@ impl UserData {
@@ -146,7 +169,7 @@ impl UserData {
|
|
|
|
|
|
|
|
|
|
pub fn set_password_checked(&mut self, old: String, new: String) -> Result<(), Error> { |
|
|
|
|
self.validate_password(old)?; |
|
|
|
|
self.set_password(new); |
|
|
|
|
self.set_password(new)?; |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -187,15 +210,40 @@ impl DataStorage {
@@ -187,15 +210,40 @@ impl DataStorage {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn init(&mut self) -> Result<(), Error> { |
|
|
|
|
println!("1"); |
|
|
|
|
self.ensure_folder_structure()?; |
|
|
|
|
println!("2"); |
|
|
|
|
self.load_redirects()?; |
|
|
|
|
println!("3"); |
|
|
|
|
self.load_text()?; |
|
|
|
|
println!("4"); |
|
|
|
|
self.load_files()?; |
|
|
|
|
println!("lets go"); |
|
|
|
|
self.load_users()?; |
|
|
|
|
|
|
|
|
|
self.print_init_message(); |
|
|
|
|
|
|
|
|
|
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> { |
|
|
|
|
if self.text.contains_key(&id) { |
|
|
|
|
return Err(Error::IDCollision); |
|
|
|
@ -232,6 +280,14 @@ impl DataStorage {
@@ -232,6 +280,14 @@ impl DataStorage {
|
|
|
|
|
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> { |
|
|
|
|
if self.files.contains_key(&id) { |
|
|
|
|
return Err(Error::IDCollision); |
|
|
|
@ -239,16 +295,14 @@ impl DataStorage {
@@ -239,16 +295,14 @@ impl DataStorage {
|
|
|
|
|
|
|
|
|
|
self.files.insert(id.clone(), file_meta); |
|
|
|
|
|
|
|
|
|
self.write_file_meta(id); |
|
|
|
|
self.write_file_meta(id)?; |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn delete_file(&mut self, id: String) -> Result<(), Error> { |
|
|
|
|
let file_meta = self |
|
|
|
|
.files |
|
|
|
|
self.files |
|
|
|
|
.remove(&id) |
|
|
|
|
.ok_or(Error::ShitMissing(id.clone()))? |
|
|
|
|
.1; |
|
|
|
|
.ok_or(Error::ShitMissing(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"))?; |
|
|
|
@ -256,6 +310,13 @@ impl DataStorage {
@@ -256,6 +310,13 @@ impl DataStorage {
|
|
|
|
|
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> { |
|
|
|
|
if self.redirects.contains_key(&id) { |
|
|
|
|
return Err(Error::IDCollision); |
|
|
|
@ -335,18 +396,23 @@ impl DataStorage {
@@ -335,18 +396,23 @@ impl DataStorage {
|
|
|
|
|
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(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(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn load_redirects(&mut self) -> Result<(), Error> { |
|
|
|
|
let f = File::options() |
|
|
|
|
.create(true) |
|
|
|
|
.read(true) |
|
|
|
|
.open(self.base_dir.join(REDIRECTS_FILE.as_str()))?; |
|
|
|
|
let contents = read_to_string(self.base_dir.join(REDIRECTS_FILE.as_str()))?; |
|
|
|
|
|
|
|
|
|
for line in io::BufReader::new(f).lines() { |
|
|
|
|
let (id, url) = line? |
|
|
|
|
for line in contents.lines() { |
|
|
|
|
let (id, url) = line |
|
|
|
|
.split_once(": ") |
|
|
|
|
.map(|(id, url)| (id.to_string(), url.to_string())) |
|
|
|
|
.ok_or(Error::Metadata)?; |
|
|
|
@ -411,7 +477,7 @@ impl DataStorage {
@@ -411,7 +477,7 @@ impl DataStorage {
|
|
|
|
|
for line in read_to_string(self.base_dir.join(USERS_FILE.as_str()))?.lines() { |
|
|
|
|
if line.starts_with("---") { |
|
|
|
|
is_admin = false; |
|
|
|
|
break; |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
let user = UserData::parse(line, is_admin)?; |
|
|
|
|
self.users.insert(user.username.clone(), user); |
|
|
|
@ -420,7 +486,7 @@ impl DataStorage {
@@ -420,7 +486,7 @@ impl DataStorage {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn write_redirects(&self) -> Result<(), Error> { |
|
|
|
|
let mut fuckery: String = self |
|
|
|
|
let fuckery: String = self |
|
|
|
|
.redirects |
|
|
|
|
.iter() |
|
|
|
|
.map(|meow| format!("{}: {}\n", meow.key(), meow.value())) |
|
|
|
@ -432,7 +498,7 @@ impl DataStorage {
@@ -432,7 +498,7 @@ impl DataStorage {
|
|
|
|
|
|
|
|
|
|
fn write_users(&self) -> Result<(), Error> { |
|
|
|
|
let mut fuckery = String::from("---"); |
|
|
|
|
self.users.iter().map(|item| { |
|
|
|
|
self.users.iter().for_each(|item| { |
|
|
|
|
if item.value().is_admin { |
|
|
|
|
fuckery = format!("{}\n{}", item.value().to_string(), fuckery) |
|
|
|
|
} else { |
|
|
|
|