asonix
e0858b7b3d
Currently i18n'd - login page - register page - cookie page - delete account confirmation page - 404 page - 500 page - part of account settings
364 lines
8.6 KiB
Rust
364 lines
8.6 KiB
Rust
use crate::State;
|
|
use actix_web::{
|
|
client::Client, http::StatusCode, web, HttpRequest, HttpResponse, ResponseError, Scope,
|
|
};
|
|
use hyaenidae_profiles::pictrs::ImageInfo;
|
|
use hyaenidae_toolkit::Image;
|
|
use url::Url;
|
|
|
|
pub(crate) use hyaenidae_profiles::pictrs::ImageType;
|
|
|
|
pub(crate) struct IconImage {
|
|
key: String,
|
|
title: String,
|
|
}
|
|
|
|
pub(crate) struct ThumbnailImage {
|
|
key: String,
|
|
title: String,
|
|
}
|
|
|
|
pub(crate) struct FullImage {
|
|
key: String,
|
|
title: String,
|
|
}
|
|
|
|
pub(crate) struct BannerImage {
|
|
key: String,
|
|
title: String,
|
|
}
|
|
|
|
impl Image for IconImage {
|
|
fn src(&self) -> String {
|
|
largest_icon(&self.key, ImageType::Png)
|
|
}
|
|
|
|
fn title(&self) -> String {
|
|
self.title.clone()
|
|
}
|
|
|
|
fn jpeg_srcset(&self) -> Option<String> {
|
|
None
|
|
}
|
|
|
|
fn png_srcset(&self) -> Option<String> {
|
|
Some(icon_srcset(&self.key, ImageType::Png))
|
|
}
|
|
|
|
fn webp_srcset(&self) -> Option<String> {
|
|
Some(icon_srcset(&self.key, ImageType::Webp))
|
|
}
|
|
}
|
|
|
|
impl Image for ThumbnailImage {
|
|
fn src(&self) -> String {
|
|
largest_thumbnail(&self.key, ImageType::Png)
|
|
}
|
|
|
|
fn title(&self) -> String {
|
|
self.title.clone()
|
|
}
|
|
|
|
fn jpeg_srcset(&self) -> Option<String> {
|
|
None
|
|
}
|
|
|
|
fn png_srcset(&self) -> Option<String> {
|
|
Some(thumbnail_srcset(&self.key, ImageType::Png))
|
|
}
|
|
|
|
fn webp_srcset(&self) -> Option<String> {
|
|
Some(thumbnail_srcset(&self.key, ImageType::Webp))
|
|
}
|
|
}
|
|
|
|
impl Image for FullImage {
|
|
fn src(&self) -> String {
|
|
largest_image(&self.key, ImageType::Png)
|
|
}
|
|
|
|
fn title(&self) -> String {
|
|
self.title.clone()
|
|
}
|
|
|
|
fn jpeg_srcset(&self) -> Option<String> {
|
|
None
|
|
}
|
|
|
|
fn png_srcset(&self) -> Option<String> {
|
|
Some(image_srcset(&self.key, ImageType::Png))
|
|
}
|
|
|
|
fn webp_srcset(&self) -> Option<String> {
|
|
Some(image_srcset(&self.key, ImageType::Webp))
|
|
}
|
|
}
|
|
|
|
impl Image for BannerImage {
|
|
fn src(&self) -> String {
|
|
largest_banner(&self.key, ImageType::Jpeg)
|
|
}
|
|
|
|
fn title(&self) -> String {
|
|
self.title.clone()
|
|
}
|
|
|
|
fn png_srcset(&self) -> Option<String> {
|
|
None
|
|
}
|
|
|
|
fn jpeg_srcset(&self) -> Option<String> {
|
|
Some(banner_srcset(&self.key, ImageType::Jpeg))
|
|
}
|
|
|
|
fn webp_srcset(&self) -> Option<String> {
|
|
Some(banner_srcset(&self.key, ImageType::Webp))
|
|
}
|
|
}
|
|
|
|
impl IconImage {
|
|
pub(crate) fn new(key: &str, title: &str) -> Self {
|
|
IconImage {
|
|
key: key.to_owned(),
|
|
title: title.to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ThumbnailImage {
|
|
pub(crate) fn new(key: &str, title: &str) -> Self {
|
|
ThumbnailImage {
|
|
key: key.to_owned(),
|
|
title: title.to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FullImage {
|
|
pub(crate) fn new(key: &str, title: &str) -> Self {
|
|
FullImage {
|
|
key: key.to_owned(),
|
|
title: title.to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl BannerImage {
|
|
pub(crate) fn new(key: &str, title: &str) -> Self {
|
|
BannerImage {
|
|
key: key.to_owned(),
|
|
title: title.to_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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)))
|
|
.service(web::resource("/image/{size}.{kind}").route(web::get().to(serve_image)))
|
|
.service(web::resource("/thumb/{size}.{kind}").route(web::get().to(serve_thumbnail)))
|
|
}
|
|
|
|
async fn serve(
|
|
req: HttpRequest,
|
|
key: web::Path<String>,
|
|
client: web::Data<Client>,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
state.profiles.pictrs.serve_full(&req, &key, &client).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 image_srcset(key: &str, kind: ImageType) -> String {
|
|
VALID_IMAGE_SIZES
|
|
.iter()
|
|
.map(|size| format!("{} {}w", image_path(key, *size, kind), size))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
}
|
|
|
|
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 thumbnail_srcset(key: &str, kind: ImageType) -> String {
|
|
VALID_THUMBNAIL_SIZES
|
|
.iter()
|
|
.map(|size| format!("{} {}w", thumbnail_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_image(key: &str, kind: ImageType) -> String {
|
|
image_path(key, 5040, kind)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
pub(crate) fn largest_thumbnail(key: &str, kind: ImageType) -> String {
|
|
thumbnail_path(key, 512, kind)
|
|
}
|
|
|
|
fn icon_path(key: &str, size: u64, kind: ImageType) -> String {
|
|
format!("/image/icon/{}.{}?src={}", size, kind, key)
|
|
}
|
|
|
|
fn thumbnail_path(key: &str, size: u64, kind: ImageType) -> String {
|
|
format!("/image/thumb/{}.{}?src={}", size, kind, key)
|
|
}
|
|
|
|
fn banner_path(key: &str, size: u64, kind: ImageType) -> String {
|
|
format!("/image/banner/{}.{}?src={}", size, kind, key)
|
|
}
|
|
|
|
fn image_path(key: &str, size: u64, kind: ImageType) -> String {
|
|
format!("/image/image/{}.{}?src={}", size, kind, key)
|
|
}
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize)]
|
|
pub struct SrcQuery {
|
|
src: String,
|
|
}
|
|
|
|
static VALID_IMAGE_SIZES: &[u64] = &[420, 840, 1260, 1680, 2520, 5040];
|
|
|
|
async fn serve_image(
|
|
req: HttpRequest,
|
|
path: web::Path<(u64, ImageType)>,
|
|
src: web::Query<SrcQuery>,
|
|
client: web::Data<Client>,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
let (size, kind) = path.into_inner();
|
|
|
|
if !VALID_IMAGE_SIZES.contains(&size) {
|
|
return Err(SizeError(size).into());
|
|
}
|
|
|
|
state
|
|
.profiles
|
|
.pictrs
|
|
.serve_thumbnail(&req, &src.src, kind, size, &client)
|
|
.await
|
|
}
|
|
|
|
static VALID_ICON_SIZES: &[u64] = &[64, 128, 256, 512];
|
|
|
|
async fn serve_icon(
|
|
req: HttpRequest,
|
|
path: web::Path<(u64, ImageType)>,
|
|
src: web::Query<SrcQuery>,
|
|
client: web::Data<Client>,
|
|
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, &client)
|
|
.await
|
|
}
|
|
|
|
static VALID_THUMBNAIL_SIZES: &[u64] = &[64, 128, 256, 512];
|
|
|
|
async fn serve_thumbnail(
|
|
req: HttpRequest,
|
|
path: web::Path<(u64, ImageType)>,
|
|
src: web::Query<SrcQuery>,
|
|
client: web::Data<Client>,
|
|
state: web::Data<State>,
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
let (size, kind) = path.into_inner();
|
|
|
|
if !VALID_THUMBNAIL_SIZES.contains(&size) {
|
|
return Err(SizeError(size).into());
|
|
}
|
|
|
|
state
|
|
.profiles
|
|
.pictrs
|
|
.serve_thumbnail(&req, &src.src, kind, size, &client)
|
|
.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>,
|
|
client: web::Data<Client>,
|
|
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, &client)
|
|
.await
|
|
}
|