Files
Agent-System-Backend/internal/store/config.go
T
weichen 72e6ce66b4 Advance the core gitlink and serialize empty lists as arrays
Why: backend main pinned core 019617e (unfetchable post-migration) and the
store List* functions returned nil slices that encode as JSON null, which
crashed the frontend nav with .map on null (the 2026-09-30 white-screen).
Evidence: go test ./... across all packages; store regression
TestEmptyListsSerializeAsEmptyArrays asserts [] for empty project lists.
Impact: all 17 store list sites now initialize empty slices; build-blocker
core pin resolved; frontend no longer receives null for empty lists.
Refs: documents/records/2026-09-30-history-migration.md
2026-09-30 03:20:57 +00:00

151 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import (
"database/sql"
"errors"
core "agent-system-core"
)
// ---- 画像(builtin=owner'' 共享;用户画像按 owner 隔离)----
func (s *Store) SaveProfile(p core.AgentProfile) error {
_, err := s.db.Exec(s.q(`INSERT INTO profiles(id,owner,builtin,payload) VALUES(?,?,?,?)
ON CONFLICT(id) DO UPDATE SET owner=excluded.owner, builtin=excluded.builtin, payload=excluded.payload`),
p.ID, p.Owner, boolInt(p.Builtin), marshalJSON(p))
return err
}
// ListProfiles 返回该租户的画像 + 共享内置画像(owner=”)。
func (s *Store) ListProfiles(owner string) ([]core.AgentProfile, error) {
rows, err := s.db.Query(s.q(`SELECT payload FROM profiles WHERE owner=? OR builtin=1 ORDER BY builtin DESC, id`), owner)
if err != nil {
return nil, err
}
defer rows.Close()
out := []core.AgentProfile{}
for rows.Next() {
var payload string
if err := rows.Scan(&payload); err != nil {
return nil, err
}
p, _ := unmarshalJSON[core.AgentProfile](payload)
out = append(out, p)
}
return out, rows.Err()
}
func (s *Store) GetProfile(owner, id string) (core.AgentProfile, error) {
var payload string
err := s.db.QueryRow(s.q(`SELECT payload FROM profiles WHERE id=? AND (owner=? OR builtin=1)`), id, owner).Scan(&payload)
if errors.Is(err, sql.ErrNoRows) {
return core.AgentProfile{}, ErrNotFound
}
if err != nil {
return core.AgentProfile{}, err
}
return unmarshalJSON[core.AgentProfile](payload)
}
func (s *Store) DeleteProfile(owner, id string) error {
var builtin int
err := s.db.QueryRow(s.q(`SELECT builtin FROM profiles WHERE id=? AND owner=?`), id, owner).Scan(&builtin)
if errors.Is(err, sql.ErrNoRows) {
return ErrNotFound
}
if err != nil {
return err
}
if builtin == 1 {
return errors.New("store: builtin profile cannot be deleted")
}
_, err = s.db.Exec(s.q(`DELETE FROM profiles WHERE id=? AND owner=?`), id, owner)
return err
}
// ---- 设置(scope = user | project:<id>;PK=(owner,scope),租户隔离)----
func (s *Store) GetSettingsScope(owner, scope string) (map[string]any, error) {
var payload string
err := s.db.QueryRow(s.q(`SELECT "values" FROM settings WHERE owner=? AND scope=?`), owner, scope).Scan(&payload)
if errors.Is(err, sql.ErrNoRows) {
return map[string]any{}, nil
}
if err != nil {
return nil, err
}
out, err := unmarshalJSON[map[string]any](payload)
if err != nil {
return nil, err
}
if out == nil {
out = map[string]any{}
}
return out, nil
}
// MergeSettingsScope 读改写走事务 + scope 锁:并发合并不再丢失更新。
func (s *Store) MergeSettingsScope(owner, scope string, values map[string]any) error {
tx, err := s.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if err := s.dialect.lockScope(tx, owner, scope); err != nil {
return err
}
var payload string
err = tx.QueryRow(s.q(`SELECT "values" FROM settings WHERE owner=? AND scope=?`), owner, scope).Scan(&payload)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
cur := map[string]any{}
if payload != "" {
cur, _ = unmarshalJSON[map[string]any](payload)
}
merged := core.MergeSettings(cur, values)
_, err = tx.Exec(s.q(`INSERT INTO settings(owner,scope,"values") VALUES(?,?,?)
ON CONFLICT(owner,scope) DO UPDATE SET "values"=excluded."values"`), owner, scope, marshalJSON(merged))
if err != nil {
return err
}
return tx.Commit()
}
func (s *Store) DeleteSettingsScope(owner, scope string) error {
_, err := s.db.Exec(s.q(`DELETE FROM settings WHERE owner=? AND scope=?`), owner, scope)
return err
}
// ---- 插件注册表(节点级:无租户维度;加载状态在 pluginhost 内存态)----
func (s *Store) UpsertPlugin(id, dir string, m core.PluginManifest) error {
_, err := s.db.Exec(s.q(`INSERT INTO plugins(id,dir,manifest,enabled,loaded_at) VALUES(?,?,?,?,?)
ON CONFLICT(id) DO UPDATE SET dir=excluded.dir, manifest=excluded.manifest, enabled=excluded.enabled, loaded_at=excluded.loaded_at`),
id, dir, marshalJSON(m), 1, nowISO())
return err
}
func (s *Store) ListPlugins() ([]core.PluginManifest, error) {
rows, err := s.db.Query(s.q(`SELECT manifest FROM plugins ORDER BY id`))
if err != nil {
return nil, err
}
defer rows.Close()
out := []core.PluginManifest{}
for rows.Next() {
var payload string
if err := rows.Scan(&payload); err != nil {
return nil, err
}
m, _ := unmarshalJSON[core.PluginManifest](payload)
out = append(out, m)
}
return out, rows.Err()
}
func (s *Store) RemovePlugin(id string) error {
_, err := s.db.Exec(s.q(`DELETE FROM plugins WHERE id=?`), id)
return err
}