Changeset 0.22.4 (#146)

This commit is contained in:
2026-03-02 22:16:08 +00:00
parent 9940fb5831
commit 3953dcf364
25 changed files with 2254 additions and 145 deletions

View File

@@ -29,6 +29,7 @@ import (
"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/workspace"
@@ -57,7 +58,7 @@ func main() {
var keyResolver *crypto.KeyResolver
var objStore storage.ObjectStore
var healthAccum *health.Accumulator
var healthStore *postgres.HealthStore
var healthStore health.Store
if err := database.Connect(cfg); err != nil {
log.Printf("⚠ Database unavailable: %v", err)
@@ -94,10 +95,28 @@ func main() {
stores = postgres.NewStores(database.DB)
// Provider health accumulator (v0.22.0)
healthStore = postgres.NewHealthStore(database.DB)
if database.IsSQLite() {
healthStore = sqliteStore.NewHealthStore()
} else {
healthStore = postgres.NewHealthStore(database.DB)
}
healthAccum = health.NewAccumulator(healthStore)
defer healthAccum.Stop()
// Auto-disable: deactivate providers that are "down" for N consecutive
// hourly windows. Configured via PROVIDER_AUTO_DISABLE_THRESHOLD env var.
// Default: 3 (3 consecutive "down" hours triggers deactivation). Set to 0 to disable.
autoDisableThreshold := 3
if v := cfg.ProviderAutoDisableThreshold; v >= 0 {
autoDisableThreshold = v
}
if autoDisableThreshold > 0 {
if ad, ok := healthStore.(health.AutoDisabler); ok {
healthAccum.SetAutoDisable(ad, autoDisableThreshold)
log.Printf(" 🛡️ Provider auto-disable: %d consecutive down windows", autoDisableThreshold)
}
}
// Background health cleanup: prune windows older than 7 days
go func() {
ticker := time.NewTicker(6 * time.Hour)
@@ -472,6 +491,15 @@ func main() {
protected.DELETE("/projects/:id/notes/:noteId", projectH.RemoveNote)
protected.GET("/projects/:id/notes", projectH.ListNotes)
// Project files (v0.22.4) — wired via attachH which is declared later,
// so we use a closure that captures the variable.
protected.POST("/projects/:id/files", func(c *gin.Context) {
handlers.NewAttachmentHandler(stores, objStore, extQueue).UploadToProject(c)
})
protected.GET("/projects/:id/files", func(c *gin.Context) {
handlers.NewAttachmentHandler(stores, objStore, extQueue).ListByProject(c)
})
// Notifications (v0.20.0)
notifH := handlers.NewNotificationHandler(stores, hub)
protected.GET("/notifications", notifH.List)
@@ -533,6 +561,10 @@ func main() {
protected.GET("/attachments/:id/download", attachH.Download)
protected.DELETE("/attachments/:id", attachH.DeleteAttachment)
// Export (v0.22.4) — pandoc-based markdown → PDF/DOCX conversion
exportH := handlers.NewExportHandler()
protected.POST("/export", exportH.Convert)
// Hook: clean up storage files when channels are deleted
handlers.SetChannelDeleteHook(attachH.CleanupChannelStorage)