1use std::fmt::Display;
2use serde::{Serialize, Deserialize};
3use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
4use paste::paste;
5use std::sync::LazyLock;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Service {
9 pub id: usize,
10 pub created: usize,
11 pub owner: usize,
12 pub name: String,
13 pub files: Vec<ServiceFsEntry>,
14 pub revision: usize,
15}
16
17impl Service {
18 pub fn new(name: String, owner: usize) -> Self {
20 Self {
21 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
22 created: unix_epoch_timestamp(),
23 owner,
24 name,
25 files: Vec::new(),
26 revision: unix_epoch_timestamp(),
27 }
28 }
29
30 pub fn file(&self, path: &str) -> Option<(ServiceFsEntry, Vec<String>)> {
35 let segments = path.chars().filter(|x| x == &'/').count();
36
37 let mut path = path.split("/");
38 let mut path_segment = path.next().unwrap();
39 let mut ids = Vec::new();
40 let mut i = 0;
41
42 let mut f = &self.files;
43
44 while let Some(nf) = f.iter().find(|x| x.name == path_segment) {
45 ids.push(nf.id.clone());
46
47 if i == segments {
48 return Some((nf.to_owned(), ids));
49 }
50
51 f = &nf.children;
52 path_segment = path.next().unwrap();
53 i += 1;
54 }
55
56 None
57 }
58
59 pub fn file_mut(&mut self, id_path: Vec<String>) -> Option<&mut ServiceFsEntry> {
64 let total_segments = id_path.len();
65 let mut i = 0;
66
67 let mut f = &mut self.files;
68 for segment in id_path {
69 if let Some(nf) = f.iter_mut().find(|x| (**x).id == segment) {
70 if i == total_segments - 1 {
71 return Some(nf);
72 }
73
74 f = &mut nf.children;
75 i += 1;
76 } else {
77 break;
78 }
79 }
80
81 None
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87pub enum ServiceFsMime {
88 #[serde(alias = "text/html")]
89 Html,
90 #[serde(alias = "text/css")]
91 Css,
92 #[serde(alias = "text/javascript")]
93 Js,
94 #[serde(alias = "application/json")]
95 Json,
96 #[serde(alias = "text/plain")]
97 Plain,
98}
99
100impl Display for ServiceFsMime {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 f.write_str(match self {
103 Self::Html => "text/html",
104 Self::Css => "text/css",
105 Self::Js => "text/javascript",
106 Self::Json => "application/json",
107 Self::Plain => "text/plain",
108 })
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ServiceFsEntry {
115 pub id: String,
117 pub name: String,
118 pub mime: ServiceFsMime,
119 pub children: Vec<ServiceFsEntry>,
120 pub content: String,
121}
122
123macro_rules! domain_tld_display_match {
124 ($self:ident, $($tld:ident),+ $(,)?) => {
125 match $self {
126 $(
127 Self::$tld => stringify!($tld).to_lowercase(),
128 )+
129 }
130 }
131}
132
133macro_rules! domain_tld_strings {
134 ($($tld:ident),+ $(,)?) => {
135 $(
136 paste! {
137 const [<TLD_ $tld:snake:upper>]: LazyLock<String> = LazyLock::new(|| stringify!($tld).to_lowercase());
139 }
140 )+
141 }
142}
143
144macro_rules! domain_tld_from_match {
145 ($value:ident, $($tld:ident),+ $(,)?) => {
146 {
147 $(
148 paste! {
149 let [<$tld:snake:lower>] = &*[<TLD_ $tld:snake:upper>];
150 }
151 )+;
152
153 $(
155 if $value == paste!{ [<$tld:snake:lower>] } {
156 return Self::$tld;
157 }
158 )+
159
160 return Self::Bunny;
161 }
162 }
163}
164
165macro_rules! define_domain_tlds {
166 ($($tld:ident),+ $(,)?) => {
167 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168 pub enum DomainTld {
169 $($tld),+
170 }
171
172 domain_tld_strings!($($tld),+);
173
174 impl From<&str> for DomainTld {
175 fn from(value: &str) -> Self {
176 domain_tld_from_match!(
177 value, $($tld),+
178 )
179 }
180 }
181
182 impl Display for DomainTld {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 f.write_str(&domain_tld_display_match!(
186 self, $($tld),+
187 ))
188 }
189 }
190
191 pub const TLDS_VEC: LazyLock<Vec<&str>> = LazyLock::new(|| vec![$(stringify!($tld)),+]);
193 }
194}
195
196define_domain_tlds!(
197 Bunny, Tet, Cool, Qwerty, Boy, Girl, Them, Quack, Bark, Meow, Silly, Wow, Neko, Yay, Lol, Love,
198 Fun, Gay, City, Woah, Clown, Apple, Yaoi, Yuri, World, Wav, Zero, Evil, Dragon, Yum, Site, All,
199 Me, Bug, Slop, Retro, Eye, Neo, Spring, Nurse, Pony
200);
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct Domain {
204 pub id: usize,
205 pub created: usize,
206 pub owner: usize,
207 pub name: String,
208 pub tld: DomainTld,
209 pub data: Vec<(String, DomainData)>,
213}
214
215impl Domain {
216 pub fn new(name: String, tld: DomainTld, owner: usize) -> Self {
218 Self {
219 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
220 created: unix_epoch_timestamp(),
221 owner,
222 name,
223 tld,
224 data: Vec::new(),
225 }
226 }
227
228 pub fn from_str(value: &str) -> (String, String, DomainTld, String) {
233 let no_protocol = value.replace("atto://", "");
234
235 let mut s: Vec<&str> = no_protocol.split("/").next().unwrap().split(".").collect();
238 s.reverse();
239 let mut s = s.into_iter();
240
241 let tld = DomainTld::from(s.next().unwrap());
242 let domain = s.next().unwrap_or("default.bunny");
243 let subdomain = s.next().unwrap_or("@");
244
245 let mut chars = no_protocol.chars();
247 let mut char = '.';
248
249 while char != '/' {
250 char = chars.next().unwrap_or('/');
253 }
254
255 let path: String = chars.collect();
256
257 (subdomain.to_owned(), domain.to_owned(), tld, path)
259 }
260
261 pub fn http_assets(input: String) -> String {
265 input.replace("\"atto://", "/api/v1/file?addr=atto://")
272 }
273
274 pub fn service(&self, subdomain: &str) -> Option<usize> {
276 let s = self.data.iter().find(|x| x.0 == subdomain)?;
277 match s.1 {
278 DomainData::Service(ref id) => Some(match id.parse::<usize>() {
279 Ok(id) => id,
280 Err(_) => return None,
281 }),
282 _ => None,
283 }
284 }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
288pub enum DomainData {
289 Service(String),
292 Text(String),
294}