package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/go-chi/chi/v5" chiMiddleware "github.com/go-chi/chi/v5/middleware" "github.com/restxlsx/restxlsx/internal/api" "github.com/restxlsx/restxlsx/internal/auth" "github.com/restxlsx/restxlsx/internal/config" "github.com/restxlsx/restxlsx/internal/excel" "github.com/restxlsx/restxlsx/internal/graphql" "github.com/restxlsx/restxlsx/internal/logger" ) func main() { cfg, err := config.Load() if err != nil { fmt.Fprintf(os.Stderr, "config error: %v\n", err) os.Exit(1) } log, err := logger.New(cfg.Quiet, cfg.LogReads, cfg.LogFile) if err != nil { fmt.Fprintf(os.Stderr, "logger error: %v\n", err) os.Exit(1) } log.Info("starting restxlsx", "file", cfg.FilePath, "host", cfg.Host, "port", cfg.Port, "debug", cfg.Debug, "auth", cfg.AuthRole, ) engine, err := excel.Open(cfg.FilePath) if err != nil { log.Error("failed to open excel file", "error", err) os.Exit(1) } tables := engine.ListTables() log.Info("discovered tables", "tables", tables) authHandler := auth.New(cfg.AuthRole) r := chi.NewRouter() r.Use(chiMiddleware.RequestID) r.Use(chiMiddleware.RealIP) r.Use(chiMiddleware.Logger) r.Use(chiMiddleware.Recoverer) r.Use(authHandler.Middleware) writeRoute := auth.RequireWrite ddlRoute := auth.RequireDDL r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok"}`)) }) // REST API restHandler := api.NewRestAPI(engine, log) r.Get("/api/tables", restHandler.ListTables) r.Route("/api/{table}", func(r chi.Router) { r.Get("/", restHandler.ListRecords) r.Post("/", writeRoute(restHandler.CreateRecord)) r.Get("/{id}", restHandler.GetRecord) r.Put("/{id}", writeRoute(restHandler.UpdateRecord)) r.Delete("/{id}", writeRoute(restHandler.DeleteRecord)) }) // GraphQL gqlHandler, err := graphql.New(engine, log) if err != nil { log.Error("failed to create graphql handler", "error", err) os.Exit(1) } r.Post("/graphql", writeRoute(gqlHandler.ServeHTTP)) if cfg.Debug { r.Get("/graphql", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(graphiQLPage) }) oapi := api.NewOpenAPI(engine, cfg.Debug) r.Get("/openapi.json", oapi.Handler) r.Get("/swagger", oapi.SwaggerHandler) r.Get("/swagger/*", oapi.SwaggerHandler) } // SQL endpoint sqlHandler := api.NewSQLHandler(engine, log) r.Post("/api/sql", writeRoute(sqlHandler.ServeHTTP)) // DDL endpoints (admin only) r.Route("/api/ddl", func(r chi.Router) { ddl := api.NewDDLHandler(engine, log) r.Post("/sheets", ddlRoute(ddl.CreateSheet)) r.Delete("/sheets/{sheet}", ddlRoute(ddl.DropSheet)) r.Post("/tables", ddlRoute(ddl.CreateTable)) r.Delete("/tables/{table}", ddlRoute(ddl.DropTable)) }) addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) srv := &http.Server{ Addr: addr, Handler: r, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) go func() { log.Info("listening", "addr", addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Error("server error", "error", err) os.Exit(1) } }() <-quit log.Info("shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Error("shutdown error", "error", err) os.Exit(1) } log.Info("stopped") } var graphiQLPage = []byte(` GraphiQL
Loading...
`)