flowscope_cli/server/
state.rs1use std::collections::HashMap;
7use std::path::PathBuf;
8use std::time::SystemTime;
9
10use anyhow::{Context, Result};
11#[cfg(feature = "templating")]
12use flowscope_core::TemplateConfig;
13use flowscope_core::{Dialect, FileSource, SchemaMetadata};
14use tokio::sync::RwLock;
15
16#[derive(Debug, Clone)]
18pub struct ServerConfig {
19 pub dialect: Dialect,
21 pub watch_dirs: Vec<PathBuf>,
23 pub static_files: Option<Vec<FileSource>>,
25 pub metadata_url: Option<String>,
27 pub metadata_schema: Option<String>,
29 pub port: u16,
31 pub open_browser: bool,
33 pub schema_path: Option<PathBuf>,
35 #[cfg(feature = "templating")]
37 pub template_config: Option<TemplateConfig>,
38}
39
40pub struct AppState {
42 pub config: ServerConfig,
44 pub files: RwLock<Vec<FileSource>>,
46 pub schema: RwLock<Option<SchemaMetadata>>,
48 pub mtimes: RwLock<HashMap<PathBuf, SystemTime>>,
50}
51
52impl AppState {
53 pub async fn new(config: ServerConfig) -> Result<Self> {
55 let (files, mtimes) = if let Some(ref static_files) = config.static_files {
57 (static_files.clone(), HashMap::new())
59 } else {
60 let watch_dirs = config.watch_dirs.clone();
62 let scan_result =
63 tokio::task::spawn_blocking(move || super::scan_sql_files(&watch_dirs))
64 .await
65 .context("File scan task was cancelled")?;
66 scan_result.context("Failed to scan SQL files")?
67 };
68 let file_count = files.len();
69
70 let schema = Self::load_schema(&config).await?;
72
73 if file_count > 0 {
74 println!("flowscope: loaded {} SQL file(s)", file_count);
75 }
76
77 Ok(Self {
78 config,
79 files: RwLock::new(files),
80 schema: RwLock::new(schema),
81 mtimes: RwLock::new(mtimes),
82 })
83 }
84
85 #[cfg(feature = "metadata-provider")]
91 async fn load_schema(config: &ServerConfig) -> Result<Option<SchemaMetadata>> {
92 if let Some(ref url) = config.metadata_url {
93 let url = url.clone();
94 let schema_filter = config.metadata_schema.clone();
95 let fetch_result = tokio::task::spawn_blocking(move || {
96 crate::metadata::fetch_metadata_from_database(&url, schema_filter)
97 })
98 .await
99 .context("Metadata fetch task was cancelled")?;
100 let schema = fetch_result.context("Failed to fetch database metadata")?;
101 println!("flowscope: loaded schema from database");
102 return Ok(Some(schema));
103 }
104 Self::load_schema_from_file(config)
105 }
106
107 #[cfg(not(feature = "metadata-provider"))]
108 async fn load_schema(config: &ServerConfig) -> Result<Option<SchemaMetadata>> {
109 Self::load_schema_from_file(config)
110 }
111
112 fn load_schema_from_file(config: &ServerConfig) -> Result<Option<SchemaMetadata>> {
113 if let Some(ref path) = config.schema_path {
114 let schema = crate::schema::load_schema_from_ddl(path, config.dialect)?;
115 println!("flowscope: loaded schema from DDL file: {}", path.display());
116 return Ok(Some(schema));
117 }
118 Ok(None)
119 }
120
121 pub async fn reload_files(&self) -> Result<()> {
128 if self.config.static_files.is_some() {
130 return Ok(());
131 }
132
133 let watch_dirs = self.config.watch_dirs.clone();
134
135 let scan_result = tokio::task::spawn_blocking(move || super::scan_sql_files(&watch_dirs))
137 .await
138 .context("File scan task was cancelled")?;
139 let (files, mtimes) = scan_result.context("Failed to scan SQL files")?;
140
141 let count = files.len();
142 *self.files.write().await = files;
143 *self.mtimes.write().await = mtimes;
144 println!("flowscope: reloaded {} SQL file(s)", count);
145 Ok(())
146 }
147}