Changeset 0.8.5 (#48)
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
-- ==========================================
|
||||
-- Migration 019: (superseded by 020)
|
||||
-- ==========================================
|
||||
-- Original CREATE TABLE IF NOT EXISTS was a no-op because
|
||||
-- user_model_preferences already existed from migration 005.
|
||||
-- The actual rework is in 020_user_model_preferences_rework.sql.
|
||||
-- ==========================================
|
||||
SELECT 1;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- ==========================================
|
||||
-- Migration 019: User Model Preferences (rework)
|
||||
-- ==========================================
|
||||
-- The user_model_preferences table was created in 005 with:
|
||||
-- id UUID PK, user_id UUID, model_config_id UUID FK, is_enabled BOOL
|
||||
-- That schema ties preferences to model_configs rows (global only).
|
||||
-- We need string-based model_id to support personal provider models too,
|
||||
-- plus a 'hidden' column with clearer semantics.
|
||||
--
|
||||
-- Strategy: add new columns, add unique constraint for UPSERT.
|
||||
-- Old columns (model_config_id, is_enabled) remain for backward compat.
|
||||
-- ==========================================
|
||||
|
||||
-- Add new columns if they don't exist
|
||||
ALTER TABLE user_model_preferences ADD COLUMN IF NOT EXISTS model_id VARCHAR(255);
|
||||
ALTER TABLE user_model_preferences ADD COLUMN IF NOT EXISTS hidden BOOLEAN DEFAULT false;
|
||||
ALTER TABLE user_model_preferences ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();
|
||||
|
||||
-- Make model_config_id nullable (new rows use model_id instead)
|
||||
ALTER TABLE user_model_preferences ALTER COLUMN model_config_id DROP NOT NULL;
|
||||
|
||||
-- Unique constraint for UPSERT — NULLs are distinct in PG so old rows won't conflict
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_model_pref_user_model
|
||||
ON user_model_preferences (user_id, model_id);
|
||||
125
server/handlers/model_prefs.go
Normal file
125
server/handlers/model_prefs.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetModelPreferences returns the user's hidden model list.
|
||||
// GET /api/v1/models/preferences
|
||||
func GetModelPreferences(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
|
||||
rows, err := database.DB.Query(`
|
||||
SELECT model_id, hidden FROM user_model_preferences
|
||||
WHERE user_id = $1
|
||||
`, userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to query preferences"})
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type pref struct {
|
||||
ModelID string `json:"model_id"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
prefs := make([]pref, 0)
|
||||
for rows.Next() {
|
||||
var p pref
|
||||
if err := rows.Scan(&p.ModelID, &p.Hidden); err != nil {
|
||||
continue
|
||||
}
|
||||
prefs = append(prefs, p)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"preferences": prefs})
|
||||
}
|
||||
|
||||
// SetModelPreference sets hidden state for a single model.
|
||||
// PUT /api/v1/models/preferences
|
||||
func SetModelPreference(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
|
||||
var req struct {
|
||||
ModelID string `json:"model_id" binding:"required"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
_, err := database.DB.Exec(`
|
||||
INSERT INTO user_model_preferences (user_id, model_id, hidden, updated_at)
|
||||
VALUES ($1, $2, $3, NOW())
|
||||
ON CONFLICT (user_id, model_id)
|
||||
DO UPDATE SET hidden = EXCLUDED.hidden, updated_at = NOW()
|
||||
`, userID, req.ModelID, req.Hidden)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] Failed to save model preference for user %s, model %s: %v", userID, req.ModelID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save preference: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"model_id": req.ModelID, "hidden": req.Hidden})
|
||||
}
|
||||
|
||||
// BulkSetModelPreferences sets hidden state for multiple models at once.
|
||||
// POST /api/v1/models/preferences/bulk
|
||||
func BulkSetModelPreferences(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
|
||||
var req struct {
|
||||
ModelIDs []string `json:"model_ids" binding:"required"`
|
||||
Hidden bool `json:"hidden"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.ModelIDs) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"updated": 0})
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to begin transaction"})
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
stmt, err := tx.Prepare(`
|
||||
INSERT INTO user_model_preferences (user_id, model_id, hidden, updated_at)
|
||||
VALUES ($1, $2, $3, NOW())
|
||||
ON CONFLICT (user_id, model_id)
|
||||
DO UPDATE SET hidden = EXCLUDED.hidden, updated_at = NOW()
|
||||
`)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to prepare statement"})
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
updated := 0
|
||||
for _, modelID := range req.ModelIDs {
|
||||
if _, err := stmt.Exec(userID, modelID, req.Hidden); err != nil {
|
||||
log.Printf("[WARN] Failed to save preference for model %s: %v", modelID, err)
|
||||
continue
|
||||
}
|
||||
updated++
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to commit"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"updated": updated})
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -110,12 +111,6 @@ func (h *PresetHandler) ListUserPresets(c *gin.Context) {
|
||||
func (h *PresetHandler) CreateUserPreset(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
|
||||
// Gate: personal presets require user_providers_enabled
|
||||
if !isUserProvidersEnabled() {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "personal presets are disabled by admin"})
|
||||
return
|
||||
}
|
||||
|
||||
var req createPresetRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
@@ -159,7 +154,8 @@ func (h *PresetHandler) CreateUserPreset(c *gin.Context) {
|
||||
).Scan(&id)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create preset"})
|
||||
log.Printf("[WARN] Failed to create user preset: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create preset: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,9 @@ func main() {
|
||||
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)
|
||||
|
||||
// User Settings & Profile
|
||||
settings := handlers.NewSettingsHandler()
|
||||
|
||||
Reference in New Issue
Block a user