link shorting

This commit is contained in:
Alex-Emmet 2026-01-20 16:18:59 +01:00
commit ae67a8dad6
5 changed files with 117 additions and 0 deletions

View file

@ -13,6 +13,9 @@ pub enum DataTypes {
accepted_ids,
uuid,
register_id,
link,
settings,
settings_name,
chat_partner_id,
@ -124,6 +127,9 @@ pub enum CommunicationType {
error_no_call_id,
error_invalid_call_id,
success,
shorten_link,
settings_save,
settings_load,
settings_list,

View file

@ -2,4 +2,5 @@ pub mod api;
pub mod omikron_connection;
pub mod omikron_manager;
pub mod server;
pub mod short_link;
pub mod socket;

View file

@ -1,4 +1,5 @@
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::server::short_link::add_short_link;
use crate::sql::connection_status::ConnectionType;
use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id};
use crate::sql::user_online_tracker::{self};
@ -472,6 +473,21 @@ impl OmikronConnection {
}
}
}
if cv.is_type(CommunicationType::shorten_link) {
if let Some(link) = cv.get_data(DataTypes::link) {
if let Some(link) = link.as_str() {
if let Ok(short_link) = add_short_link(link).await {
let response = CommunicationValue::new(CommunicationType::shorten_link)
.with_id(cv.get_id())
.add_data(DataTypes::link, JsonValue::String(short_link));
self.send_message(&response).await;
return;
}
}
}
}
let response =
CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id());
self.send_message(&response).await;

View file

@ -1,5 +1,6 @@
use crate::log;
use crate::server::api;
use crate::server::short_link::get_short_link;
use crate::server::socket;
use crate::util::file_util::load_file_buf;
@ -126,6 +127,22 @@ impl Service<HttpRequest<Incoming>> for HttpService {
};
Ok(api::handle(&path, headers.clone(), body_string).await)
} else if path.starts_with("/direct") {
let short = path.split('/').nth(2).unwrap_or_default();
if let Ok(long) = get_short_link(short).await {
let response = HttpResponse::builder()
.status(StatusCode::FOUND)
.header("Location", long)
.body(Full::new(Bytes::from("")))
.unwrap();
Ok(response)
} else {
let response = HttpResponse::builder()
.status(StatusCode::NOT_FOUND)
.body(Full::new(Bytes::from("Short link not found")))
.unwrap();
Ok(response)
}
} else {
let response = HttpResponse::builder()
.status(StatusCode::BAD_REQUEST)

77
src/server/short_link.rs Normal file
View file

@ -0,0 +1,77 @@
use dashmap::DashMap;
use once_cell::sync::Lazy;
use rand::{Rng, thread_rng};
static LINKS: Lazy<DashMap<String, String>> = Lazy::new(DashMap::new);
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ#1234567890";
pub async fn add_short_link(long: &str) -> Result<String, ()> {
let raw = generate_unique_short_link().await;
LINKS.insert(raw.clone(), long.to_string());
Ok(format!(
"omega.tensamin.net/direct/{}",
format_with_dashes(&raw)
))
}
async fn generate_unique_short_link() -> String {
loop {
let short = generate_short_link().await;
if !LINKS.contains_key(&short) {
return short;
}
}
}
pub async fn generate_short_link() -> String {
let len = short_length();
let mut rng = thread_rng();
(0..len)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect()
}
pub async fn get_short_link(short: &str) -> Result<String, ()> {
let normalized = normalize_short(short);
LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(())
}
/* ---------------- helpers ---------------- */
fn short_length() -> usize {
let count = LINKS.len();
match count {
0..=1_999 => 4,
2_000..=999_999 => 8,
_ => 12,
}
}
fn format_with_dashes(s: &str) -> String {
s.chars()
.collect::<Vec<_>>()
.chunks(4)
.map(|c| c.iter().collect::<String>())
.collect::<Vec<_>>()
.join("-")
}
fn normalize_short(input: &str) -> String {
input
.chars()
.filter(|c| *c != '-')
.map(|c| match c {
'Q' | 'O' => '0',
'I' => 'l',
_ => c,
})
.collect()
}