feat: hashids

This commit is contained in:
Heng-Yi Wu 2022-11-01 21:15:13 +08:00
parent e258bcc2bd
commit b5da40fbdc
No known key found for this signature in database
GPG key ID: 3A63404431B91B99
10 changed files with 2165 additions and 16 deletions

2067
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -31,6 +31,7 @@ env_logger = "0.9.0"
actix-web-httpauth = "0.6.0" actix-web-httpauth = "0.6.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
syntect = "5.0" syntect = "5.0"
harsh = "0.2"
[profile.release] [profile.release]
lto = true lto = true

View file

@ -68,6 +68,9 @@ pub struct Args {
#[clap(short, long, env = "MICROBIN_NO_FILE_UPLOAD")] #[clap(short, long, env = "MICROBIN_NO_FILE_UPLOAD")]
pub no_file_upload: bool, pub no_file_upload: bool,
#[clap(long, env = "MICROBIN_HASH_IDS")]
pub hash_ids: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -1,6 +1,7 @@
use crate::dbio::save_to_file; use crate::dbio::save_to_file;
use crate::pasta::PastaFile; use crate::pasta::PastaFile;
use crate::util::animalnumbers::to_animal_names; use crate::util::animalnumbers::to_animal_names;
use crate::util::hashids::to_hashids;
use crate::util::misc::is_valid_url; use crate::util::misc::is_valid_url;
use crate::{AppState, Pasta, ARGS}; use crate::{AppState, Pasta, ARGS};
use actix_multipart::Multipart; use actix_multipart::Multipart;
@ -126,8 +127,11 @@ pub async fn create(
} }
}; };
std::fs::create_dir_all(format!("./pasta_data/public/{}", &new_pasta.id_as_animals())) std::fs::create_dir_all(format!(
.unwrap(); "./pasta_data/public/{}",
&new_pasta.id_as_animals()
))
.unwrap();
let filepath = format!( let filepath = format!(
"./pasta_data/public/{}/{}", "./pasta_data/public/{}/{}",
@ -157,7 +161,12 @@ pub async fn create(
save_to_file(&pastas); save_to_file(&pastas);
let slug = if ARGS.hash_ids {
to_hashids(id)
} else {
to_animal_names(id)
};
Ok(HttpResponse::Found() Ok(HttpResponse::Found()
.append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, to_animal_names(id)))) .append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, slug)))
.finish()) .finish())
} }

View file

