Changeset 0.30.0 (#199)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
@@ -66,6 +66,23 @@ type PackageStore interface {
|
||||
|
||||
// DeleteUserSettings removes per-user settings, reverting to defaults.
|
||||
DeleteUserSettings(ctx context.Context, pkgID, userID string) error
|
||||
|
||||
// ── Scoped visibility (v0.30.0) ────────────────
|
||||
|
||||
// ListVisiblePackages returns packages visible to the given user:
|
||||
// global packages + team packages for user's teams + personal packages.
|
||||
ListVisiblePackages(ctx context.Context, userID string) ([]PackageRegistration, error)
|
||||
|
||||
// ── Package lifecycle (v0.30.0) ─────────────────
|
||||
|
||||
// SetSchemaVersion updates the current schema version for a package.
|
||||
SetSchemaVersion(ctx context.Context, id string, version int) error
|
||||
|
||||
// GetPackageSettings returns the admin-configured package-level settings.
|
||||
GetPackageSettings(ctx context.Context, id string) (json.RawMessage, error)
|
||||
|
||||
// SetPackageSettings stores admin-configured package-level settings.
|
||||
SetPackageSettings(ctx context.Context, id string, settings json.RawMessage) error
|
||||
}
|
||||
|
||||
// PackageRegistration is a row from the packages table.
|
||||
@@ -84,9 +101,11 @@ type PackageRegistration struct {
|
||||
Manifest map[string]any `json:"manifest" db:"manifest"`
|
||||
Enabled bool `json:"enabled" db:"enabled"`
|
||||
Status string `json:"status" db:"status"`
|
||||
Source string `json:"source" db:"source"`
|
||||
InstalledAt string `json:"installed_at" db:"installed_at"`
|
||||
UpdatedAt string `json:"updated_at" db:"updated_at"`
|
||||
SchemaVersion int `json:"schema_version" db:"schema_version"`
|
||||
PackageSettings json.RawMessage `json:"package_settings" db:"package_settings"`
|
||||
Source string `json:"source" db:"source"`
|
||||
InstalledAt string `json:"installed_at" db:"installed_at"`
|
||||
UpdatedAt string `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// UserPackage combines package info with per-user settings for API responses.
|
||||
|
||||
@@ -106,13 +106,16 @@ func (s *PackageStore) Create(ctx context.Context, pkg *store.PackageRegistratio
|
||||
}
|
||||
return DB.QueryRowContext(ctx, `
|
||||
INSERT INTO packages (id, title, type, version, description, author, tier,
|
||||
is_system, scope, team_id, installed_by, manifest, enabled, status, source)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
|
||||
is_system, scope, team_id, installed_by, manifest, enabled, status,
|
||||
schema_version, package_settings, source)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)
|
||||
RETURNING installed_at, updated_at`,
|
||||
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.Status, pkg.Source,
|
||||
manifestJSON, pkg.Enabled, pkg.Status,
|
||||
pkg.SchemaVersion, defaultJSON(pkg.PackageSettings),
|
||||
pkg.Source,
|
||||
).Scan(&pkg.InstalledAt, &pkg.UpdatedAt)
|
||||
}
|
||||
|
||||
@@ -154,6 +157,13 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
WHERE p.enabled = true
|
||||
AND p.type IN ('extension', 'full')
|
||||
AND (p.is_system = true OR COALESCE(pus.is_enabled, true) = true)
|
||||
AND (
|
||||
p.scope = 'global'
|
||||
OR (p.scope = 'personal' AND p.installed_by = $1)
|
||||
OR (p.scope = 'team' AND p.team_id IN (
|
||||
SELECT team_id FROM team_members WHERE user_id = $1
|
||||
))
|
||||
)
|
||||
ORDER BY p.title`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -165,6 +175,7 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
var up store.UserPackage
|
||||
var teamID, installedBy sql.NullString
|
||||
var manifestJSON []byte
|
||||
var pkgSettings []byte
|
||||
var userEnabled sql.NullBool
|
||||
var userSettings []byte
|
||||
|
||||
@@ -172,8 +183,9 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
&up.ID, &up.Title, &up.Type, &up.Version, &up.Description,
|
||||
&up.Author, &up.Tier, &up.IsSystem, &up.Scope,
|
||||
&teamID, &installedBy,
|
||||
&manifestJSON, &up.Enabled, &up.Status, &up.Source,
|
||||
&up.InstalledAt, &up.UpdatedAt,
|
||||
&manifestJSON, &up.Enabled, &up.Status,
|
||||
&up.SchemaVersion, &pkgSettings,
|
||||
&up.Source, &up.InstalledAt, &up.UpdatedAt,
|
||||
&userEnabled, &userSettings,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -181,6 +193,7 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
up.TeamID = NullableStringPtr(teamID)
|
||||
up.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal(manifestJSON, &up.Manifest)
|
||||
up.PackageSettings = json.RawMessage(pkgSettings)
|
||||
if userEnabled.Valid {
|
||||
up.UserEnabled = &userEnabled.Bool
|
||||
}
|
||||
@@ -235,18 +248,22 @@ func (s *PackageStore) DeleteUserSettings(ctx context.Context, pkgID, userID str
|
||||
// so column additions don't silently break positional Scan().
|
||||
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.status, p.source, p.installed_at, p.updated_at`
|
||||
p.manifest, p.enabled, p.status,
|
||||
p.schema_version, p.package_settings,
|
||||
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 []byte
|
||||
var pkgSettings []byte
|
||||
err := DB.QueryRowContext(ctx, query, args...).Scan(
|
||||
&pkg.ID, &pkg.Title, &pkg.Type, &pkg.Version, &pkg.Description,
|
||||
&pkg.Author, &pkg.Tier, &pkg.IsSystem, &pkg.Scope,
|
||||
&teamID, &installedBy,
|
||||
&manifestJSON, &pkg.Enabled, &pkg.Status, &pkg.Source,
|
||||
&pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
&manifestJSON, &pkg.Enabled, &pkg.Status,
|
||||
&pkg.SchemaVersion, &pkgSettings,
|
||||
&pkg.Source, &pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@@ -257,6 +274,7 @@ func (s *PackageStore) scanOne(ctx context.Context, query string, args ...interf
|
||||
pkg.TeamID = NullableStringPtr(teamID)
|
||||
pkg.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal(manifestJSON, &pkg.Manifest)
|
||||
pkg.PackageSettings = json.RawMessage(pkgSettings)
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
@@ -272,23 +290,83 @@ func (s *PackageStore) scanMany(ctx context.Context, query string, args ...inter
|
||||
var pkg store.PackageRegistration
|
||||
var teamID, installedBy sql.NullString
|
||||
var manifestJSON []byte
|
||||
var pkgSettings []byte
|
||||
if err := rows.Scan(
|
||||
&pkg.ID, &pkg.Title, &pkg.Type, &pkg.Version, &pkg.Description,
|
||||
&pkg.Author, &pkg.Tier, &pkg.IsSystem, &pkg.Scope,
|
||||
&teamID, &installedBy,
|
||||
&manifestJSON, &pkg.Enabled, &pkg.Status, &pkg.Source,
|
||||
&pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
&manifestJSON, &pkg.Enabled, &pkg.Status,
|
||||
&pkg.SchemaVersion, &pkgSettings,
|
||||
&pkg.Source, &pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg.TeamID = NullableStringPtr(teamID)
|
||||
pkg.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal(manifestJSON, &pkg.Manifest)
|
||||
pkg.PackageSettings = json.RawMessage(pkgSettings)
|
||||
result = append(result, pkg)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// ── Scoped visibility (v0.30.0) ──────────────────
|
||||
|
||||
func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) ([]store.PackageRegistration, error) {
|
||||
return s.scanMany(ctx, `
|
||||
SELECT `+pkgCols+`
|
||||
FROM packages p
|
||||
WHERE p.enabled = true
|
||||
AND (
|
||||
p.scope = 'global'
|
||||
OR (p.scope = 'personal' AND p.installed_by = $1)
|
||||
OR (p.scope = 'team' AND p.team_id IN (
|
||||
SELECT team_id FROM team_members WHERE user_id = $1
|
||||
))
|
||||
)
|
||||
ORDER BY p.source, p.title`, userID)
|
||||
}
|
||||
|
||||
// ── Package lifecycle (v0.30.0) ──────────────────
|
||||
|
||||
func (s *PackageStore) SetSchemaVersion(ctx context.Context, id string, version int) error {
|
||||
result, err := DB.ExecContext(ctx,
|
||||
`UPDATE packages SET schema_version = $2, updated_at = NOW() WHERE id = $1`,
|
||||
id, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, _ := result.RowsAffected()
|
||||
if affected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) GetPackageSettings(ctx context.Context, id string) (json.RawMessage, error) {
|
||||
var settings []byte
|
||||
err := DB.QueryRowContext(ctx,
|
||||
`SELECT package_settings FROM packages WHERE id = $1`, id).Scan(&settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(settings), nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) SetPackageSettings(ctx context.Context, id string, settings json.RawMessage) error {
|
||||
result, err := DB.ExecContext(ctx,
|
||||
`UPDATE packages SET package_settings = $2, updated_at = NOW() WHERE id = $1`,
|
||||
id, []byte(settings))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, _ := result.RowsAffected()
|
||||
if affected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nullStrPtr converts *string to sql.NullString for nullable FK columns.
|
||||
func nullStrPtr(s *string) sql.NullString {
|
||||
if s == nil {
|
||||
@@ -296,3 +374,11 @@ func nullStrPtr(s *string) sql.NullString {
|
||||
}
|
||||
return sql.NullString{String: *s, Valid: true}
|
||||
}
|
||||
|
||||
// defaultJSON returns the raw message or '{}' if nil/empty.
|
||||
func defaultJSON(raw json.RawMessage) []byte {
|
||||
if len(raw) == 0 {
|
||||
return []byte("{}")
|
||||
}
|
||||
return []byte(raw)
|
||||
}
|
||||
|
||||
@@ -111,13 +111,16 @@ func (s *PackageStore) Create(ctx context.Context, pkg *store.PackageRegistratio
|
||||
}
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
INSERT INTO packages (id, title, type, version, description, author, tier,
|
||||
is_system, scope, team_id, installed_by, manifest, enabled, status, source,
|
||||
is_system, scope, team_id, installed_by, manifest, enabled, status,
|
||||
schema_version, package_settings, source,
|
||||
installed_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
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.Status, pkg.Source,
|
||||
manifestJSON, pkg.Enabled, pkg.Status,
|
||||
pkg.SchemaVersion, defaultJSON(pkg.PackageSettings),
|
||||
pkg.Source,
|
||||
now.Format(timeFmt), now.Format(timeFmt),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -165,7 +168,14 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
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)
|
||||
AND (
|
||||
p.scope = 'global'
|
||||
OR (p.scope = 'personal' AND p.installed_by = ?)
|
||||
OR (p.scope = 'team' AND p.team_id IN (
|
||||
SELECT team_id FROM team_members WHERE user_id = ?
|
||||
))
|
||||
)
|
||||
ORDER BY p.title`, userID, userID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,6 +186,7 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
var up store.UserPackage
|
||||
var teamID, installedBy sql.NullString
|
||||
var manifestJSON string
|
||||
var pkgSettings string
|
||||
var enabledInt int
|
||||
var isSystemInt int
|
||||
var userEnabled sql.NullBool
|
||||
@@ -185,8 +196,9 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
&up.ID, &up.Title, &up.Type, &up.Version, &up.Description,
|
||||
&up.Author, &up.Tier, &isSystemInt, &up.Scope,
|
||||
&teamID, &installedBy,
|
||||
&manifestJSON, &enabledInt, &up.Status, &up.Source,
|
||||
&up.InstalledAt, &up.UpdatedAt,
|
||||
&manifestJSON, &enabledInt, &up.Status,
|
||||
&up.SchemaVersion, &pkgSettings,
|
||||
&up.Source, &up.InstalledAt, &up.UpdatedAt,
|
||||
&userEnabled, &userSettings,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
@@ -196,6 +208,7 @@ func (s *PackageStore) ListForUser(ctx context.Context, userID string) ([]store.
|
||||
up.TeamID = NullableStringPtr(teamID)
|
||||
up.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal([]byte(manifestJSON), &up.Manifest)
|
||||
up.PackageSettings = json.RawMessage(pkgSettings)
|
||||
if userEnabled.Valid {
|
||||
up.UserEnabled = &userEnabled.Bool
|
||||
}
|
||||
@@ -248,20 +261,24 @@ func (s *PackageStore) DeleteUserSettings(ctx context.Context, pkgID, userID str
|
||||
|
||||
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.status, p.source, p.installed_at, p.updated_at`
|
||||
p.manifest, p.enabled, p.status,
|
||||
p.schema_version, p.package_settings,
|
||||
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 pkgSettings 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.Status, &pkg.Source,
|
||||
&pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
&manifestJSON, &enabledInt, &pkg.Status,
|
||||
&pkg.SchemaVersion, &pkgSettings,
|
||||
&pkg.Source, &pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@@ -274,6 +291,7 @@ func (s *PackageStore) scanOne(ctx context.Context, query string, args ...interf
|
||||
pkg.TeamID = NullableStringPtr(teamID)
|
||||
pkg.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal([]byte(manifestJSON), &pkg.Manifest)
|
||||
pkg.PackageSettings = json.RawMessage(pkgSettings)
|
||||
return &pkg, nil
|
||||
}
|
||||
|
||||
@@ -289,14 +307,16 @@ func (s *PackageStore) scanMany(ctx context.Context, query string, args ...inter
|
||||
var pkg store.PackageRegistration
|
||||
var teamID, installedBy sql.NullString
|
||||
var manifestJSON string
|
||||
var pkgSettings 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.Status, &pkg.Source,
|
||||
&pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
&manifestJSON, &enabledInt, &pkg.Status,
|
||||
&pkg.SchemaVersion, &pkgSettings,
|
||||
&pkg.Source, &pkg.InstalledAt, &pkg.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -305,14 +325,80 @@ func (s *PackageStore) scanMany(ctx context.Context, query string, args ...inter
|
||||
pkg.TeamID = NullableStringPtr(teamID)
|
||||
pkg.InstalledBy = NullableStringPtr(installedBy)
|
||||
json.Unmarshal([]byte(manifestJSON), &pkg.Manifest)
|
||||
pkg.PackageSettings = json.RawMessage(pkgSettings)
|
||||
result = append(result, pkg)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// ── Scoped visibility (v0.30.0) ──────────────────
|
||||
|
||||
func (s *PackageStore) ListVisiblePackages(ctx context.Context, userID string) ([]store.PackageRegistration, error) {
|
||||
return s.scanMany(ctx, `
|
||||
SELECT `+pkgCols+`
|
||||
FROM packages p
|
||||
WHERE p.enabled = 1
|
||||
AND (
|
||||
p.scope = 'global'
|
||||
OR (p.scope = 'personal' AND p.installed_by = ?)
|
||||
OR (p.scope = 'team' AND p.team_id IN (
|
||||
SELECT team_id FROM team_members WHERE user_id = ?
|
||||
))
|
||||
)
|
||||
ORDER BY p.source, p.title`, userID, userID)
|
||||
}
|
||||
|
||||
// ── Package lifecycle (v0.30.0) ──────────────────
|
||||
|
||||
func (s *PackageStore) SetSchemaVersion(ctx context.Context, id string, version int) error {
|
||||
result, err := DB.ExecContext(ctx,
|
||||
`UPDATE packages SET schema_version = ?, updated_at = datetime('now') WHERE id = ?`,
|
||||
version, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, _ := result.RowsAffected()
|
||||
if affected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) GetPackageSettings(ctx context.Context, id string) (json.RawMessage, error) {
|
||||
var settings string
|
||||
err := DB.QueryRowContext(ctx,
|
||||
`SELECT package_settings FROM packages WHERE id = ?`, id).Scan(&settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.RawMessage(settings), nil
|
||||
}
|
||||
|
||||
func (s *PackageStore) SetPackageSettings(ctx context.Context, id string, settings json.RawMessage) error {
|
||||
result, err := DB.ExecContext(ctx,
|
||||
`UPDATE packages SET package_settings = ?, updated_at = datetime('now') WHERE id = ?`,
|
||||
string(settings), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, _ := result.RowsAffected()
|
||||
if affected == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nullStrPtr(s *string) sql.NullString {
|
||||
if s == nil {
|
||||
return sql.NullString{}
|
||||
}
|
||||
return sql.NullString{String: *s, Valid: true}
|
||||
}
|
||||
|
||||
// defaultJSON returns the raw message or '{}' if nil/empty.
|
||||
func defaultJSON(raw json.RawMessage) string {
|
||||
if len(raw) == 0 {
|
||||
return "{}"
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user