use rusqlite::Row; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Project { /// workspace.id(也是旧 projects.id,向后兼容所有关联表) pub id: String, /// profile.id,用于跨空间共享更新 pub profile_id: Option, /// 等于 id,前端可用于区分语义 pub workspace_id: Option, /// 关联的 git_repos.id(仓库锚点) pub repo_id: Option, pub name: String, pub description: Option, #[serde(rename = "type")] pub type_: Option, // ── profile 级(项目档案,跨空间共享)──────────────────────── pub status: Option, pub priority: Option, pub tech_stack: Option, pub tags: Option, pub repo_url: Option, pub upstream_url: Option, pub default_branch: Option, pub staging_url: Option, pub prod_url: Option, pub server_note: Option, // ── workspace 级(每空间独立)─────────────────────────────── pub space_id: Option, pub wsl_path: Option, pub win_path: Option, pub platform: Option, pub recent_focus: Option, pub startup_commands: Option, pub last_push_at: Option, pub updated_at: Option, } impl Project { /// 从 project_workspaces JOIN project_profiles 的查询结果构造 pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, profile_id: row.get("profile_id").ok(), workspace_id: row.get("workspace_id").ok(), repo_id: row.get("repo_id").ok(), name: row.get("name")?, description: row.get("description").ok(), type_: row.get("type").ok(), status: row.get("status").ok(), priority: row.get("priority").ok(), tech_stack: row.get("tech_stack").ok(), tags: row.get("tags").ok(), repo_url: row.get("repo_url").ok(), upstream_url: row.get("upstream_url").ok(), default_branch: row.get("default_branch").ok(), staging_url: row.get("staging_url").ok(), prod_url: row.get("prod_url").ok(), server_note: row.get("server_note").ok(), space_id: row.get("space_id").ok(), wsl_path: row.get("wsl_path").ok(), win_path: row.get("win_path").ok(), platform: row.get("platform").ok(), recent_focus: row.get("recent_focus").ok(), startup_commands: row.get("startup_commands").ok(), last_push_at: row.get("last_push_at").ok(), updated_at: row.get("updated_at").ok(), }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ActivityLog { pub id: i64, pub project_id: String, pub summary: String, pub created_at: Option, } impl ActivityLog { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, project_id: row.get("project_id")?, summary: row.get("summary")?, created_at: row.get("created_at")?, }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ProductGroup { pub id: String, pub name: String, pub description: Option, pub space_id: Option, pub sort_order: i32, } impl ProductGroup { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, name: row.get("name")?, description: row.get("description")?, space_id: row.get("space_id").ok(), sort_order: row.get("sort_order").unwrap_or(0), }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ProjectGroupMap { pub project_id: String, pub group_id: String, pub role: Option, } impl ProjectGroupMap { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { project_id: row.get("project_id")?, group_id: row.get("group_id")?, role: row.get("role")?, }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Space { pub id: String, pub name: String, pub is_current: bool, pub created_at: Option, } impl Space { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, name: row.get("name")?, is_current: row.get::<_, i64>("is_current")? != 0, created_at: row.get("created_at")?, }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct GitAccount { pub id: String, pub username: String, pub avatar_url: Option, pub provider: String, pub created_at: Option, } impl GitAccount { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, username: row.get("username")?, avatar_url: row.get("avatar_url")?, provider: row.get("provider")?, created_at: row.get("created_at")?, }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DeployCommand { pub id: String, pub project_id: String, pub name: String, pub command: String, pub run_in: String, } impl DeployCommand { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, project_id: row.get("project_id")?, name: row.get("name")?, command: row.get("command")?, run_in: row.get("run_in")?, }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct CicdConfig { pub id: String, pub project_id: String, pub config_type: String, pub trigger_branch: String, pub build_command: Option, pub node_version: Option, pub server_host: Option, pub server_user: Option, pub deploy_path: Option, pub start_command: Option, pub oss_endpoint: Option, pub created_at: Option, } impl CicdConfig { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, project_id: row.get("project_id")?, config_type: row.get("config_type")?, trigger_branch: row.get("trigger_branch")?, build_command: row.get("build_command").ok(), node_version: row.get("node_version").ok(), server_host: row.get("server_host").ok(), server_user: row.get("server_user").ok(), deploy_path: row.get("deploy_path").ok(), start_command: row.get("start_command").ok(), oss_endpoint: row.get("oss_endpoint").ok(), created_at: row.get("created_at").ok(), }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WorkflowResult { pub content: String, pub path: String, pub secrets: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct GitRepo { pub id: String, pub repo_url: String, pub name: String, pub description: Option, pub default_branch: Option, pub upstream_url: Option, pub staging_url: Option, pub prod_url: Option, pub server_note: Option, pub status: Option, pub priority: Option, pub tech_stack: Option, pub last_synced_at: Option, pub created_at: Option, } impl GitRepo { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, repo_url: row.get("repo_url")?, name: row.get("name")?, description: row.get("description").ok(), default_branch: row.get("default_branch").ok(), upstream_url: row.get("upstream_url").ok(), staging_url: row.get("staging_url").ok(), prod_url: row.get("prod_url").ok(), server_note: row.get("server_note").ok(), status: row.get("status").ok(), priority: row.get("priority").ok(), tech_stack: row.get("tech_stack").ok(), last_synced_at: row.get("last_synced_at").ok(), created_at: row.get("created_at").ok(), }) } } // ── 源码库 ───────────────────────────────────────────────────────────────── #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SourceCategory { pub id: String, pub name: String, pub icon: String, pub color: String, pub sort_order: i32, } impl SourceCategory { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, name: row.get("name")?, icon: row.get::<_, String>("icon").unwrap_or_else(|_| "📁".into()), color: row.get::<_, String>("color").unwrap_or_else(|_| "#6B7280".into()), sort_order: row.get("sort_order").unwrap_or(0), }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SourceProject { pub id: String, pub name: String, pub description: Option, pub category_id: Option, pub language: Option, pub tags: Option, pub repo_url: Option, pub introduction: Option, pub readme: Option, pub notes: Option, pub created_at: Option, pub updated_at: Option, // join 字段 pub category_name: Option, pub version_count: Option, } impl SourceProject { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, name: row.get("name")?, description: row.get("description").ok(), category_id: row.get("category_id").ok(), language: row.get("language").ok(), tags: row.get("tags").ok(), repo_url: row.get("repo_url").ok(), introduction: row.get("introduction").ok(), readme: row.get("readme").ok(), notes: row.get("notes").ok(), created_at: row.get("created_at").ok(), updated_at: row.get("updated_at").ok(), category_name: row.get("category_name").ok(), version_count: row.get("version_count").ok(), }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SourceVersion { pub id: String, pub project_id: String, pub version: String, pub file_path: String, pub archive_path: Option, pub file_size: i64, pub is_primary: bool, pub dir_tree: Option, pub notes: Option, pub imported_at: Option, } impl SourceVersion { pub fn from_row(row: &Row<'_>) -> rusqlite::Result { Ok(Self { id: row.get("id")?, project_id: row.get("project_id")?, version: row.get("version")?, file_path: row.get("file_path")?, archive_path: row.get("archive_path").ok(), file_size: row.get("file_size").unwrap_or(0), is_primary: row.get::<_, i64>("is_primary").unwrap_or(0) != 0, dir_tree: row.get("dir_tree").ok(), notes: row.get("notes").ok(), imported_at: row.get("imported_at").ok(), }) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct DirEntry { pub name: String, #[serde(rename = "type")] pub type_: String, pub children_count: Option, }