@ -2,6 +2,7 @@ use crate::args::Args;
use crate::dbio::save_to_file; use crate::dbio::save_to_file;
use crate::endpoints::errors::ErrorTemplate; use crate::endpoints::errors::ErrorTemplate;
use crate::util::animalnumbers::to_u64; use crate::util::animalnumbers::to_u64;
use crate::util::hashids::to_u64 as hashid_to_u64;
use crate::util::misc::remove_expired; use crate::util::misc::remove_expired;
use crate::{AppState, Pasta, ARGS}; use crate::{AppState, Pasta, ARGS};
use actix_multipart::Multipart; use actix_multipart::Multipart;
@ -20,7 +21,11 @@ struct EditTemplate<'a> {
pub async fn get_edit(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse { pub async fn get_edit(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()).unwrap_or(0); let id = if ARGS.hash_ids {
hashid_to_u64(&*id).unwrap_or(0)
} else {
to_u64(&*id.into_inner()).unwrap_or(0)
};
remove_expired(&mut pastas); remove_expired(&mut pastas);
@ -59,7 +64,11 @@ pub async fn post_edit(
.finish()); .finish());
} }
let id = to_u64(&*id.into_inner()).unwrap_or(0); let id = if ARGS.hash_ids {
hashid_to_u64(&*id).unwrap_or(0)
} else {
to_u64(&*id.into_inner()).unwrap_or(0)
};
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
@ -85,7 +94,10 @@ pub async fn post_edit(
save_to_file(&pastas); save_to_file(&pastas);
return Ok(HttpResponse::Found() return Ok(HttpResponse::Found()
.append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, pastas[i].id_as_animals()))) .append_header((
"Location",
format!("{}/pasta/{}", ARGS.public_path, pastas[i].id_as_animals()),
))
.finish()); .finish());
} else { } else {
break; break;

View file

@ -5,6 +5,7 @@ use crate::args::{Args, ARGS};
use crate::endpoints::errors::ErrorTemplate; use crate::endpoints::errors::ErrorTemplate;
use crate::pasta::Pasta; use crate::pasta::Pasta;
use crate::util::animalnumbers::to_u64; use crate::util::animalnumbers::to_u64;
use crate::util::hashids::to_u64 as hashid_to_u64;
use crate::util::misc::remove_expired; use crate::util::misc::remove_expired;
use crate::AppState; use crate::AppState;
@ -19,7 +20,13 @@ struct PastaTemplate<'a> {
pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse { pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()).unwrap_or(0); println!("{}", id);
let id = if ARGS.hash_ids {
hashid_to_u64(&*id).unwrap_or(0)
} else {
to_u64(&*id.into_inner()).unwrap_or(0)
};
println!("{}", id); println!("{}", id);
@ -47,7 +54,11 @@ pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpR
pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse { pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()).unwrap_or(0); let id = if ARGS.hash_ids {
hashid_to_u64(&*id).unwrap_or(0)
} else {
to_u64(&*id.into_inner()).unwrap_or(0)
};
remove_expired(&mut pastas); remove_expired(&mut pastas);
@ -74,7 +85,11 @@ pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> Ht
pub async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String { pub async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()).unwrap_or(0); let id = if ARGS.hash_ids {
hashid_to_u64(&*id).unwrap_or(0)
} else {
to_u64(&*id.into_inner()).unwrap_or(0)
};
remove_expired(&mut pastas); remove_expired(&mut pastas);

View file

@ -4,6 +4,7 @@ use crate::args::ARGS;
use crate::endpoints::errors::ErrorTemplate; use crate::endpoints::errors::ErrorTemplate;
use crate::pasta::PastaFile; use crate::pasta::PastaFile;
use crate::util::animalnumbers::to_u64; use crate::util::animalnumbers::to_u64;
use crate::util::hashids::to_u64 as hashid_to_u64;
use crate::util::misc::remove_expired; use crate::util::misc::remove_expired;
use crate::AppState; use crate::AppState;
use askama::Template; use askama::Template;
@ -19,20 +20,30 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()).unwrap_or(0); let id = if ARGS.hash_ids {
hashid_to_u64(&*id).unwrap_or(0)
} else {
to_u64(&*id.into_inner()).unwrap_or(0)
};
for (i, pasta) in pastas.iter().enumerate() { for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id { if pasta.id == id {
// remove the file itself // remove the file itself
if let Some(PastaFile { name, .. }) = &pasta.file { if let Some(PastaFile { name, .. }) = &pasta.file {
if fs::remove_file(format!("./pasta_data/public/{}/{}", pasta.id_as_animals(), name)) if fs::remove_file(format!(
.is_err() "./pasta_data/public/{}/{}",
pasta.id_as_animals(),
name
))
.is_err()
{ {
log::error!("Failed to delete file {}!", name) log::error!("Failed to delete file {}!", name)
} }
// and remove the containing directory // and remove the containing directory
if fs::remove_dir(format!("./pasta_data/public/{}/", pasta.id_as_animals())).is_err() { if fs::remove_dir(format!("./pasta_data/public/{}/", pasta.id_as_animals()))
.is_err()
{
log::error!("Failed to delete directory {}!", name) log::error!("Failed to delete directory {}!", name)
} }
} }

View file

@ -23,6 +23,7 @@ pub mod util {
pub mod animalnumbers; pub mod animalnumbers;
pub mod auth; pub mod auth;
pub mod dbio; pub mod dbio;
pub mod hashids;
pub mod misc; pub mod misc;
pub mod syntaxhighlighter; pub mod syntaxhighlighter;
} }
@ -66,8 +67,14 @@ async fn main() -> std::io::Result<()> {
match fs::create_dir_all("./pasta_data/public") { match fs::create_dir_all("./pasta_data/public") {
Ok(dir) => dir, Ok(dir) => dir,
Err(error) => { Err(error) => {
log::error!("Couldn't create data directory ./pasta_data/public/: {:?}", error); log::error!(
panic!("Couldn't create data directory ./pasta_data/public/: {:?}", error); "Couldn't create data directory ./pasta_data/public/: {:?}",
error
);
panic!(
"Couldn't create data directory ./pasta_data/public/: {:?}",
error
);
} }
}; };

View file

@ -4,7 +4,9 @@ use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::path::Path; use std::path::Path;
use crate::args::ARGS;
use crate::util::animalnumbers::to_animal_names; use crate::util::animalnumbers::to_animal_names;
use crate::util::hashids::to_hashids;
use crate::util::syntaxhighlighter::html_highlight; use crate::util::syntaxhighlighter::html_highlight;
#[derive(Serialize, Deserialize, PartialEq, Eq)] #[derive(Serialize, Deserialize, PartialEq, Eq)]
@ -44,7 +46,11 @@ pub struct Pasta {
impl Pasta { impl Pasta {
pub fn id_as_animals(&self) -> String { pub fn id_as_animals(&self) -> String {
to_animal_names(self.id) if ARGS.hash_ids {
to_hashids(self.id)
} else {
to_animal_names(self.id)
}
} }
pub fn created_as_string(&self) -> String { pub fn created_as_string(&self) -> String {

18
src/util/hashids.rs Normal file
View file

@ -0,0 +1,18 @@
use harsh::Harsh;
use lazy_static::lazy_static;
lazy_static! {
pub static ref HARSH: Harsh = Harsh::builder().length(6).build().unwrap();
}
pub fn to_hashids(number: u64) -> String {
HARSH.encode(&[number])
}
pub fn to_u64(hash_id: &str) -> Result<u64, &str> {
let ids = HARSH
.decode(hash_id)
.map_err(|_e| "Failed to decode hash ID")?;
let id = ids.get(0).ok_or("No ID found in hash ID")?;
Ok(*id)
}