// File Path: monorepo/cloud/maplepress-backend/internal/domain/page/page.go package page import ( "time" "github.com/gocql/gocql" ) // Page represents a WordPress page/post indexed in the system type Page struct { // Identity SiteID gocql.UUID `json:"site_id"` // Partition key PageID string `json:"page_id"` // Clustering key (WordPress page ID) TenantID gocql.UUID `json:"tenant_id"` // For additional isolation // Content Title string `json:"title"` Content string `json:"content"` // HTML stripped Excerpt string `json:"excerpt"` // Summary URL string `json:"url"` // Canonical URL // Metadata Status string `json:"status"` // publish, draft, trash PostType string `json:"post_type"` // page, post Author string `json:"author"` // Timestamps PublishedAt time.Time `json:"published_at"` ModifiedAt time.Time `json:"modified_at"` IndexedAt time.Time `json:"indexed_at"` // When we indexed it // Search MeilisearchDocID string `json:"meilisearch_doc_id"` // ID in Meilisearch index // Audit CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` // CWE-359: IP address tracking for GDPR compliance (90-day expiration) CreatedFromIPAddress string `json:"-"` // Encrypted IP address, never exposed in JSON CreatedFromIPTimestamp time.Time `json:"-"` // For 90-day expiration tracking ModifiedFromIPAddress string `json:"-"` // Encrypted IP address, never exposed in JSON ModifiedFromIPTimestamp time.Time `json:"-"` // For 90-day expiration tracking } // Status constants const ( StatusPublish = "publish" StatusDraft = "draft" StatusTrash = "trash" ) // PostType constants const ( PostTypePage = "page" PostTypePost = "post" ) // NewPage creates a new Page entity func NewPage(siteID, tenantID gocql.UUID, pageID string, title, content, excerpt, url, status, postType, author string, publishedAt, modifiedAt time.Time, encryptedIP string) *Page { now := time.Now() return &Page{ SiteID: siteID, PageID: pageID, TenantID: tenantID, Title: title, Content: content, Excerpt: excerpt, URL: url, Status: status, PostType: postType, Author: author, PublishedAt: publishedAt, ModifiedAt: modifiedAt, IndexedAt: now, MeilisearchDocID: "", // Set after indexing in Meilisearch CreatedAt: now, UpdatedAt: now, // CWE-359: Encrypted IP address tracking for GDPR compliance CreatedFromIPAddress: encryptedIP, CreatedFromIPTimestamp: now, ModifiedFromIPAddress: encryptedIP, ModifiedFromIPTimestamp: now, } } // IsPublished checks if the page is published func (p *Page) IsPublished() bool { return p.Status == StatusPublish } // ShouldIndex checks if the page should be indexed in search func (p *Page) ShouldIndex() bool { // Only index published pages return p.IsPublished() } // GetMeilisearchID returns the Meilisearch document ID func (p *Page) GetMeilisearchID() string { if p.MeilisearchDocID != "" { return p.MeilisearchDocID } // Use page_id as fallback return p.PageID } // SetMeilisearchID sets the Meilisearch document ID func (p *Page) SetMeilisearchID(docID string) { p.MeilisearchDocID = docID p.UpdatedAt = time.Now() } // MarkIndexed updates the indexed timestamp func (p *Page) MarkIndexed() { p.IndexedAt = time.Now() p.UpdatedAt = time.Now() } // Update updates the page content func (p *Page) Update(title, content, excerpt, url, status, author string, modifiedAt time.Time) { p.Title = title p.Content = content p.Excerpt = excerpt p.URL = url p.Status = status p.Author = author p.ModifiedAt = modifiedAt p.UpdatedAt = time.Now() }