rush_sync_server/commands/create/
command.rs

1// CreateCommand - src/commands/create/command.rs
2use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerInfo, ServerStatus};
5use crate::server::utils::port::{find_next_available_port, is_port_available};
6use crate::server::utils::validation::validate_server_name;
7use std::future::Future;
8use std::pin::Pin;
9use uuid::Uuid;
10
11#[derive(Debug, Default)]
12pub struct CreateCommand;
13
14impl CreateCommand {
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl Command for CreateCommand {
21    fn name(&self) -> &'static str {
22        "create"
23    }
24    fn description(&self) -> &'static str {
25        "Create a new web server"
26    }
27    fn matches(&self, command: &str) -> bool {
28        command.trim().to_lowercase().starts_with("create")
29    }
30
31    fn execute_sync(&self, args: &[&str]) -> Result<String> {
32        let ctx = crate::server::shared::get_shared_context();
33
34        match args.len() {
35            0 => self.create_server(ctx, None, None),
36            1 => {
37                if let Ok(port) = args[0].parse::<u16>() {
38                    self.create_server(ctx, None, Some(port))
39                } else {
40                    self.create_server(ctx, Some(args[0].to_string()), None)
41                }
42            }
43            2 => match args[1].parse::<u16>() {
44                Ok(port) => self.create_server(ctx, Some(args[0].to_string()), Some(port)),
45                Err(_) => Err(AppError::Validation("Invalid port".to_string())),
46            },
47            _ => Err(AppError::Validation("Too many parameters".to_string())),
48        }
49    }
50
51    fn execute_async<'a>(
52        &'a self,
53        args: &'a [&'a str],
54    ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
55        Box::pin(async move { self.execute_sync(args) })
56    }
57
58    fn supports_async(&self) -> bool {
59        true
60    }
61    fn priority(&self) -> u8 {
62        65
63    }
64}
65
66impl CreateCommand {
67    fn create_server(
68        &self,
69        ctx: &ServerContext,
70        custom_name: Option<String>,
71        custom_port: Option<u16>,
72    ) -> Result<String> {
73        let id = Uuid::new_v4().to_string();
74        let has_custom_name = custom_name.is_some();
75        let has_custom_port = custom_port.is_some();
76
77        let name = if let Some(custom_name) = custom_name {
78            validate_server_name(&custom_name)?;
79            let servers = ctx.servers.read().unwrap();
80            if servers.values().any(|s| s.name == custom_name) {
81                return Err(AppError::Validation(format!(
82                    "Server-Name '{}' bereits vergeben!",
83                    custom_name
84                )));
85            }
86            custom_name
87        } else {
88            let server_number = self.find_next_server_number(ctx);
89            format!("rush-sync-server-{:03}", server_number)
90        };
91
92        let port = if let Some(custom_port) = custom_port {
93            if custom_port < 1024 {
94                return Err(AppError::Validation("Port muss >= 1024 sein!".to_string()));
95            }
96            let servers = ctx.servers.read().unwrap();
97            if servers.values().any(|s| s.port == custom_port) {
98                return Err(AppError::Validation(format!(
99                    "Port {} bereits verwendet!",
100                    custom_port
101                )));
102            }
103            if !is_port_available(custom_port) {
104                return Err(AppError::Validation(format!(
105                    "Port {} bereits belegt!",
106                    custom_port
107                )));
108            }
109            custom_port
110        } else {
111            find_next_available_port(ctx)?
112        };
113
114        let timestamp = std::time::SystemTime::now()
115            .duration_since(std::time::UNIX_EPOCH)
116            .unwrap_or_default()
117            .as_secs();
118
119        let server_info = ServerInfo {
120            id: id.clone(),
121            name: name.clone(),
122            port,
123            status: ServerStatus::Stopped,
124            created_at: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
125            created_timestamp: timestamp,
126        };
127
128        ctx.servers.write().unwrap().insert(id.clone(), server_info);
129
130        let success_msg = if has_custom_name || has_custom_port {
131            format!(
132                "Custom Server erstellt: '{}' (ID: {}) auf Port {}",
133                name,
134                &id[0..8],
135                port
136            )
137        } else {
138            format!(
139                "Server erstellt: '{}' (ID: {}) auf Port {}",
140                name,
141                &id[0..8],
142                port
143            )
144        };
145
146        Ok(success_msg)
147    }
148
149    fn find_next_server_number(&self, ctx: &ServerContext) -> u32 {
150        let servers = ctx.servers.read().unwrap();
151        let mut existing_numbers = Vec::new();
152
153        for server in servers.values() {
154            if let Some(number_str) = server.name.strip_prefix("rush-sync-server-") {
155                if let Ok(number) = number_str.parse::<u32>() {
156                    existing_numbers.push(number);
157                }
158            }
159        }
160
161        existing_numbers.sort();
162        let mut next_number = 1;
163        for &existing in &existing_numbers {
164            if existing == next_number {
165                next_number += 1;
166            } else {
167                break;
168            }
169        }
170        next_number
171    }
172}