Files
chat/internal/server/routes.go
clawbot b78d526f02 style: fix all golangci-lint issues and format code (refs #17)
Fix 380 lint violations across all Go source files including wsl_v5,
nlreturn, noinlineerr, errcheck, funlen, funcorder, tagliatelle,
perfsprint, modernize, revive, gosec, ireturn, mnd, forcetypeassert,
cyclop, and others.

Key changes:
- Split large handler/command functions into smaller methods
- Extract scan helpers for database queries
- Reorder exported/unexported methods per funcorder
- Add sentinel errors in models package
- Use camelCase JSON tags per tagliatelle defaults
- Add package comments
- Fix .gitignore to not exclude cmd/chat-cli directory
2026-02-26 06:27:56 -08:00

108 lines
2.5 KiB
Go

package server
import (
"io/fs"
"net/http"
"time"
"git.eeqj.de/sneak/chat/web"
sentryhttp "github.com/getsentry/sentry-go/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/viper"
)
const routeTimeout = 60 * time.Second
// SetupRoutes configures the HTTP routes and middleware chain.
func (s *Server) SetupRoutes() {
s.router = chi.NewRouter()
s.router.Use(middleware.Recoverer)
s.router.Use(middleware.RequestID)
s.router.Use(s.mw.Logging())
if viper.GetString("METRICS_USERNAME") != "" {
s.router.Use(s.mw.Metrics())
}
s.router.Use(s.mw.CORS())
s.router.Use(middleware.Timeout(routeTimeout))
if s.sentryEnabled {
sentryHandler := sentryhttp.New(sentryhttp.Options{
Repanic: true,
})
s.router.Use(sentryHandler.Handle)
}
// Health check
s.router.Get("/.well-known/healthcheck.json", s.h.HandleHealthCheck())
// Protected metrics endpoint
if viper.GetString("METRICS_USERNAME") != "" {
s.router.Group(func(r chi.Router) {
r.Use(s.mw.MetricsAuth())
r.Get("/metrics", http.HandlerFunc(promhttp.Handler().ServeHTTP))
})
}
// API v1
s.router.Route("/api/v1", func(r chi.Router) {
r.Get("/server", s.h.HandleServerInfo())
r.Post("/session", s.h.HandleCreateSession())
// Unified state and message endpoints
r.Get("/state", s.h.HandleState())
r.Get("/messages", s.h.HandleGetMessages())
r.Post("/messages", s.h.HandleSendCommand())
r.Get("/history", s.h.HandleGetHistory())
// Channels
r.Get("/channels", s.h.HandleListAllChannels())
r.Get("/channels/{channel}/members", s.h.HandleChannelMembers())
})
// Serve embedded SPA
distFS, err := fs.Sub(web.Dist, "dist")
if err != nil {
s.log.Error("failed to get web dist filesystem", "error", err)
} else {
fileServer := http.FileServer(http.FS(distFS))
s.router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
s.serveSPA(distFS, fileServer, w, r)
})
}
}
func (s *Server) serveSPA(
distFS fs.FS,
fileServer http.Handler,
w http.ResponseWriter,
r *http.Request,
) {
readFS, ok := distFS.(fs.ReadFileFS)
if !ok {
http.Error(w, "filesystem error", http.StatusInternalServerError)
return
}
// Try to serve the file; fall back to index.html for SPA routing.
f, err := readFS.ReadFile(r.URL.Path[1:])
if err != nil || len(f) == 0 {
indexHTML, _ := readFS.ReadFile("index.html")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(indexHTML)
return
}
fileServer.ServeHTTP(w, r)
}