Changeset 0.32.0 (#206)
This commit is contained in:
120
server/main.go
120
server/main.go
@@ -13,34 +13,34 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/auth"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/compaction"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/config"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/crypto"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/events"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/extraction"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/filters"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/sandbox"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/handlers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/health"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/knowledge"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/memory"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/middleware"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/notifications"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/pages"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/providers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/roles"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/routing"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/scheduler"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/storage"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
postgres "git.gobha.me/xcaliber/chat-switchboard/store/postgres"
|
||||
sqliteStore "git.gobha.me/xcaliber/chat-switchboard/store/sqlite"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/tools"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/tools/search"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/treepath"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/workspace"
|
||||
"chat-switchboard/auth"
|
||||
"chat-switchboard/compaction"
|
||||
"chat-switchboard/config"
|
||||
"chat-switchboard/crypto"
|
||||
"chat-switchboard/database"
|
||||
"chat-switchboard/events"
|
||||
"chat-switchboard/extraction"
|
||||
"chat-switchboard/filters"
|
||||
"chat-switchboard/sandbox"
|
||||
"chat-switchboard/handlers"
|
||||
"chat-switchboard/health"
|
||||
"chat-switchboard/knowledge"
|
||||
"chat-switchboard/memory"
|
||||
"chat-switchboard/middleware"
|
||||
"chat-switchboard/notifications"
|
||||
"chat-switchboard/pages"
|
||||
"chat-switchboard/providers"
|
||||
"chat-switchboard/roles"
|
||||
"chat-switchboard/routing"
|
||||
"chat-switchboard/scheduler"
|
||||
"chat-switchboard/storage"
|
||||
"chat-switchboard/store"
|
||||
postgres "chat-switchboard/store/postgres"
|
||||
sqliteStore "chat-switchboard/store/sqlite"
|
||||
"chat-switchboard/tools"
|
||||
"chat-switchboard/tools/search"
|
||||
"chat-switchboard/treepath"
|
||||
"chat-switchboard/workspace"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -400,9 +400,8 @@ func main() {
|
||||
// ── WebSocket Hub ─────────────────────────
|
||||
hub := events.NewHub(bus, middleware.GetAllowedOrigins(cfg))
|
||||
|
||||
// ── WebSocket Ticket Store (v0.28.8) ─────
|
||||
ticketStore := events.NewTicketStore()
|
||||
defer ticketStore.Stop()
|
||||
// ── WebSocket Ticket Store (v0.32.0: PG-backed for cross-pod) ─────
|
||||
ticketAdapter := &events.TicketValidatorAdapter{Store: stores.Tickets}
|
||||
|
||||
// ── Notification Service (v0.20.0) ───────
|
||||
var notifSvc *notifications.Service
|
||||
@@ -457,8 +456,30 @@ func main() {
|
||||
})
|
||||
})
|
||||
|
||||
// v0.32.0: Kubernetes probe endpoints
|
||||
// Liveness: process is alive and serving (no dependency checks).
|
||||
base.GET("/healthz/live", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
})
|
||||
// Readiness: process can serve traffic (PG reachable).
|
||||
// Failing readiness pulls the pod from the Service — new requests
|
||||
// route to healthy replicas.
|
||||
base.GET("/healthz/ready", func(c *gin.Context) {
|
||||
if database.DB == nil {
|
||||
c.JSON(503, gin.H{"error": "database not initialized"})
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
if err := database.DB.PingContext(ctx); err != nil {
|
||||
c.JSON(503, gin.H{"error": "database unavailable"})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
// WebSocket endpoint
|
||||
base.GET("/ws", middleware.WsAuth(cfg, stores.Users, userCache, ticketStore), hub.HandleWebSocket)
|
||||
base.GET("/ws", middleware.WsAuth(cfg, stores.Users, userCache, ticketAdapter), hub.HandleWebSocket)
|
||||
|
||||
// ── Auth routes (rate limited) ──────────────
|
||||
authMode, err := auth.ParseMode(cfg.AuthMode)
|
||||
@@ -500,7 +521,7 @@ func main() {
|
||||
log.Printf(" 🔑 Auth mode: %s", authMode)
|
||||
|
||||
authH := handlers.NewAuthHandler(cfg, stores, uekCache, authProvider)
|
||||
authLimiter := middleware.NewRateLimiter(5, 5)
|
||||
authLimiter := middleware.NewRateLimiter(stores.RateLimits, 5, 8)
|
||||
|
||||
api := base.Group("/api/v1")
|
||||
{
|
||||
@@ -549,7 +570,7 @@ func main() {
|
||||
// Client fetches this, then connects with ?ticket=<opaque>.
|
||||
protected.POST("/ws/ticket", func(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
ticket, err := ticketStore.Issue(userID)
|
||||
ticket, err := stores.Tickets.Issue(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to issue ticket"})
|
||||
return
|
||||
@@ -596,7 +617,7 @@ func main() {
|
||||
Ts: time.Now().UnixMilli(),
|
||||
}
|
||||
for _, pid := range pids {
|
||||
hub.SendToUser(pid, evt)
|
||||
hub.PublishToUser(pid, evt)
|
||||
}
|
||||
}
|
||||
c.JSON(200, gin.H{"ok": true})
|
||||
@@ -983,6 +1004,20 @@ func main() {
|
||||
teamAssignH := handlers.NewWorkflowAssignmentHandler(stores, hub)
|
||||
teamScoped.GET("/assignments", teamAssignH.ListForTeam)
|
||||
|
||||
// Team workflows — self-service (v0.31.2)
|
||||
teamWfH := handlers.NewWorkflowHandler(stores)
|
||||
teamScoped.GET("/workflows", teamWfH.ListTeamWorkflows)
|
||||
teamScoped.POST("/workflows", teamWfH.CreateTeamWorkflow)
|
||||
teamScoped.GET("/workflows/:id", teamWfH.GetTeamWorkflow)
|
||||
teamScoped.PATCH("/workflows/:id", teamWfH.UpdateTeamWorkflow)
|
||||
teamScoped.DELETE("/workflows/:id", teamWfH.DeleteTeamWorkflow)
|
||||
teamScoped.GET("/workflows/:id/stages", teamWfH.ListTeamWorkflowStages)
|
||||
teamScoped.POST("/workflows/:id/stages", teamWfH.CreateTeamWorkflowStage)
|
||||
teamScoped.PUT("/workflows/:id/stages/:sid", teamWfH.UpdateTeamWorkflowStage)
|
||||
teamScoped.DELETE("/workflows/:id/stages/:sid", teamWfH.DeleteTeamWorkflowStage)
|
||||
teamScoped.PATCH("/workflows/:id/stages/reorder", teamWfH.ReorderTeamWorkflowStages)
|
||||
teamScoped.POST("/workflows/:id/publish", teamWfH.PublishTeamWorkflow)
|
||||
|
||||
// Team tasks — admin CRUD (v0.27.5)
|
||||
teamTaskH := handlers.NewTaskHandler(stores)
|
||||
teamScoped.POST("/tasks", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.CreateTeamTask)
|
||||
@@ -990,21 +1025,6 @@ func main() {
|
||||
teamScoped.DELETE("/tasks/:id", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.Delete)
|
||||
teamScoped.POST("/tasks/:id/run", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.RunNow)
|
||||
teamScoped.POST("/tasks/:id/kill", middleware.RequirePermission(auth.PermTaskCreate, stores), teamTaskH.KillRun)
|
||||
|
||||
// Team workflows — self-service CRUD (v0.31.2)
|
||||
teamWfH := handlers.NewWorkflowHandler(stores)
|
||||
teamScoped.GET("/workflows", teamWfH.ListTeamWorkflows)
|
||||
teamScoped.POST("/workflows", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.CreateTeamWorkflow)
|
||||
teamScoped.GET("/workflows/:id", teamWfH.GetTeamWorkflow)
|
||||
teamScoped.PATCH("/workflows/:id", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.UpdateTeamWorkflow)
|
||||
teamScoped.DELETE("/workflows/:id", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.DeleteTeamWorkflow)
|
||||
teamScoped.GET("/workflows/:id/stages", teamWfH.ListTeamWorkflowStages)
|
||||
teamScoped.POST("/workflows/:id/stages", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.CreateTeamWorkflowStage)
|
||||
teamScoped.PUT("/workflows/:id/stages/:sid", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.UpdateTeamWorkflowStage)
|
||||
teamScoped.DELETE("/workflows/:id/stages/:sid", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.DeleteTeamWorkflowStage)
|
||||
teamScoped.PATCH("/workflows/:id/stages/reorder", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.ReorderTeamWorkflowStages)
|
||||
teamScoped.POST("/workflows/:id/publish", middleware.RequirePermission(auth.PermWorkflowCreate, stores), teamWfH.PublishTeamWorkflow)
|
||||
teamScoped.GET("/workflows/:id/versions/:version", teamWfH.GetTeamWorkflowVersion)
|
||||
}
|
||||
|
||||
// Team task viewing for all members (v0.27.5)
|
||||
|
||||
Reference in New Issue
Block a user