uploads: centralise via ChatHubApi::save_upload, refactor handlers
Nightly Build / build (push) Successful in 6m41s

Extract shared upload seam in skald-core, move Telegram and web
handlers to use it. Simplify media attachment routing. Clean up
unused deps and dead code.
This commit is contained in:
2026-07-22 19:19:29 +01:00
parent e70c4a90f3
commit 6f0461f7f5
15 changed files with 350 additions and 214 deletions
+15 -1
View File
@@ -5,7 +5,7 @@ use tokio::sync::broadcast;
use crate::events::GlobalEvent;
use crate::interface_tool::InterfaceTool;
use crate::message_meta::MessageMetadata;
use crate::message_meta::{Attachment, MessageMetadata};
// ── SendMessageOptions ────────────────────────────────────────────────────────
@@ -59,6 +59,20 @@ pub trait ChatHubApi: Send + Sync {
opts: SendMessageOptions,
) -> anyhow::Result<()>;
/// Persist an uploaded file for `source_id` into the owner's
/// `~/uploads/{session}/` and return its [`Attachment`] (home-relative agent
/// path). Channel adapters (e.g. the Telegram plugin) call this instead of
/// writing files themselves, so the core owns *where* uploads land and every
/// surface produces a path the agent can actually reach. The recognized
/// magic-byte MIME wins over the caller-claimed `client_mime`.
async fn save_upload(
&self,
source_id: &str,
file_name: &str,
client_mime: Option<String>,
bytes: &[u8],
) -> anyhow::Result<Attachment>;
/// Create a new session for the source, discarding the previous one.
async fn clear(&self, source_id: &str) -> anyhow::Result<i64>;
+4 -3
View File
@@ -12,9 +12,10 @@
use serde::{Deserialize, Serialize};
/// One file attached by the user to a message. `path` is relative to the project
/// root (e.g. `data/uploads/123/file.pdf`) so it is both servable under `/data/…`
/// and resolvable by the filesystem tools.
/// One file attached by the user to a message. `path` is a home-relative agent
/// path (e.g. `uploads/123/file.pdf`) — the caller's container home is its root,
/// so the fs-tools, `execute_cmd`, the file viewer (`/api/file`) and the media
/// inliner all resolve it to the same physical file.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attachment {
pub path: String,
+6
View File
@@ -22,6 +22,12 @@
use std::path::{Component, Path, PathBuf};
use std::sync::{Arc, RwLock};
/// The subdirectory of a user's home where chat uploads are saved
/// (`{home}/uploads/{session_id}/…`, reachable by the agent as `uploads/…`).
/// Shared by the upload handler (write path) and the media inliner (containment
/// root) so the two anchors can never drift.
pub const UPLOADS_SUBDIR: &str = "uploads";
/// One shared folder mounted into a user's container.
#[derive(Debug, Clone)]
pub struct SharedMount {