use std::sync::Arc; use axum::extract::{Path, State}; use axum::response::{IntoResponse, Response}; use axum::{Extension, Json}; use entity::file::{self, Entity as File}; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use crate::AppState; use crate::errors::{AppError, Error, ErrorKind, WithKind}; use crate::middlewares::auth::UserId; use crate::utils::blob::MmapStream; pub async fn route( State(app): State>, Extension(UserId(user_id)): Extension, Path(id): Path, ) -> Result { // Allow access if: user owns the file OR file belongs to user's chat let file = File::find_by_id(id) .one(&app.conn) .await .kind(ErrorKind::Internal)? .ok_or(Json(Error { error: ErrorKind::ResourceNotFound, reason: "".to_owned(), }))?; // Check access: owner_id matches OR (owner_id is None and chat belongs to user) let has_access = if let Some(owner) = file.owner_id { owner == user_id } else if let Some(chat_id) = file.chat_id { // Generated image - verify chat belongs to user use entity::chat::{self, Entity as Chat}; Chat::find_by_id(chat_id) .filter(chat::Column::OwnerId.eq(user_id)) .one(&app.conn) .await .kind(ErrorKind::Internal)? .is_some() } else { false }; if !has_access { return Err(Json(Error { error: ErrorKind::ResourceNotFound, reason: "".to_owned(), }) .into()); } let reader = app.blob.get(id).ok_or(Json(Error { error: ErrorKind::ResourceNotFound, reason: "File data not found".to_owned(), }))?; let content_length = reader.as_ref().len(); let mut headers = axum::http::HeaderMap::new(); if let Some(mime) = file.mime_type { headers.insert( axum::http::header::CONTENT_TYPE, axum::http::HeaderValue::from_str(mime.as_str()).unwrap(), ); } headers.insert( axum::http::header::CONTENT_LENGTH, axum::http::HeaderValue::from_str(&content_length.to_string()).unwrap(), ); let stream: MmapStream = reader.into(); let body = axum::body::Body::from_stream(stream); Ok((headers, body).into_response()) }