package handler import ( "embed" "encoding/json" "io/fs" "net/http" "teraclone/internal/service" "github.com/gorilla/websocket" ) //go:embed web/* var webFiles embed.FS type AppHandler struct { appService *service.AppService fileServer http.Handler } type socketRequest struct { Type string `json:"type"` Data string `json:"data"` } type socketResponse struct { Type string `json:"type"` Data string `json:"data,omitempty"` Prompt string `json:"prompt,omitempty"` WorkingDir string `json:"workingDir,omitempty"` } var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func NewAppHandler(appService *service.AppService) *AppHandler { webRoot, err := fs.Sub(webFiles, "web") if err != nil { panic(err) } return &AppHandler{ appService: appService, fileServer: http.FileServer(http.FS(webRoot)), } } func (h *AppHandler) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/", h.handleRoot) mux.HandleFunc("/health", h.handleHealth) mux.HandleFunc("/ws", h.handleWebSocket) mux.Handle("/assets/", http.StripPrefix("/assets/", h.fileServer)) } func (h *AppHandler) RegisterLegacyRoutes(mux *http.ServeMux) { mux.HandleFunc("/legacy", h.handleLegacyRoot) mux.HandleFunc("/legacy/", h.handleLegacyRoot) mux.HandleFunc("/legacy/ws", h.handleLegacyWebSocket) mux.Handle("/legacy/assets/", http.StripPrefix("/legacy/assets/", h.fileServer)) } func (h *AppHandler) HandleHealth(w http.ResponseWriter, r *http.Request) { h.handleHealth(w, r) } func (h *AppHandler) handleRoot(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } http.ServeFileFS(w, r, webFiles, "web/index.html") } func (h *AppHandler) handleHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") response := map[string]string{ "status": h.appService.HealthStatus(), } _ = json.NewEncoder(w).Encode(response) } func (h *AppHandler) handleLegacyRoot(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/legacy" && r.URL.Path != "/legacy/" { http.NotFound(w, r) return } http.ServeFileFS(w, r, webFiles, "web/index.html") } func (h *AppHandler) handleLegacyWebSocket(w http.ResponseWriter, r *http.Request) { h.handleWebSocket(w, r) } func (h *AppHandler) handleWebSocket(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { return } defer conn.Close() session, err := h.appService.NewTerminalSession() if err != nil { _ = conn.WriteJSON(socketResponse{ Type: "error", Data: err.Error(), }) return } _ = conn.WriteJSON(socketResponse{ Type: "welcome", Data: session.WelcomeMessage(), Prompt: session.Prompt(), WorkingDir: session.Snapshot().WorkingDir, }) for { var request socketRequest if err := conn.ReadJSON(&request); err != nil { return } if request.Type != "input" { continue } result := session.Execute(request.Data) _ = conn.WriteJSON(socketResponse{ Type: "output", Data: result.Output, Prompt: result.Prompt, WorkingDir: result.WorkingDir, }) } }