use crate::{api::AppState, docker, docker::containers};
use chrono::Utc;
use shared::{
DeployState, EnvVarValue, Event, Response as RpResponse, ServiceSource, ServiceStatus,
};
pub async fn handle(state: AppState, service_id: String) -> RpResponse {
let svc = match crate::db::services::get(&state.db, &service_id).await {
Ok(Some(s)) => s,
Ok(None) => return RpResponse::err("NotFound", "serviço não encontrado"),
Err(e) => return RpResponse::err("DatabaseError", e.to_string()),
};
if let Err(e) = crate::db::services::update_status(
&state.db,
&service_id,
&ServiceStatus::Stopping,
svc.live_container_id.as_deref(),
)
.await
{
return RpResponse::err("DatabaseError", e.to_string());
}
state.bus.publish(Event::ServiceStatusChanged {
service_id: service_id.clone(),
status: ServiceStatus::Stopping,
});
if let ServiceSource::Compose(compose) = &svc.spec.source {
let pid = &svc.spec.project_id;
let network_name = match crate::db::projects::network_name(&state.db, pid).await {
Ok(n) => n,
Err(e) => return RpResponse::err("DatabaseError", e.to_string()),
};
let mut env_map: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
if let Ok(Some(project)) = crate::db::projects::get(&state.db, pid).await {
for ev in &project.env_vars {
let value = match &ev.value {
EnvVarValue::Plain(v) => v.clone(),
EnvVarValue::Secret(name) => {
state.secrets.get_raw(pid, name).await.unwrap_or_default()
}
};
env_map.insert(ev.key.clone(), value);
}
}
for ev in &svc.spec.env_vars {
let value = match &ev.value {
EnvVarValue::Plain(v) => v.clone(),
EnvVarValue::Secret(name) => {
state.secrets.get_raw(pid, name).await.unwrap_or_default()
}
};
env_map.insert(ev.key.clone(), value);
}
let env_vars: Vec<(String, String)> = env_map.into_iter().collect();
return stop_compose(
&state,
&service_id,
&svc.compose_project_name(),
&compose.content,
&network_name,
&env_vars,
)
.await;
}
let all_ids = match containers::find_all_by_service_id(&state.docker.inner, &service_id).await {
Ok(ids) => ids,
Err(e) => return RpResponse::err("DockerError", e.to_string()),
};
let ids_to_stop: Vec<String> = if !all_ids.is_empty() {
all_ids
} else if let Some(cid) = &svc.live_container_id {
vec![cid.clone()]
} else {
containers::find_live_replicas(&state.docker.inner, &service_id)
.await
.map(|v| v.into_iter().map(|r| r.id).collect())
.unwrap_or_default()
};
if ids_to_stop.is_empty() {
return finish_stop(&state, &service_id, svc.live_container_id.as_deref()).await;
}
for cid in &ids_to_stop {
if let Err(e) = containers::stop_graceful(&state.docker.inner, cid, 10).await {
return RpResponse::err("DockerError", e.to_string());
}
}
let primary_id = ids_to_stop
.first()
.map(|s| s.as_str())
.or(svc.live_container_id.as_deref());
finish_stop(&state, &service_id, primary_id).await
}
async fn stop_compose(
state: &AppState,
service_id: &str,
project_name: &str,
content: &str,
network_name: &str,
env_vars: &[(String, String)],
) -> RpResponse {
if let Err(e) = docker::compose::down(content, project_name, network_name, env_vars).await {
return RpResponse::err("DockerError", e.to_string());
}
finish_stop(state, service_id, None).await
}
async fn finish_stop(state: &AppState, service_id: &str, container_id: Option<&str>) -> RpResponse {
if let Err(e) = crate::db::services::update_status(
&state.db,
service_id,
&ServiceStatus::Stopped,
container_id,
)
.await
{
return RpResponse::err("DatabaseError", e.to_string());
}
if let Ok(history) = crate::db::deployments::list_for_service(&state.db, service_id, 1).await
&& let Some(dep) = history.into_iter().find(|d| d.state == DeployState::Live)
{
let _ = crate::db::deployments::transition(
&state.db,
&dep.id,
&DeployState::Live,
DeployState::Stopped,
None,
)
.await;
state.bus.publish(Event::DeployStateChanged {
deployment_id: dep.id,
service_id: service_id.to_string(),
state: DeployState::Stopped,
timestamp: Utc::now(),
message: None,
});
}
state.bus.publish(Event::ServiceStatusChanged {
service_id: service_id.to_string(),
status: ServiceStatus::Stopped,
});
RpResponse::Ok
}