generated from methanium/template
Big updates
This commit is contained in:
parent
de54eb0b90
commit
56ae701d02
5 changed files with 401 additions and 214 deletions
|
|
@ -5,7 +5,7 @@ use axum::{
|
|||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::{get, post},
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use tower_http::{
|
||||
services::{ServeDir, ServeFile},
|
||||
|
|
@ -25,6 +25,7 @@ pub async fn serve(
|
|||
let app = Router::new()
|
||||
.route("/api/admin", get(admin))
|
||||
.route("/api/incidents", get(list).post(create))
|
||||
.route("/api/incidents/{id}", delete(remove))
|
||||
.route("/api/incidents/{id}/resolve", post(resolve))
|
||||
.fallback_service(assets)
|
||||
.layer(TraceLayer::new_for_http())
|
||||
|
|
@ -62,9 +63,20 @@ async fn resolve(State(state): State<Arc<AppState>>, Path(id): Path<Uuid>) -> Re
|
|||
}
|
||||
}
|
||||
|
||||
async fn remove(State(state): State<Arc<AppState>>, Path(id): Path<Uuid>) -> Response {
|
||||
match state.incidents.delete(id).await {
|
||||
Ok(_) => {
|
||||
state.publish().await;
|
||||
StatusCode::NO_CONTENT.into_response()
|
||||
}
|
||||
Err(error) => error_response(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(error: IncidentError) -> Response {
|
||||
let status = match error {
|
||||
IncidentError::Empty => StatusCode::BAD_REQUEST,
|
||||
IncidentError::Active => StatusCode::CONFLICT,
|
||||
IncidentError::NotFound => StatusCode::NOT_FOUND,
|
||||
IncidentError::Storage(_) | IncidentError::Parse(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ pub enum IncidentError {
|
|||
Empty,
|
||||
#[error("incident not found")]
|
||||
NotFound,
|
||||
#[error("active incidents cannot be deleted")]
|
||||
Active,
|
||||
#[error("failed to persist incidents: {0}")]
|
||||
Storage(#[from] std::io::Error),
|
||||
#[error("failed to parse incidents: {0}")]
|
||||
|
|
@ -73,6 +75,20 @@ impl IncidentStore {
|
|||
Ok(resolved)
|
||||
}
|
||||
|
||||
pub async fn delete(&self, id: Uuid) -> Result<Incident, IncidentError> {
|
||||
let mut incidents = self.incidents.lock().await;
|
||||
let index = incidents
|
||||
.iter()
|
||||
.position(|incident| incident.id == id)
|
||||
.ok_or(IncidentError::NotFound)?;
|
||||
if incidents[index].resolved_at.is_none() {
|
||||
return Err(IncidentError::Active);
|
||||
}
|
||||
let deleted = incidents.remove(index);
|
||||
self.persist(&incidents).await?;
|
||||
Ok(deleted)
|
||||
}
|
||||
|
||||
async fn persist(&self, incidents: &[Incident]) -> Result<(), std::io::Error> {
|
||||
if let Some(parent) = self.path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
|
|
@ -125,4 +141,35 @@ mod tests {
|
|||
.await;
|
||||
assert!(matches!(result, Err(IncidentError::Empty)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn only_deletes_resolved_incidents() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("incidents.json");
|
||||
let store = IncidentStore::load(path.clone()).await.unwrap();
|
||||
let created = store
|
||||
.create(NewIncident {
|
||||
title: "Service disruption".into(),
|
||||
message: "Investigating elevated errors.".into(),
|
||||
severity: Severity::Major,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
store.delete(created.id).await,
|
||||
Err(IncidentError::Active)
|
||||
));
|
||||
store.resolve(created.id).await.unwrap();
|
||||
store.delete(created.id).await.unwrap();
|
||||
|
||||
assert!(
|
||||
IncidentStore::load(path)
|
||||
.await
|
||||
.unwrap()
|
||||
.list()
|
||||
.await
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue