104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
package platform
|
|
|
|
import (
|
|
"database/sql"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type App struct {
|
|
config Config
|
|
database *sql.DB
|
|
logger *slog.Logger
|
|
mux *http.ServeMux
|
|
migrations []Migration
|
|
}
|
|
|
|
func NewApp(cfg Config, database *sql.DB, logger *slog.Logger) *App {
|
|
app := &App{config: cfg, database: database, logger: logger, mux: http.NewServeMux()}
|
|
app.registerPlatformRoutes()
|
|
return app
|
|
}
|
|
|
|
func (a *App) Config() Config { return a.config }
|
|
func (a *App) Database() *sql.DB { return a.database }
|
|
func (a *App) Logger() *slog.Logger { return a.logger }
|
|
|
|
func (a *App) Handle(pattern string, handler http.Handler) {
|
|
a.mux.Handle(pattern, handler)
|
|
}
|
|
|
|
func (a *App) HandleFunc(pattern string, handler http.HandlerFunc) {
|
|
a.mux.HandleFunc(pattern, handler)
|
|
}
|
|
|
|
func (a *App) Handler() http.Handler {
|
|
return requestSecurityHeaders(a.mux)
|
|
}
|
|
|
|
func (a *App) registerPlatformRoutes() {
|
|
a.mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
|
|
WriteJSON(w, http.StatusOK, map[string]string{"status": "ok", "product": "sense"})
|
|
})
|
|
a.mux.HandleFunc("GET /readyz", a.ready)
|
|
a.mux.HandleFunc("GET /api/v1/system/info", func(w http.ResponseWriter, _ *http.Request) {
|
|
WriteJSON(w, http.StatusOK, map[string]any{
|
|
"product": "Sense",
|
|
"database": a.config.DatabaseMode,
|
|
"modules": []string{},
|
|
})
|
|
})
|
|
// Deliberately do not register upstream demo/code-generation routes.
|
|
if a.config.StaticDir != "" {
|
|
a.registerStaticFiles(a.config.StaticDir)
|
|
}
|
|
}
|
|
|
|
func (a *App) ready(w http.ResponseWriter, r *http.Request) {
|
|
if a.database == nil {
|
|
WriteJSON(w, http.StatusOK, map[string]string{"status": "ready", "database": "memory"})
|
|
return
|
|
}
|
|
ctx, cancel := timeLimitedContext(r, 2*time.Second)
|
|
defer cancel()
|
|
if err := a.database.PingContext(ctx); err != nil {
|
|
WriteJSON(w, http.StatusServiceUnavailable, map[string]string{"status": "not_ready", "database": "unavailable"})
|
|
return
|
|
}
|
|
WriteJSON(w, http.StatusOK, map[string]string{"status": "ready", "database": "postgres"})
|
|
}
|
|
|
|
func (a *App) registerStaticFiles(directory string) {
|
|
absolute, err := filepath.Abs(directory)
|
|
if err != nil {
|
|
a.logger.Warn("invalid UI static directory", "error", err)
|
|
return
|
|
}
|
|
fileSystem := os.DirFS(absolute)
|
|
a.mux.Handle("GET /assets/", http.StripPrefix("/", http.FileServer(http.FS(fileSystem))))
|
|
a.mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
|
path := strings.TrimPrefix(filepath.ToSlash(filepath.Clean(r.URL.Path)), "/")
|
|
if path == "." || path == "" {
|
|
path = "index.html"
|
|
}
|
|
if _, err := fs.Stat(fileSystem, path); err != nil {
|
|
path = "index.html"
|
|
}
|
|
http.ServeFileFS(w, r, fileSystem, path)
|
|
})
|
|
}
|
|
|
|
func requestSecurityHeaders(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Header().Set("X-Frame-Options", "DENY")
|
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|