bobashare_web/api/v1/
delete.rs

1//! API to delete an upload
2
3use axum::{
4    extract::{Path, State},
5    response::{IntoResponse, Response},
6};
7use bobashare::storage::file::{DeleteUploadError, OpenUploadError};
8use displaydoc::Display;
9use hyper::StatusCode;
10use thiserror::Error;
11use tracing::{event, instrument, Level};
12
13use super::ApiErrorExt;
14use crate::AppState;
15
16/// Errors that could occur when deleting an upload
17#[derive(Debug, Error, Display)]
18pub enum DeleteError {
19    /// an upload at the specified id was not found
20    NotFound,
21    /// incorrect delete key
22    IncorrectKey,
23
24    /// internal server error
25    InternalServer(#[from] anyhow::Error),
26}
27impl From<DeleteUploadError> for DeleteError {
28    fn from(err: DeleteUploadError) -> Self {
29        match err {
30            DeleteUploadError::NotFound => Self::NotFound,
31            e => Self::InternalServer(e.into()),
32        }
33    }
34}
35impl IntoResponse for DeleteError {
36    fn into_response(self) -> Response {
37        let code = match self {
38            Self::NotFound => StatusCode::NOT_FOUND,
39            Self::IncorrectKey => StatusCode::FORBIDDEN,
40            Self::InternalServer(_) => StatusCode::INTERNAL_SERVER_ERROR,
41        };
42        self.into_response_with_code(code)
43    }
44}
45
46/// Delete an upload
47///
48/// # Request
49///
50/// `DELETE /api/v1/delete/:id`
51///
52/// ## Body
53///
54/// Should contain the key used to delete the upload (`delete_key` in
55/// [`UploadResponse`]).
56///
57/// [`UploadResponse`]: super::upload::UploadResponse::delete_key
58///
59/// # Response
60///
61/// ## Success
62///
63/// - 204 No Content
64#[instrument(skip(state))]
65pub async fn delete(
66    state: State<&'static AppState>,
67    Path(id): Path<String>,
68    key: String,
69) -> Result<impl IntoResponse, DeleteError> {
70    let key = key.trim();
71    event!(Level::DEBUG, "reading upload metadata");
72    let metadata = state
73        .backend
74        .read_upload_metadata(&id)
75        .await
76        .map_err(|e| match e {
77            OpenUploadError::NotFound(_) => DeleteError::NotFound,
78            e => DeleteError::InternalServer(
79                anyhow::Error::new(e).context("error reading upload metadata"),
80            ),
81        })?;
82    if metadata.is_expired() {
83        event!(
84            Level::INFO,
85            "upload was already expired anyway, deleting and sending NotFound response"
86        );
87        state.backend.delete_upload(&id).await?;
88        return Err(DeleteError::NotFound);
89    }
90    if metadata.delete_key != key {
91        event!(Level::INFO, "provided delete key was incorrect");
92        return Err(DeleteError::IncorrectKey);
93    }
94
95    event!(Level::DEBUG, "delete key was correct; deleting upload");
96    state.backend.delete_upload(&id).await?;
97
98    event!(Level::INFO, id, "successfully deleted upload");
99    Ok(StatusCode::NO_CONTENT)
100}