1 use {
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 /// Whether this app is a web application or a Tauri cross-platform app.
15 #[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
16 #[repr(u8)]
17 #[tsync]
18 pub enum AppType {
19 /// Web application (SPA, SSR, static site). All legacy Projects map here.
20 #[default]
21 Web = 0,
22 /// Cross-platform desktop/mobile application built with Tauri.
23 Tauri = 1,
24 }
25
26 impl 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 /// A deployable frontend application on the smbCloud platform.
36 ///
37 /// Replaces the legacy `Project` as the primary unit of deployment.
38 /// A `FrontendApp` belongs to a `Tenant` directly and is associated with an
39 /// owner workspace (Project). It can be shared across multiple workspaces.
40 #[derive(Deserialize, Serialize, Debug, Clone)]
41 #[tsync]
42 pub struct FrontendApp {
43 pub id: String,
44 pub name: String,
45 pub app_type: AppType,
46 pub runner: Runner,
47 #[serde(default)]
48 pub deployment_method: DeploymentMethod,
49 pub project_id: i32,
50 pub tenant_id: i32,
51 pub repository: Option<String>,
52 pub description: Option<String>,
53 pub project_ids: Vec<i32>,
54
55 // ── CLI-local deployment config fields ───────────────────────────────────
56 // These are not persisted in the database; they are stored in the local
57 // .smbcloud config file alongside the FrontendApp record.
58 /// Deployment kind, e.g. "vite-spa". Absent for server-side runners.
59 pub kind: Option<String>,
60 /// Local source directory to build from, e.g. "frontend/my-app".
61 pub source: Option<String>,
62 /// Build output directory relative to `source`, e.g. "dist".
63 pub output: Option<String>,
64 /// Package manager to use for the build step, e.g. "pnpm".
65 pub package_manager: Option<String>,
66 /// PM2 process name to restart after a nextjs-ssr deploy.
67 pub pm2_app: Option<String>,
68 /// Path to a shared lib directory to rsync before deploying.
69 pub shared_lib: Option<String>,
70 /// SSH command to run on the server after rsyncing the shared lib.
71 pub compile_cmd: Option<String>,
72 /// Remote destination path on the server.
73 pub path: Option<String>,
74
75 pub created_at: DateTime<Utc>,
76 pub updated_at: DateTime<Utc>,
77 }
78
79 impl Display for FrontendApp {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(
82 f,
83 "ID: {}, Name: {}, Type: {}",
84 self.id, self.name, self.app_type
85 )
86 }
87 }
88
89 /// Payload for creating a new FrontendApp via the API.
90 #[derive(Serialize, Debug, Deserialize, Clone)]
91 #[tsync]
92 pub struct FrontendAppCreate {
93 pub name: String,
94 pub project_id: i32,
95 pub app_type: AppType,
96 pub runner: Runner,
97 #[serde(default)]
98 pub deployment_method: DeploymentMethod,
99 pub repository: Option<String>,
100 pub description: Option<String>,
101 }
102
103 /// A deployment record tied to a FrontendApp.
104 #[derive(Deserialize, Serialize, Debug)]
105 #[tsync]
106 pub struct FrontendAppDeployment {
107 pub id: i32,
108 pub frontend_app_id: String,
109 pub commit_hash: String,
110 pub status: DeploymentStatus,
111 #[serde(with = "ar_date_format")]
112 pub created_at: DateTime<Utc>,
113 #[serde(with = "ar_date_format")]
114 pub updated_at: DateTime<Utc>,
115 }
116
117 #[cfg(test)]
118 mod tests {
119 use super::*;
120 use serde_json::json;
121
122 #[test]
123 fn test_frontend_app_create_serialization() {
124 let create = FrontendAppCreate {
125 name: "my-app".to_owned(),
126 project_id: 1,
127 app_type: AppType::Web,
128 runner: Runner::NodeJs,
129 deployment_method: DeploymentMethod::Git,
130 repository: Some("my-repo".to_owned()),
131 description: None,
132 };
133 let value = serde_json::to_value(&create).unwrap();
134 assert_eq!(value["app_type"], json!(0));
135 assert_eq!(value["runner"], json!(0));
136 assert_eq!(value["deployment_method"], json!(0));
137 }
138
139 #[test]
140 fn test_app_type_display() {
141 assert_eq!(AppType::Web.to_string(), "Web");
142 assert_eq!(AppType::Tauri.to_string(), "Tauri");
143 }
144 }