Changeset 0.9.0 (#50)
This commit is contained in:
123
server/main.go
123
server/main.go
@@ -11,6 +11,8 @@ import (
|
||||
"git.gobha.me/xcaliber/chat-switchboard/handlers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/middleware"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/providers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
postgres "git.gobha.me/xcaliber/chat-switchboard/store/postgres"
|
||||
_ "git.gobha.me/xcaliber/chat-switchboard/tools" // registers built-in tools via init()
|
||||
)
|
||||
|
||||
@@ -20,6 +22,8 @@ func main() {
|
||||
// Register LLM providers
|
||||
providers.Init()
|
||||
|
||||
var stores store.Stores
|
||||
|
||||
if err := database.Connect(cfg); err != nil {
|
||||
log.Printf("⚠ Database unavailable: %v", err)
|
||||
log.Println(" Running in unmanaged mode (no persistence)")
|
||||
@@ -28,8 +32,12 @@ func main() {
|
||||
if err := database.Migrate(); err != nil {
|
||||
log.Fatalf("❌ Schema migration failed: %v", err)
|
||||
}
|
||||
|
||||
// Initialize store layer
|
||||
stores = postgres.NewStores(database.DB)
|
||||
|
||||
// Bootstrap admin from env (K8s secret) — upserts on every restart
|
||||
handlers.BootstrapAdmin(cfg)
|
||||
handlers.BootstrapAdmin(cfg, stores)
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
@@ -37,7 +45,6 @@ func main() {
|
||||
r.Use(middleware.CORS())
|
||||
|
||||
// ── Base path group ──────────────────────
|
||||
// All routes live under cfg.BasePath (e.g. "/dev", "/test", or "")
|
||||
base := r.Group(cfg.BasePath)
|
||||
|
||||
// ── EventBus + WebSocket Hub ─────────────
|
||||
@@ -54,11 +61,11 @@ func main() {
|
||||
})
|
||||
})
|
||||
|
||||
// WebSocket endpoint — auth via ?token= query param
|
||||
// WebSocket endpoint
|
||||
base.GET("/ws", middleware.Auth(cfg), hub.HandleWebSocket)
|
||||
|
||||
// ── Auth routes (rate limited) ──────────────
|
||||
auth := handlers.NewAuthHandler(cfg)
|
||||
auth := handlers.NewAuthHandler(cfg, stores)
|
||||
authLimiter := middleware.NewRateLimiter(1, 5)
|
||||
|
||||
api := base.Group("/api/v1")
|
||||
@@ -72,9 +79,8 @@ func main() {
|
||||
"database": database.IsConnected(),
|
||||
"providers": providers.List(),
|
||||
}
|
||||
// Include registration status for frontend
|
||||
if database.IsConnected() {
|
||||
info["registration_enabled"] = isRegistrationOpen()
|
||||
info["registration_enabled"] = handlers.IsRegistrationEnabled(stores)
|
||||
}
|
||||
c.JSON(200, info)
|
||||
})
|
||||
@@ -92,7 +98,7 @@ func main() {
|
||||
protected := api.Group("")
|
||||
protected.Use(middleware.Auth(cfg))
|
||||
{
|
||||
// Channels (unified: replaces /chats)
|
||||
// Channels
|
||||
channels := handlers.NewChannelHandler()
|
||||
protected.GET("/channels", channels.ListChannels)
|
||||
protected.POST("/channels", channels.CreateChannel)
|
||||
@@ -116,21 +122,26 @@ func main() {
|
||||
comp := handlers.NewCompletionHandler()
|
||||
protected.POST("/chat/completions", comp.Complete)
|
||||
|
||||
// API Configs
|
||||
apiCfg := handlers.NewAPIConfigHandler()
|
||||
protected.GET("/api-configs", apiCfg.ListConfigs)
|
||||
protected.POST("/api-configs", apiCfg.CreateConfig)
|
||||
protected.GET("/api-configs/:id", apiCfg.GetConfig)
|
||||
protected.PUT("/api-configs/:id", apiCfg.UpdateConfig)
|
||||
protected.DELETE("/api-configs/:id", apiCfg.DeleteConfig)
|
||||
// Provider Configs (user-facing — replaces /api-configs)
|
||||
provCfg := handlers.NewProviderConfigHandler(stores)
|
||||
protected.GET("/api-configs", provCfg.ListConfigs) // backward compat
|
||||
protected.POST("/api-configs", provCfg.CreateConfig)
|
||||
protected.GET("/api-configs/:id", provCfg.GetConfig)
|
||||
protected.PUT("/api-configs/:id", provCfg.UpdateConfig)
|
||||
protected.DELETE("/api-configs/:id", provCfg.DeleteConfig)
|
||||
protected.GET("/api-configs/:id/models", provCfg.ListModels)
|
||||
protected.POST("/api-configs/:id/models/fetch", provCfg.FetchModels)
|
||||
|
||||
// Models (per-config and aggregate)
|
||||
protected.GET("/api-configs/:id/models", apiCfg.ListModels)
|
||||
protected.GET("/models", apiCfg.ListAllModels)
|
||||
protected.GET("/models/enabled", apiCfg.ListEnabledModels)
|
||||
protected.GET("/models/preferences", handlers.GetModelPreferences)
|
||||
protected.PUT("/models/preferences", handlers.SetModelPreference)
|
||||
protected.POST("/models/preferences/bulk", handlers.BulkSetModelPreferences)
|
||||
// Models (unified resolver — replaces scattered endpoints)
|
||||
modelH := handlers.NewModelHandler(stores)
|
||||
protected.GET("/models/enabled", modelH.ListEnabledModels)
|
||||
protected.GET("/models", modelH.ListEnabledModels) // alias
|
||||
|
||||
// Model Preferences
|
||||
modelPrefs := handlers.NewModelPrefsHandler(stores)
|
||||
protected.GET("/models/preferences", modelPrefs.GetPreferences)
|
||||
protected.PUT("/models/preferences", modelPrefs.SetPreference)
|
||||
protected.POST("/models/preferences/bulk", modelPrefs.BulkSetPreferences)
|
||||
|
||||
// User Settings & Profile
|
||||
settings := handlers.NewSettingsHandler()
|
||||
@@ -142,24 +153,31 @@ func main() {
|
||||
protected.GET("/settings", settings.GetSettings)
|
||||
protected.PUT("/settings", settings.UpdateSettings)
|
||||
|
||||
// Model Presets (user)
|
||||
presets := handlers.NewPresetHandler()
|
||||
protected.GET("/presets", presets.ListUserPresets)
|
||||
protected.POST("/presets", presets.CreateUserPreset)
|
||||
protected.PUT("/presets/:id", presets.UpdateUserPreset)
|
||||
protected.DELETE("/presets/:id", presets.DeleteUserPreset)
|
||||
// Personas (replaces /presets)
|
||||
personas := handlers.NewPersonaHandler(stores)
|
||||
protected.GET("/presets", personas.ListUserPersonas) // backward compat
|
||||
protected.POST("/presets", personas.CreateUserPersona)
|
||||
protected.PUT("/presets/:id", personas.UpdateUserPersona)
|
||||
protected.DELETE("/presets/:id", personas.DeleteUserPersona)
|
||||
protected.POST("/presets/:id/avatar", handlers.UploadPresetAvatar)
|
||||
protected.DELETE("/presets/:id/avatar", handlers.DeletePresetAvatar)
|
||||
|
||||
// Notes
|
||||
notes := handlers.NewNoteHandler()
|
||||
protected.GET("/notes", notes.List)
|
||||
protected.POST("/notes", notes.Create)
|
||||
protected.GET("/notes/search", notes.Search)
|
||||
protected.GET("/notes/folders", notes.ListFolders)
|
||||
protected.POST("/notes/bulk-delete", notes.BulkDelete)
|
||||
protected.GET("/notes/:id", notes.Get)
|
||||
protected.PUT("/notes/:id", notes.Update)
|
||||
protected.DELETE("/notes/:id", notes.Delete)
|
||||
|
||||
// Teams (user: my teams)
|
||||
teams := handlers.NewTeamHandler()
|
||||
protected.GET("/teams/mine", teams.MyTeams)
|
||||
|
||||
// Team admin self-service (requires team admin role, not sys-admin)
|
||||
// Team admin self-service
|
||||
teamScoped := protected.Group("/teams/:teamId")
|
||||
teamScoped.Use(middleware.RequireTeamAdmin())
|
||||
{
|
||||
@@ -176,22 +194,15 @@ func main() {
|
||||
teamScoped.DELETE("/providers/:id", teams.DeleteTeamProvider)
|
||||
teamScoped.GET("/providers/:id/models", teams.ListTeamProviderModels)
|
||||
|
||||
// Team presets
|
||||
teamPresets := handlers.NewPresetHandler()
|
||||
teamScoped.GET("/presets", teamPresets.ListTeamPresets)
|
||||
teamScoped.POST("/presets", teamPresets.CreateTeamPreset)
|
||||
teamScoped.DELETE("/presets/:id", teamPresets.DeleteTeamPreset)
|
||||
// Team personas
|
||||
teamPersonas := handlers.NewPersonaHandler(stores)
|
||||
teamScoped.GET("/presets", teamPersonas.ListTeamPersonas)
|
||||
teamScoped.POST("/presets", teamPersonas.CreateTeamPersona)
|
||||
teamScoped.DELETE("/presets/:id", teamPersonas.DeleteTeamPersona)
|
||||
}
|
||||
protected.POST("/notes", notes.Create)
|
||||
protected.GET("/notes/search", notes.Search)
|
||||
protected.GET("/notes/folders", notes.ListFolders)
|
||||
protected.POST("/notes/bulk-delete", notes.BulkDelete)
|
||||
protected.GET("/notes/:id", notes.Get)
|
||||
protected.PUT("/notes/:id", notes.Update)
|
||||
protected.DELETE("/notes/:id", notes.Delete)
|
||||
|
||||
// Public global settings (non-admin users can read safe subset)
|
||||
adm := handlers.NewAdminHandler()
|
||||
adm := handlers.NewAdminHandler(stores)
|
||||
protected.GET("/settings/public", adm.PublicSettings)
|
||||
}
|
||||
|
||||
@@ -200,7 +211,7 @@ func main() {
|
||||
admin.Use(middleware.Auth(cfg))
|
||||
admin.Use(middleware.RequireAdmin())
|
||||
{
|
||||
adm := handlers.NewAdminHandler()
|
||||
adm := handlers.NewAdminHandler(stores)
|
||||
|
||||
// User management
|
||||
admin.GET("/users", adm.ListUsers)
|
||||
@@ -218,35 +229,31 @@ func main() {
|
||||
// Stats
|
||||
admin.GET("/stats", adm.GetStats)
|
||||
|
||||
// Global API Configs
|
||||
// Global Provider Configs
|
||||
admin.GET("/configs", adm.ListGlobalConfigs)
|
||||
admin.POST("/configs", adm.CreateGlobalConfig)
|
||||
admin.PUT("/configs/:id", adm.UpdateGlobalConfig)
|
||||
admin.DELETE("/configs/:id", adm.DeleteGlobalConfig)
|
||||
|
||||
// Model Configs
|
||||
// Model Catalog
|
||||
admin.GET("/models", adm.ListModelConfigs)
|
||||
admin.POST("/models/fetch", adm.FetchModels)
|
||||
admin.PUT("/models/bulk", adm.BulkUpdateModels)
|
||||
admin.PUT("/models/:id", adm.UpdateModelConfig)
|
||||
admin.DELETE("/models/:id", adm.DeleteModelConfig)
|
||||
|
||||
// Model Presets (admin)
|
||||
presetAdm := handlers.NewPresetHandler()
|
||||
admin.GET("/presets", presetAdm.ListAdminPresets)
|
||||
admin.POST("/presets", presetAdm.CreateAdminPreset)
|
||||
admin.PUT("/presets/:id", presetAdm.UpdateAdminPreset)
|
||||
admin.DELETE("/presets/:id", presetAdm.DeleteAdminPreset)
|
||||
// Personas (admin global)
|
||||
personaAdm := handlers.NewPersonaHandler(stores)
|
||||
admin.GET("/presets", personaAdm.ListAdminPersonas)
|
||||
admin.POST("/presets", personaAdm.CreateAdminPersona)
|
||||
admin.PUT("/presets/:id", personaAdm.UpdateAdminPersona)
|
||||
admin.DELETE("/presets/:id", personaAdm.DeleteAdminPersona)
|
||||
admin.POST("/presets/:id/avatar", handlers.UploadPresetAvatar)
|
||||
admin.DELETE("/presets/:id/avatar", handlers.DeletePresetAvatar)
|
||||
|
||||
// Teams (admin)
|
||||
teamAdm := handlers.NewTeamHandler()
|
||||
admin.GET("/teams", teamAdm.ListTeams)
|
||||
|
||||
// Audit log
|
||||
admin.GET("/audit", adm.ListAuditLog)
|
||||
admin.GET("/audit/actions", adm.ListAuditActions)
|
||||
admin.POST("/teams", teamAdm.CreateTeam)
|
||||
admin.GET("/teams/:id", teamAdm.GetTeam)
|
||||
admin.PUT("/teams/:id", teamAdm.UpdateTeam)
|
||||
@@ -255,6 +262,10 @@ func main() {
|
||||
admin.POST("/teams/:id/members", teamAdm.AddMember)
|
||||
admin.PUT("/teams/:id/members/:memberId", teamAdm.UpdateMember)
|
||||
admin.DELETE("/teams/:id/members/:memberId", teamAdm.RemoveMember)
|
||||
|
||||
// Audit log
|
||||
admin.GET("/audit", adm.ListAuditLog)
|
||||
admin.GET("/audit/actions", adm.ListAuditActions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +282,3 @@ func main() {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func isRegistrationOpen() bool {
|
||||
return handlers.IsRegistrationEnabled()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user