readysite / website / CLAUDE.md
10.6 KB
CLAUDE.md

CLAUDE.md

This file provides guidance to Claude Code when working with the Website CMS.

Project Overview

Website CMS is an AI-native content management system that combines:

  • WordPress: Anyone can create dynamic websites
  • PocketBase: Flexible data management (collections, documents, ACL)
  • AI-First: Conversation-driven site building and content editing

Single-site solution (like WordPress/PocketBase) - no multi-tenancy complexity.

Target Audience

Anyone who wants to build and ship software. With AI making creation accessible to everyone, the remaining barrier is DevOps — ReadySite removes it. Current users should be comfortable with HTML/CSS and API key configuration, but the goal is to make this accessible to non-technical users too (WYSIWYG editor on roadmap).

Key Decisions

  1. AI Configuration: OAuth for OpenAI/Anthropic is NOT viable (neither offers it). Continue with API key approach.
  2. Versioning: Replacing boolean Published with version-based system (draft/published per PageContent).
  3. Visual Editor: Adding WYSIWYG "Editor" tab using Tiptap/React alongside existing Code tab.
  4. Terminology: Keep "Collections" (PocketBase-inspired), "URL Slug" as-is.

Architecture

website/
├── CLAUDE.md              # This file
├── main.go                # Application entry point
├── models/                # Pure data types + relationship methods
│   ├── db.go              # Database setup + collection variables
│   ├── user.go            # User
│   ├── page.go            # Page (HTML content)
│   ├── collection.go      # Collection (dynamic schemas)
│   ├── document.go        # Document (collection records)
│   ├── conversation.go    # Conversation (AI chat)
│   ├── message.go         # Message
│   ├── mutation.go        # Mutation (undo support)
│   └── acl.go             # ACLRule
├── internal/              # Business logic (imports models)
│   ├── access/            # ACL, JWT tokens, password hashing, rate limiting
│   ├── api/               # API filter parsing + OpenAPI documentation
│   ├── assist/            # AI helpers, tool calls, tool definitions + executor
│   ├── audit/             # Audit logging
│   ├── content/           # Consolidated: page tree, path finder, slug validation, events,
│   │                      # template functions, rule expressions, schema parsing/validation
│   ├── helpers/           # Utilities (pagination, etc.)
│   ├── seed/              # Database seeding
│   └── views/             # View collection query execution
├── controllers/
└── views/

Package Roles

