186 lines
5.2 KiB
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)
|
|
}
|