Changeset 0.28.0.6 (#178)
This commit is contained in:
1027
server/handlers/extension_test.go
Normal file
1027
server/handlers/extension_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,11 @@ package handlers
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
@@ -19,6 +21,13 @@ func NewExtensionHandler(stores store.Stores) *ExtensionHandler {
|
||||
return &ExtensionHandler{stores: stores}
|
||||
}
|
||||
|
||||
// validTiers is the set of accepted extension tier values.
|
||||
var validTiers = map[string]bool{
|
||||
models.ExtTierBrowser: true,
|
||||
models.ExtTierStarlark: true,
|
||||
models.ExtTierSidecar: true,
|
||||
}
|
||||
|
||||
// ── User endpoints ──────────────────────────────
|
||||
|
||||
// ListUserExtensions returns all enabled extensions for the current user,
|
||||
@@ -147,6 +156,12 @@ func (h *ExtensionHandler) AdminInstallExtension(c *gin.Context) {
|
||||
body.Manifest = json.RawMessage("{}")
|
||||
}
|
||||
|
||||
// Validate tier
|
||||
if !validTiers[body.Tier] {
|
||||
c.JSON(400, gin.H{"error": "invalid tier: must be browser, starlark, or sidecar"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check for duplicate ext_id
|
||||
existing, err := h.stores.Extensions.GetByExtID(c.Request.Context(), body.ExtID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
@@ -173,6 +188,10 @@ func (h *ExtensionHandler) AdminInstallExtension(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := h.stores.Extensions.Create(c.Request.Context(), ext); err != nil {
|
||||
if database.IsUniqueViolation(err) {
|
||||
c.JSON(409, gin.H{"error": "extension with ext_id '" + body.ExtID + "' already installed"})
|
||||
return
|
||||
}
|
||||
c.JSON(500, gin.H{"error": "failed to install extension"})
|
||||
return
|
||||
}
|
||||
@@ -197,7 +216,9 @@ func (h *ExtensionHandler) AdminUpdateExtension(c *gin.Context) {
|
||||
|
||||
var body struct {
|
||||
Name *string `json:"name"`
|
||||
Version *string `json:"version"`
|
||||
Description *string `json:"description"`
|
||||
Author *string `json:"author"`
|
||||
IsSystem *bool `json:"is_system"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
Manifest *json.RawMessage `json:"manifest"`
|
||||
@@ -210,9 +231,15 @@ func (h *ExtensionHandler) AdminUpdateExtension(c *gin.Context) {
|
||||
if body.Name != nil {
|
||||
ext.Name = *body.Name
|
||||
}
|
||||
if body.Version != nil {
|
||||
ext.Version = *body.Version
|
||||
}
|
||||
if body.Description != nil {
|
||||
ext.Description = *body.Description
|
||||
}
|
||||
if body.Author != nil {
|
||||
ext.Author = *body.Author
|
||||
}
|
||||
if body.IsSystem != nil {
|
||||
ext.IsSystem = *body.IsSystem
|
||||
}
|
||||
@@ -241,10 +268,14 @@ func (h *ExtensionHandler) ServeExtensionAsset(c *gin.Context) {
|
||||
// path := c.Param("path") // reserved for future multi-file support
|
||||
|
||||
ext, err := h.stores.Extensions.GetByExtID(c.Request.Context(), extID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(404, gin.H{"error": "extension not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "failed to fetch extension"})
|
||||
return
|
||||
}
|
||||
|
||||
if !ext.IsEnabled {
|
||||
c.JSON(404, gin.H{"error": "extension not enabled"})
|
||||
@@ -282,10 +313,14 @@ func (h *ExtensionHandler) GetExtensionManifest(c *gin.Context) {
|
||||
extID := c.Param("id")
|
||||
|
||||
ext, err := h.stores.Extensions.GetByExtID(c.Request.Context(), extID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(404, gin.H{"error": "extension not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "failed to fetch extension"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{"data": ext.Manifest})
|
||||
}
|
||||
@@ -311,6 +346,7 @@ func (h *ExtensionHandler) ListBrowserToolSchemas(c *gin.Context) {
|
||||
Tools []json.RawMessage `json:"tools"`
|
||||
}
|
||||
if err := json.Unmarshal(ext.Manifest, &manifest); err != nil {
|
||||
log.Printf("[extensions] failed to parse manifest for %s: %v", ext.ExtID, err)
|
||||
continue
|
||||
}
|
||||
toolSchemas = append(toolSchemas, manifest.Tools...)
|
||||
|
||||
@@ -54,6 +54,8 @@ func (s *ExtensionStore) ListAll(ctx context.Context) ([]models.Extension, error
|
||||
return s.scanMany(ctx, `SELECT * FROM extensions ORDER BY name`)
|
||||
}
|
||||
|
||||
// ListEnabled returns all globally enabled extensions regardless of user.
|
||||
// TODO(v0.29.0): Used by Starlark runtime to load server-side extensions at startup.
|
||||
func (s *ExtensionStore) ListEnabled(ctx context.Context) ([]models.Extension, error) {
|
||||
return s.scanMany(ctx, `SELECT * FROM extensions WHERE is_enabled = true ORDER BY name`)
|
||||
}
|
||||
@@ -112,6 +114,8 @@ func (s *ExtensionStore) ListForUser(ctx context.Context, userID string) ([]mode
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// GetUserSettings returns per-user settings for a specific extension.
|
||||
// TODO(v0.29.0): Used by Starlark runtime to load per-user config for server-side extensions.
|
||||
func (s *ExtensionStore) GetUserSettings(ctx context.Context, extID, userID string) (*models.ExtensionUserSettings, error) {
|
||||
var eus models.ExtensionUserSettings
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
@@ -137,6 +141,8 @@ func (s *ExtensionStore) SetUserSettings(ctx context.Context, eus *models.Extens
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUserSettings removes per-user settings, reverting to defaults.
|
||||
// TODO(v0.29.0): Exposed via user settings UI when Starlark extensions have per-user config.
|
||||
func (s *ExtensionStore) DeleteUserSettings(ctx context.Context, extID, userID string) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
DELETE FROM extension_user_settings WHERE extension_id = $1 AND user_id = $2`,
|
||||
|
||||
@@ -61,6 +61,8 @@ func (s *ExtensionStore) ListAll(ctx context.Context) ([]models.Extension, error
|
||||
return s.scanMany(ctx, `SELECT * FROM extensions ORDER BY name`)
|
||||
}
|
||||
|
||||
// ListEnabled returns all globally enabled extensions regardless of user.
|
||||
// TODO(v0.29.0): Used by Starlark runtime to load server-side extensions at startup.
|
||||
func (s *ExtensionStore) ListEnabled(ctx context.Context) ([]models.Extension, error) {
|
||||
return s.scanMany(ctx, `SELECT * FROM extensions WHERE is_enabled = 1 ORDER BY name`)
|
||||
}
|
||||
@@ -119,6 +121,8 @@ func (s *ExtensionStore) ListForUser(ctx context.Context, userID string) ([]mode
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// GetUserSettings returns per-user settings for a specific extension.
|
||||
// TODO(v0.29.0): Used by Starlark runtime to load per-user config for server-side extensions.
|
||||
func (s *ExtensionStore) GetUserSettings(ctx context.Context, extID, userID string) (*models.ExtensionUserSettings, error) {
|
||||
var eus models.ExtensionUserSettings
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
@@ -144,6 +148,8 @@ func (s *ExtensionStore) SetUserSettings(ctx context.Context, eus *models.Extens
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUserSettings removes per-user settings, reverting to defaults.
|
||||
// TODO(v0.29.0): Exposed via user settings UI when Starlark extensions have per-user config.
|
||||
func (s *ExtensionStore) DeleteUserSettings(ctx context.Context, extID, userID string) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
DELETE FROM extension_user_settings WHERE extension_id = ? AND user_id = ?`,
|
||||
|
||||
Reference in New Issue
Block a user