Package Role Imports
models Pure data types, relationship traversal pkg/database only
internal/* Constants, business logic, JSON parsing models
controllers HTTP handlers, queries models, internal/*

Key rule: Models never import internal packages. Internal packages import models.

Models API

Types only have instance methods for relationship traversal:

// Page traversal
page.Parent()    // *Page - parent page
page.Children()  // []*Page - child pages
page.Siblings()  // []*Page - same-level pages
page.Path()      // string - full URL path

// Collection → Documents
collection.Documents()     // []*Document
collection.DocumentCount() // int

// Conversation → Messages → Mutations
conversation.User()         // *User
conversation.Messages()     // []*Message
conversation.LastMessage()  // *Message
message.Conversation()      // *Conversation
message.Mutations()         // []*Mutation
mutation.Message()          // *Message

// Document data access (for templates)
doc.GetString("title")
doc.GetInt("price")
doc.GetBool("published")
doc.GetTime("createdAt")
doc.GetStrings("tags")
doc.Set("field", value)
doc.SetAll(data)

Data Model

Model Fields Purpose
User Email, PasswordHash, Name, Role, Verified Authentication
Page ParentID, Title, Description, HTML, Position, Published Website pages
Collection Name, Schema, Rules, System Dynamic data schemas
Document CollectionID, Data Collection records
Conversation UserID, Title, Context AI chat sessions
Message ConversationID, Role, Content, ToolCalls Chat messages
Mutation MessageID, Action, EntityType, Before/AfterState Undo support
ACLRule SubjectType, SubjectID, ResourceType, Permission Access control

Internal Packages

internal/access

// Constants
access.RoleAdmin, access.RoleUser, access.RoleViewer
access.SubjectUser, access.SubjectRole, access.SubjectPublic
access.PermRead, access.PermWrite, access.PermDelete, access.PermAdmin
access.JWTExpiration  // 30 days

// ACL Functions
access.IsAdmin(user) bool
access.Grants(have, want string) bool
access.CheckAccess(user, resourceType, resourceID, permission) bool

// Authentication Functions (JWT, sessions, passwords)
access.SetSessionCookie(w, r, userID) error
access.ClearSessionCookie(w, r)
access.GetUserFromJWT(r) *User
access.HashPassword(password) (string, error)
access.CheckPassword(password, hash) bool

// Auth Collection Functions (API token auth)
access.CreateAuthToken(recordID, collectionID, tokenKey) (string, error)
access.ParseAuthToken(tokenString) (recordID, collectionID, tokenKey, error)
access.GetAuthRecordFromRequest(r) (recordID, collectionID)
access.GenerateTokenKey() string

// Rate Limiting
access.AuthLimiter, access.UploadLimiter, access.ChatLimiter, access.APILimiter
access.RateLimitAuth(handler) http.Handler
access.RateLimitUpload(handler) http.Handler
access.RateLimitChat(handler) http.Handler
access.ResourceLimiter  // Configurable per-collection rate limiter
access.ParseRateLimitConfig(json) *RateLimitConfig
access.SetRateLimitHeaders(w, result)

internal/content

// Schema field types
content.Text, content.Number, content.Bool, content.Date, ...

// Schema types
content.Field{Name, Type, Required, Unique, Options}

// Schema functions
content.GetFields(collection) ([]Field, error)
content.SetFields(collection, fields) error
content.GetField(collection, name) (*Field, error)

// Schema validation
content.ValidateDocument(collection, data) error
content.ValidateDocumentJSON(collection, jsonStr) error

// Template functions for pages
content.Documents(collectionID, filter...)  // Get documents
content.Document(id)                        // Get single document
content.Collection(id)                      // Get collection
content.Page(id)                            // Get page
content.Pages(parentID...)                  // Get pages
content.PublishedPages(parentID...)         // Get published pages
content.Partial(id)                         // Get partial
content.Partials()                          // Get all partials
content.SiteName()                          // Site name setting
content.SiteDescription()                   // Site description
content.AllFuncs()                          // All template functions

// Rule expression evaluation
content.RuleContext{User, Record, OldRecord, Request}
content.EvaluateRule(expr, ctx) (bool, error)

// Page tree building
content.TreeNode{Page, Children}
content.BuildTree() []*TreeNode

// Page path lookup
content.FindByPath(path) *Page

// Slug validation
content.ValidateSlug(slug) error
content.CheckSlugAvailable(slug, resourceType) error

// Collection events (real-time pub/sub)
content.CollectionEvents  // Global event bus
content.EventCreate, content.EventUpdate, content.EventDelete
bus.Subscribe(collectionID) *Subscriber
bus.Unsubscribe(sub)
bus.PublishCreate(collectionID, recordID, record)
bus.PublishUpdate(collectionID, recordID, record)
bus.PublishDelete(collectionID, recordID)

internal/assist

// Constants
assist.RoleUser, assist.RoleAssistant, assist.RoleSystem
assist.ActionCreate, assist.ActionUpdate, assist.ActionDelete

// Types
assist.ToolCall{ID, Name, Arguments, Result, Error}
assist.ConversationContext{CurrentPageID, ...}

// Functions
assist.GetToolCalls(message) ([]ToolCall, error)
assist.SetToolCalls(message, calls) error
assist.GetContext(conversation) (*ConversationContext, error)
assist.SetContext(conversation, ctx) error
assist.CanUndo(mutation), assist.CanRedo(mutation) bool

// Tool definitions and executor
assist.Executor - executes AI tool calls
assist.NewExecutor(ctx, userID, conversationID) *Executor
executor.Execute(toolCalls) []ToolResult
assist.ToolDefinitions() []openai.Tool  // Available tools for AI

ID as Slug

All models use ID as both primary key and URL slug:

page := &models.Page{Title: "About"}
page.ID = "about"  // Set before insert
models.Pages.Insert(page)

// Access via ID
page, _ := models.Pages.Get("about")
page.Path() // "/about"

Commands

go run ./website           # Run (in-memory database)
DB_PATH=/data/website.db go run ./website  # Run with local file database
go build ./website         # Build
go test ./website/...      # Test
go doc ./website/models    # View API

Environment Variables

Variable Description
DB_PATH Local SQLite file path (e.g., /data/website.db)
DB_URL Remote libSQL server URL (e.g., libsql://mydb.turso.io)
DB_TOKEN Authentication token for remote libSQL server
AUTH_SECRET JWT signing secret (required for authentication)
ENV Set to production to disable development features

Database Selection: Uses engines.NewAuto() which selects based on environment:

  • DB_URL + DB_TOKEN → Remote replica (syncs with Turso/libSQL primary)
  • DB_PATH only → Local file database
  • Neither → In-memory database (data lost on restart)

Known Limitations

Template Functions Don't Have Request Access

Template functions in internal/content/ don't receive *http.Request. Query params must be passed through template context in site.go.

Controllers

The website has 14 controllers:

Controller Purpose
acl Access control list management
api REST API endpoints
audit Audit log viewing
auth Authentication (login, signup, password reset)
chat AI conversation interface
collections Dynamic collection management
dashboard Admin dashboard
files File upload and management
health Health check endpoint
pages Page CRUD operations
partials Reusable template partials
setup Initial setup wizard
site Public site rendering
workspace User workspace management
← Back