Files
xarmian 6461aafd16 feat(store): attachments table + Attachment model (TASK-869) (#286)
Adds the schema groundwork for inline images and file uploads — see
DOC-865 (Attachments — architecture & migration design).

- migrations/047_attachments.sql — SQLite migration. Table + 4 indexes
  (workspace, item, hash, parent). Partial indexes on workspace/item/parent
  match the items table convention. The hash index is full (not partial)
  so dedupe can resurrect a soft-deleted blob if the same bytes are
  re-uploaded without writing a duplicate.
- pgmigrations/026_attachments.sql — Postgres mirror with BIGINT for
  size_bytes; same partial-index pattern.
- internal/models/attachment.go — Go model with all columns. Uses
  pointer types for nullable columns (item_id, width, height, parent_id,
  variant, deleted_at) so JSON omitempty works correctly.

No call sites yet — purely schema groundwork. Verified the migration
runs cleanly on a fresh install and on the live dev DB.

Parent: PLAN-866.
2026-04-29 11:34:35 -04:00

41 lines
1.6 KiB
Go

package models
import "time"
// Attachment models a single uploaded blob in the attachments table.
//
// See [[Attachments — architecture & migration design]] (DOC-865). Storage is
// content-addressed: StorageKey is "<backend>:<sha256>" so the driver registry
// resolves the prefix to a concrete AttachmentStore. Item content references
// attachments by UUID via "pad-attachment:<id>" — never by URL — so a backend
// migration (FS → S3) leaves item content untouched.
//
// Derived blobs (thumbnails) are rows of their own with ParentID pointing at
// the original and Variant set to one of "thumb-sm" / "thumb-md". They count
// against the workspace storage quota — they are real bytes on disk.
type Attachment struct {
ID string `json:"id"`
WorkspaceID string `json:"workspace_id"`
ItemID *string `json:"item_id,omitempty"` // nil = orphan, eligible for GC
UploadedBy string `json:"uploaded_by"`
StorageKey string `json:"storage_key"` // "<backend>:<hash>"
ContentHash string `json:"content_hash"`
MimeType string `json:"mime_type"`
SizeBytes int64 `json:"size_bytes"`
Filename string `json:"filename"`
Width *int `json:"width,omitempty"` // images only
Height *int `json:"height,omitempty"` // images only
ParentID *string `json:"parent_id,omitempty"`
Variant *string `json:"variant,omitempty"` // "original" | "thumb-sm" | "thumb-md"
CreatedAt time.Time `json:"created_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
// Attachment variants.
const (
AttachmentVariantOriginal = "original"
AttachmentVariantThumbSm = "thumb-sm"
AttachmentVariantThumbMd = "thumb-md"
)