Add image serving with CC license attribution
- S3 image proxy at /img/{path} with cache headers
- Species images displayed on species + cultivar detail pages
- Full CC attribution below each image: author, license, Wikimedia Commons link
- Image struct updated with source_url and license fields
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
use axum::extract::{Multipart, Path, State};
|
||||
use axum::http::{header, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::Json;
|
||||
|
||||
use crate::auth::AuthUser;
|
||||
@@ -7,6 +9,38 @@ use crate::db::models::Image;
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::state::AppState;
|
||||
|
||||
/// Serve an image directly from S3, with Content-Type and cache headers.
|
||||
pub async fn serve_image(
|
||||
State(state): State<AppState>,
|
||||
Path(key): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
match s3::download(&state.s3, &state.config.s3_bucket, &key).await {
|
||||
Ok(data) => {
|
||||
let ct = if key.ends_with(".png") {
|
||||
"image/png"
|
||||
} else if key.ends_with(".webp") {
|
||||
"image/webp"
|
||||
} else if key.ends_with(".gif") {
|
||||
"image/gif"
|
||||
} else if key.ends_with(".svg") {
|
||||
"image/svg+xml"
|
||||
} else {
|
||||
"image/jpeg"
|
||||
};
|
||||
(
|
||||
StatusCode::OK,
|
||||
[
|
||||
(header::CONTENT_TYPE, ct),
|
||||
(header::CACHE_CONTROL, "public, max-age=86400"),
|
||||
],
|
||||
data,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
Err(_) => StatusCode::NOT_FOUND.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_for_entity(
|
||||
State(state): State<AppState>,
|
||||
Path((entity_type, entity_id)): Path<(String, uuid::Uuid)>,
|
||||
|
||||
@@ -41,6 +41,8 @@ pub fn router(state: AppState) -> Router {
|
||||
.route("/api/v1/images/{entity_type}/{entity_id}", get(images::list_for_entity))
|
||||
.route("/api/v1/images", post(images::upload))
|
||||
.route("/api/v1/images/{id}", delete(images::remove))
|
||||
// Image file serving (S3 proxy)
|
||||
.route("/img/{*path}", get(images::serve_image))
|
||||
// Search
|
||||
.route("/api/v1/search", get(search::search))
|
||||
// OIDC auth
|
||||
|
||||
Reference in New Issue
Block a user