package models import ( "time" ) // User represents the single admin user type User struct { ID string `json:"id"` Username string `json:"username"` PasswordHash string `json:"-"` Preferences map[string]interface{} `json:"preferences,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // SearchHistoryItem tracks user search history for autocomplete type SearchHistoryItem struct { Keyword string `json:"keyword"` LastSearchedAt time.Time `json:"last_searched_at"` SearchCount int `json:"search_count"` } // Category represents a folder/tag grouping for feeds type Category struct { ID string `json:"id"` Title string `json:"title"` ParentID *string `json:"parent_id,omitempty"` SortOrder int `json:"sort_order"` FeedCount int `json:"feed_count,omitempty"` CreatedAt time.Time `json:"created_at"` } // Feed represents a subscribed RSS/Atom/JSON feed type Feed struct { ID string `json:"id"` Title string `json:"title"` FeedURL string `json:"feed_url"` SiteURL string `json:"site_url"` IconURL string `json:"icon_url,omitempty"` CategoryID *string `json:"category_id,omitempty"` CategoryTitle string `json:"category_title,omitempty"` IsVirtualDigest bool `json:"is_virtual_digest"` RefreshIntervalMinutes int `json:"refresh_interval_minutes"` TimeoutSeconds int `json:"timeout_seconds"` ETag string `json:"etag,omitempty"` LastModified string `json:"last_modified,omitempty"` LastFetchedAt *time.Time `json:"last_fetched_at,omitempty"` FetchError string `json:"fetch_error,omitempty"` UnreadCount int `json:"unread_count"` TotalCount int `json:"total_count"` CreatedAt time.Time `json:"created_at"` } // FeedEntry represents an individual article or digest entry type FeedEntry struct { ID string `json:"id"` FeedID string `json:"feed_id"` FeedTitle string `json:"feed_title"` FeedSiteURL string `json:"feed_site_url,omitempty"` GUID string `json:"guid"` URL string `json:"url"` Title string `json:"title"` Author string `json:"author,omitempty"` Content string `json:"content"` // Immutable original RSS content RawContent string `json:"raw_content,omitempty"` PublishedAt time.Time `json:"published_at"` IsRead bool `json:"is_read"` IsStarred bool `json:"is_starred"` ShareUUID *string `json:"share_uuid"` Variants map[string]EntryVariant `json:"variants,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at"` } // EntryVariant represents an extensible enrichment result (readability, keywords, ai_summary, tts) type EntryVariant struct { EntryID string `json:"entry_id"` VariantKey string `json:"variant_key"` ContentType string `json:"content_type"` // 'text', 'html', 'json', 'yaml', 'audio/mp3', 'binary' TextValue string `json:"text_value,omitempty"` BlobPath string `json:"blob_path,omitempty"` StreamURL string `json:"stream_url,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // Rule represents a filter/automation hook rule type Rule struct { ID string `json:"id"` Name string `json:"name"` Trigger string `json:"trigger"` // 'on_post_created', 'on_post_updated', 'on_variant_updated', 'manual' Conditions []RuleCondition `json:"conditions"` MatchMode string `json:"match_mode"` // 'all' (AND) or 'any' (OR) Actions []RuleAction `json:"actions"` IsActive bool `json:"is_active"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // RuleCondition represents a single condition predicate type RuleCondition struct { Field string `json:"field"` // 'feed_id', 'title', 'author', 'variants.keywords', 'variants.readability_full_text' Operator string `json:"operator"` // 'eq', 'neq', 'in', 'contains', 'matches' Value interface{} `json:"value"` } // RuleAction represents an action triggered by a rule type RuleAction struct { Type string `json:"type"` // 'readability_scrape', 'mark_read', 'mark_starred', 'extract_keywords', 'ai_summary', 'tts' Params map[string]interface{} `json:"params,omitempty"` } // Stats represents dashboard aggregate counts type Stats struct { TotalFeeds int `json:"total_feeds"` TotalUnread int `json:"total_unread"` TotalStarred int `json:"total_starred"` TotalShared int `json:"total_shared"` TotalCategories int `json:"total_categories"` TotalRules int `json:"total_rules"` } // LoginRequest is the body for POST /api/auth/login type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` } // LoginResponse is the response returned on successful auth type LoginResponse struct { Token string `json:"token"` User User `json:"user"` } // CreateFeedRequest is the body for POST /api/feeds type CreateFeedRequest struct { FeedURL string `json:"feed_url"` CategoryID *string `json:"category_id,omitempty"` Title *string `json:"title,omitempty"` IconURL *string `json:"icon_url,omitempty"` RefreshIntervalMinutes *int `json:"refresh_interval_minutes,omitempty"` TimeoutSeconds *int `json:"timeout_seconds,omitempty"` } // UpdateFeedRequest is the body for PUT /api/feeds/{id} type UpdateFeedRequest struct { Title *string `json:"title,omitempty"` CategoryID *string `json:"category_id,omitempty"` IconURL *string `json:"icon_url,omitempty"` RefreshIntervalMinutes *int `json:"refresh_interval_minutes,omitempty"` TimeoutSeconds *int `json:"timeout_seconds,omitempty"` } // UpdateEntryReadRequest type UpdateEntryReadRequest struct { IsRead bool `json:"is_read"` } // UpdateEntryStarRequest type UpdateEntryStarRequest struct { IsStarred bool `json:"is_starred"` } // MarkAllReadRequest type MarkAllReadRequest struct { FeedID *string `json:"feed_id,omitempty"` CategoryID *string `json:"category_id,omitempty"` Before *string `json:"before,omitempty"` } // ChangePasswordRequest is the body for PUT /api/auth/password type ChangePasswordRequest struct { OldPassword string `json:"old_password"` NewPassword string `json:"new_password"` } // Job represents a background task in the persistent job queue type Job struct { ID string `json:"id"` Type string `json:"type"` Payload map[string]interface{} `json:"payload"` Status string `json:"status"` // 'pending', 'running', 'completed', 'failed', 'cancelled' Progress int `json:"progress"` Result map[string]interface{} `json:"result,omitempty"` ErrorMessage string `json:"error_message,omitempty"` Attempts int `json:"attempts"` MaxAttempts int `json:"max_attempts"` IdempotencyKey string `json:"idempotency_key,omitempty"` RunAt time.Time `json:"run_at"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // JobFilter is used to filter job listings type JobFilter struct { Type string Status string Limit int Offset int } // PatchEntryRequest is the body for PATCH /api/entries/{id} type PatchEntryRequest struct { IsRead *bool `json:"is_read,omitempty"` IsStarred *bool `json:"is_starred,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` Variants map[string]*EntryVariant `json:"variants,omitempty"` } // EntryDiff represents the delta between OldState and NewState of an Entry type EntryDiff struct { FieldsChanged []string `json:"fields_changed"` // e.g. ["is_read", "is_starred", "metadata.tags"] VariantsAdded []string `json:"variants_added"` // e.g. ["readability_full_text"] VariantsChanged []string `json:"variants_changed"` // e.g. ["ai_summary"] VariantsRemoved []string `json:"variants_removed"` } // HasVariantAddition checks if a specific variant key was newly added in this transition func (d EntryDiff) HasVariantAddition(variantKey string) bool { for _, k := range d.VariantsAdded { if k == variantKey { return true } } return false } // HasVariantChange checks if a specific variant key was modified in this transition func (d EntryDiff) HasVariantChange(variantKey string) bool { for _, k := range d.VariantsChanged { if k == variantKey { return true } } return false } // HasFieldChange checks if a specific top-level or metadata field changed func (d EntryDiff) HasFieldChange(fieldName string) bool { for _, f := range d.FieldsChanged { if f == fieldName { return true } } return false } // PromptTemplate represents an AI prompt template type PromptTemplate struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description,omitempty"` Prompt string `json:"prompt"` IsDefault bool `json:"is_default,omitempty"` } // AISettings represents AI provider configuration type AISettings struct { Enabled bool `json:"enabled"` Provider string `json:"provider"` BaseURL string `json:"base_url"` APIKey string `json:"api_key"` Model string `json:"model"` Temperature float64 `json:"temperature"` MaxTokens int `json:"max_tokens"` Prompts []PromptTemplate `json:"prompts"` }