Create and restart paths enable IsSystemDefault channels; SyncPage auto-starts after save. Co-authored-by: Cursor <cursoragent@cursor.com>
129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
package dbsync
|
||
|
||
import (
|
||
"fmt"
|
||
"path/filepath"
|
||
"strings"
|
||
)
|
||
|
||
// DefaultChannelOpts 创建公司默认同步通道(Z12c)。
|
||
type DefaultChannelOpts struct {
|
||
TenantID int64
|
||
AgentID int64
|
||
Name string
|
||
RemoteDriver Driver
|
||
RemoteDSN string
|
||
OnlineDBID string // 空则用 channel id
|
||
DatabaseName string
|
||
}
|
||
|
||
// FindSystemDefaultChannel 返回该公司 IsSystemDefault 通道(若有多条取最新启用的)。
|
||
func (s *FileStore) FindSystemDefaultChannel(tenantID int64) (*Channel, error) {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
list, err := s.readChannels()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var best *Channel
|
||
for i := range list {
|
||
ch := list[i]
|
||
if ch.TenantID != tenantID || !ch.IsSystemDefault {
|
||
continue
|
||
}
|
||
cp := ch
|
||
if best == nil || cp.UpdatedAt.After(best.UpdatedAt) {
|
||
best = &cp
|
||
}
|
||
}
|
||
return best, nil
|
||
}
|
||
|
||
// EnsureSystemDefaultChannel 若无默认同步通道则创建;返回通道(已存在则复用)。
|
||
func (s *FileStore) EnsureSystemDefaultChannel(opts DefaultChannelOpts) (Channel, error) {
|
||
if opts.TenantID <= 0 {
|
||
return Channel{}, fmt.Errorf("tenant_id required")
|
||
}
|
||
if existing, err := s.FindSystemDefaultChannel(opts.TenantID); err == nil && existing != nil {
|
||
ch := *existing
|
||
if opts.AgentID > 0 && ch.AgentID == 0 {
|
||
ch.AgentID = opts.AgentID
|
||
saved, err := s.SaveChannel(ch)
|
||
if err != nil {
|
||
return Channel{}, err
|
||
}
|
||
return saved, nil
|
||
}
|
||
return ch, nil
|
||
}
|
||
driver := opts.RemoteDriver
|
||
if driver == "" {
|
||
driver = DriverPostgres
|
||
}
|
||
dsn := strings.TrimSpace(opts.RemoteDSN)
|
||
if dsn == "" {
|
||
driver = DriverSQLite
|
||
dsn = filepath.ToSlash(filepath.Join(".", "data", "dbsync", fmt.Sprintf("tenant_%d_online.db", opts.TenantID)))
|
||
dsn = "file:" + dsn + "?_pragma=busy_timeout(5000)"
|
||
}
|
||
name := strings.TrimSpace(opts.Name)
|
||
if name == "" {
|
||
name = fmt.Sprintf("公司默认同步 #%d", opts.TenantID)
|
||
}
|
||
ch := Channel{
|
||
TenantID: opts.TenantID,
|
||
Name: name,
|
||
Enabled: true, // 默认运行中:可收 agent push,并预热 remote
|
||
Direction: DirLocalToRemote,
|
||
ConflictPolicy: PolicyLWWSource,
|
||
IsSystemDefault: true,
|
||
AgentID: opts.AgentID,
|
||
Local: Endpoint{
|
||
Driver: DriverSQLite,
|
||
DSN: "file:./data/dbsync/local_placeholder.db?_pragma=busy_timeout(5000)",
|
||
Tables: nil,
|
||
},
|
||
Remote: Endpoint{
|
||
Driver: driver,
|
||
DSN: dsn,
|
||
Tables: nil,
|
||
},
|
||
PKColumns: map[string]string{},
|
||
}
|
||
if err := ValidateChannelConfig(&ch); err != nil {
|
||
return Channel{}, err
|
||
}
|
||
saved, err := s.SaveChannel(ch)
|
||
if err != nil {
|
||
return Channel{}, err
|
||
}
|
||
return saved, nil
|
||
}
|
||
|
||
// ResolveOnlineDBID 默认 online_db_id:优先显式值,否则通道 id。
|
||
func ResolveOnlineDBID(explicit, channelID string) string {
|
||
if s := strings.TrimSpace(explicit); s != "" {
|
||
return s
|
||
}
|
||
return strings.TrimSpace(channelID)
|
||
}
|
||
|
||
// EnsureAndStartSystemDefaultChannel 创建/复用公司默认同步通道,并确保处于运行中(Enabled + runner)。
|
||
func (m *Manager) EnsureAndStartSystemDefaultChannel(opts DefaultChannelOpts) (Channel, error) {
|
||
if m == nil || m.store == nil {
|
||
return Channel{}, fmt.Errorf("dbsync not enabled")
|
||
}
|
||
ch, err := m.store.EnsureSystemDefaultChannel(opts)
|
||
if err != nil {
|
||
return Channel{}, err
|
||
}
|
||
if err := m.StartChannel(ch.ID); err != nil {
|
||
return ch, fmt.Errorf("start default channel: %w", err)
|
||
}
|
||
if updated, err := m.store.GetChannel(ch.ID); err == nil {
|
||
return *updated, nil
|
||
}
|
||
ch.Enabled = true
|
||
return ch, nil
|
||
}
|