136 lines
3.4 KiB
Rust
136 lines
3.4 KiB
Rust
|
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
|
||
|
}
|