bobashare_web/api/v1/
info.rs

1//! API to get metadata about an upload
2
3use axum::{
4    extract::{Path, State},
5    response::{IntoResponse, Response},
6    Json,
7};
8use bobashare::storage::file::OpenUploadError;
9use chrono::{DateTime, Utc};
10use displaydoc::Display;
11use hyper::StatusCode;
12use serde::Serialize;
13use thiserror::Error;
14use tracing::{event, instrument, Level};
15
16use super::ApiErrorExt;
17use crate::AppState;
18
19/// Successful upload info API response
20#[derive(Debug, Clone, Serialize)]
21pub struct InfoResponse {
22    /// ID of the upload
23    pub id: String,
24    /// URL of the upload
25    pub url: String,
26    /// direct URL to download the upload file
27    pub direct_url: String,
28    /// filename of the uploaded file
29    pub filename: String,
30    /// MIME type of the file
31    pub mimetype: String,
32    /// date the upload was created
33    pub creation_date: DateTime<Utc>,
34    /// date the upload expires, or None if it never expires
35    pub expiry_date: Option<DateTime<Utc>>,
36    // don't accidentally send `delete_key` lol
37}
38
39/// Errors when querying info about an upload
40#[derive(Debug, Error, Display)]
41pub enum InfoError {
42    /// an upload at the specified id was not found
43    NotFound,
44
45    /// internal server error
46    InternalServer(#[from] anyhow::Error),
47}
48impl IntoResponse for InfoError {
49    fn into_response(self) -> Response {
50        let code = match self {
51            Self::NotFound => StatusCode::NOT_FOUND,
52            Self::InternalServer(_) => StatusCode::INTERNAL_SERVER_ERROR,
53        };
54
55        self.into_response_with_code(code)
56    }
57}
58
59/// Get information (metadata) about an upload
60///
61/// # Request
62///
63/// `GET /api/v1/info/:id`
64///
65/// # Response
66///
67/// ## Success
68///
69/// - 200 OK
70/// - JSON body created from [`InfoResponse`]
71#[instrument(skip(state))]
72pub async fn info(
73    state: State<&'static AppState>,
74    Path(id): Path<String>,
75) -> Result<impl IntoResponse, InfoError> {
76    event!(Level::DEBUG, id, "reading upload metadata");
77    let metadata = state
78        .backend
79        .read_upload_metadata(&id)
80        .await
81        .map_err(|e| match e {
82            OpenUploadError::NotFound(_) => InfoError::NotFound,
83            e => InfoError::InternalServer(
84                anyhow::Error::new(e).context("error reading upload metadata"),
85            ),
86        })?;
87
88    let url = state.base_url.join(&id).unwrap().to_string();
89    let direct_url = state.raw_url.join(&id).unwrap().to_string();
90    event!(Level::INFO, "successfully queried upload metadata");
91    Ok(Json(InfoResponse {
92        id,
93        url,
94        direct_url,
95        filename: metadata.filename,
96        mimetype: metadata.mimetype.to_string(),
97        creation_date: metadata.creation_date,
98        expiry_date: metadata.expiry_date,
99    }))
100}