hyaenidae/server/src/images.rs

136 lines
3.4 KiB
Rust
Raw Normal View History

2021-01-06 08:21:37 +00:00
use crate::State;
use actix_web::{http::StatusCode, web, HttpRequest, HttpResponse, ResponseError, Scope};
use hyaenidae_profiles::pictrs::ImageInfo;
use url::Url;
pub(crate) use hyaenidae_profiles::pictrs::ImageType;
#[derive(Clone)]
pub(super) struct Images {
base_url: Url,
}
impl Images {
pub(super) fn new(base_url: Url) -> Self {
Images { base_url }
}
}
impl ImageInfo for Images {
fn image_url(&self, key: &str) -> Url {
let mut url = self.base_url.clone();
url.set_path(&format!("/image/full/{}", key));
url
}
}
pub(super) fn scope() -> Scope {
web::scope("/image")
.service(web::resource("/full/{key}").route(web::get().to(serve)))
.service(web::resource("/icon/{size}.{kind}").route(web::get().to(serve_icon)))
.service(web::resource("/banner/{size}.{kind}").route(web::get().to(serve_banner)))
}
async fn serve(
req: HttpRequest,
key: web::Path<String>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
state.profiles.pictrs.serve_full(&req, &key).await
}
#[derive(Debug, thiserror::Error)]
#[error("Invalid image size requested: {0}px")]
struct SizeError(u64);
impl ResponseError for SizeError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest()
.content_type("text/plain")
.body(self.to_string())
}
}
pub(crate) fn icon_srcset(key: &str, kind: ImageType) -> String {
VALID_ICON_SIZES
.iter()
.map(|size| format!("{} {}w", icon_path(key, *size, kind), size))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn banner_srcset(key: &str, kind: ImageType) -> String {
VALID_BANNER_SIZES
.iter()
.map(|size| format!("{} {}w", banner_path(key, *size, kind), size))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn largest_banner(key: &str, kind: ImageType) -> String {
banner_path(key, 1680, kind)
}
pub(crate) fn largest_icon(key: &str, kind: ImageType) -> String {
icon_path(key, 512, kind)
}
fn icon_path(key: &str, size: u64, kind: ImageType) -> String {
format!("/image/icon/{}.{}?src={}", size, kind, key)
}
fn banner_path(key: &str, size: u64, kind: ImageType) -> String {
format!("/image/banner/{}.{}?src={}", size, kind, key)
}
#[derive(Clone, Debug, serde::Deserialize)]
pub struct SrcQuery {
src: String,
}
static VALID_ICON_SIZES: &[u64] = &[64, 128, 256, 512];
async fn serve_icon(
req: HttpRequest,
path: web::Path<(u64, ImageType)>,
src: web::Query<SrcQuery>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
let (size, kind) = path.into_inner();
if !VALID_ICON_SIZES.contains(&size) {
return Err(SizeError(size).into());
}
state
.profiles
.pictrs
.serve_icon(&req, &src.src, kind, size)
.await
}
static VALID_BANNER_SIZES: &[u64] = &[420, 840, 1260, 1680];
async fn serve_banner(
req: HttpRequest,
path: web::Path<(u64, ImageType)>,
src: web::Query<SrcQuery>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
let (size, kind) = path.into_inner();
if !VALID_BANNER_SIZES.contains(&size) {
return Err(SizeError(size).into());
}
state
.profiles
.pictrs
.serve_banner(&req, &src.src, kind, size)
.await
}