use crate::engine::containers;
use crate::features::services::ServicesContext;
use shared::{Response as RpResponse, ServiceSource};
async fn remove_containers(state: &ServicesContext, id: &str) -> Result<(), RpResponse> {
let Ok(Some(svc)) = crate::store::services::get(&state.db, id).await else {
return Ok(());
};
if let err @ RpResponse::Err { .. } =
crate::features::deploy::stop::handle(state.deploy.clone(), id.to_string()).await
{
return Err(err);
}
if !matches!(svc.spec.source, ServiceSource::Compose(_)) {
let ids = containers::find_all_by_service_id(&state.docker.inner, id)
.await
.map_err(|e| RpResponse::err("RuntimeError", e.to_string()))?;
for cid in ids {
containers::remove(&state.docker.inner, &cid)
.await
.map_err(|e| RpResponse::err("RuntimeError", e.to_string()))?;
}
}
Ok(())
}
pub async fn handle(state: ServicesContext, id: String) -> RpResponse {
if let Ok(Some(svc)) = crate::store::services::get(&state.db, &id).await
&& let Ok(all) =
crate::features::services::project_deps(&state.db, &svc.spec.project_id).await
{
let dependents = crate::features::deploy::deps::direct_dependents(&all, &id);
if !dependents.is_empty() {
let names: Vec<String> = dependents.into_iter().map(|(_, n)| n).collect();
return RpResponse::err(
"DependencyInUse",
format!(
"outros serviços dependem deste: {} — remova a dependência neles antes de apagar",
names.join(", ")
),
);
}
}
if let Err(e) = remove_containers(&state, &id).await {
return e;
}
let mut host_port = None;
if let Ok(Some(svc)) = crate::store::services::get(&state.db, &id).await {
state.ingress.remove_domains(&svc.spec);
if let Some(port) = svc.spec.host_port {
state.ingress.remove_port_route(port);
host_port = Some(port);
}
}
match crate::store::services::delete(&state.db, &id).await {
Ok(true) => {
if let Some(port) = host_port
&& !crate::features::ingress::external_ports::port_in_use_by_other(
&state.db,
port,
Some(&id),
)
.await
{
crate::features::ingress::firewall::ensure_denied_bg(port);
}
match crate::store::job::delete_by_trigger_service(&state.db, &id).await {
Ok(n) if n > 0 => {
tracing::info!(service_id = %id, jobs = n, "service_delete: jobs gatilhados removidos em cascata");
}
Err(e) => {
tracing::warn!(service_id = %id, error = %e, "service_delete: falha ao remover jobs gatilhados (ficarão órfãos)");
}
_ => {}
}
RpResponse::Ok
}
Ok(false) => RpResponse::err("NotFound", "service not found"),
Err(e) => RpResponse::err("DatabaseError", e.to_string()),
}
}