use std::fmt::Display;
use serde::{Serialize, Deserialize};
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
use paste::paste;
use std::sync::LazyLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Service {
pub id: usize,
pub created: usize,
pub owner: usize,
pub name: String,
pub files: Vec<ServiceFsEntry>,
pub revision: usize,
}
impl Service {
pub fn new(name: String, owner: usize) -> Self {
Self {
id: Snowflake::new().to_string().parse::<usize>().unwrap(),
created: unix_epoch_timestamp(),
owner,
name,
files: Vec::new(),
revision: unix_epoch_timestamp(),
}
}
pub fn file(&self, path: &str) -> Option<(ServiceFsEntry, Vec<String>)> {
let segments = path.chars().filter(|x| x == &'/').count();
let mut path = path.split("/");
let mut path_segment = path.next().unwrap();
let mut ids = Vec::new();
let mut i = 0;
let mut f = &self.files;
while let Some(nf) = f.iter().find(|x| x.name == path_segment) {
ids.push(nf.id.clone());
if i == segments {
return Some((nf.to_owned(), ids));
}
f = &nf.children;
path_segment = path.next().unwrap();
i += 1;
}
None
}
pub fn file_mut(&mut self, id_path: Vec<String>) -> Option<&mut ServiceFsEntry> {
let total_segments = id_path.len();
let mut i = 0;
let mut f = &mut self.files;
for segment in id_path {
if let Some(nf) = f.iter_mut().find(|x| (**x).id == segment) {
if i == total_segments - 1 {
return Some(nf);
}
f = &mut nf.children;
i += 1;
} else {
break;
}
}
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ServiceFsMime {
#[serde(alias = "text/html")]
Html,
#[serde(alias = "text/css")]
Css,
#[serde(alias = "text/javascript")]
Js,
#[serde(alias = "application/json")]
Json,
#[serde(alias = "text/plain")]
Plain,
}
impl Display for ServiceFsMime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Html => "text/html",
Self::Css => "text/css",
Self::Js => "text/javascript",
Self::Json => "application/json",
Self::Plain => "text/plain",
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceFsEntry {
pub id: String,
pub name: String,
pub mime: ServiceFsMime,
pub children: Vec<ServiceFsEntry>,
pub content: String,
}
macro_rules! domain_tld_display_match {
($self:ident, $($tld:ident),+ $(,)?) => {
match $self {
$(
Self::$tld => stringify!($tld).to_lowercase(),
)+
}
}
}
macro_rules! domain_tld_strings {
($($tld:ident),+ $(,)?) => {
$(
paste! {
const [<TLD_ $tld:snake:upper>]: LazyLock<String> = LazyLock::new(|| stringify!($tld).to_lowercase());
}
)+
}
}
macro_rules! domain_tld_from_match {
($value:ident, $($tld:ident),+ $(,)?) => {
{
$(
paste! {
let [<$tld:snake:lower>] = &*[<TLD_ $tld:snake:upper>];
}
)+;
$(
if $value == paste!{ [<$tld:snake:lower>] } {
return Self::$tld;
}
)+
return Self::Bunny;
}
}
}
macro_rules! define_domain_tlds {
($($tld:ident),+ $(,)?) => {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DomainTld {
$($tld),+
}
domain_tld_strings!($($tld),+);
impl From<&str> for DomainTld {
fn from(value: &str) -> Self {
domain_tld_from_match!(
value, $($tld),+
)
}
}
impl Display for DomainTld {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&domain_tld_display_match!(
self, $($tld),+
))
}
}
pub const TLDS_VEC: LazyLock<Vec<&str>> = LazyLock::new(|| vec![$(stringify!($tld)),+]);
}
}
define_domain_tlds!(
Bunny, Tet, Cool, Qwerty, Boy, Girl, Them, Quack, Bark, Meow, Silly, Wow, Neko, Yay, Lol, Love,
Fun, Gay, City, Woah, Clown, Apple, Yaoi, Yuri, World, Wav, Zero, Evil, Dragon, Yum, Site, All,
Me, Bug, Slop, Retro, Eye, Neo, Spring, Nurse, Pony
);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Domain {
pub id: usize,
pub created: usize,
pub owner: usize,
pub name: String,
pub tld: DomainTld,
pub data: Vec<(String, DomainData)>,
}
impl Domain {
pub fn new(name: String, tld: DomainTld, owner: usize) -> Self {
Self {
id: Snowflake::new().to_string().parse::<usize>().unwrap(),
created: unix_epoch_timestamp(),
owner,
name,
tld,
data: Vec::new(),
}
}
pub fn from_str(value: &str) -> (String, String, DomainTld, String) {
let no_protocol = value.replace("atto://", "");
let mut s: Vec<&str> = no_protocol.split("/").next().unwrap().split(".").collect();
s.reverse();
let mut s = s.into_iter();
let tld = DomainTld::from(s.next().unwrap());
let domain = s.next().unwrap_or("default.bunny");
let subdomain = s.next().unwrap_or("@");
let mut chars = no_protocol.chars();
let mut char = '.';
while char != '/' {
char = chars.next().unwrap_or('/');
}
let path: String = chars.collect();
(subdomain.to_owned(), domain.to_owned(), tld, path)
}
pub fn http_assets(input: String) -> String {
input.replace("\"atto://", "/api/v1/file?addr=atto://")
}
pub fn service(&self, subdomain: &str) -> Option<usize> {
let s = self.data.iter().find(|x| x.0 == subdomain)?;
match s.1 {
DomainData::Service(ref id) => Some(match id.parse::<usize>() {
Ok(id) => id,
Err(_) => return None,
}),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DomainData {
Service(String),
Text(String),
}