package api import ( "encoding/json" "net/http" "time" "github.com/explorer/virtual-banker/backend/realtime" "github.com/explorer/virtual-banker/backend/session" "github.com/gorilla/mux" ) // Server handles HTTP requests type Server struct { sessionManager *session.Manager realtimeGateway *realtime.Gateway router *mux.Router } // NewServer creates a new API server func NewServer(sessionManager *session.Manager, realtimeGateway *realtime.Gateway) *Server { s := &Server{ sessionManager: sessionManager, realtimeGateway: realtimeGateway, router: mux.NewRouter(), } s.setupRoutes() return s } // setupRoutes sets up all API routes func (s *Server) setupRoutes() { api := s.router.PathPrefix("/v1").Subrouter() // Session routes api.HandleFunc("/sessions", s.handleCreateSession).Methods("POST") api.HandleFunc("/sessions/{id}/refresh-token", s.handleRefreshToken).Methods("POST") api.HandleFunc("/sessions/{id}/end", s.handleEndSession).Methods("POST") // Realtime WebSocket api.HandleFunc("/realtime/{id}", s.HandleRealtimeWebSocket) // Health check s.router.HandleFunc("/health", s.handleHealth).Methods("GET") } // CreateSessionRequest represents a session creation request type CreateSessionRequest struct { TenantID string `json:"tenant_id"` UserID string `json:"user_id"` AuthAssertion string `json:"auth_assertion"` PortalContext map[string]interface{} `json:"portal_context,omitempty"` } // CreateSessionResponse represents a session creation response type CreateSessionResponse struct { SessionID string `json:"session_id"` EphemeralToken string `json:"ephemeral_token"` Config *session.TenantConfig `json:"config"` ExpiresAt time.Time `json:"expires_at"` } // handleCreateSession handles POST /v1/sessions func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) { var req CreateSessionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body", err) return } if req.TenantID == "" || req.UserID == "" || req.AuthAssertion == "" { writeError(w, http.StatusBadRequest, "tenant_id, user_id, and auth_assertion are required", nil) return } sess, err := s.sessionManager.CreateSession(r.Context(), req.TenantID, req.UserID, req.AuthAssertion) if err != nil { writeError(w, http.StatusInternalServerError, "failed to create session", err) return } resp := CreateSessionResponse{ SessionID: sess.ID, EphemeralToken: sess.EphemeralToken, Config: sess.Config, ExpiresAt: sess.ExpiresAt, } writeJSON(w, http.StatusCreated, resp) } // RefreshTokenResponse represents a token refresh response type RefreshTokenResponse struct { EphemeralToken string `json:"ephemeral_token"` ExpiresAt time.Time `json:"expires_at"` } // handleRefreshToken handles POST /v1/sessions/:id/refresh-token func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) sessionID := vars["id"] if sessionID == "" { writeError(w, http.StatusBadRequest, "session_id is required", nil) return } newToken, err := s.sessionManager.RefreshToken(r.Context(), sessionID) if err != nil { if err.Error() == "session expired" { writeError(w, http.StatusUnauthorized, "session expired", err) return } writeError(w, http.StatusInternalServerError, "failed to refresh token", err) return } sess, err := s.sessionManager.GetSession(r.Context(), sessionID) if err != nil { writeError(w, http.StatusInternalServerError, "failed to get session", err) return } resp := RefreshTokenResponse{ EphemeralToken: newToken, ExpiresAt: sess.ExpiresAt, } writeJSON(w, http.StatusOK, resp) } // handleEndSession handles POST /v1/sessions/:id/end func (s *Server) handleEndSession(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) sessionID := vars["id"] if sessionID == "" { writeError(w, http.StatusBadRequest, "session_id is required", nil) return } if err := s.sessionManager.EndSession(r.Context(), sessionID); err != nil { writeError(w, http.StatusInternalServerError, "failed to end session", err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ended"}) } // handleHealth handles GET /health func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "healthy"}) } // ServeHTTP implements http.Handler func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) } // writeJSON writes a JSON response func writeJSON(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) } // ErrorResponse represents an error response type ErrorResponse struct { Error string `json:"error"` Message string `json:"message,omitempty"` } // writeError writes an error response func writeError(w http.ResponseWriter, status int, message string, err error) { resp := ErrorResponse{ Error: message, Message: func() string { if err != nil { return err.Error() } return "" }(), } writeJSON(w, status, resp) }