readysite / website / internal / content / seed / seed.go
5.2 KB
seed.go
// Package seed provides initial data seeding for the website.
package seed

import (
	"crypto/rand"
	"embed"
	"encoding/hex"
	"log"
	"os"
	"strings"

	"github.com/readysite/readysite/pkg/database"
	"github.com/readysite/readysite/website/internal/access"
	"github.com/readysite/readysite/website/internal/helpers"
	"github.com/readysite/readysite/website/models"
)

//go:embed templates/*.html
var templates embed.FS

// All runs all seeding functions and migrations.
// Seeding only happens once - tracked via the seed_complete setting.
func All() {
	// Run migrations first (always)
	Migrations()

	// Check if seeding has already been completed
	if helpers.GetSetting(models.SettingSeedComplete) == "true" {
		log.Println("seed: skipping (already seeded)")
		return
	}

	log.Println("seed: starting initial database seeding...")

	// Seed default data
	Partials() // Partials first so pages can use them
	DefaultPages()
	Collections()
	BlogPages()
	Files()
	Notes() // AI context notes

	// Mark seeding as complete so it won't run again
	helpers.SetSetting(models.SettingSeedComplete, "true")

	log.Println("seed: initial database seeding complete")

	// Auto-setup from environment variables (if provided by hosting)
	AutoSetup()
}

// AutoSetup creates the admin user and site settings from environment variables.
// This allows the hosting platform to pre-configure a site, skipping the setup wizard.
// If ADMIN_EMAIL is not set or setup is already complete, this is a no-op.
func AutoSetup() {
	if helpers.GetSetting(models.SettingSetupComplete) == "true" {
		return
	}

	email := os.Getenv("ADMIN_EMAIL")
	if email == "" {
		return
	}

	log.Println("seed: auto-setup from environment variables...")

	// Derive name from ADMIN_NAME or email prefix
	name := os.Getenv("ADMIN_NAME")
	if name == "" {
		name = strings.SplitN(email, "@", 2)[0]
	}

	// Generate a random password (user accesses via hosting panel, not password)
	randomBytes := make([]byte, 32)
	rand.Read(randomBytes)
	password := hex.EncodeToString(randomBytes)

	hash, err := access.HashPassword(password)
	if err != nil {
		log.Printf("seed: auto-setup failed to hash password: %v", err)
		return
	}

	// Create admin user
	user := &models.User{
		Email:        email,
		PasswordHash: hash,
		Name:         name,
		Role:         access.RoleAdmin,
		Verified:     true,
	}
	if _, err := models.Users.Insert(user); err != nil {
		log.Printf("seed: auto-setup failed to create admin user: %v", err)
		return
	}

	// Set site settings
	if siteName := os.Getenv("SITE_NAME"); siteName != "" {
		helpers.SetSetting(models.SettingSiteName, siteName)
	}
	if siteDesc := os.Getenv("SITE_DESC"); siteDesc != "" {
		helpers.SetSetting(models.SettingSiteDescription, siteDesc)
	}

	// Mark setup as complete to skip the wizard
	helpers.SetSetting(models.SettingSetupComplete, "true")

	log.Printf("seed: auto-setup complete (admin: %s)", email)
}

// BlogPages seeds the blog example pages if they don't exist.
// This creates: /blog, /blog-view, /blog-edit, /blog-new
func BlogPages() {
	// Check if blog page already exists
	if _, err := models.Pages.Get("blog"); err == nil {
		return // Already seeded
	}

	log.Println("Seeding blog example pages...")

	// Blog listing page
	blogHTML, err := templates.ReadFile("templates/blog.html")
	if err != nil {
		log.Printf("seed: failed to read blog.html: %v", err)
		return
	}
	blogPage := &models.Page{
		Model:    database.Model{ID: "blog"},
		Position: 10,
	}
	if _, err := models.Pages.Insert(blogPage); err != nil {
		log.Printf("seed: failed to insert blog page: %v", err)
		return
	}
	savePageContent(blogPage, "Blog", "Latest posts and updates", string(blogHTML), "", models.StatusPublished)

	// Blog view page
	viewHTML, err := templates.ReadFile("templates/blog-view.html")
	if err != nil {
		log.Printf("seed: failed to read blog-view.html: %v", err)
		return
	}
	viewPage := &models.Page{
		Model:    database.Model{ID: "blog-view"},
		Position: 11,
	}
	if _, err := models.Pages.Insert(viewPage); err != nil {
		log.Printf("seed: failed to insert blog-view page: %v", err)
		return
	}
	savePageContent(viewPage, "View Post", "Read blog post", string(viewHTML), "", models.StatusPublished)

	// Blog edit page
	editHTML, err := templates.ReadFile("templates/blog-edit.html")
	if err != nil {
		log.Printf("seed: failed to read blog-edit.html: %v", err)
		return
	}
	editPage := &models.Page{
		Model:    database.Model{ID: "blog-edit"},
		Position: 12,
	}
	if _, err := models.Pages.Insert(editPage); err != nil {
		log.Printf("seed: failed to insert blog-edit page: %v", err)
		return
	}
	savePageContent(editPage, "Edit Post", "Edit blog post", string(editHTML), "", models.StatusPublished)

	// Blog new page
	newHTML, err := templates.ReadFile("templates/blog-new.html")
	if err != nil {
		log.Printf("seed: failed to read blog-new.html: %v", err)
		return
	}
	newPage := &models.Page{
		Model:    database.Model{ID: "blog-new"},
		Position: 13,
	}
	if _, err := models.Pages.Insert(newPage); err != nil {
		log.Printf("seed: failed to insert blog-new page: %v", err)
		return
	}
	savePageContent(newPage, "New Post", "Create a new blog post", string(newHTML), "", models.StatusPublished)

	log.Println("Blog example pages seeded successfully")
}
← Back