Add complete markdown editor with Go backend and React/TypeScript frontend. Backend: - Cobra CLI with configurable host, port, data-dir, static-dir flags - REST API for CRUD operations on markdown files (GET, POST, PUT, DELETE) - File storage with flat .md structure - Comprehensive Logrus logging for all operations - Static asset serving for frontend Frontend: - React 18 + TypeScript + Tailwind CSS - Live markdown editor with GFM preview (react-markdown) - File management UI (list, create, open, save, delete) - Theme system (Light/Dark/System) with localStorage persistence - Responsive design (320px - 1920px+) Testing: - 6 backend tests covering CRUD round-trip, validation, error handling - 19 frontend tests covering API, theme system, and UI components - All tests passing with single 'make test' command Build: - Frontend compiles to optimized assets in dist/ - Backend can serve frontend via --static-dir flag
81 lines
1.9 KiB
Go
81 lines
1.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
|
|
"github.com/evanreichard/markdown-editor/internal/api"
|
|
"github.com/evanreichard/markdown-editor/internal/config"
|
|
"github.com/evanreichard/markdown-editor/internal/logging"
|
|
"github.com/evanreichard/markdown-editor/internal/router"
|
|
"github.com/evanreichard/markdown-editor/internal/storage"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
dataDir string
|
|
port string
|
|
host string
|
|
staticDir string
|
|
)
|
|
|
|
var rootCmd = &cobra.Command{
|
|
Use: "server",
|
|
Short: "Markdown editor server",
|
|
Long: "A markdown editor server with REST API and frontend support",
|
|
Run: runServer,
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.Flags().StringVar(&dataDir, "data-dir", "./data", "Storage path for markdown files")
|
|
rootCmd.Flags().StringVar(&port, "port", "8080", "Server port")
|
|
rootCmd.Flags().StringVar(&host, "host", "127.0.0.1", "Bind address")
|
|
rootCmd.Flags().StringVar(&staticDir, "static-dir", "./frontend/dist", "Path to static assets")
|
|
}
|
|
|
|
func runServer(cmd *cobra.Command, args []string) {
|
|
cfg := &config.Config{
|
|
DataDir: dataDir,
|
|
Port: port,
|
|
Host: host,
|
|
}
|
|
|
|
logging.Init()
|
|
logging.Logger.WithFields(map[string]interface{}{
|
|
"data_dir": cfg.DataDir,
|
|
"host": cfg.Host,
|
|
"port": cfg.Port,
|
|
"static_dir": staticDir,
|
|
}).Info("Starting markdown editor server")
|
|
|
|
// Initialize storage
|
|
store, err := storage.New(cfg.DataDir)
|
|
if err != nil {
|
|
logging.Logger.WithError(err).Fatal("Failed to initialize storage")
|
|
}
|
|
|
|
// Initialize handlers
|
|
handlers := api.New(store)
|
|
|
|
// Create router
|
|
mux := router.New(handlers, staticDir)
|
|
|
|
// Wrap with logging middleware
|
|
handler := logging.RequestMiddleware(mux)
|
|
|
|
// Start server
|
|
addr := cfg.Addr()
|
|
logging.Logger.WithField("addr", addr).Info("Server listening")
|
|
if err := http.ListenAndServe(addr, handler); err != nil {
|
|
logging.Logger.WithError(err).Fatal("Server error")
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
if err := rootCmd.Execute(); err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
}
|