Files
insertr/internal/api/handlers.go

316 lines
9.1 KiB
Go

package api
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/insertr/insertr/internal/auth"
"github.com/insertr/insertr/internal/content"
"github.com/insertr/insertr/internal/db"
"github.com/insertr/insertr/internal/engine"
)
// ContentHandler handles all content-related HTTP requests
type ContentHandler struct {
repository db.ContentRepository
authService *auth.AuthService
siteManager *content.SiteManager
engine *engine.ContentEngine
}
// NewContentHandler creates a new content handler
func NewContentHandler(database *db.Database, authService *auth.AuthService) *ContentHandler {
// Create repository for all database operations
repository := database.NewContentRepository()
return &ContentHandler{
repository: repository,
authService: authService,
siteManager: nil, // Will be set via SetSiteManager
engine: engine.NewContentEngine(repository),
}
}
// SetSiteManager sets the site manager for file enhancement
func (h *ContentHandler) SetSiteManager(siteManager *content.SiteManager) {
h.siteManager = siteManager
}
// GetContent handles GET /api/content/{id}
func (h *ContentHandler) GetContent(w http.ResponseWriter, r *http.Request) {
contentID := chi.URLParam(r, "id")
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
http.Error(w, "site_id parameter is required", http.StatusBadRequest)
return
}
content, err := h.repository.GetContent(context.Background(), siteID, contentID)
if err != nil {
if err == sql.ErrNoRows {
http.Error(w, "Content not found", http.StatusNotFound)
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)
}
// GetAllContent handles GET /api/content with site_id parameter
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
}
contentMap, err := h.repository.GetAllContent(context.Background(), siteID)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
// Convert map to slice for consistent API response
var content []db.ContentItem
for _, item := range contentMap {
content = append(content, item)
}
response := db.ContentResponse{Content: content}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// GetBulkContent handles POST /api/content/bulk for batch fetching
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 {
http.Error(w, "at least one content ID is required", http.StatusBadRequest)
return
}
contentMap, err := h.repository.GetBulkContent(context.Background(), siteID, contentIDs)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
// Convert map to slice for consistent API response
var content []db.ContentItem
for _, item := range contentMap {
content = append(content, item)
}
response := db.ContentResponse{Content: content}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// CreateOrUpdateContent handles POST /api/content for creating/updating content
func (h *ContentHandler) CreateOrUpdateContent(w http.ResponseWriter, r *http.Request) {
userInfo, authErr := h.authService.ExtractUserFromRequest(r)
if authErr != nil {
http.Error(w, "Authentication required", http.StatusUnauthorized)
return
}
var req CreateContentRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
// Use engine to generate ID from HTML structure
result, err := h.engine.ProcessContent(engine.ContentInput{
HTML: []byte(req.HTMLMarkup),
FilePath: req.FilePath,
SiteID: req.SiteID,
Mode: engine.IDGeneration,
})
if err != nil {
http.Error(w, fmt.Sprintf("Failed to process content: %v", err), http.StatusInternalServerError)
return
}
if len(result.Elements) == 0 {
http.Error(w, "No content elements found in markup", http.StatusBadRequest)
return
}
contentID := result.Elements[0].ID
// Check if content already exists
existingContent, _ := h.repository.GetContent(context.Background(), req.SiteID, contentID)
var content *db.ContentItem
if existingContent == nil {
// Create new content
content, err = h.repository.CreateContent(
context.Background(),
req.SiteID,
contentID,
req.HTMLContent,
req.OriginalTemplate,
userInfo.ID,
)
} else {
// Update existing content - this would need UpdateContent method in repository
// For now, we'll return the existing content
content = existingContent
}
if err != nil {
http.Error(w, fmt.Sprintf("Failed to save content: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(content)
}
// DeleteContent handles DELETE /api/content/{id}
func (h *ContentHandler) DeleteContent(w http.ResponseWriter, r *http.Request) {
_, authErr := h.authService.ExtractUserFromRequest(r)
if authErr != nil {
http.Error(w, "Authentication required", http.StatusUnauthorized)
return
}
contentID := chi.URLParam(r, "id")
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
http.Error(w, "site_id parameter is required", http.StatusBadRequest)
return
}
// Note: DeleteContent method would need to be added to repository interface
// For now, return not implemented
_ = contentID // Suppress unused variable warning
http.Error(w, "Delete operation not yet implemented", http.StatusNotImplemented)
}
// GetCollection handles GET /api/collections/{id}
func (h *ContentHandler) GetCollection(w http.ResponseWriter, r *http.Request) {
collectionID := chi.URLParam(r, "id")
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
http.Error(w, "site_id parameter is required", http.StatusBadRequest)
return
}
collection, err := h.repository.GetCollection(context.Background(), siteID, collectionID)
if err != nil {
if err == sql.ErrNoRows {
http.Error(w, "Collection not found", http.StatusNotFound)
return
}
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(collection)
}
// GetCollectionItems handles GET /api/collections/{id}/items
func (h *ContentHandler) GetCollectionItems(w http.ResponseWriter, r *http.Request) {
collectionID := chi.URLParam(r, "id")
siteID := r.URL.Query().Get("site_id")
if siteID == "" {
http.Error(w, "site_id parameter is required", http.StatusBadRequest)
return
}
items, err := h.repository.GetCollectionItems(context.Background(), siteID, collectionID)
if err != nil {
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(items)
}
// CreateCollectionItem handles POST /api/collections/{id}/items
func (h *ContentHandler) CreateCollectionItem(w http.ResponseWriter, r *http.Request) {
userInfo, authErr := h.authService.ExtractUserFromRequest(r)
if authErr != nil {
http.Error(w, "Authentication required", http.StatusUnauthorized)
return
}
collectionID := chi.URLParam(r, "id")
var req CreateCollectionItemRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
if req.SiteID == "" || req.CollectionID == "" {
req.SiteID = r.URL.Query().Get("site_id")
req.CollectionID = collectionID
}
if req.SiteID == "" {
http.Error(w, "site_id is required", http.StatusBadRequest)
return
}
if req.TemplateID == 0 {
req.TemplateID = 1 // Default to first template
}
// Use atomic collection item creation from repository
createdItem, err := h.repository.CreateCollectionItemAtomic(
context.Background(),
req.SiteID,
req.CollectionID,
req.TemplateID,
userInfo.ID,
)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to create collection item: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(createdItem)
}
// RegisterRoutes registers all the content API routes
func (h *ContentHandler) RegisterRoutes(r chi.Router) {
r.Route("/api", func(r chi.Router) {
// Content routes
r.Get("/content/{id}", h.GetContent)
r.Get("/content", h.GetAllContent)
r.Post("/content/bulk", h.GetBulkContent)
r.Post("/content", h.CreateOrUpdateContent)
r.Delete("/content/{id}", h.DeleteContent)
// Collection routes
r.Get("/collections/{id}", h.GetCollection)
r.Get("/collections/{id}/items", h.GetCollectionItems)
r.Post("/collections/{id}/items", h.CreateCollectionItem)
})
}