readysite / website / models / page.go
4.5 KB
page.go
package models

import "github.com/readysite/readysite/pkg/database"

// PageContent status values
const (
	StatusDraft     = "draft"
	StatusPublished = "published"
)

// Page represents a page in the website.
// ID can be set to a URL-friendly slug before insert (e.g., "about", "contact").
// Content (Title, Description, HTML) is stored in PageContent for versioning.
// Published status is derived from PageContent versions (not stored on Page).
type Page struct {
	database.Model
	ParentID string // Parent page ID (empty for root pages)
	Position int    // Order within parent
}

// PageContent represents a versioned content snapshot for a page.
// Each edit creates a new PageContent record, preserving history.
type PageContent struct {
	database.Model
	PageID      string // Parent page ID
	Title       string // Page title at this version
	Description string // Meta description at this version
	HTML        string // Page content as HTML at this version
	CreatedBy   string // UserID who created this version
	Status      string // "draft" or "published" (empty defaults to draft)
}

// IsPublished returns true if the page has any published content version.
// This replaces the old Published boolean field with version-based status.
func (p *Page) IsPublished() bool {
	return p.HasPublishedContent()
}

// Status returns "published" if any version is published, otherwise "draft".
func (p *Page) Status() string {
	if p.HasPublishedContent() {
		return StatusPublished
	}
	return StatusDraft
}

// Parent returns the parent page, or nil if this is a root page.
func (p *Page) Parent() (*Page, error) {
	if p.ParentID == "" {
		return nil, nil
	}
	return Pages.Get(p.ParentID)
}

// Children returns child pages ordered by position.
func (p *Page) Children() ([]*Page, error) {
	return Pages.Search("WHERE ParentID = ? ORDER BY Position", p.ID)
}

// Siblings returns pages at the same level (same parent).
func (p *Page) Siblings() ([]*Page, error) {
	return Pages.Search("WHERE ParentID = ? AND ID != ? ORDER BY Position", p.ParentID, p.ID)
}

// Path returns the full URL path for this page.
// The homepage (ID "home") maps to "/" instead of "/home".
func (p *Page) Path() string {
	// Special case: homepage maps to root
	if p.ID == "home" && p.ParentID == "" {
		return "/"
	}
	if p.ParentID == "" {
		return "/" + p.ID
	}
	parent, _ := p.Parent()
	if parent == nil {
		return "/" + p.ID
	}
	return parent.Path() + "/" + p.ID
}

// LatestContent returns the most recent content version for this page.
func (p *Page) LatestContent() *PageContent {
	contents, err := PageContents.Search("WHERE PageID = ? ORDER BY CreatedAt DESC LIMIT 1", p.ID)
	if err != nil || len(contents) == 0 {
		return nil
	}
	return contents[0]
}

// Contents returns all content versions ordered by most recent first.
func (p *Page) Contents() ([]*PageContent, error) {
	return PageContents.Search("WHERE PageID = ? ORDER BY CreatedAt DESC", p.ID)
}

// PublishedContent returns the most recent published content version.
func (p *Page) PublishedContent() *PageContent {
	contents, err := PageContents.Search("WHERE PageID = ? AND Status = ? ORDER BY CreatedAt DESC LIMIT 1", p.ID, StatusPublished)
	if err != nil || len(contents) == 0 {
		return nil
	}
	return contents[0]
}

// HasPublishedContent returns true if any version is published.
func (p *Page) HasPublishedContent() bool {
	return PageContents.Count("WHERE PageID = ? AND Status = ?", p.ID, StatusPublished) > 0
}

// Title returns the title from the latest content version.
func (p *Page) Title() string {
	if c := p.LatestContent(); c != nil {
		return c.Title
	}
	return ""
}

// Description returns the description from the latest content version.
func (p *Page) Description() string {
	if c := p.LatestContent(); c != nil {
		return c.Description
	}
	return ""
}

// HTML returns the HTML from the latest content version.
func (p *Page) HTML() string {
	if c := p.LatestContent(); c != nil {
		return c.HTML
	}
	return ""
}

// Page returns the parent page for this content.
func (pc *PageContent) Page() (*Page, error) {
	return Pages.Get(pc.PageID)
}

// Creator returns the user who created this version.
func (pc *PageContent) Creator() (*User, error) {
	if pc.CreatedBy == "" {
		return nil, nil
	}
	return Users.Get(pc.CreatedBy)
}

// IsPublished returns true if this content version is published.
func (pc *PageContent) IsPublished() bool {
	return pc.Status == StatusPublished
}

// IsDraft returns true if this content version is a draft.
func (pc *PageContent) IsDraft() bool {
	return pc.Status != StatusPublished
}
← Back