1use {
2 crate::{
3 ar_date_format,
4 project::{DeploymentMethod, DeploymentStatus},
5 runner::Runner,
6 },
7 chrono::{DateTime, Utc},
8 serde::{Deserialize, Serialize},
9 serde_repr::{Deserialize_repr, Serialize_repr},
10 std::fmt::Display,
11 tsync::tsync,
12};
13
14#[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
16#[repr(u8)]
17#[tsync]
18pub enum AppType {
19 #[default]
21 Web = 0,
22 Tauri = 1,
24}
25
26impl Display for AppType {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 AppType::Web => write!(f, "Web"),
30 AppType::Tauri => write!(f, "Tauri"),
31 }
32 }
33}
34
35#[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
37#[repr(u8)]
38#[tsync]
39pub enum RepoKind {
40 #[default]
41 SingleApp = 0,
42 Monorepo = 1,
43}
44
45impl Display for RepoKind {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 RepoKind::SingleApp => write!(f, "Single-app repo"),
49 RepoKind::Monorepo => write!(f, "Monorepo"),
50 }
51 }
52}
53
54#[derive(Deserialize)]
68#[serde(untagged)]
69enum EnumWireValue {
70 Int(u8),
71 Str(String),
72}
73
74fn deserialize_repo_kind<'de, D>(deserializer: D) -> Result<RepoKind, D::Error>
75where
76 D: serde::Deserializer<'de>,
77{
78 match EnumWireValue::deserialize(deserializer)? {
79 EnumWireValue::Int(0) => Ok(RepoKind::SingleApp),
80 EnumWireValue::Int(1) => Ok(RepoKind::Monorepo),
81 EnumWireValue::Str(value) if value == "single_app" => Ok(RepoKind::SingleApp),
82 EnumWireValue::Str(value) if value == "monorepo" => Ok(RepoKind::Monorepo),
83 EnumWireValue::Int(other) => Err(serde::de::Error::custom(format!(
84 "unknown repo_kind: {other}"
85 ))),
86 EnumWireValue::Str(other) => Err(serde::de::Error::custom(format!(
87 "unknown repo_kind: {other}"
88 ))),
89 }
90}
91
92fn deserialize_runner<'de, D>(deserializer: D) -> Result<Runner, D::Error>
93where
94 D: serde::Deserializer<'de>,
95{
96 match EnumWireValue::deserialize(deserializer)? {
97 EnumWireValue::Int(0) => Ok(Runner::NodeJs),
98 EnumWireValue::Int(1) => Ok(Runner::Static),
99 EnumWireValue::Int(2) => Ok(Runner::Ruby),
100 EnumWireValue::Int(3) => Ok(Runner::Swift),
101 EnumWireValue::Int(4) => Ok(Runner::Rust),
102 EnumWireValue::Int(255) => Ok(Runner::Monorepo),
103 EnumWireValue::Str(value) => match value.as_str() {
104 "node_js" => Ok(Runner::NodeJs),
105 "static" => Ok(Runner::Static),
106 "ruby" => Ok(Runner::Ruby),
107 "swift" => Ok(Runner::Swift),
108 "rust" => Ok(Runner::Rust),
109 "monorepo" => Ok(Runner::Monorepo),
110 other => Err(serde::de::Error::custom(format!("unknown runner: {other}"))),
111 },
112 EnumWireValue::Int(other) => {
113 Err(serde::de::Error::custom(format!("unknown runner: {other}")))
114 }
115 }
116}
117
118fn deserialize_deployment_method<'de, D>(deserializer: D) -> Result<DeploymentMethod, D::Error>
119where
120 D: serde::Deserializer<'de>,
121{
122 match EnumWireValue::deserialize(deserializer)? {
123 EnumWireValue::Int(0) => Ok(DeploymentMethod::Git),
124 EnumWireValue::Int(1) => Ok(DeploymentMethod::Rsync),
125 EnumWireValue::Str(value) if value == "git" => Ok(DeploymentMethod::Git),
126 EnumWireValue::Str(value) if value == "rsync" => Ok(DeploymentMethod::Rsync),
127 EnumWireValue::Int(other) => Err(serde::de::Error::custom(format!(
128 "unknown deployment_method: {other}"
129 ))),
130 EnumWireValue::Str(other) => Err(serde::de::Error::custom(format!(
131 "unknown deployment_method: {other}"
132 ))),
133 }
134}
135
136#[derive(Deserialize, Serialize, Debug, Clone)]
137#[tsync]
138pub struct DeployRepo {
139 pub id: i64,
140 pub name: String,
141 pub repository: String,
142 pub root_path: String,
143 #[serde(default, deserialize_with = "deserialize_repo_kind")]
146 pub repo_kind: RepoKind,
147 #[serde(default, deserialize_with = "deserialize_runner")]
148 pub runner: Runner,
149 #[serde(default, deserialize_with = "deserialize_deployment_method")]
150 pub deployment_method: DeploymentMethod,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub frontend_apps: Option<Vec<FrontendApp>>,
155}
156
157#[derive(Serialize, Debug, Deserialize, Clone)]
159#[tsync]
160pub struct DeployRepoCreate {
161 pub project_id: i32,
162 pub name: String,
163 pub repository: String,
164 pub repo_kind: RepoKind,
165 pub runner: Runner,
166 #[serde(default)]
167 pub deployment_method: DeploymentMethod,
168}
169
170#[derive(Deserialize, Serialize, Debug, Clone)]
171#[tsync]
172pub struct FrontendApp {
173 pub id: String,
174 pub name: String,
175 pub app_type: AppType,
176 pub runner: Runner,
177 #[serde(default)]
178 pub deployment_method: DeploymentMethod,
179 pub project_id: i32,
180 pub tenant_id: i32,
181 pub repository: Option<String>,
182 pub description: Option<String>,
183 pub deploy_repo_id: Option<i64>,
184 pub source_path: Option<String>,
185 pub deploy_repo: Option<DeployRepo>,
186 pub project_ids: Vec<i32>,
187
188 #[serde(default)]
193 pub kind: Option<String>,
194 #[serde(default)]
196 pub source: Option<String>,
197 #[serde(default)]
199 pub output: Option<String>,
200 #[serde(default)]
202 pub package_manager: Option<String>,
203 #[serde(default)]
205 pub pm2_app: Option<String>,
206 #[serde(default)]
208 pub shared_lib: Option<String>,
209 #[serde(default)]
211 pub compile_cmd: Option<String>,
212 #[serde(default)]
214 pub path: Option<String>,
215 #[serde(default)]
216 pub remote_path: Option<String>,
217 #[serde(default)]
218 pub output_path: Option<String>,
219 #[serde(default)]
220 pub build_command: Option<String>,
221 #[serde(default)]
222 pub install_command: Option<String>,
223 #[serde(default)]
224 pub binary_name: Option<String>,
225 #[serde(default)]
226 pub build_target: Option<String>,
227 #[serde(default)]
228 pub port: Option<u16>,
229 #[serde(default)]
230 pub shared_lib_path: Option<String>,
231
232 pub created_at: DateTime<Utc>,
233 pub updated_at: DateTime<Utc>,
234}
235
236impl Display for FrontendApp {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 write!(
239 f,
240 "ID: {}, Name: {}, Type: {}",
241 self.id, self.name, self.app_type
242 )
243 }
244}
245
246#[derive(Serialize, Debug, Deserialize, Clone)]
248#[tsync]
249pub struct FrontendAppCreate {
250 pub name: String,
251 pub project_id: i32,
252 pub app_type: AppType,
253 pub runner: Runner,
254 #[serde(default)]
255 pub deployment_method: DeploymentMethod,
256 pub repository: Option<String>,
257 pub description: Option<String>,
258 pub deploy_repo_id: Option<i64>,
260 pub source_path: Option<String>,
262}
263
264#[derive(Deserialize, Serialize, Debug)]
266#[tsync]
267pub struct FrontendAppDeployment {
268 pub id: i32,
269 pub frontend_app_id: String,
270 pub commit_hash: String,
271 pub status: DeploymentStatus,
272 #[serde(with = "ar_date_format")]
273 pub created_at: DateTime<Utc>,
274 #[serde(with = "ar_date_format")]
275 pub updated_at: DateTime<Utc>,
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281 use serde_json::json;
282
283 #[test]
284 fn test_frontend_app_create_serialization() {
285 let create = FrontendAppCreate {
286 name: "my-app".to_owned(),
287 project_id: 1,
288 app_type: AppType::Web,
289 runner: Runner::NodeJs,
290 deployment_method: DeploymentMethod::Git,
291 repository: Some("my-repo".to_owned()),
292 description: None,
293 deploy_repo_id: Some(7),
294 source_path: Some("apps/web".to_owned()),
295 };
296 let value = serde_json::to_value(&create).unwrap();
297 assert_eq!(value["app_type"], json!(0));
298 assert_eq!(value["runner"], json!(0));
299 assert_eq!(value["deployment_method"], json!(0));
300 assert_eq!(value["deploy_repo_id"], json!(7));
301 assert_eq!(value["source_path"], json!("apps/web"));
302 }
303
304 #[test]
305 fn test_deploy_repo_create_serialization() {
306 let create = DeployRepoCreate {
307 project_id: 1,
308 name: "my-repo".to_owned(),
309 repository: "my-repo".to_owned(),
310 repo_kind: RepoKind::Monorepo,
311 runner: Runner::Monorepo,
312 deployment_method: DeploymentMethod::Git,
313 };
314 let value = serde_json::to_value(&create).unwrap();
315 assert_eq!(value["repo_kind"], json!(1));
316 assert_eq!(value["runner"], json!(255));
317 assert_eq!(value["deployment_method"], json!(0));
318 }
319
320 #[test]
321 fn test_app_type_display() {
322 assert_eq!(AppType::Web.to_string(), "Web");
323 assert_eq!(AppType::Tauri.to_string(), "Tauri");
324 }
325
326 #[test]
327 fn test_deploy_repo_deserializes_integer_enums() {
328 let deploy_repo: DeployRepo = serde_json::from_value(json!({
329 "id": 5,
330 "name": "my-repo",
331 "repository": "my-repo",
332 "root_path": ".",
333 "repo_kind": 1,
334 "runner": 255,
335 "deployment_method": 0
336 }))
337 .unwrap();
338 assert_eq!(deploy_repo.repo_kind, RepoKind::Monorepo);
339 assert_eq!(deploy_repo.runner, Runner::Monorepo);
340 assert_eq!(deploy_repo.deployment_method, DeploymentMethod::Git);
341 }
342
343 #[test]
344 fn test_deploy_repo_deserializes_legacy_string_enums() {
345 let deploy_repo: DeployRepo = serde_json::from_value(json!({
346 "id": 5,
347 "name": "my-repo",
348 "repository": "my-repo",
349 "root_path": ".",
350 "repo_kind": "monorepo",
351 "runner": "monorepo",
352 "deployment_method": "git"
353 }))
354 .unwrap();
355 assert_eq!(deploy_repo.repo_kind, RepoKind::Monorepo);
356 assert_eq!(deploy_repo.runner, Runner::Monorepo);
357 assert_eq!(deploy_repo.deployment_method, DeploymentMethod::Git);
358 }
359}