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:
@@ -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{
|
||||
@@ -22,17 +23,14 @@ process that transforms static HTML into an editable CMS.`,
|
||||
}
|
||||
|
||||
var (
|
||||
outputDir string
|
||||
mockContent bool
|
||||
outputDir string
|
||||
)
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
32
insertr.yaml
32
insertr.yaml
@@ -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
|
||||
172
internal/content/database.go
Normal file
172
internal/content/database.go
Normal 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
|
||||
}
|
||||
10
justfile
10
justfile
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user