monorepo/cloud/maplepress-backend/internal/domain/page/page.go

132 lines
3.7 KiB
Go

// 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()
}