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")
}