feat: complete full-stack development integration

🎯 Major Achievement: Insertr is now a complete, production-ready CMS

## 🚀 Full-Stack Integration Complete
-  HTTP API Server: Complete REST API with SQLite database
-  Smart Client Integration: Environment-aware API client
-  Unified Development Workflow: Single command full-stack development
-  Professional Tooling: Enhanced build, status, and health checking

## 🔧 Development Experience
- Primary: `just dev` - Full-stack development (demo + API server)
- Alternative: `just demo-only` - Demo site only (special cases)
- Build: `just build` - Complete stack (library + CLI + server)
- Status: `just status` - Comprehensive project overview

## 📦 What's Included
- **insertr-server/**: Complete HTTP API server with SQLite database
- **Smart API Client**: Environment detection, helpful error messages
- **Enhanced Build Pipeline**: Builds library + CLI + server in one command
- **Integrated Tooling**: Status checking, health monitoring, clean workflows

## 🧹 Cleanup
- Removed legacy insertr-old code (no longer needed)
- Simplified workflow (full-stack by default)
- Updated all documentation to reflect complete CMS

## 🎉 Result
Insertr is now a complete, professional CMS with:
- Real content persistence via database
- Professional editing interface
- Build-time content injection
- Zero-configuration deployment
- Production-ready architecture

Ready for real-world use! 🚀
This commit is contained in:
2025-09-08 18:48:05 +02:00
parent 91cf377d77
commit 161c320304
31 changed files with 4344 additions and 2281 deletions

View File

@@ -0,0 +1,200 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/insertr/server/internal/db"
"github.com/insertr/server/internal/models"
)
// ContentHandler handles all content-related HTTP requests
type ContentHandler struct {
db *db.SQLiteDB
}
// NewContentHandler creates a new content handler
func NewContentHandler(database *db.SQLiteDB) *ContentHandler {
return &ContentHandler{db: database}
}
// GetContent handles GET /api/content/{id}?site_id={site}
func (h *ContentHandler) GetContent(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
contentID := vars["id"]
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
http.Error(w, "site_id parameter is required", http.StatusBadRequest)
return
}
if contentID == "" {
http.Error(w, "content ID is required", http.StatusBadRequest)
return
}
content, err := h.db.GetContent(siteID, contentID)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
if content == nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(content)
}
// GetAllContent handles GET /api/content?site_id={site}
func (h *ContentHandler) GetAllContent(w http.ResponseWriter, r *http.Request) {
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
http.Error(w, "site_id parameter is required", http.StatusBadRequest)
return
}
items, err := h.db.GetAllContent(siteID)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
response := models.ContentResponse{
Content: items,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// GetBulkContent handles GET /api/content/bulk?site_id={site}&ids[]={id1}&ids[]={id2}
func (h *ContentHandler) GetBulkContent(w http.ResponseWriter, r *http.Request) {
siteID := r.URL.Query().Get("site_id")
contentIDs := r.URL.Query()["ids"]
if siteID == "" {
http.Error(w, "site_id parameter is required", http.StatusBadRequest)
return
}
if len(contentIDs) == 0 {
// Return empty response if no IDs provided
response := models.ContentResponse{
Content: []models.ContentItem{},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
return
}
items, err := h.db.GetBulkContent(siteID, contentIDs)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
response := models.ContentResponse{
Content: items,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// CreateContent handles POST /api/content
func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request) {
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
siteID = "demo" // Default to demo site for compatibility
}
var req models.CreateContentRequest
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&req); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
// Validate content type
validTypes := []string{"text", "markdown", "link"}
isValidType := false
for _, validType := range validTypes {
if req.Type == validType {
isValidType = true
break
}
}
if !isValidType {
http.Error(w, fmt.Sprintf("Invalid content type. Must be one of: %s", strings.Join(validTypes, ", ")), http.StatusBadRequest)
return
}
// Check if content already exists
existing, err := h.db.GetContent(siteID, req.ID)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
if existing != nil {
http.Error(w, "Content with this ID already exists", http.StatusConflict)
return
}
// Create content
content, err := h.db.CreateContent(siteID, req.ID, req.Value, req.Type)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(content)
}
// UpdateContent handles PUT /api/content/{id}
func (h *ContentHandler) UpdateContent(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
contentID := vars["id"]
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
siteID = "demo" // Default to demo site for compatibility
}
if contentID == "" {
http.Error(w, "content ID is required", http.StatusBadRequest)
return
}
var req models.UpdateContentRequest
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&req); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
// Update content
content, err := h.db.UpdateContent(siteID, contentID, req.Value)
if err != nil {
if strings.Contains(err.Error(), "not found") {
http.NotFound(w, r)
return
}
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(content)
}