6 files changed
+155
-3
crates/cli/src/project/crud_read.rs
+1
@@ -143,6 +143,7 @@ pub(crate) async fn process_project_use(env: Environment, id: String) -> Result<
143
144
let config = Config {
145
current_project: Some(project),
146
+ current_frontend_app: None,
147
current_auth_app: None,
148
};
149
crates/smbcloud-model/src/app_auth.rs
+2
-2
@@ -2,7 +2,7 @@ use crate::ar_date_format;
2
use chrono::{DateTime, Utc};
3
use serde::{Deserialize, Serialize};
4
5
-#[derive(Serialize, Deserialize, Debug)]
5
+#[derive(Serialize, Deserialize, Debug, Clone)]
6
pub struct AuthApp {
7
pub id: String,
8
pub secret: Option<String>,
@@ -13,7 +13,7 @@ pub struct AuthApp {
13
pub updated_at: DateTime<Utc>,
14
}
15
16
-#[derive(Serialize, Deserialize, Debug)]
16
+#[derive(Serialize, Deserialize, Debug, Clone)]
17
pub struct AuthAppCreate {
18
pub name: String,
19
pub description: String,
crates/smbcloud-model/src/frontend_app.rs
new
+144
@@ -0,0 +1,144 @@
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
+}
crates/smbcloud-model/src/lib.rs
+1
@@ -2,6 +2,7 @@ pub mod account;
2
pub mod app_auth;
3
pub mod error_codes;
4
pub mod forgot;
5
+pub mod frontend_app;
6
pub mod login;
7
pub mod oauth;
8
pub mod project;
crates/smbcloud-model/src/project.rs
+5
-1
@@ -31,9 +31,13 @@ impl Display for DeploymentMethod {
31
}
32
}
33
34
-#[derive(Deserialize, Debug, Serialize)]
34
+#[derive(Deserialize, Debug, Serialize, Clone)]
35
pub 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
crates/smbcloud-utils/src/lib.rs
+2
@@ -29,6 +29,7 @@ pub async fn get_config() -> Result<Config> {
29
if !path.exists() {
30
let config = Config {
31
current_project: None,
32
+ current_frontend_app: None,
33
current_auth_app: None,
34
};
35
return Ok(config);
@@ -47,6 +48,7 @@ pub async fn get_config() -> Result<Config> {
48
None => {
49
let config = Config {
50
current_project: None,
51
+ current_frontend_app: None,
52
current_auth_app: None,
53
};
54
Ok(config)