use super::*;
use boatramp_core::function::FunctionSummary;
#[derive(serde::Deserialize)]
pub(super) struct FunctionQuery {
site: Option<String>,
}
pub(super) async fn list_functions(
State(deploy): State<DeployStore>,
Extension(project): axum::extract::Extension<ProjectContext>,
axum::extract::Query(query): axum::extract::Query<FunctionQuery>,
) -> Response {
use boatramp_core::function;
let sites = match &query.site {
Some(s) => vec![s.clone()],
None => match deploy.all_sites(project.as_ref()).await {
Ok(s) => s,
Err(err) => return deploy_error_response(err),
},
};
let mut out: Vec<FunctionSummary> = Vec::new();
for site in sites {
let manifest = match deploy.current_manifest(project.as_ref(), &site).await {
Ok(Some(m)) => m,
Ok(None) => continue,
Err(err) => return deploy_error_response(err),
};
let (specs, triggers) = function::desugar(&manifest.config);
for f in function::materialize(&specs, &site, &manifest.files, 0) {
let trigs = triggers
.iter()
.filter(|t| t.target.as_ref().map(|r| r.name.as_str()) == Some(f.name.as_str()))
.map(std::string::ToString::to_string)
.collect();
out.push(FunctionSummary {
name: format!("{site}/{}", f.name),
owner: format!("site:{site}"),
runtime: f.config.runtime.as_str().to_string(),
version: f.active,
triggers: trigs,
});
}
}
if query.site.is_none() {
match deploy.list_stored_functions(project.as_ref()).await {
Ok(stored) => {
for f in stored {
out.push(FunctionSummary {
name: f.name.clone(),
owner: f.owner.to_string(),
runtime: f.config.runtime.as_str().to_string(),
version: f.active,
triggers: vec![format!("invoke {}", f.name)],
});
}
}
Err(err) => return deploy_error_response(err),
}
}
Json(out).into_response()
}
#[derive(serde::Deserialize)]
pub(super) struct FunctionUpsert {
pub(super) component: String,
#[serde(default)]
pub(super) config: boatramp_core::function::FunctionConfig,
#[serde(default)]
pub(super) lifecycle: boatramp_core::function::Lifecycle,
}
pub(super) async fn deploy_function(
State(deploy): State<DeployStore>,
Extension(project): axum::extract::Extension<ProjectContext>,
Path(name): Path<String>,
Json(body): Json<FunctionUpsert>,
) -> Response {
use boatramp_core::function::{Function, Owner};
if let Some(resp) = reject_invalid_name("function", &name) {
return resp;
}
match deploy.has_blob(&body.component).await {
Ok(true) => {}
Ok(false) => {
return (
StatusCode::BAD_REQUEST,
format!("component blob {} not uploaded\n", body.component),
)
.into_response()
}
Err(err) => return deploy_error_response(err),
}
let now = now_unix();
let f = match deploy.get_function(project.as_ref(), &name).await {
Ok(Some(mut existing)) => {
existing.config = body.config;
existing.upsert_version(&body.component, body.lifecycle, now);
existing
}
Ok(None) => Function::new(
name.clone(),
Owner::Project("default".to_string()),
&body.component,
body.config,
body.lifecycle,
now,
),
Err(err) => return deploy_error_response(err),
};
if let Err(err) = deploy.put_function(project.as_ref(), &f).await {
return deploy_error_response(err);
}
Json(f).into_response()
}
#[derive(serde::Deserialize)]
pub(super) struct RollbackBody {
pub(super) to: String,
}
pub(super) async fn rollback_function(
State(deploy): State<DeployStore>,
Extension(project): axum::extract::Extension<ProjectContext>,
Path(name): Path<String>,
Json(body): Json<RollbackBody>,
) -> Response {
match deploy.get_function(project.as_ref(), &name).await {
Ok(Some(mut f)) => match f.rollback(&body.to) {
Ok(()) => {
if let Err(err) = deploy.put_function(project.as_ref(), &f).await {
return deploy_error_response(err);
}
Json(f).into_response()
}
Err(msg) => (StatusCode::BAD_REQUEST, format!("{msg}\n")).into_response(),
},
Ok(None) => (StatusCode::NOT_FOUND, format!("no function {name:?}\n")).into_response(),
Err(err) => deploy_error_response(err),
}
}
#[derive(serde::Deserialize)]
pub(super) struct AliasBody {
pub(super) version: String,
}
pub(super) async fn alias_function(
State(deploy): State<DeployStore>,
Extension(project): axum::extract::Extension<ProjectContext>,
Path((name, label)): Path<(String, String)>,
Json(body): Json<AliasBody>,
) -> Response {
match deploy.get_function(project.as_ref(), &name).await {
Ok(Some(mut f)) => match f.set_alias(&label, &body.version) {
Ok(()) => {
if let Err(err) = deploy.put_function(project.as_ref(), &f).await {
return deploy_error_response(err);
}
Json(f).into_response()
}
Err(msg) => (StatusCode::BAD_REQUEST, format!("{msg}\n")).into_response(),
},
Ok(None) => (StatusCode::NOT_FOUND, format!("no function {name:?}\n")).into_response(),
Err(err) => deploy_error_response(err),
}
}
pub(super) async fn remove_function(
State(deploy): State<DeployStore>,
Extension(project): axum::extract::Extension<ProjectContext>,
Path(name): Path<String>,
) -> Response {
match deploy.delete_function(project.as_ref(), &name).await {
Ok(_) => StatusCode::NO_CONTENT.into_response(),
Err(err) => deploy_error_response(err),
}
}