Files
virtual-banker/backend/api/routes.go

186 lines
5.2 KiB
Go

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