package storage import ( "strings" "time" "gardomatic.kleiax.de/internal/platform/validate" ) // MaxJournalAttachmentSize is the largest journal attachment accepted, in bytes. const MaxJournalAttachmentSize = 25 << 20 // JournalModelInterface persists journal entries and their attachments. type JournalModelInterface interface { Insert(entry JournalEntry) (JournalEntry, error) Get(gardenID, id int) (JournalEntry, error) GetAllForGarden(gardenID int, entryType JournalEntryType) ([]JournalEntry, error) Update(gardenID int, entry JournalEntry) (JournalEntry, error) Delete(gardenID, id int) error InsertAttachment(gardenID, entryID int, attachment JournalAttachment) (JournalAttachment, error) GetAttachment(gardenID, entryID, attachmentID int) (JournalAttachment, error) DeleteAttachment(gardenID, entryID, attachmentID int) error } // JournalEntryType distinguishes chronological journal entries from pinboard // notes while sharing the same persistence model. type JournalEntryType string // Supported journal entry types. const ( JournalEntryTypeJournal JournalEntryType = "journal" JournalEntryTypePinboard JournalEntryType = "pinboard" ) // JournalEntry is a garden note with optional tags and attachments. type JournalEntry struct { ID int `json:"id"` GardenID int `json:"garden_id"` AuthorID int `json:"author_id"` AuthorName string `json:"author_name"` AuthorColor string `json:"author_color"` EntryType JournalEntryType `json:"entry_type"` Title string `json:"title"` Body string `json:"body"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Version int `json:"version"` Tags []string `json:"tags,omitempty"` Attachments []JournalAttachment `json:"attachments,omitempty"` } // JournalAttachment contains either inline binary data or a reference to a // reusable image-library item. type JournalAttachment struct { ID int `json:"id"` EntryID int `json:"entry_id"` FileName string `json:"file_name"` MediaType string `json:"media_type"` Size int64 `json:"size"` CreatedAt time.Time `json:"created_at"` Data []byte `json:"-"` ImageID *int `json:"image_id,omitempty"` } // ValidateJournalEntry applies the journal-entry input rules. func ValidateJournalEntry(v *validate.Validator, entry JournalEntry) { entryType := entry.EntryType if entryType == "" { entryType = JournalEntryTypeJournal } v.Check(validate.PermittedValue(entryType, JournalEntryTypeJournal, JournalEntryTypePinboard), "entry_type", "must be journal or pinboard") if entryType == JournalEntryTypeJournal { v.Check(strings.TrimSpace(entry.Title) != "", "title", "must be provided") } v.Check(len(entry.Title) <= 500, "title", "must not be more than 500 bytes long") v.Check(len(entry.Body) <= 100_000, "body", "must not be more than 100000 bytes long") }