Web UI: paperclip button for file upload (images, PDFs, text), inline attachment cards with file icon/name/size, image thumbnails with fullscreen overlay, attachment display in thread panel. Threads: always-visible reply count badges on messages, clickable to open thread panel. reply_count and attachments enriched in all API responses via batch queries. MCP: attachments parameter on send_message tool, updated tool descriptions for threading and attachment workflow guidance. Backend: file type validation (allowlist), AttachmentLinker interface to avoid circular deps, GetReplyCounts batch query, EnrichMessages method on MessagingService. Admin CLI: synapbus attachments backup/restore with tar.gz archives, dedup-safe restore. 24 new test cases across 4 packages. All 24 test packages pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
64 lines
1.8 KiB
Go
64 lines
1.8 KiB
Go
package attachments
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// MaxFileSize is the maximum allowed upload size (50 MB).
|
|
const MaxFileSize = 50 * 1024 * 1024 // 50 MB
|
|
|
|
// Sentinel errors.
|
|
var (
|
|
ErrNotFound = errors.New("attachment not found")
|
|
ErrFileTooLarge = errors.New("file exceeds maximum size of 50MB")
|
|
ErrEmptyFile = errors.New("empty file not allowed")
|
|
ErrFileMissing = errors.New("attachment file missing from disk")
|
|
ErrUnsupportedType = errors.New("unsupported file type: only images (jpg, png, gif, webp, svg), PDFs, and text files are allowed")
|
|
)
|
|
|
|
// Attachment represents the metadata for a stored file.
|
|
type Attachment struct {
|
|
ID int64 `json:"id"`
|
|
Hash string `json:"hash"`
|
|
OriginalFilename string `json:"original_filename"`
|
|
Size int64 `json:"size"`
|
|
MIMEType string `json:"mime_type"`
|
|
MessageID *int64 `json:"message_id,omitempty"`
|
|
UploadedBy string `json:"uploaded_by"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// UploadRequest contains the parameters for uploading an attachment.
|
|
type UploadRequest struct {
|
|
Content io.Reader
|
|
Filename string
|
|
MIMEType string
|
|
MessageID *int64
|
|
UploadedBy string
|
|
}
|
|
|
|
// UploadResult contains the result of a successful upload.
|
|
type UploadResult struct {
|
|
Hash string `json:"hash"`
|
|
Size int64 `json:"size"`
|
|
MIMEType string `json:"mime_type"`
|
|
Filename string `json:"original_filename"`
|
|
}
|
|
|
|
// DownloadResult contains the result of a successful download.
|
|
type DownloadResult struct {
|
|
Content io.ReadCloser
|
|
Hash string
|
|
Filename string
|
|
MIMEType string
|
|
Size int64
|
|
}
|
|
|
|
// GCResult contains the result of a garbage collection run.
|
|
type GCResult struct {
|
|
FilesRemoved int `json:"files_removed"`
|
|
BytesReclaimed int64 `json:"bytes_reclaimed"`
|
|
}
|