use axum::{
body::Bytes,
extract::{Path, Query, State},
http::{header, StatusCode},
response::{IntoResponse, Response},
Json,
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::db::{queries::plugin_module, DbPool};
use crate::error::AppResult;
#[derive(Debug, Deserialize)]
pub struct RegisterQuery {
pub version: i32,
#[serde(default)]
pub media_type: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct RegisterResponse {
pub path: String,
pub version: i32,
pub digest: String,
pub bytes: usize,
}
pub async fn register(
State(pool): State<DbPool>,
Path(path): Path<String>,
Query(q): Query<RegisterQuery>,
body: Bytes,
) -> AppResult<Json<RegisterResponse>> {
let digest = hex::encode(Sha256::digest(&body));
let media_type = q
.media_type
.unwrap_or_else(|| "application/wasm".to_string());
plugin_module::upsert(&pool, &path, q.version, &digest, &media_type, &body).await?;
tracing::info!(
plugin_path = %path,
version = q.version,
digest = %digest,
bytes = body.len(),
"registered plug-in module"
);
Ok(Json(RegisterResponse {
path,
version: q.version,
digest,
bytes: body.len(),
}))
}
#[derive(Debug, Deserialize)]
pub struct FetchQuery {
pub version: i32,
#[serde(default)]
pub digest: Option<String>,
}
pub async fn fetch(
State(pool): State<DbPool>,
Path(path): Path<String>,
Query(q): Query<FetchQuery>,
) -> AppResult<Response> {
let Some(row) = plugin_module::get(&pool, &path, q.version).await? else {
return Ok((
StatusCode::NOT_FOUND,
format!("plug-in {path}@{} not found", q.version),
)
.into_response());
};
if let Some(expected) = q.digest.as_deref() {
if expected != row.digest {
return Ok((
StatusCode::CONFLICT,
format!(
"digest mismatch for {path}@{}: stored {}, requested {}",
q.version, row.digest, expected
),
)
.into_response());
}
}
Ok((
[
(header::CONTENT_TYPE, row.media_type),
(header::ETAG, format!("\"{}\"", row.digest)),
],
row.bytes,
)
.into_response())
}