1use crate::jobs::{Job, JobID};
22use crate::utils::defaults::DEFAULT_TEXT_ITEM;
23use crate::utils::types::RUMString;
24use askama::PrimitiveType;
25use axum::extract::State;
26use tower_http::cors::CorsLayer;
27pub use phf_macros::phf_ordered_map as rumtk_create_const_ordered_map;
28use crate::defaults::{DEFAULT_LANG_ITEM, DEFAULT_THEME_ITEM};
29use crate::{NestedNestedTextMap, NestedTextMap, PipelineGroup, RootNestedNestedTextMap, TextMap};
30use reqwest::header;
31use rumtk_core::base::RUMVec;
32use rumtk_core::net::tcp::SafeLock;
33use rumtk_core::pipelines::pipeline_types::RUMCommandLine;
34use rumtk_core::serde::{RUMDeJson, RUMSerJson};
35use rumtk_core::types::RUMID;
36use rumtk_core::types::{RUMHashMap, RUMOrderedMap};
37use rumtk_core::{rumtk_generate_id, rumtk_new_lock};
38
39#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
40pub struct FlagsConf {
41 pub custom_css: bool,
42 pub enable_icons: bool,
43 pub enable_captcha: bool,
44}
45#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
46pub struct HeaderConf {
47 pub logo_source: Option<RUMString>,
48 pub icon_source: Option<RUMString>,
49 pub icon_type: Option<RUMString>,
50 pub disable_navlinks: bool,
51 pub disable_logo: bool,
52}
53
54#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
55pub struct FooterConf {
56 pub socials_list: RUMString,
57 pub disable_contact_button: bool,
58}
59
60#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
61pub struct PipelineConf {
62 pub settings: Option<TextMap>,
63 pub data_templates: Option<NestedTextMap>,
64 pub targets: Option<TextMap>,
65 pub categories: Option<RUMHashMap<RUMString, PipelineGroup>>
66}
67
68impl PipelineConf {
69 pub fn get_settings(&self) -> Option<&TextMap> {
70 self.settings.as_ref()
71 }
72
73 pub fn get_pipeline_category(&self, pipeline_category: &str) -> Option<&PipelineGroup> {
74 match self.categories {
75 Some(ref categories) => {
76 match categories.get(pipeline_category) {
77 Some(pipelines) => Some(pipelines),
78 None => None
79 }
80 }
81 None => None,
82 }
83 }
84 pub fn get_available_pipeline_names(&self) -> Vec<&RUMString> {
85 match self.targets.as_ref() {
86 Some(group) => {
87 let mut keys = group.keys().collect::<Vec<&RUMString>>();
88 keys.sort_unstable();
89 keys
90 },
91 None => vec![]
92 }
93 }
94 pub fn get_pipeline(&self, pipeline_category: &str, pipeline_name: &str) -> RUMCommandLine {
95 match self.get_pipeline_category(pipeline_category) {
96 Some(group) => match group.get(pipeline_name) {
97 Some(pipeline) => pipeline.to_owned(),
98 None => RUMCommandLine::new()
99 },
100 None => RUMCommandLine::new()
101 }
102 }
103
104 pub fn get_target(&self, profile: &str) -> RUMString {
105 match self.targets.as_ref() {
106 Some(targets) => match targets.get(profile) {
107 Some(pipeline) => pipeline.to_owned(),
108 None => RUMString::default()
109 },
110 None => RUMString::default()
111 }
112 }
113
114 pub fn get_template(&self, name: &str) -> Option<&TextMap> {
115 match self.data_templates.as_ref() {
116 Some(templates) => templates.get(name),
117 None => None
118 }
119 }
120
121 pub fn get_available_data_templates(&self) -> Vec<&RUMString> {
122 match self.data_templates.as_ref() {
123 Some(group) => {
124 let mut keys = group.keys().collect::<Vec<&RUMString>>();
125 keys.sort_unstable();
126 keys
127 },
128 None => vec![]
129 }
130 }
131}
132
133#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
134pub struct PageConf {
135 pub url: RUMString,
136 pub _static: bool,
137}
138
139pub type PageMap = RUMOrderedMap<RUMString, PageConf>;
140
141#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
142pub struct RouterConf {
143 pub pages: Option<PageMap>,
144 pub redirect: Option<TextMap>,
145 pub service_routes: Option<NestedTextMap>,
146}
147
148impl RouterConf {
149 pub fn get_page(&self, name: &RUMString) -> Option<&PageConf> {
150 match &self.pages {
151 Some(pages) => pages.get(name),
152 None => None
153 }
154 }
155
156 pub fn get_redirect(&self, name: &RUMString) -> Option<&RUMString> {
157 match &self.redirect {
158 Some(redirects) => redirects.get(name),
159 None => None
160 }
161 }
162
163 pub fn get_service_route(&self, name: &RUMString) -> Option<TextMap> {
164 match &self.service_routes {
165 Some(service_routes) => Some(service_routes.get(name)?.clone()),
166 None => None
167 }
168 }
169}
170
171#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone, Default)]
172pub struct CORSConf {
173 pub origins: Option<RUMVec<RUMString>>,
174 pub methods: Option<RUMVec<RUMString>>,
175 pub headers: Option<RUMVec<RUMString>>,
176 pub allow_credentials: bool,
177 pub allow_private_network: bool,
178}
179
180impl CORSConf {
181 pub fn build_cors_layer(&self) -> CorsLayer {
182 use axum::http::HeaderValue;
183 use axum::http::{header, method};
184
185 let mut cors = CorsLayer::new();
186 match &self.origins {
187 Some(origins) => {
188 for origin in origins {
189 let o = origin.parse::<HeaderValue>().unwrap();
190 cors = cors.allow_origin(o);
191 }
192 },
193 None => {},
194 }
195 match &self.methods {
196 Some(methods) => {
197 let m: RUMVec<method::Method> = methods.iter().map(|method| method.parse::<method::Method>().unwrap()).collect();
198 cors = cors.allow_methods(m);
199 },
200 None => {}
201 }
202 match &self.headers {
203 Some(headers) => {
204 let h: RUMVec<header::HeaderName> = headers.iter().map(|header| header.parse::<header::HeaderName>().unwrap()).collect();
205 cors = cors.allow_headers(h);
206 },
207 None => {}
208 }
209 cors = cors.allow_credentials(self.allow_credentials);
210 if self.allow_credentials {
211 cors = cors.allow_headers([header::AUTHORIZATION, header::ACCEPT]);
212 }
213 cors = cors.allow_private_network(self.allow_private_network);
214 cors
215 }
216}
217
218#[derive(RUMSerJson, RUMDeJson, PartialEq, Debug, Clone)]
226pub struct AppConf {
227 pub title: RUMString,
228 pub description: RUMString,
229 pub company: RUMString,
230 pub copyright: RUMString,
231 pub lang: RUMString,
232 pub theme: RUMString,
233 pub flags: FlagsConf,
234 pub header_conf: HeaderConf,
235 pub footer_conf: FooterConf,
236
237 pub strings: RootNestedNestedTextMap,
238 pub config: NestedNestedTextMap,
239 pub pipelines: PipelineConf,
240 pub router: RouterConf,
241 pub cors: Option<CORSConf>,
242 pub captcha: Option<TextMap>,
243 }
245
246impl AppConf {
247 pub fn update_site_info(
248 &mut self,
249 title: RUMString,
250 description: RUMString,
251 company: RUMString,
252 copyright: RUMString,
253 ) {
254 if !title.is_empty() {
255 self.title = title;
256 }
257 if !company.is_empty() {
258 self.company = company;
259 }
260 if !description.is_empty() {
261 self.description = description;
262 }
263 if !copyright.is_empty() {
264 self.copyright = copyright;
265 }
266 }
267
268 pub fn get_pipelines(&self) -> &PipelineConf {
269 &self.pipelines
270 }
271
272 pub fn get_text(&self, item: &str) -> NestedTextMap {
273 match self.strings.get(&self.lang) {
274 Some(l) => match l.get(item) {
275 Some(i) => i.clone(),
276 None => NestedTextMap::default(),
277 },
278 None => NestedTextMap::default(),
279 }
280 }
281
282 pub fn get_section(&self, section: &str) -> TextMap {
283 match self.config.get(&self.lang) {
284 Some(l) => match l.get(section) {
285 Some(i) => i.clone(),
286 None => self.get_default_item(section),
287 },
288 None => self.get_default_item(section),
289 }
290 }
291
292 pub fn get_default_item(&self, section: &str) -> TextMap {
293 match self.config.get(DEFAULT_TEXT_ITEM) {
294 Some(l) => match l.get(section) {
295 Some(i) => i.clone(),
296 None => TextMap::default(),
297 },
298 None => TextMap::default(),
299 }
300 }
301}
302
303impl Default for AppConf {
304 fn default() -> Self {
305 AppConf {
306 title: "".to_string(),
307 description: "".to_string(),
308 company: "".to_string(),
309 copyright: "".to_string(),
310 lang: DEFAULT_LANG_ITEM.to_string(),
311 theme: DEFAULT_THEME_ITEM.to_string(),
312 flags: FlagsConf::default(),
313 header_conf: HeaderConf::default(),
314 footer_conf: FooterConf::default(),
315 strings: RootNestedNestedTextMap::default(),
316 config: NestedNestedTextMap::default(),
317 pipelines: PipelineConf::default(),
318 router: RouterConf::default(),
319 cors: Some(CORSConf::default()),
320 captcha: None
321 }
322 }
323}
324
325pub type ClipboardID = RUMString;
326#[derive(Default, Debug, Clone)]
332pub struct AppState {
333 config: AppConf,
334 clipboard: NestedTextMap,
335 jobs: RUMHashMap<RUMID, Job>,
336}
337
338pub type SharedAppState = SafeLock<AppState>;
339
340impl AppState {
341 pub fn new() -> AppState {
342 AppState {
343 config: AppConf::default(),
344 clipboard: NestedTextMap::default(),
345 jobs: RUMHashMap::default(),
346 }
347 }
348
349 pub fn new_safe() -> SharedAppState {
350 rumtk_new_lock!(AppState::new())
351 }
352
353 pub fn from_safe(conf: AppConf) -> SharedAppState {
354 rumtk_new_lock!(AppState::from(conf))
355 }
356
357 pub fn get_config(&self) -> &AppConf {
358 &self.config
359 }
360
361 pub fn get_config_mut(&mut self) -> &mut AppConf {
362 &mut self.config
363 }
364
365 pub fn has_clipboard(&self, id: &ClipboardID) -> bool {
366 self.clipboard.contains_key(id)
367 }
368
369 pub fn has_job(&self, id: &JobID) -> bool {
370 self.jobs.contains_key(id)
371 }
372
373 pub fn push_job_result(&mut self, id: &JobID, job: Job) {
374 self.jobs.insert(id.clone(), job);
375 }
376
377 pub fn push_to_clipboard(&mut self, data: TextMap) -> ClipboardID {
378 let clipboard_id = rumtk_generate_id!().to_string();
379 self.clipboard.insert(clipboard_id.clone(), data);
380 clipboard_id
381 }
382
383 pub fn request_clipboard_slice(&mut self) -> ClipboardID {
384 let clipboard_id = rumtk_generate_id!().to_string();
385 self.clipboard
386 .insert(clipboard_id.clone(), TextMap::default());
387 clipboard_id
388 }
389
390 pub fn pop_job(&mut self, id: &RUMID) -> Option<Job> {
391 self.jobs.remove(id)
392 }
393
394 pub fn pop_clipboard(&mut self, id: &ClipboardID) -> Option<TextMap> {
395 self.clipboard.shift_remove(id)
396 }
397}
398
399impl From<AppConf> for AppState {
400 fn from(config: AppConf) -> Self {
401 AppState {
402 config,
403 clipboard: NestedTextMap::default(),
404 jobs: RUMHashMap::default(),
405 }
406 }
407}
408
409pub type RouterAppState = State<SharedAppState>;
410
411#[macro_export]
454macro_rules! rumtk_web_load_conf {
455 ( $args:expr ) => {{
456 use $crate::defaults::{DEFAULT_APP_CONFIG};
457 rumtk_web_load_conf!($args, DEFAULT_APP_CONFIG)
458 }};
459 ( $args:expr, $path:expr ) => {{
460 use rumtk_core::rumtk_deserialize;
461 use rumtk_core::strings::RUMStringConversions;
462 use rumtk_core::types::RUMHashMap;
463 use $crate::AppConf;
464 use std::fs;
465
466 use $crate::rumtk_web_save_conf;
467 use $crate::utils::{AppState, TextMap};
468
469 let json = match fs::read_to_string($path) {
470 Ok(json) => json,
471 Err(err) => rumtk_web_save_conf!($path),
472 };
473
474 let mut conf: AppConf = match rumtk_deserialize!(&json) {
475 Ok(conf) => conf,
476 Err(err) => panic!(
477 "The App config file in {} does not meet the expected structure. \
478 See the documentation for more information. Error: {}\n{}",
479 $path, err, json
480 ),
481 };
482 conf.update_site_info(
483 $args.title.clone(),
484 $args.description.clone(),
485 $args.company.clone(),
486 $args.copyright.clone(),
487 );
488 AppState::from_safe(conf)
489 }};
490}
491
492#[macro_export]
524macro_rules! rumtk_web_save_conf {
525 ( ) => {{
526 $crate::utils::defaults::DEFAULT_APP_CONFIG;
527 rumtk_web_save_conf!(DEFAULT_APP_CONFIG)
528 }};
529 ( $path:expr ) => {{
530 use rumtk_core::rumtk_serialize;
531 use rumtk_core::strings::RUMStringConversions;
532 use std::fs;
533 use $crate::utils::AppConf;
534
535 let json = rumtk_serialize!(&AppConf::default()).unwrap_or_default();
536 fs::write($path, &json);
537 json
538 }};
539}
540
541#[macro_export]
546macro_rules! rumtk_web_get_config_string {
547 ( $conf:expr, $item:expr ) => {{
548 use $crate::rumtk_web_get_config;
549 use $crate::AppConf;
550 rumtk_web_get_config!($conf).get_text($item)
551 }};
552}
553
554#[macro_export]
560macro_rules! rumtk_web_get_config_section {
561 ( $conf:expr, $item:expr ) => {{
562 use $crate::rumtk_web_get_config;
563 use $crate::AppConf;
564 rumtk_web_get_config!($conf).get_section($item)
565 }};
566}
567
568#[macro_export]
586macro_rules! rumtk_web_get_pipelines {
587 ( $conf:expr ) => {{
588 use $crate::rumtk_web_get_config;
589 use $crate::AppConf;
590 rumtk_web_get_config!($conf).get_pipelines()
591 }};
592}
593
594#[macro_export]
612macro_rules! rumtk_web_get_config {
613 ( $state:expr ) => {{
614 use rumtk_core::{rumtk_lock_read};
615 rumtk_lock_read!($state.clone()).get_config()
616 }};
617}
618
619#[macro_export]
641macro_rules! rumtk_web_set_config {
642 ( $state:expr ) => {{
643 use rumtk_core::rumtk_lock_write;
644 rumtk_lock_write!($state.clone()).get_config_mut()
645 }};
646}
647
648#[macro_export]
667macro_rules! rumtk_web_modify_state {
668 ( $state:expr ) => {{
669 use rumtk_core::rumtk_lock_write;
670 rumtk_lock_write!($state.clone())
671 }};
672}