Changeset 0.11.0 (#62)
This commit is contained in:
194
server/store/postgres/extension.go
Normal file
194
server/store/postgres/extension.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
)
|
||||
|
||||
type ExtensionStore struct{}
|
||||
|
||||
func NewExtensionStore() *ExtensionStore { return &ExtensionStore{} }
|
||||
|
||||
func (s *ExtensionStore) Create(ctx context.Context, ext *models.Extension) error {
|
||||
return DB.QueryRowContext(ctx, `
|
||||
INSERT INTO extensions (ext_id, name, version, tier, description, author,
|
||||
manifest, is_system, is_enabled, scope, team_id, installed_by)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
|
||||
RETURNING id, created_at, updated_at`,
|
||||
ext.ExtID, ext.Name, ext.Version, ext.Tier, ext.Description, ext.Author,
|
||||
ext.Manifest, ext.IsSystem, ext.IsEnabled, ext.Scope,
|
||||
models.NullString(ext.TeamID), models.NullString(ext.InstalledBy),
|
||||
).Scan(&ext.ID, &ext.CreatedAt, &ext.UpdatedAt)
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) GetByID(ctx context.Context, id string) (*models.Extension, error) {
|
||||
return s.scanOne(ctx, `SELECT * FROM extensions WHERE id = $1`, id)
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) GetByExtID(ctx context.Context, extID string) (*models.Extension, error) {
|
||||
return s.scanOne(ctx, `SELECT * FROM extensions WHERE ext_id = $1`, extID)
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) Update(ctx context.Context, id string, ext *models.Extension) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
UPDATE extensions SET
|
||||
name = $2, version = $3, description = $4, author = $5,
|
||||
manifest = $6, is_system = $7, is_enabled = $8,
|
||||
updated_at = now()
|
||||
WHERE id = $1`,
|
||||
id, ext.Name, ext.Version, ext.Description, ext.Author,
|
||||
ext.Manifest, ext.IsSystem, ext.IsEnabled,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) Delete(ctx context.Context, id string) error {
|
||||
_, err := DB.ExecContext(ctx, `DELETE FROM extensions WHERE id = $1`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) ListAll(ctx context.Context) ([]models.Extension, error) {
|
||||
return s.scanMany(ctx, `SELECT * FROM extensions ORDER BY name`)
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) ListEnabled(ctx context.Context) ([]models.Extension, error) {
|
||||
return s.scanMany(ctx, `SELECT * FROM extensions WHERE is_enabled = true ORDER BY name`)
|
||||
}
|
||||
|
||||
// ListForUser returns all enabled extensions with user-specific overrides merged in.
|
||||
// System extensions are always included (users can't disable them).
|
||||
// Non-system extensions respect per-user enabled toggle.
|
||||
func (s *ExtensionStore) ListForUser(ctx context.Context, userID string) ([]models.UserExtension, error) {
|
||||
rows, err := DB.QueryContext(ctx, `
|
||||
SELECT e.id, e.ext_id, e.name, e.version, e.tier, e.description, e.author,
|
||||
e.manifest, e.is_system, e.is_enabled, e.scope, e.team_id, e.installed_by,
|
||||
e.created_at, e.updated_at,
|
||||
eus.is_enabled AS user_enabled,
|
||||
eus.settings AS user_settings
|
||||
FROM extensions e
|
||||
LEFT JOIN extension_user_settings eus
|
||||
ON eus.extension_id = e.id AND eus.user_id = $1
|
||||
WHERE e.is_enabled = true
|
||||
AND (e.is_system = true OR COALESCE(eus.is_enabled, true) = true)
|
||||
ORDER BY e.name`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []models.UserExtension
|
||||
for rows.Next() {
|
||||
var ue models.UserExtension
|
||||
var teamID, installedBy sql.NullString
|
||||
var userEnabled sql.NullBool
|
||||
var userSettings []byte
|
||||
|
||||
if err := rows.Scan(
|
||||
&ue.ID, &ue.ExtID, &ue.Name, &ue.Version, &ue.Tier,
|
||||
&ue.Description, &ue.Author, &ue.Manifest, &ue.IsSystem,
|
||||
&ue.IsEnabled, &ue.Scope, &teamID, &installedBy,
|
||||
&ue.CreatedAt, &ue.UpdatedAt,
|
||||
&userEnabled, &userSettings,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ue.TeamID = NullableStringPtr(teamID)
|
||||
ue.InstalledBy = NullableStringPtr(installedBy)
|
||||
if userEnabled.Valid {
|
||||
ue.UserEnabled = &userEnabled.Bool
|
||||
}
|
||||
if userSettings != nil {
|
||||
raw := json.RawMessage(userSettings)
|
||||
ue.UserSettings = &raw
|
||||
}
|
||||
result = append(result, ue)
|
||||
}
|
||||
if result == nil {
|
||||
result = make([]models.UserExtension, 0)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) GetUserSettings(ctx context.Context, extID, userID string) (*models.ExtensionUserSettings, error) {
|
||||
var eus models.ExtensionUserSettings
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
SELECT extension_id, user_id, settings, is_enabled
|
||||
FROM extension_user_settings
|
||||
WHERE extension_id = $1 AND user_id = $2`,
|
||||
extID, userID,
|
||||
).Scan(&eus.ExtensionID, &eus.UserID, &eus.Settings, &eus.IsEnabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &eus, nil
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) SetUserSettings(ctx context.Context, eus *models.ExtensionUserSettings) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
INSERT INTO extension_user_settings (extension_id, user_id, settings, is_enabled)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (extension_id, user_id)
|
||||
DO UPDATE SET settings = EXCLUDED.settings, is_enabled = EXCLUDED.is_enabled`,
|
||||
eus.ExtensionID, eus.UserID, eus.Settings, eus.IsEnabled,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
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`,
|
||||
extID, userID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// ── Internal helpers ────────────────────────────
|
||||
|
||||
func (s *ExtensionStore) scanOne(ctx context.Context, query string, args ...interface{}) (*models.Extension, error) {
|
||||
var ext models.Extension
|
||||
var teamID, installedBy sql.NullString
|
||||
err := DB.QueryRowContext(ctx, query, args...).Scan(
|
||||
&ext.ID, &ext.ExtID, &ext.Name, &ext.Version, &ext.Tier,
|
||||
&ext.Description, &ext.Author, &ext.Manifest, &ext.IsSystem,
|
||||
&ext.IsEnabled, &ext.Scope, &teamID, &installedBy,
|
||||
&ext.CreatedAt, &ext.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ext.TeamID = NullableStringPtr(teamID)
|
||||
ext.InstalledBy = NullableStringPtr(installedBy)
|
||||
return &ext, nil
|
||||
}
|
||||
|
||||
func (s *ExtensionStore) scanMany(ctx context.Context, query string, args ...interface{}) ([]models.Extension, error) {
|
||||
rows, err := DB.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []models.Extension
|
||||
for rows.Next() {
|
||||
var ext models.Extension
|
||||
var teamID, installedBy sql.NullString
|
||||
if err := rows.Scan(
|
||||
&ext.ID, &ext.ExtID, &ext.Name, &ext.Version, &ext.Tier,
|
||||
&ext.Description, &ext.Author, &ext.Manifest, &ext.IsSystem,
|
||||
&ext.IsEnabled, &ext.Scope, &teamID, &installedBy,
|
||||
&ext.CreatedAt, &ext.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ext.TeamID = NullableStringPtr(teamID)
|
||||
ext.InstalledBy = NullableStringPtr(installedBy)
|
||||
result = append(result, ext)
|
||||
}
|
||||
if result == nil {
|
||||
result = make([]models.Extension, 0)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user