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/// 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]
42pub 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
79impl 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]
92pub 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]
106pub 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)]
118mod 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}