use bytes::Bytes; use db::attachment::attachments as att_entity; use db::attachment::attachments::OwnerType; use db::conn::sea_orm::*; use object_store::path::Path; use time::OffsetDateTime; use uuid::Uuid; use crate::error::{IssueError, IssueResult}; use crate::features::IssueService; use super::super::{AttachmentView, UploadAttachmentInput}; use super::helpers::require_authenticated; pub async fn upload_attachment( svc: &IssueService, actor: Uuid, channel_id: Uuid, owner_type: OwnerType, owner_id: Uuid, input: UploadAttachmentInput, ) -> IssueResult { require_authenticated(actor)?; crate::features::issue::usecase::load_issue(svc, channel_id, owner_id).await?; let size = input.data.len() as i64; if size > svc.config.max_attachment_size_bytes { return Err(IssueError::BadRequest(format!( "attachment too large, max {} bytes", svc.config.max_attachment_size_bytes ))); } if input.filename.trim().is_empty() { return Err(IssueError::BadRequest("filename is required".into())); } let id = Uuid::new_v4(); let path = Path::from(format!( "issues/{channel_id}/{owner_id}/{owner_type:?}/{id}-{}", input.filename )); svc.storage .put(&path, Bytes::from(input.data)) .await .map_err(|e| IssueError::Internal(format!("storage put failed: {e}")))?; let db = svc.db.writer(); let now = OffsetDateTime::now_utc(); let active: att_entity::ActiveModel = att_entity::ActiveModel::builder() .set_id(id) .set_owner_type(owner_type) .set_owner_id(owner_id) .set_filename(input.filename) .set_content_type(input.content_type) .set_size(size) .set_url(path.to_string()) .set_uploaded_by(Some(actor)) .set_created_at(now) .into(); let saved = active.insert(&db).await?; Ok(AttachmentView::from(saved)) }