brewlog/src/application/routes/api/system/backup.rs
Jon Seager edc0021792
refactor: split large directories into coffee/auth/analytics subdirs
- Restructure domain/, repositories/, and routes/api/ into themed
  subdirectories: coffee/, auth/, analytics/ (and system/ for routes)
- Convert super:: relative imports to crate:: absolute paths
- Promote macro visibility from pub(super) to pub(crate)
- Add pub use re-exports for backward-compatible import paths
2026-02-08 18:16:16 +00:00

76 lines
2.3 KiB
Rust

use axum::Json;
use axum::extract::State;
use axum::http::{StatusCode, header};
use axum::response::{IntoResponse, Response};
use crate::application::auth::AuthenticatedUser;
use crate::application::errors::{ApiError, AppError};
use crate::application::state::AppState;
use crate::infrastructure::backup::BackupData;
/// GET /api/v1/backup — export all data as JSON (requires authentication)
///
/// Returns the backup with a `Content-Disposition: attachment` header so
/// browsers trigger a file download while API/CLI consumers can ignore it.
pub(crate) async fn export_backup(
State(state): State<AppState>,
_auth_user: AuthenticatedUser,
) -> Result<Response, ApiError> {
let data = state
.backup_service
.export()
.await
.map_err(|e| AppError::unexpected(e.to_string()))?;
let body = serde_json::to_string(&data).map_err(|e| AppError::unexpected(e.to_string()))?;
let filename = format!(
"brewlog-backup-{}.json",
chrono::Utc::now().format("%Y-%m-%d")
);
Ok((
[
(header::CONTENT_TYPE, "application/json".to_string()),
(
header::CONTENT_DISPOSITION,
format!("attachment; filename=\"{filename}\""),
),
],
body,
)
.into_response())
}
/// POST /api/v1/backup/restore — restore from JSON backup (requires authentication)
pub(crate) async fn restore_backup(
State(state): State<AppState>,
_auth_user: AuthenticatedUser,
Json(payload): Json<BackupData>,
) -> Result<Response, ApiError> {
state.backup_service.restore(payload).await.map_err(|e| {
let msg = e.to_string();
if msg.contains("not empty") {
ApiError::from(AppError::Conflict(msg))
} else {
ApiError::from(AppError::unexpected(msg))
}
})?;
Ok(StatusCode::NO_CONTENT.into_response())
}
/// POST /api/v1/backup/reset — delete all coffee data (requires authentication)
pub(crate) async fn reset_database(
State(state): State<AppState>,
_auth_user: AuthenticatedUser,
) -> Result<Response, ApiError> {
state
.backup_service
.reset()
.await
.map_err(|e| AppError::unexpected(e.to_string()))?;
tracing::info!("database reset: all coffee data deleted");
Ok(StatusCode::NO_CONTENT.into_response())
}