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
}