forge_core_local_deployment/
lib.rs

1use std::{collections::HashMap, sync::Arc};
2
3use async_trait::async_trait;
4use forge_core_db::DBService;
5use forge_core_deployment::{Deployment, DeploymentError};
6use forge_core_executors::profile::ExecutorConfigs;
7use forge_core_services::services::{
8    analytics::{AnalyticsConfig, AnalyticsContext, AnalyticsService, generate_user_id},
9    approvals::Approvals,
10    auth::AuthService,
11    config::{Config, load_config_from_file, save_config_to_file},
12    container::ContainerService,
13    drafts::DraftsService,
14    events::EventService,
15    file_search_cache::FileSearchCache,
16    filesystem::FilesystemService,
17    forge_config::ForgeConfigService,
18    git::GitService,
19    image::ImageService,
20    omni::{OmniConfig, OmniService},
21    profile_loader::ProfileCacheManager,
22};
23use forge_core_utils::{assets::config_path, msg_store::MsgStore};
24use tokio::sync::RwLock;
25use uuid::Uuid;
26
27use crate::container::LocalContainerService;
28mod command;
29pub mod container;
30
31#[derive(Clone)]
32pub struct LocalDeployment {
33    config: Arc<RwLock<Config>>,
34    user_id: String,
35    db: DBService,
36    analytics: Option<AnalyticsService>,
37    msg_stores: Arc<RwLock<HashMap<Uuid, Arc<MsgStore>>>>,
38    container: LocalContainerService,
39    git: GitService,
40    auth: AuthService,
41    image: ImageService,
42    filesystem: FilesystemService,
43    events: EventService,
44    file_search_cache: Arc<FileSearchCache>,
45    approvals: Approvals,
46    drafts: DraftsService,
47    forge_config: ForgeConfigService,
48    omni: Arc<RwLock<OmniService>>,
49    profile_cache: ProfileCacheManager,
50}
51
52#[async_trait]
53impl Deployment for LocalDeployment {
54    async fn new() -> Result<Self, DeploymentError> {
55        let mut raw_config = load_config_from_file(&config_path()).await;
56
57        let profiles = ExecutorConfigs::get_cached();
58        if !raw_config.onboarding_acknowledged
59            && let Ok(recommended_executor) = profiles.get_recommended_executor_profile().await
60        {
61            raw_config.executor_profile = recommended_executor;
62        }
63
64        // Check if app version has changed and set release notes flag
65        {
66            let current_version = forge_core_utils::version::APP_VERSION;
67            let stored_version = raw_config.last_app_version.as_deref();
68
69            if stored_version != Some(current_version) {
70                // Show release notes only if this is an upgrade (not first install)
71                raw_config.show_release_notes = stored_version.is_some();
72                raw_config.last_app_version = Some(current_version.to_string());
73            }
74        }
75
76        // Always save config (may have been migrated or version updated)
77        save_config_to_file(&raw_config, &config_path()).await?;
78
79        let config = Arc::new(RwLock::new(raw_config));
80        let user_id = generate_user_id();
81        let analytics = AnalyticsConfig::new().map(AnalyticsService::new);
82        let git = GitService::new();
83        let msg_stores = Arc::new(RwLock::new(HashMap::new()));
84        let auth = AuthService::new();
85        let filesystem = FilesystemService::new();
86
87        // Create shared components for EventService
88        let events_msg_store = Arc::new(MsgStore::new());
89        let events_entry_count = Arc::new(RwLock::new(0));
90
91        // Create DB with event hooks
92        let db = {
93            let hook = EventService::create_hook(
94                events_msg_store.clone(),
95                events_entry_count.clone(),
96                DBService::new().await?, // Temporary DB service for the hook
97            );
98            DBService::new_with_after_connect(hook).await?
99        };
100
101        let image = ImageService::new(db.clone().pool)?;
102        {
103            let image_service = image.clone();
104            tokio::spawn(async move {
105                tracing::info!("Starting orphaned image cleanup...");
106                if let Err(e) = image_service.delete_orphaned_images().await {
107                    tracing::error!("Failed to clean up orphaned images: {}", e);
108                }
109            });
110        }
111
112        let approvals = Approvals::new(msg_stores.clone());
113
114        // We need to make analytics accessible to the ContainerService
115        // TODO: Handle this more gracefully
116        let analytics_ctx = analytics.as_ref().map(|s| AnalyticsContext {
117            user_id: user_id.clone(),
118            analytics_service: s.clone(),
119        });
120        let container = LocalContainerService::new(
121            db.clone(),
122            msg_stores.clone(),
123            config.clone(),
124            git.clone(),
125            image.clone(),
126            analytics_ctx,
127            approvals.clone(),
128        );
129        container.spawn_worktree_cleanup().await;
130
131        let events = EventService::new(db.clone(), events_msg_store, events_entry_count);
132        let drafts = DraftsService::new(db.clone(), image.clone());
133        let file_search_cache = Arc::new(FileSearchCache::new());
134
135        // Initialize forge-specific services
136        let forge_config = ForgeConfigService::new(db.pool.clone());
137        let omni = Arc::new(RwLock::new(OmniService::new(OmniConfig::default())));
138        let profile_cache = ProfileCacheManager::new();
139
140        Ok(Self {
141            config,
142            user_id,
143            db,
144            analytics,
145            msg_stores,
146            container,
147            git,
148            auth,
149            image,
150            filesystem,
151            events,
152            file_search_cache,
153            approvals,
154            drafts,
155            forge_config,
156            omni,
157            profile_cache,
158        })
159    }
160
161    fn user_id(&self) -> &str {
162        &self.user_id
163    }
164
165    fn shared_types() -> Vec<String> {
166        vec![]
167    }
168
169    fn config(&self) -> &Arc<RwLock<Config>> {
170        &self.config
171    }
172
173    fn db(&self) -> &DBService {
174        &self.db
175    }
176
177    fn analytics(&self) -> &Option<AnalyticsService> {
178        &self.analytics
179    }
180
181    fn container(&self) -> &impl ContainerService {
182        &self.container
183    }
184    fn auth(&self) -> &AuthService {
185        &self.auth
186    }
187
188    fn git(&self) -> &GitService {
189        &self.git
190    }
191
192    fn image(&self) -> &ImageService {
193        &self.image
194    }
195
196    fn filesystem(&self) -> &FilesystemService {
197        &self.filesystem
198    }
199
200    fn msg_stores(&self) -> &Arc<RwLock<HashMap<Uuid, Arc<MsgStore>>>> {
201        &self.msg_stores
202    }
203
204    fn events(&self) -> &EventService {
205        &self.events
206    }
207
208    fn file_search_cache(&self) -> &Arc<FileSearchCache> {
209        &self.file_search_cache
210    }
211
212    fn approvals(&self) -> &Approvals {
213        &self.approvals
214    }
215
216    fn drafts(&self) -> &DraftsService {
217        &self.drafts
218    }
219
220    fn forge_config(&self) -> &ForgeConfigService {
221        &self.forge_config
222    }
223
224    fn omni(&self) -> &Arc<RwLock<OmniService>> {
225        &self.omni
226    }
227
228    fn profile_cache(&self) -> &ProfileCacheManager {
229        &self.profile_cache
230    }
231}