use crate::connections::s3_connection; use crate::controllers::HttpResult; use crate::models::PhotoID; use crate::services::photos_service; use actix_web::http::header; use actix_web::{web, HttpRequest, HttpResponse}; use std::ops::Add; use std::time::{Duration, UNIX_EPOCH}; #[derive(serde::Deserialize)] pub struct PhotoIdPath { id: String, } pub async fn get_full_size(id: web::Path, req: HttpRequest) -> HttpResult { get_photo(&id, true, req).await } pub async fn get_thumbnail(id: web::Path, req: HttpRequest) -> HttpResult { get_photo(&id, false, req).await } async fn get_photo(id: &PhotoIdPath, full_size: bool, req: HttpRequest) -> HttpResult { let id = match PhotoID::from_signed_hash(&id.id) { None => { return Ok(HttpResponse::Unauthorized().body("Invalid hash")); } Some(p) => p, }; let photo = photos_service::get_by_id(id).await?; let (hash, content_type) = match full_size { true => (photo.sha512.as_str(), photo.mime_type.as_str()), false => (photo.thumb_sha512.as_str(), "application/png"), }; // Check if an upload is un-necessary if let Some(c) = req.headers().get(header::IF_NONE_MATCH) { if c.to_str().unwrap_or("") == hash { return Ok(HttpResponse::NotModified().finish()); } } if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) { let date_str = c.to_str().unwrap_or(""); if let Ok(date) = httpdate::parse_http_date(date_str) { if date .add(Duration::from_secs(1)) .duration_since(UNIX_EPOCH) .unwrap() .as_secs() >= photo.time_create as u64 { return Ok(HttpResponse::NotModified().finish()); } } } let bytes = s3_connection::get_file(&match full_size { true => photo.photo_path(), false => photo.thumbnail_path(), }) .await?; Ok(HttpResponse::Ok() .content_type(content_type) .insert_header(("etag", hash)) .insert_header(( "last-modified", httpdate::fmt_http_date(UNIX_EPOCH + Duration::from_secs(photo.time_create as u64)), )) .body(bytes)) }