Skip to main content

smbcloud_model/
frontend_app.rs

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/// 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]
18pub 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
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/// A deployable frontend application on the smbCloud platform.
36///
37/// A `FrontendApp` is the unit that actually ships.
38///
39/// - `Project` is the umbrella workspace
40/// - `DeployRepo` is the git repository or monorepo root
41/// - `FrontendApp` is the deployable app inside that repo
42///
43/// A `FrontendApp` belongs to a `Tenant`, is associated with an owner workspace,
44/// and may optionally point at a `DeployRepo` plus a repo-relative `source_path`
45/// for monorepo deployments.
46#[derive(Deserialize, Serialize, Debug, Clone)]
47#[tsync]
48pub struct DeployRepo {
49    pub id: i64,
50    pub name: String,
51    pub repository: String,
52    pub root_path: String,
53    pub repo_kind: Option<String>,
54    pub runner: Option<String>,
55    pub deployment_method: Option<String>,
56}
57
58#[derive(Deserialize, Serialize, Debug, Clone)]
59#[tsync]
60pub struct FrontendApp {
61    pub id: String,
62    pub name: String,
63    pub app_type: AppType,
64    pub runner: Runner,
65    #[serde(default)]
66    pub deployment_method: DeploymentMethod,
67    pub project_id: i32,
68    pub tenant_id: i32,
69    pub repository: Option<String>,
70    pub description: Option<String>,
71    pub deploy_repo_id: Option<i64>,
72    pub source_path: Option<String>,
73    pub deploy_repo: Option<DeployRepo>,
74    pub project_ids: Vec<i32>,
75
76    // ── CLI-local deployment config fields ───────────────────────────────────
77    // These are not persisted in the database; they are stored in the local
78    // .smbcloud config file alongside the FrontendApp record.
79    /// Deployment kind, e.g. "vite-spa". Absent for server-side runners.
80    #[serde(default)]
81    pub kind: Option<String>,
82    /// Local source directory to build from, e.g. "frontend/my-app".
83    #[serde(default)]
84    pub source: Option<String>,
85    /// Build output directory relative to `source`, e.g. "dist".
86    #[serde(default)]
87    pub output: Option<String>,
88    /// Package manager to use for the build step, e.g. "pnpm".
89    #[serde(default)]
90    pub package_manager: Option<String>,
91    /// PM2 process name to restart after a nextjs-ssr deploy.
92    #[serde(default)]
93    pub pm2_app: Option<String>,
94    /// Path to a shared lib directory to rsync before deploying.
95    #[serde(default)]
96    pub shared_lib: Option<String>,
97    /// SSH command to run on the server after rsyncing the shared lib.
98    #[serde(default)]
99    pub compile_cmd: Option<String>,
100    /// Remote destination path on the server.
101    #[serde(default)]
102    pub path: Option<String>,
103    #[serde(default)]
104    pub remote_path: Option<String>,
105    #[serde(default)]
106    pub output_path: Option<String>,
107    #[serde(default)]
108    pub build_command: Option<String>,
109    #[serde(default)]
110    pub install_command: Option<String>,
111    #[serde(default)]
112    pub binary_name: Option<String>,
113    #[serde(default)]
114    pub build_target: Option<String>,
115    #[serde(default)]
116    pub port: Option<u16>,
117    #[serde(default)]
118    pub shared_lib_path: Option<String>,
119
120    pub created_at: DateTime<Utc>,
121    pub updated_at: DateTime<Utc>,
122}
123
124impl Display for FrontendApp {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(
127            f,
128            "ID: {}, Name: {}, Type: {}",
129            self.id, self.name, self.app_type
130        )
131    }
132}
133
134/// Payload for creating a new FrontendApp via the API.
135#[derive(Serialize, Debug, Deserialize, Clone)]
136#[tsync]
137pub struct FrontendAppCreate {
138    pub name: String,
139    pub project_id: i32,
140    pub app_type: AppType,
141    pub runner: Runner,
142    #[serde(default)]
143    pub deployment_method: DeploymentMethod,
144    pub repository: Option<String>,
145    pub description: Option<String>,
146}
147
148/// A deployment record tied to a FrontendApp.
149#[derive(Deserialize, Serialize, Debug)]
150#[tsync]
151pub struct FrontendAppDeployment {
152    pub id: i32,
153    pub frontend_app_id: String,
154    pub commit_hash: String,
155    pub status: DeploymentStatus,
156    #[serde(with = "ar_date_format")]
157    pub created_at: DateTime<Utc>,
158    #[serde(with = "ar_date_format")]
159    pub updated_at: DateTime<Utc>,
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use serde_json::json;
166
167    #[test]
168    fn test_frontend_app_create_serialization() {
169        let create = FrontendAppCreate {
170            name: "my-app".to_owned(),
171            project_id: 1,
172            app_type: AppType::Web,
173            runner: Runner::NodeJs,
174            deployment_method: DeploymentMethod::Git,
175            repository: Some("my-repo".to_owned()),
176            description: None,
177        };
178        let value = serde_json::to_value(&create).unwrap();
179        assert_eq!(value["app_type"], json!(0));
180        assert_eq!(value["runner"], json!(0));
181        assert_eq!(value["deployment_method"], json!(0));
182    }
183
184    #[test]
185    fn test_app_type_display() {
186        assert_eq!(AppType::Web.to_string(), "Web");
187        assert_eq!(AppType::Tauri.to_string(), "Tauri");
188    }
189}