config: unify configuration with multi-site support

- Remove mock_content setting (working database loop makes it unnecessary)
- Change server.dev_mode to global dev_mode setting for consistency
- Update CLI to use cli.site_id and cli.output for scoped configuration
- Implement database client for CLI enhance command (complete static site loop)
- Update justfile to use INSERTR_DATABASE_PATH environment variable
- Enable multi-site architecture: server is site-agnostic, CLI is site-specific
- Unified insertr.yaml now supports both server and CLI with minimal config
This commit is contained in:
2025-09-10 19:28:59 +02:00
parent 8a709a5250
commit c572428e45
6 changed files with 216 additions and 45 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/spf13/viper"
"github.com/insertr/insertr/internal/content"
"github.com/insertr/insertr/internal/db"
)
var enhanceCmd = &cobra.Command{
@@ -23,16 +24,13 @@ process that transforms static HTML into an editable CMS.`,
var (
outputDir string
mockContent bool
)
func init() {
enhanceCmd.Flags().StringVarP(&outputDir, "output", "o", "./dist", "Output directory for enhanced files")
enhanceCmd.Flags().BoolVar(&mockContent, "mock", true, "Use mock content for development")
// Bind flags to viper
viper.BindPFlag("build.output", enhanceCmd.Flags().Lookup("output"))
viper.BindPFlag("mock_content", enhanceCmd.Flags().Lookup("mock"))
viper.BindPFlag("cli.output", enhanceCmd.Flags().Lookup("output"))
}
func runEnhance(cmd *cobra.Command, args []string) {
@@ -47,21 +45,24 @@ func runEnhance(cmd *cobra.Command, args []string) {
dbPath := viper.GetString("database.path")
apiURL := viper.GetString("api.url")
apiKey := viper.GetString("api.key")
siteID := viper.GetString("site_id")
mockContent := viper.GetBool("mock_content")
siteID := viper.GetString("cli.site_id")
outputDir := viper.GetString("cli.output")
// Create content client
var client content.ContentClient
if mockContent || (apiURL == "" && dbPath == "") {
fmt.Printf("🧪 Using mock content for development\n")
client = content.NewMockClient()
} else if apiURL != "" {
if apiURL != "" {
fmt.Printf("🌐 Using content API: %s\n", apiURL)
client = content.NewHTTPClient(apiURL, apiKey)
} else {
} else if dbPath != "" {
fmt.Printf("🗄️ Using database: %s\n", dbPath)
// TODO: Implement database client for direct DB access
fmt.Printf("⚠️ Direct database access not yet implemented, using mock content\n")
database, err := db.NewDatabase(dbPath)
if err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}
defer database.Close()
client = content.NewDatabaseClient(database)
} else {
fmt.Printf("🧪 No database or API configured, using mock content\n")
client = content.NewMockClient()
}

View File

@@ -48,7 +48,7 @@ func init() {
viper.BindPFlag("database.path", rootCmd.PersistentFlags().Lookup("db"))
viper.BindPFlag("api.url", rootCmd.PersistentFlags().Lookup("api-url"))
viper.BindPFlag("api.key", rootCmd.PersistentFlags().Lookup("api-key"))
viper.BindPFlag("site_id", rootCmd.PersistentFlags().Lookup("site-id"))
viper.BindPFlag("cli.site_id", rootCmd.PersistentFlags().Lookup("site-id"))
rootCmd.AddCommand(enhanceCmd)
rootCmd.AddCommand(serveCmd)

View File

@@ -35,14 +35,14 @@ func init() {
// Bind flags to viper
viper.BindPFlag("server.port", serveCmd.Flags().Lookup("port"))
viper.BindPFlag("server.dev_mode", serveCmd.Flags().Lookup("dev-mode"))
viper.BindPFlag("dev_mode", serveCmd.Flags().Lookup("dev-mode"))
}
func runServe(cmd *cobra.Command, args []string) {
// Get configuration values
port := viper.GetInt("server.port")
dbPath := viper.GetString("database.path")
devMode := viper.GetBool("server.dev_mode")
devMode := viper.GetBool("dev_mode")
// Initialize database
database, err := db.NewDatabase(dbPath)

View File

@@ -1,25 +1,23 @@
# Insertr Configuration File
# This file provides default configuration for the unified insertr binary
# Insertr Unified Configuration
# Server and CLI configuration - library manages its own config
# Global settings
dev_mode: false # Development mode (affects server CORS, CLI verbosity)
# Database configuration
database:
path: "./insertr.db" # SQLite file path or PostgreSQL connection string
path: "./insertr.db" # SQLite file or PostgreSQL connection string
# API configuration (for remote content API)
api:
url: "" # Content API URL (leave empty to use local database)
key: "" # API authentication key
# Server configuration
# Server configuration (multi-site ready)
server:
port: 8080 # HTTP server port
dev_mode: false # Enable development mode features
port: 8080 # HTTP API server port
# Build configuration
build:
input: "./src" # Default input directory for enhancement
# CLI enhancement configuration
cli:
site_id: "demo" # Default site ID for CLI operations
output: "./dist" # Default output directory for enhanced files
# Global settings
site_id: "demo" # Default site ID for content lookup
mock_content: false # Use mock content instead of real data
# API client configuration (for CLI remote mode)
api:
url: "" # Content API URL (empty = use local database)
key: "" # API authentication key

View File

@@ -0,0 +1,172 @@
package content
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/insertr/insertr/internal/db"
"github.com/insertr/insertr/internal/db/postgresql"
"github.com/insertr/insertr/internal/db/sqlite"
)
// DatabaseClient implements ContentClient for direct database access
type DatabaseClient struct {
db *db.Database
}
// NewDatabaseClient creates a new database content client
func NewDatabaseClient(database *db.Database) *DatabaseClient {
return &DatabaseClient{
db: database,
}
}
// GetContent fetches a single content item by ID
func (d *DatabaseClient) GetContent(siteID, contentID string) (*ContentItem, error) {
ctx := context.Background()
var content interface{}
var err error
switch d.db.GetDBType() {
case "sqlite3":
content, err = d.db.GetSQLiteQueries().GetContent(ctx, sqlite.GetContentParams{
ID: contentID,
SiteID: siteID,
})
case "postgresql":
content, err = d.db.GetPostgreSQLQueries().GetContent(ctx, postgresql.GetContentParams{
ID: contentID,
SiteID: siteID,
})
default:
return nil, fmt.Errorf("unsupported database type: %s", d.db.GetDBType())
}
if err != nil {
if err == sql.ErrNoRows {
return nil, nil // Content not found, return nil without error
}
return nil, fmt.Errorf("database error: %w", err)
}
item := d.convertToContentItem(content)
return &item, nil
}
// GetBulkContent fetches multiple content items by IDs
func (d *DatabaseClient) GetBulkContent(siteID string, contentIDs []string) (map[string]ContentItem, error) {
if len(contentIDs) == 0 {
return make(map[string]ContentItem), nil
}
ctx := context.Background()
var dbContent interface{}
var err error
switch d.db.GetDBType() {
case "sqlite3":
dbContent, err = d.db.GetSQLiteQueries().GetBulkContent(ctx, sqlite.GetBulkContentParams{
SiteID: siteID,
Ids: contentIDs,
})
case "postgresql":
dbContent, err = d.db.GetPostgreSQLQueries().GetBulkContent(ctx, postgresql.GetBulkContentParams{
SiteID: siteID,
Ids: contentIDs,
})
default:
return nil, fmt.Errorf("unsupported database type: %s", d.db.GetDBType())
}
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
items := d.convertToContentItemList(dbContent)
// Convert slice to map for easy lookup
result := make(map[string]ContentItem)
for _, item := range items {
result[item.ID] = item
}
return result, nil
}
// GetAllContent fetches all content for a site
func (d *DatabaseClient) GetAllContent(siteID string) (map[string]ContentItem, error) {
ctx := context.Background()
var dbContent interface{}
var err error
switch d.db.GetDBType() {
case "sqlite3":
dbContent, err = d.db.GetSQLiteQueries().GetAllContent(ctx, siteID)
case "postgresql":
dbContent, err = d.db.GetPostgreSQLQueries().GetAllContent(ctx, siteID)
default:
return nil, fmt.Errorf("unsupported database type: %s", d.db.GetDBType())
}
if err != nil {
return nil, fmt.Errorf("database error: %w", err)
}
items := d.convertToContentItemList(dbContent)
// Convert slice to map for easy lookup
result := make(map[string]ContentItem)
for _, item := range items {
result[item.ID] = item
}
return result, nil
}
// convertToContentItem converts database models to content.ContentItem
func (d *DatabaseClient) convertToContentItem(content interface{}) ContentItem {
switch d.db.GetDBType() {
case "sqlite3":
c := content.(sqlite.Content)
return ContentItem{
ID: c.ID,
SiteID: c.SiteID,
Value: c.Value,
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
}
case "postgresql":
c := content.(postgresql.Content)
return ContentItem{
ID: c.ID,
SiteID: c.SiteID,
Value: c.Value,
Type: c.Type,
UpdatedAt: time.Unix(c.UpdatedAt, 0).Format(time.RFC3339),
}
}
return ContentItem{} // Should never happen
}
// convertToContentItemList converts database model lists to content.ContentItem slice
func (d *DatabaseClient) convertToContentItemList(contentList interface{}) []ContentItem {
switch d.db.GetDBType() {
case "sqlite3":
list := contentList.([]sqlite.Content)
items := make([]ContentItem, len(list))
for i, content := range list {
items[i] = d.convertToContentItem(content)
}
return items
case "postgresql":
list := contentList.([]postgresql.Content)
items := make([]ContentItem, len(list))
for i, content := range list {
items[i] = d.convertToContentItem(content)
}
return items
}
return []ContentItem{} // Should never happen
}

View File

@@ -34,7 +34,7 @@ dev: build-lib build
# Start API server with prefixed output
echo "🔌 Starting API server (localhost:8080)..."
./insertr serve --dev-mode --db ./dev.db 2>&1 | sed 's/^/🔌 [SERVER] /' &
INSERTR_DATABASE_PATH=./dev.db ./insertr serve --dev-mode 2>&1 | sed 's/^/🔌 [SERVER] /' &
SERVER_PID=$!
# Wait for server startup
@@ -70,7 +70,7 @@ demo-only:
dev-about: build-lib build
#!/usr/bin/env bash
echo "🚀 Starting full-stack development (about page)..."
./insertr serve --dev-mode --db ./dev.db &
INSERTR_DATABASE_PATH=./dev.db ./insertr serve --dev-mode &
SERVER_PID=$!
sleep 3
npx --prefer-offline live-server demo-site --port=3000 --host=localhost --open=/about.html
@@ -120,15 +120,15 @@ enhance input="demo-site" output="dist":
# Start content API server (default port 8080)
serve port="8080":
./insertr serve --port {{port}} --dev-mode --db ./dev.db
INSERTR_DATABASE_PATH=./dev.db ./insertr serve --port {{port}} --dev-mode
# Start API server in production mode
serve-prod port="8080" db="./insertr.db":
./insertr serve --port {{port}} --db {{db}}
INSERTR_DATABASE_PATH={{db}} ./insertr serve --port {{port}}
# Start API server with auto-restart on Go file changes
serve-dev port="8080":
find . -name "*.go" | entr -r ./insertr serve --port {{port}} --dev-mode --db ./dev.db
find . -name "*.go" | entr -r bash -c 'INSERTR_DATABASE_PATH=./dev.db ./insertr serve --port {{port}} --dev-mode'
# Check API server health
health port="8080":