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
10/// How the project's files are delivered to the server.
11///
12/// `Git`   — the classic smbCloud flow: push to a remote git repo, the server
13///           builds and restarts the process.
14/// `Rsync` — files are transferred directly with rsync over SSH; no build step
15///           runs on the server. Ideal for pre-built static sites or assets.
16#[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
17#[repr(u8)]
18#[tsync]
19pub enum DeploymentMethod {
20    #[default]
21    Git = 0,
22    Rsync = 1,
23}
24
25impl Display for DeploymentMethod {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            DeploymentMethod::Git => write!(f, "Git"),
29            DeploymentMethod::Rsync => write!(f, "Rsync"),
30        }
31    }
32}
33
34#[derive(Deserialize, Debug, Serialize, Clone)]
35pub struct Config {
36    /// Legacy project field — kept for backward compatibility during migration.
37    pub current_project: Option<Project>,
38    /// The active FrontendApp for CLI deploy operations.
39    #[serde(default)]
40    pub current_frontend_app: Option<crate::frontend_app::FrontendApp>,
41    pub current_auth_app: Option<AuthApp>,
42}
43
44#[derive(Deserialize, Serialize, Debug, Clone)]
45#[tsync]
46pub struct Project {
47    pub id: i32,
48    pub name: String,
49    pub runner: Runner,
50    /// Defaults to `Git` when absent (older API responses won't include the field).
51    #[serde(default)]
52    pub deployment_method: DeploymentMethod,
53    pub path: Option<String>,
54    pub repository: Option<String>,
55    pub description: Option<String>,
56    pub created_at: DateTime<Utc>,
57    pub updated_at: DateTime<Utc>,
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/connected-devices".
61    /// Used by vite-spa deploys as the working directory for the build step.
62    /// Distinct from `path`, which is the remote destination on the server.
63    pub source: Option<String>,
64    /// Build output directory relative to `source`, e.g. "dist".
65    pub output: Option<String>,
66    /// Package manager to use for the build step, e.g. "pnpm".
67    pub package_manager: Option<String>,
68    /// PM2 process name to restart after a nextjs-ssr deploy, e.g. "my-app".
69    /// Matches the name passed to `pm2 start` on the server.
70    pub pm2_app: Option<String>,
71    /// Port the standalone server binds to (default: 3000). Must match nginx upstream configuration.
72    #[serde(default)]
73    pub port: Option<u16>,
74    /// Path to a shared lib directory to rsync to the server before deploying,
75    /// e.g. "lib". Used by Rails apps that depend on native gems built from
76    /// monorepo-level source. Relative to the repo root.
77    pub shared_lib: Option<String>,
78    /// SSH command to run on the server after rsyncing the shared lib,
79    /// e.g. "cd ~/lib/gems/gem_error_codes && rbenv local 3.4.2 && bundle install && bundle exec rake compile".
80    pub compile_cmd: Option<String>,
81}
82
83impl Display for Project {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(f, "ID: {}, Name: {}", self.id, self.name,)
86    }
87}
88#[derive(Serialize, Debug, Deserialize, Clone)]
89#[tsync]
90pub struct ProjectCreate {
91    pub name: String,
92    pub repository: String,
93    pub description: String,
94    pub runner: Runner,
95    #[serde(default)]
96    pub deployment_method: DeploymentMethod,
97}
98
99#[derive(Deserialize, Serialize, Debug)]
100#[tsync]
101pub struct Deployment {
102    pub id: i32,
103    pub project_id: i32,
104    pub commit_hash: String,
105    pub status: DeploymentStatus,
106    #[serde(with = "ar_date_format")]
107    pub created_at: DateTime<Utc>,
108    #[serde(with = "ar_date_format")]
109    pub updated_at: DateTime<Utc>,
110}
111
112#[derive(Deserialize, Serialize, Debug)]
113pub struct DeploymentPayload {
114    pub commit_hash: String,
115    pub status: DeploymentStatus,
116}
117
118#[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy)] // Added Clone, Copy
119#[repr(u8)]
120#[tsync]
121pub enum DeploymentStatus {
122    Started = 0,
123    Failed,
124    Done,
125}
126
127impl Display for DeploymentStatus {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            DeploymentStatus::Started => write!(f, "🚀"),
131            DeploymentStatus::Failed => write!(f, "❌"),
132            DeploymentStatus::Done => write!(f, "✅"),
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use serde_json::json;
141    #[test]
142    fn test_project_create() {
143        let project_create = ProjectCreate {
144            name: "test".to_owned(),
145            repository: "test".to_owned(),
146            description: "test".to_owned(),
147            runner: Runner::NodeJs,
148            deployment_method: DeploymentMethod::Git,
149        };
150        let json = json!({
151            "name": "test",
152            "repository": "test",
153            "description": "test",
154            "runner": 0,
155            "deployment_method": 0
156        });
157        assert_eq!(serde_json::to_value(project_create).unwrap(), json);
158    }
159
160    #[test]
161    fn test_deployment_status_display() {
162        assert_eq!(format!("{}", DeploymentStatus::Started), "🚀");
163        assert_eq!(DeploymentStatus::Started.to_string(), "🚀");
164
165        assert_eq!(format!("{}", DeploymentStatus::Failed), "❌");
166        assert_eq!(DeploymentStatus::Failed.to_string(), "❌");
167
168        assert_eq!(format!("{}", DeploymentStatus::Done), "✅");
169        assert_eq!(DeploymentStatus::Done.to_string(), "✅");
170    }
171}