use axum::Json;
use axum::extract::State;
use serde::Deserialize;
use crate::auth::SuperAdminAuth;
use crate::backup::{self, BackupEnvelope, ImportResult};
use crate::keys::seed_store::create_secret_store;
use crate::server::AppState;
use crate::store::keyspaces;
use vti_common::audit::{AuditEvent, BackupData};
use vti_common::error::AppError;
#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ExportRequest {
pub password: String,
#[serde(default)]
pub include_audit: bool,
}
#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ImportRequest {
pub backup: BackupEnvelope,
pub password: String,
#[serde(default)]
pub confirm: bool,
}
#[utoipa::path(
post, path = "/backup/export", tag = "backup",
security(("bearer_jwt" = [])),
request_body = ExportRequest,
responses(
(status = 200, description = "Encrypted full-state backup", body = BackupEnvelope),
(status = 400, description = "Password too short"),
(status = 401, description = "Missing or invalid bearer token"),
(status = 403, description = "Caller is not a super-admin"),
),
)]
pub async fn export(
SuperAdminAuth(auth): SuperAdminAuth,
State(state): State<AppState>,
Json(req): Json<ExportRequest>,
) -> Result<Json<BackupEnvelope>, AppError> {
let store = create_secret_store(&*state.config.read().await)?;
let envelope =
backup::export_backup(&state, store.as_ref(), &req.password, req.include_audit).await?;
if let Some(writer) = state.audit_writer.as_ref() {
writer
.write(
&auth.did,
None,
AuditEvent::BackupExported(BackupData {
keyspace_count: keyspaces::BACKED_UP.len() as u32,
vtc_did: envelope.source_did.clone(),
}),
)
.await?;
}
Ok(Json(envelope))
}
#[utoipa::path(
post, path = "/backup/import", tag = "backup",
security(("bearer_jwt" = [])),
request_body = ImportRequest,
responses(
(status = 200, description = "Import applied, or (confirm=false) a preview", body = ImportResult),
(status = 400, description = "Malformed / unsupported backup"),
(status = 401, description = "Wrong backup password or invalid bearer token"),
(status = 403, description = "Caller is not a super-admin"),
(status = 409, description = "Backup vtc_did does not match this VTC"),
),
)]
pub async fn import(
SuperAdminAuth(auth): SuperAdminAuth,
State(state): State<AppState>,
Json(req): Json<ImportRequest>,
) -> Result<Json<ImportResult>, AppError> {
let store = create_secret_store(&*state.config.read().await)?;
let result = backup::import_backup(
&state,
store.as_ref(),
&req.backup,
&req.password,
req.confirm,
)
.await?;
if result.status == "imported"
&& let Some(writer) = state.audit_writer.as_ref()
{
writer
.write(
&auth.did,
None,
AuditEvent::BackupImported(BackupData {
keyspace_count: result.counts.len() as u32,
vtc_did: result.source_did.clone(),
}),
)
.await?;
}
Ok(Json(result))
}