Some checks failed
continuous-integration/drone/push Build is failing
Embed frontend build output directly into Go binary using //go:embed. This removes runtime dependency on ../frontend/public/ path and simplifies Docker builds by serving assets from embedded filesystem. - Add backend/web/embed.go with embed.FS directive - Update server to serve from embedded static assets - Update Makefile to copy frontend build to web/static/ - Update Dockerfile for simplified multi-stage build - Update frontend package.json output paths - Remove custom 'oc' command from flake.nix dev shell
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"reichard.io/aethera/internal/api"
|
|
"reichard.io/aethera/internal/store"
|
|
"reichard.io/aethera/web"
|
|
)
|
|
|
|
func StartServer(settingsStore store.Store, dataDir, listenAddress string, listenPort int) {
|
|
mux := http.NewServeMux()
|
|
|
|
// Create API Instance - use settingsStore as the unified store for both settings and chat
|
|
logger := logrus.New()
|
|
api := api.New(settingsStore, dataDir, logger)
|
|
|
|
// Serve embedded static assets
|
|
staticFS, err := fs.Sub(web.Assets, "static")
|
|
if err != nil {
|
|
logrus.Fatal("Failed to create static filesystem: ", err)
|
|
}
|
|
mux.Handle("GET /", http.FileServer(http.FS(staticFS)))
|
|
|
|
// Serve Generated Data
|
|
genFS := http.FileServer(http.Dir(path.Join(dataDir, "generated")))
|
|
mux.Handle("GET /generated/", http.StripPrefix("/generated/", genFS))
|
|
|
|
// Register API Routes
|
|
mux.HandleFunc("POST /api/images", api.PostImage)
|
|
mux.HandleFunc("GET /api/settings", api.GetSettings)
|
|
mux.HandleFunc("POST /api/settings", api.PostSettings)
|
|
mux.HandleFunc("GET /api/models", api.GetModels)
|
|
mux.HandleFunc("GET /api/images", api.GetImages)
|
|
mux.HandleFunc("DELETE /api/images/{filename}", api.DeleteImage)
|
|
|
|
// Register Chat Management Routes
|
|
mux.HandleFunc("GET /api/chats", api.GetChats)
|
|
mux.HandleFunc("POST /api/chats", api.PostChat)
|
|
mux.HandleFunc("GET /api/chats/{chatId}", api.GetChat)
|
|
mux.HandleFunc("POST /api/chats/{chatId}", api.PostChatMessage)
|
|
mux.HandleFunc("DELETE /api/chats/{chatId}", api.DeleteChat)
|
|
|
|
// Wrap Logging
|
|
wrappedMux := loggingMiddleware(mux)
|
|
|
|
logrus.Infof("Starting server on %s:%d with data directory: %s", listenAddress, listenPort, dataDir)
|
|
logrus.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", listenAddress, listenPort), wrappedMux))
|
|
}
|
|
|
|
// loggingMiddleware wraps an http.Handler and logs requests
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
ww := &responseWriterWrapper{ResponseWriter: w}
|
|
next.ServeHTTP(ww, r)
|
|
|
|
logrus.WithFields(logrus.Fields{
|
|
"datetime": start.UTC().Format(time.RFC3339),
|
|
"method": r.Method,
|
|
"path": r.URL.Path,
|
|
"remote": r.RemoteAddr,
|
|
"status": ww.getStatusCode(),
|
|
"latency": time.Since(start),
|
|
}).Infof("%s %s", r.Method, r.URL.Path)
|
|
})
|
|
}
|
|
|
|
// responseWriterWrapper wraps http.ResponseWriter to capture status code
|
|
type responseWriterWrapper struct {
|
|
http.ResponseWriter
|
|
statusCode int
|
|
}
|
|
|
|
func (w *responseWriterWrapper) Flush() {
|
|
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
}
|
|
|
|
func (rw *responseWriterWrapper) getStatusCode() int {
|
|
if rw.statusCode == 0 {
|
|
return 200
|
|
}
|
|
return rw.statusCode
|
|
}
|
|
|
|
func (rw *responseWriterWrapper) WriteHeader(code int) {
|
|
if code > 0 {
|
|
rw.statusCode = code
|
|
}
|
|
rw.ResponseWriter.WriteHeader(code)
|
|
}
|