Phase 1: Go backend scaffold
- Project structure: cmd/boosapi, internal/, pkg/ - Config loader (all env vars from Node.js original) - Database layer with SQLite/MySQL/Postgres support - Migration runner (sequential SQL files) - Health check endpoint (GET /healthz) - HTTP middleware: logger, auth, CORS, panic recovery - chi router with /admin, /v1, /api route groups - Graceful shutdown (SIGINT/SIGTERM) - Multi-stage Dockerfile (alpine, ~20MB image) - Initial schema migration (users, api_keys, sites, usage_logs) Signed-off-by: Boos4721 <boos4721@icloud.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
// Package main is the entry point for the BoosAPI server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
chimw "github.com/go-chi/chi/v5/middleware"
|
||||
|
||||
"github.com/cita-777/boosapi/internal/config"
|
||||
"github.com/cita-777/boosapi/internal/db"
|
||||
"github.com/cita-777/boosapi/internal/server/handler"
|
||||
"github.com/cita-777/boosapi/internal/server/middleware"
|
||||
)
|
||||
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
// Load config
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
slog.Error("failed to load config", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Set timezone
|
||||
if cfg.Location() != time.UTC {
|
||||
time.Local = cfg.Location()
|
||||
}
|
||||
|
||||
// Configure slog
|
||||
logLevel := new(slog.LevelVar)
|
||||
switch cfg.LogLevel {
|
||||
case "debug":
|
||||
logLevel.Set(slog.LevelDebug)
|
||||
case "info":
|
||||
logLevel.Set(slog.LevelInfo)
|
||||
case "warn":
|
||||
logLevel.Set(slog.LevelWarn)
|
||||
case "error":
|
||||
logLevel.Set(slog.LevelError)
|
||||
default:
|
||||
logLevel.Set(slog.LevelInfo)
|
||||
}
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: logLevel,
|
||||
})))
|
||||
|
||||
// Connect database
|
||||
database, err := db.Open(cfg.DBType, cfg.DBURL, cfg.DataDir)
|
||||
if err != nil {
|
||||
slog.Error("failed to connect database", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer db.Close(database)
|
||||
|
||||
// Run migrations
|
||||
if err := db.RunMigrations(database, "internal/db/migrate"); err != nil {
|
||||
slog.Error("failed to run migrations", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Build router
|
||||
r := chi.NewRouter()
|
||||
|
||||
// Global middleware
|
||||
r.Use(chimw.RequestID)
|
||||
r.Use(chimw.RealIP)
|
||||
r.Use(middleware.Recover)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.CORS)
|
||||
|
||||
// Health check (no auth required)
|
||||
r.Get("/healthz", handler.NewHealthHandler(database, version).ServeHTTP)
|
||||
|
||||
// Admin routes (require auth)
|
||||
r.Route("/admin", func(r chi.Router) {
|
||||
r.Use(middleware.AuthToken(cfg.AuthToken))
|
||||
// TODO: admin handlers
|
||||
})
|
||||
|
||||
// Proxy routes (require proxy token)
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
r.Use(middleware.ProxyToken(cfg.ProxyToken))
|
||||
// TODO: proxy handlers
|
||||
})
|
||||
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
// TODO: API handlers
|
||||
})
|
||||
|
||||
// Start server
|
||||
addr := cfg.ListenAddr()
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: r,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 300 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
slog.Info("server starting", "addr", addr, "version", version)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
<-quit
|
||||
slog.Info("shutting down...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
slog.Error("force shutdown", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
slog.Info("server stopped")
|
||||
}
|
||||
Reference in New Issue
Block a user