Skip to main content

smbcloud_model/
project.rs

1use {
2    crate::{app_auth::AuthApp, ar_date_format, runner::Runner},
3    chrono::{DateTime, Utc},
4    serde::{Deserialize, Serialize},
5    serde_repr::{Deserialize_repr, Serialize_repr},
6    std::fmt::Display,
7    tsync::tsync,
8};
9
10fn default_datetime() -> DateTime<Utc> {
11    DateTime::UNIX_EPOCH
12}
13
14/// How the project's files are delivered to the server.
15///
16/// `Git`   — the classic smbCloud flow: push to a remote git repo, the server
17///           builds and restarts the process.
18/// `Rsync` — files are transferred directly with rsync over SSH; no build step
19///           runs on the server. Ideal for pre-built static sites or assets.
20#[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
21#[repr(u8)]
22#[tsync]
23pub enum DeploymentMethod {
24    #[default]
25    Git = 0,
26    Rsync = 1,
27}
28
29impl Display for DeploymentMethod {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            DeploymentMethod::Git => write!(f, "Git"),
33            DeploymentMethod::Rsync => write!(f, "Rsync"),
34        }
35    }
36}
37
38#[derive(Deserialize, Debug, Serialize, Clone)]
39pub struct Config {
40    /// Legacy project field — kept for backward compatibility during migration.
41    pub current_project: Option<Project>,
42    /// The active FrontendApp for CLI deploy operations.
43    #[serde(default)]
44    pub current_frontend_app: Option<crate::frontend_app::FrontendApp>,
45    pub current_auth_app: Option<AuthApp>,
46}
47
48#[derive(Deserialize, Serialize, Debug, Clone)]
49#[tsync]
50pub struct Project {
51    /// Umbrella smbCloud workspace ID.
52    pub id: i32,
53    #[serde(default)]
54    pub name: String,
55    #[serde(default)]
56    pub runner: Runner,
57    /// Defaults to `Git` when absent (older API responses won't include the field).
58    #[serde(default)]
59    pub deployment_method: DeploymentMethod,
60    pub path: Option<String>,
61    pub repository: Option<String>,
62    pub description: Option<String>,
63    /// Deployable app ID for precise deployment tracking. Optional during the
64    /// migration away from project-as-app semantics.
65    pub frontend_app_id: Option<String>,
66    /// Repo ID backing this deploy target. Optional until the API exposes it
67    /// consistently to the CLI.
68    pub deploy_repo_id: Option<i64>,
69    /// Repo-relative app path for monorepo targets, e.g. "apps/web/console".
70    pub source_path: Option<String>,
71    #[serde(default = "default_datetime")]
72    pub created_at: DateTime<Utc>,
73    #[serde(default = "default_datetime")]
74    pub updated_at: DateTime<Utc>,
75    /// Deployment kind, e.g. "vite-spa", "nextjs-ssr", or "rust".
76    pub kind: Option<String>,
77    /// Local source directory to build from, e.g. "frontend/connected-devices"
78    /// or a Rust crate root like ".".
79    /// Used by local-build deploys such as vite-spa, nextjs-ssr, and rust.
80    /// Distinct from `path`, which is the remote destination on the server.
81    pub source: Option<String>,
82    /// Build output directory relative to `source`, e.g. "dist".
83    pub output: Option<String>,
84    /// Package manager to use for the build step, e.g. "pnpm".
85    pub package_manager: Option<String>,
86    /// PM2 process name to restart after a nextjs-ssr deploy, e.g. "my-app".
87    /// Matches the name passed to `pm2 start` on the server.
88    pub pm2_app: Option<String>,
89    /// Environment variables to seed into the PM2 ecosystem `env_production` block.
90    /// Populated from the server-side App record; not written to `.smb/config.toml`.
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub pm2_env: Option<std::collections::HashMap<String, serde_json::Value>>,
93    /// Port the standalone server binds to (default: 3000). Must match nginx upstream configuration.
94    #[serde(default)]
95    pub port: Option<u16>,
96    /// Path to a shared lib directory to rsync to the server before deploying,
97    /// e.g. "lib". Used by Rails apps that depend on native gems built from
98    /// monorepo-level source. Relative to the repo root.
99    pub shared_lib: Option<String>,
100    /// SSH command to run on the server after rsyncing the shared lib,
101    /// e.g. "cd ~/lib/gems/gem_error_codes && rbenv local 3.4.2 && bundle install && bundle exec rake compile".
102    pub compile_cmd: Option<String>,
103    /// Install command override, e.g. "pnpm install --frozen-lockfile".
104    #[serde(default)]
105    pub install_command: Option<String>,
106    /// Rust binary filename to upload and restart, e.g. "onde-cloud".
107    /// When absent, the CLI falls back to the Cargo package name.
108    pub binary_name: Option<String>,
109    /// Rust target triple used for local cross-compilation before upload,
110    /// e.g. "x86_64-unknown-linux-gnu".
111    pub rust_target: Option<String>,
112}
113
114impl Display for Project {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "ID: {}, Name: {}", self.id, self.name,)
117    }
118}
119#[derive(Serialize, Debug, Deserialize, Clone)]
120#[tsync]
121pub struct ProjectCreate {
122    pub name: String,
123    pub repository: String,
124    pub description: String,
125    pub runner: Runner,
126    #[serde(default)]
127    pub deployment_method: DeploymentMethod,
128}
129
130#[derive(Deserialize, Serialize, Debug)]
131#[tsync]
132pub struct Deployment {
133    pub id: i32,
134    pub project_id: i32,
135    pub frontend_app_id: Option<String>,
136    pub frontend_app_name: Option<String>,
137    pub commit_hash: String,
138    pub status: DeploymentStatus,
139    #[serde(with = "ar_date_format")]
140    pub created_at: DateTime<Utc>,
141    #[serde(with = "ar_date_format")]
142    pub updated_at: DateTime<Utc>,
143}
144
145#[derive(Deserialize, Serialize, Debug)]
146pub struct DeploymentPayload {
147    pub commit_hash: String,
148    pub status: DeploymentStatus,
149    pub frontend_app_id: Option<String>,
150}
151
152#[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy)] // Added Clone, Copy
153#[repr(u8)]
154#[tsync]
155pub enum DeploymentStatus {
156    Started = 0,
157    Failed,
158    Done,
159}
160
161impl Display for DeploymentStatus {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match self {
164            DeploymentStatus::Started => write!(f, "🚀"),
165            DeploymentStatus::Failed => write!(f, "❌"),
166            DeploymentStatus::Done => write!(f, "✅"),
167        }
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use serde_json::json;
175    #[test]
176    fn test_project_create() {
177        let project_create = ProjectCreate {
178            name: "test".to_owned(),
179            repository: "test".to_owned(),
180            description: "test".to_owned(),
181            runner: Runner::NodeJs,
182            deployment_method: DeploymentMethod::Git,
183        };
184        let json = json!({
185            "name": "test",
186            "repository": "test",
187            "description": "test",
188            "runner": 0,
189            "deployment_method": 0
190        });
191        assert_eq!(serde_json::to_value(project_create).unwrap(), json);
192    }
193
194    #[test]
195    fn test_deployment_status_display() {
196        assert_eq!(format!("{}", DeploymentStatus::Started), "🚀");
197        assert_eq!(DeploymentStatus::Started.to_string(), "🚀");
198
199        assert_eq!(format!("{}", DeploymentStatus::Failed), "❌");
200        assert_eq!(DeploymentStatus::Failed.to_string(), "❌");
201
202        assert_eq!(format!("{}", DeploymentStatus::Done), "✅");
203        assert_eq!(DeploymentStatus::Done.to_string(), "✅");
204    }
205}