Changeset 0.28.7 (#193)
This commit is contained in:
301
server/store/sqlite/packages.go
Normal file
301
server/store/sqlite/packages.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
|
||||
type PackageStore struct{}
|
||||
|
||||
func NewPackageStore() *PackageStore { return &PackageStore{} }
|
||||
|
||||
// ── Lifecycle ────────────────────────────────────
|
||||
|
||||
func (s *PackageStore) Seed(ctx context.Context, id, title, source string, manifest map[string]any) error {
|
||||
manifestJSON := ToJSON(manifest)
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
INSERT INTO packages (id, title, source, manifest, enabled, installed_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, 1, datetime('now'), datetime('now'))
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = excluded.title,
|
||||
manifest = excluded.manifest,
|
||||
source = excluded.source,
|
||||
updated_at = datetime('now')`,
|
||||
id, title, source, manifestJSON)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *PackageStore) List(ctx context.Context) ([]store.PackageRegistration, error) {
|
||||
return s.scanMany(ctx, `SELECT `+pkgCols+` FROM packages p ORDER BY p.source, p.title`)
|
||||
}
|
||||
|
||||
func (s *PackageStore) Get(ctx context.Context, id string) (*store.PackageRegistration, error) {
|
||||
return s.scanOne(ctx, `SELECT `+pkgCols+` FROM packages p WHERE p.id = ?`, id)
|
||||
}
|
||||
|
||||
func (s *PackageStore) SetEnabled(ctx context.Context, id string, enabled bool) error {
|
||||
enabledInt := 0
|
||||
if enabled {
|
||||
enabledInt = 1
|
||||
}
|
||||
result, err := DB.ExecContext(ctx,
|
||||
`UPDATE packages SET enabled = ?, updated_at = datetime('now') WHERE id = ?`,
|
||||
enabledInt, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, _ := result.RowsAffected()
|
||||
if affected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) Delete(ctx context.Context, id string) error {
|
||||
result, err := DB.ExecContext(ctx,
|
||||
`DELETE FROM packages WHERE id = ? AND source != 'core'`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, _ := result.RowsAffected()
|
||||
if affected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) ListEnabled(ctx context.Context) ([]string, error) {
|
||||
rows, err := DB.QueryContext(ctx,
|
||||
`SELECT id FROM packages WHERE enabled = 1 ORDER BY title`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
// ── Extended lifecycle ───────────────────────────
|
||||
|
||||
func (s *PackageStore) Create(ctx context.Context, pkg *store.PackageRegistration) error {
|
||||
now := time.Now().UTC()
|
||||
manifestJSON := ToJSON(pkg.Manifest)
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
INSERT INTO packages (id, title, type, version, description, author, tier,
|
||||
is_system, scope, team_id, installed_by, manifest, enabled, source,
|
||||
installed_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
pkg.ID, pkg.Title, pkg.Type, pkg.Version, pkg.Description, pkg.Author,
|
||||
pkg.Tier, pkg.IsSystem, pkg.Scope,
|
||||
nullStrPtr(pkg.TeamID), nullStrPtr(pkg.InstalledBy),
|
||||
manifestJSON, pkg.Enabled, pkg.Source,
|
||||
now.Format(timeFmt), now.Format(timeFmt),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkg.InstalledAt = now.Format(timeFmt)
|
||||
pkg.UpdatedAt = now.Format(timeFmt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) Update(ctx context.Context, id string, pkg *store.PackageRegistration) error {
|
||||
manifestJSON := ToJSON(pkg.Manifest)
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
UPDATE packages SET
|
||||
title = ?, type = ?, version = ?, description = ?, author = ?,
|
||||
tier = ?, manifest = ?, is_system = ?, enabled = ?,
|
||||
updated_at = datetime('now')
|
||||
WHERE id = ?`,
|
||||
pkg.Title, pkg.Type, pkg.Version, pkg.Description, pkg.Author,
|
||||
pkg.Tier, manifestJSON, pkg.IsSystem, pkg.Enabled, id,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *PackageStore) ListByType(ctx context.Context, pkgType string) ([]store.PackageRegistration, error) {
|
||||
return s.scanMany(ctx, `SELECT `+pkgCols+` FROM packages p WHERE p.type = ? ORDER BY p.title`, pkgType)
|
||||
}
|
||||
|
||||
func (s *PackageStore) ListEnabledByType(ctx context.Context, pkgType string) ([]store.PackageRegistration, error) {
|
||||
return s.scanMany(ctx,
|
||||
`SELECT `+pkgCols+` FROM packages p WHERE p.type = ? AND p.enabled = 1 ORDER BY p.title`,
|
||||
pkgType)
|
||||
}
|
||||
|
||||
// ── Extension runtime ────────────────────────────
|
||||
|
||||
func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.UserPackage, error) {
|
||||
rows, err := DB.QueryContext(ctx, `
|
||||
SELECT `+pkgCols+`,
|
||||
pus.is_enabled AS user_enabled,
|
||||
pus.settings AS user_settings
|
||||
FROM packages p
|
||||
LEFT JOIN package_user_settings pus
|
||||
ON pus.package_id = p.id AND pus.user_id = ?
|
||||
WHERE p.enabled = 1
|
||||
AND p.type IN ('extension', 'full')
|
||||
AND (p.is_system = 1 OR COALESCE(pus.is_enabled, 1) = 1)
|
||||
ORDER BY p.title`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []store.UserPackage
|
||||
for rows.Next() {
|
||||
var up store.UserPackage
|
||||
var teamID, installedBy sql.NullString
|
||||
var manifestJSON string
|
||||
var enabledInt int
|
||||
var isSystemInt int
|
||||
var userEnabled sql.NullBool
|
||||
var userSettings []byte
|
||||
|
||||
if err := rows.Scan(
|
||||
&up.ID, &up.Title, &up.Type, &up.Version, &up.Description,
|
||||
&up.Author, &up.Tier, &isSystemInt, &up.Scope,
|
||||
&teamID, &installedBy,
|
||||
&manifestJSON, &enabledInt, &up.Source,
|
||||
&up.InstalledAt, &up.UpdatedAt,
|
||||
&userEnabled, &userSettings,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
up.IsSystem = isSystemInt != 0
|
||||
up.Enabled = enabledInt != 0
|
||||
up.TeamID = NullableStringPtr(teamID)
|
||||
up.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal([]byte(manifestJSON), &up.Manifest)
|
||||
if userEnabled.Valid {
|
||||
up.UserEnabled = &userEnabled.Bool
|
||||
}
|
||||
if userSettings != nil {
|
||||
raw := json.RawMessage(userSettings)
|
||||
up.UserSettings = &raw
|
||||
}
|
||||
result = append(result, up)
|
||||
}
|
||||
if result == nil {
|
||||
result = make([]store.UserPackage, 0)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (s *PackageStore) GetUserSettings(ctx context.Context, pkgID, userID string) (*store.PackageUserSettings, error) {
|
||||
var pus store.PackageUserSettings
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
SELECT package_id, user_id, settings, is_enabled
|
||||
FROM package_user_settings
|
||||
WHERE package_id = ? AND user_id = ?`,
|
||||
pkgID, userID,
|
||||
).Scan(&pus.PackageID, &pus.UserID, &pus.Settings, &pus.IsEnabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pus, nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) SetUserSettings(ctx context.Context, pus *store.PackageUserSettings) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
INSERT INTO package_user_settings (package_id, user_id, settings, is_enabled)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT (package_id, user_id)
|
||||
DO UPDATE SET settings = EXCLUDED.settings, is_enabled = EXCLUDED.is_enabled`,
|
||||
pus.PackageID, pus.UserID, pus.Settings, pus.IsEnabled,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *PackageStore) DeleteUserSettings(ctx context.Context, pkgID, userID string) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
DELETE FROM package_user_settings WHERE package_id = ? AND user_id = ?`,
|
||||
pkgID, userID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────
|
||||
|
||||
const pkgCols = `p.id, p.title, p.type, p.version, p.description, p.author,
|
||||
p.tier, p.is_system, p.scope, p.team_id, p.installed_by,
|
||||
p.manifest, p.enabled, p.source, p.installed_at, p.updated_at`
|
||||
|
||||
func (s *PackageStore) scanOne(ctx context.Context, query string, args ...interface{}) (*store.PackageRegistration, error) {
|
||||
var pkg store.PackageRegistration
|
||||
var teamID, installedBy sql.NullString
|
||||
var manifestJSON string
|
||||
var enabledInt int
|
||||
var isSystemInt int
|
||||
err := DB.QueryRowContext(ctx, query, args...).Scan(
|
||||
&pkg.ID, &pkg.Title, &pkg.Type, &pkg.Version, &pkg.Description,
|
||||
&pkg.Author, &pkg.Tier, &isSystemInt, &pkg.Scope,
|
||||
&teamID, &installedBy,
|
||||
&manifestJSON, &enabledInt, &pkg.Source,
|
||||
&pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg.IsSystem = isSystemInt != 0
|
||||
pkg.Enabled = enabledInt != 0
|
||||
pkg.TeamID = NullableStringPtr(teamID)
|
||||
pkg.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal([]byte(manifestJSON), &pkg.Manifest)
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) scanMany(ctx context.Context, query string, args ...interface{}) ([]store.PackageRegistration, error) {
|
||||
rows, err := DB.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []store.PackageRegistration
|
||||
for rows.Next() {
|
||||
var pkg store.PackageRegistration
|
||||
var teamID, installedBy sql.NullString
|
||||
var manifestJSON string
|
||||
var enabledInt int
|
||||
var isSystemInt int
|
||||
if err := rows.Scan(
|
||||
&pkg.ID, &pkg.Title, &pkg.Type, &pkg.Version, &pkg.Description,
|
||||
&pkg.Author, &pkg.Tier, &isSystemInt, &pkg.Scope,
|
||||
&teamID, &installedBy,
|
||||
&manifestJSON, &enabledInt, &pkg.Source,
|
||||
&pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg.IsSystem = isSystemInt != 0
|
||||
pkg.Enabled = enabledInt != 0
|
||||
pkg.TeamID = NullableStringPtr(teamID)
|
||||
pkg.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal([]byte(manifestJSON), &pkg.Manifest)
|
||||
result = append(result, pkg)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func nullStrPtr(s *string) sql.NullString {
|
||||
if s == nil {
|
||||
return sql.NullString{}
|
||||
}
|
||||
return sql.NullString{String: *s, Valid: true}
|
||||
}
|
||||
Reference in New Issue
Block a user