feat: implement routes for serving images

This commit is contained in:
Jef Roosens 2025-01-17 13:45:28 +01:00
parent efb5b9ebea
commit 0c4fc0ef98
Signed by: Jef Roosens
GPG key ID: 21FD3D77D56BAF49
5 changed files with 71 additions and 7 deletions

View file

@ -47,3 +47,13 @@ impl NewImage {
.get_result(&mut pool.get()?)?)
}
}
impl Image {
pub fn by_id(pool: &DbPool, id: i32) -> DbResult<Option<Self>> {
Ok(images::table
.find(id)
.select(Self::as_select())
.first(&mut pool.get()?)
.optional()?)
}
}

View file

@ -10,6 +10,7 @@ pub enum AppError {
Tera(tera::Error),
Multipart(MultipartError),
IO(std::io::Error),
Other(Box<dyn std::error::Error + 'static + Send + Sync>),
BadRequest,
Unauthorized,
NotFound,
@ -22,6 +23,7 @@ impl fmt::Display for AppError {
Self::Tera(_) => write!(f, "error rendering template"),
Self::Multipart(_) => write!(f, "error processing multipart request"),
Self::IO(_) => write!(f, "io error"),
Self::Other(_) => write!(f, "other error"),
Self::BadRequest => write!(f, "bad request"),
Self::Unauthorized => write!(f, "unauthorized"),
Self::NotFound => write!(f, "not found"),
@ -35,6 +37,7 @@ impl std::error::Error for AppError {
Self::Db(err) => Some(err),
Self::Tera(err) => Some(err),
Self::IO(err) => Some(err),
Self::Other(err) => Some(err.as_ref()),
Self::Multipart(err) => Some(err),
Self::NotFound | Self::Unauthorized | Self::BadRequest => None,
}

View file

@ -1,14 +1,17 @@
use axum::{
extract::{DefaultBodyLimit, Multipart, State},
extract::{DefaultBodyLimit, Multipart, Path, Request, State},
handler::Handler,
response::Html,
routing::post,
response::{Html, IntoResponse},
routing::{get, post},
Router,
};
use chrono::NaiveDate;
use futures::TryStreamExt;
use image::{codecs::jpeg::JpegEncoder, ImageReader};
use mime::Mime;
use tokio_util::io::StreamReader;
use tower::ServiceExt;
use tower_http::services::ServeFile;
use std::{io::BufWriter, path::PathBuf};
@ -21,10 +24,54 @@ use crate::{
const THUMBNAIL_EXT: &str = "thumb";
pub fn app() -> axum::Router<crate::Context> {
Router::new().route(
"/",
post(post_image.layer(DefaultBodyLimit::max(1024 * 1024 * 20))),
)
Router::new()
.route(
"/",
post(post_image.layer(DefaultBodyLimit::max(1024 * 1024 * 20))),
)
.route("/{id}/original", get(get_image_original))
.route("/{id}/thumb", get(get_image_thumb))
}
async fn get_image(
ctx: crate::Context,
id: i32,
req: Request,
thumb: bool,
) -> super::Result<impl IntoResponse> {
let image = tokio::task::spawn_blocking(move || Image::by_id(&ctx.pool, id))
.await
.unwrap()?
.ok_or(AppError::NotFound)?;
let mime: Mime = image
.mime_type
.parse()
.map_err(|err| AppError::Other(Box::new(err)))?;
let mut path = ctx.data_dir.join(crate::IMG_DIR).join(image.id.to_string());
if thumb {
path.set_extension(THUMBNAIL_EXT);
}
Ok(ServeFile::new_with_mime(path, &mime).oneshot(req).await)
}
async fn get_image_original(
State(ctx): State<crate::Context>,
Path(id): Path<i32>,
req: Request,
) -> super::Result<impl IntoResponse> {
get_image(ctx, id, req, false).await
}
async fn get_image_thumb(
State(ctx): State<crate::Context>,
Path(id): Path<i32>,
req: Request,
) -> super::Result<impl IntoResponse> {
get_image(ctx, id, req, true).await
}
async fn post_image(