Files
aethera/backend/internal/server/server.go
Evan Reichard 59de41f827
Some checks failed
continuous-integration/drone/push Build is failing
feat(build): embed static assets into Go binary
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
2026-02-22 20:36:03 -05:00

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)
}