187 lines
5.7 KiB
Rust
187 lines
5.7 KiB
Rust
use crate::{
|
|
constants::{DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE},
|
|
errors::AppError,
|
|
extractors::{JwtClaims, UploadedAsset},
|
|
state::AppState,
|
|
};
|
|
use api_types::{
|
|
requests::{RegisterAssetRequest, TagAssetRequest},
|
|
responses::{AssetResponse, IngestResponse, TagResponse, TimelineResponse},
|
|
};
|
|
use application::{
|
|
catalog::{
|
|
GetAssetQuery, GetTimelineQuery, ReadAssetFileQuery, RegisterAssetCommand,
|
|
UpdateMetadataCommand,
|
|
},
|
|
organization::TagAssetCommand,
|
|
storage::IngestAssetCommand,
|
|
};
|
|
use axum::{
|
|
Json,
|
|
body::Body,
|
|
extract::{Multipart, Path, Query, State},
|
|
http::{StatusCode, header},
|
|
response::Response,
|
|
};
|
|
use domain::{
|
|
catalog::entities::AssetType,
|
|
errors::DomainError,
|
|
value_objects::{MetadataValue, StructuredData, SystemId},
|
|
};
|
|
|
|
#[derive(Debug, serde::Deserialize)]
|
|
pub struct TimelineParams {
|
|
pub limit: Option<u32>,
|
|
pub offset: Option<u32>,
|
|
}
|
|
|
|
pub async fn ingest(
|
|
State(state): State<AppState>,
|
|
claims: JwtClaims,
|
|
multipart: Multipart,
|
|
) -> Result<(StatusCode, Json<IngestResponse>), AppError> {
|
|
let upload = UploadedAsset::from_multipart(multipart).await?;
|
|
|
|
let cmd = IngestAssetCommand {
|
|
uploader_id: claims.user_id,
|
|
client_device_id: upload.client_device_id,
|
|
filename: upload.filename,
|
|
target_path_id: upload.target_path_id,
|
|
file_size: upload.data.len() as u64,
|
|
data: upload.data,
|
|
};
|
|
|
|
let (asset, session) = state.catalog.ingest_asset.execute(cmd).await?;
|
|
|
|
Ok((
|
|
StatusCode::CREATED,
|
|
Json(IngestResponse {
|
|
asset: AssetResponse::from_domain(&asset, &StructuredData::new()),
|
|
session_id: *session.session_id.as_uuid(),
|
|
}),
|
|
))
|
|
}
|
|
|
|
pub async fn get_asset(
|
|
State(state): State<AppState>,
|
|
claims: JwtClaims,
|
|
Path((asset_id,)): Path<(uuid::Uuid,)>,
|
|
) -> Result<Json<AssetResponse>, AppError> {
|
|
let query = GetAssetQuery {
|
|
asset_id: SystemId::from_uuid(asset_id),
|
|
user_id: claims.user_id,
|
|
};
|
|
let (asset, metadata) = state.catalog.get_asset.execute(query).await?;
|
|
Ok(Json(AssetResponse::from_domain(&asset, &metadata)))
|
|
}
|
|
|
|
pub async fn timeline(
|
|
State(state): State<AppState>,
|
|
claims: JwtClaims,
|
|
Query(params): Query<TimelineParams>,
|
|
) -> Result<Json<TimelineResponse>, AppError> {
|
|
let query = GetTimelineQuery {
|
|
owner_id: claims.user_id,
|
|
caller_id: None,
|
|
limit: params.limit.unwrap_or(DEFAULT_PAGE_SIZE).min(MAX_PAGE_SIZE),
|
|
offset: params.offset.unwrap_or(0),
|
|
};
|
|
let results = state.catalog.get_timeline.execute(query).await?;
|
|
let total = results.len();
|
|
let assets = results
|
|
.iter()
|
|
.map(|(asset, meta)| AssetResponse::from_domain(asset, meta))
|
|
.collect();
|
|
Ok(Json(TimelineResponse { assets, total }))
|
|
}
|
|
|
|
pub async fn update_metadata(
|
|
State(state): State<AppState>,
|
|
claims: JwtClaims,
|
|
Path((asset_id,)): Path<(uuid::Uuid,)>,
|
|
Json(req): Json<api_types::requests::UpdateMetadataRequest>,
|
|
) -> Result<Json<serde_json::Value>, AppError> {
|
|
let mut data = StructuredData::new();
|
|
for (k, v) in req.data {
|
|
data.insert(k, MetadataValue::from(v));
|
|
}
|
|
|
|
let cmd = UpdateMetadataCommand {
|
|
asset_id: SystemId::from_uuid(asset_id),
|
|
user_id: claims.user_id,
|
|
data,
|
|
};
|
|
state.catalog.update_metadata.execute(cmd).await?;
|
|
Ok(Json(serde_json::json!({ "status": "updated" })))
|
|
}
|
|
|
|
pub async fn serve_file(
|
|
State(state): State<AppState>,
|
|
_claims: JwtClaims,
|
|
Path((asset_id,)): Path<(uuid::Uuid,)>,
|
|
) -> Result<Response, AppError> {
|
|
let query = ReadAssetFileQuery {
|
|
asset_id: SystemId::from_uuid(asset_id),
|
|
};
|
|
let result = state.catalog.read_asset_file.execute(query).await?;
|
|
|
|
Response::builder()
|
|
.status(StatusCode::OK)
|
|
.header(header::CONTENT_TYPE, &result.mime_type)
|
|
.header(header::CONTENT_LENGTH, result.data.len())
|
|
.header(
|
|
header::CONTENT_DISPOSITION,
|
|
format!("inline; filename=\"{}\"", result.filename),
|
|
)
|
|
.body(Body::from(result.data))
|
|
.map_err(|e| AppError::from(domain::errors::DomainError::Internal(e.to_string())))
|
|
}
|
|
|
|
pub async fn tag_asset(
|
|
State(state): State<AppState>,
|
|
claims: JwtClaims,
|
|
Path((asset_id,)): Path<(uuid::Uuid,)>,
|
|
Json(req): Json<TagAssetRequest>,
|
|
) -> Result<(StatusCode, Json<TagResponse>), AppError> {
|
|
let cmd = TagAssetCommand {
|
|
asset_id: SystemId::from_uuid(asset_id),
|
|
tag_name: req.tag_name,
|
|
user_id: claims.user_id,
|
|
};
|
|
let (tag, _asset_tag) = state.organization.tag_asset.execute(cmd).await?;
|
|
Ok((StatusCode::CREATED, Json(TagResponse::from_domain(&tag))))
|
|
}
|
|
|
|
fn parse_asset_type(s: &str) -> Result<AssetType, AppError> {
|
|
match s {
|
|
"image" => Ok(AssetType::Image),
|
|
"video" => Ok(AssetType::Video),
|
|
"live_photo" => Ok(AssetType::LivePhoto),
|
|
_ => Err(AppError::from(DomainError::Validation(format!(
|
|
"Invalid asset type: {s}"
|
|
)))),
|
|
}
|
|
}
|
|
|
|
pub async fn register_asset(
|
|
State(state): State<AppState>,
|
|
claims: JwtClaims,
|
|
Json(req): Json<RegisterAssetRequest>,
|
|
) -> Result<(StatusCode, Json<AssetResponse>), AppError> {
|
|
let asset_type = parse_asset_type(&req.asset_type)?;
|
|
let cmd = RegisterAssetCommand {
|
|
volume_id: SystemId::from_uuid(req.volume_id),
|
|
relative_path: req.relative_path,
|
|
checksum: req.checksum,
|
|
asset_type,
|
|
mime_type: req.mime_type,
|
|
file_size: req.file_size,
|
|
owner_id: claims.user_id,
|
|
};
|
|
let (asset, _dup_group) = state.catalog.register_asset.execute(cmd).await?;
|
|
Ok((
|
|
StatusCode::CREATED,
|
|
Json(AssetResponse::from_domain(&asset, &StructuredData::new())),
|
|
))
|
|
}
|