package store import ( "database/sql" "encoding/json" "fmt" "github.com/yourname/cyrene-ai/pkg/logger" "time" ) // AutomationRule 自动化规则模型 type AutomationRule struct { ID string `json:"id"` UserID string `json:"user_id"` Name string `json:"name"` Description string `json:"description"` TriggerType string `json:"trigger_type"` TriggerConfig *json.RawMessage `json:"trigger_config"` Conditions *json.RawMessage `json:"conditions"` Actions *json.RawMessage `json:"actions"` Enabled bool `json:"enabled"` LastTriggeredAt *time.Time `json:"last_triggered_at,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // AutomationScene 自动化场景模型 type AutomationScene struct { ID string `json:"id"` UserID string `json:"user_id"` Name string `json:"name"` Icon string `json:"icon"` RuleIDs *json.RawMessage `json:"rule_ids"` CreatedAt time.Time `json:"created_at"` } // AutomationStore 自动化持久化存储 type AutomationStore struct { db *sql.DB } // NewAutomationStore 使用已有数据库连接初始化自动化存储并自动建表 func NewAutomationStore(db *sql.DB) (*AutomationStore, error) { store := &AutomationStore{db: db} if err := store.migrate(); err != nil { return nil, fmt.Errorf("自动化表迁移失败: %w", err) } logger.Println("[AutomationStore] 自动化持久化存储已初始化") return store, nil } // migrate 自动创建表结构 func (s *AutomationStore) migrate() error { queries := []string{ `CREATE TABLE IF NOT EXISTS automation_rules ( id VARCHAR(64) PRIMARY KEY, user_id VARCHAR(64) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT DEFAULT '', trigger_type VARCHAR(32) NOT NULL, trigger_config JSONB DEFAULT '{}', conditions JSONB DEFAULT '[]', actions JSONB NOT NULL DEFAULT '[]', enabled BOOLEAN DEFAULT TRUE, last_triggered_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() )`, `CREATE INDEX IF NOT EXISTS idx_automation_rules_user_id ON automation_rules(user_id)`, `CREATE INDEX IF NOT EXISTS idx_automation_rules_enabled ON automation_rules(enabled)`, `CREATE TABLE IF NOT EXISTS automation_scenes ( id VARCHAR(64) PRIMARY KEY, user_id VARCHAR(64) NOT NULL, name VARCHAR(255) NOT NULL, icon VARCHAR(64) DEFAULT '', rule_ids JSONB DEFAULT '[]', created_at TIMESTAMP DEFAULT NOW() )`, `CREATE INDEX IF NOT EXISTS idx_automation_scenes_user_id ON automation_scenes(user_id)`, } for _, q := range queries { if _, err := s.db.Exec(q); err != nil { return fmt.Errorf("迁移SQL执行失败: %w\nSQL: %s", err, q) } } return nil } // ========== Rule CRUD ========== // CreateRule 创建新规则 func (s *AutomationStore) CreateRule(rule *AutomationRule) error { now := time.Now() rule.CreatedAt = now rule.UpdatedAt = now triggerConfig := jsonNull(rule.TriggerConfig) conditions := jsonNull(rule.Conditions) actions := jsonNull(rule.Actions) _, err := s.db.Exec( `INSERT INTO automation_rules (id, user_id, name, description, trigger_type, trigger_config, conditions, actions, enabled, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, rule.ID, rule.UserID, rule.Name, rule.Description, rule.TriggerType, triggerConfig, conditions, actions, rule.Enabled, rule.CreatedAt, rule.UpdatedAt, ) if err != nil { return fmt.Errorf("创建规则失败: %w", err) } return nil } // GetRulesByUser 获取用户的所有规则 func (s *AutomationStore) GetRulesByUser(userID string) ([]AutomationRule, error) { rows, err := s.db.Query( `SELECT id, user_id, name, description, trigger_type, trigger_config, conditions, actions, enabled, last_triggered_at, created_at, updated_at FROM automation_rules WHERE user_id = $1 ORDER BY created_at DESC`, userID, ) if err != nil { return nil, fmt.Errorf("查询用户规则失败: %w", err) } defer rows.Close() var rules []AutomationRule for rows.Next() { var r AutomationRule if err := rows.Scan(&r.ID, &r.UserID, &r.Name, &r.Description, &r.TriggerType, &r.TriggerConfig, &r.Conditions, &r.Actions, &r.Enabled, &r.LastTriggeredAt, &r.CreatedAt, &r.UpdatedAt); err != nil { return nil, fmt.Errorf("扫描规则行失败: %w", err) } rules = append(rules, r) } if rules == nil { rules = []AutomationRule{} } return rules, rows.Err() } // GetRule 获取单个规则 func (s *AutomationStore) GetRule(id string) (*AutomationRule, error) { var r AutomationRule err := s.db.QueryRow( `SELECT id, user_id, name, description, trigger_type, trigger_config, conditions, actions, enabled, last_triggered_at, created_at, updated_at FROM automation_rules WHERE id = $1`, id, ).Scan(&r.ID, &r.UserID, &r.Name, &r.Description, &r.TriggerType, &r.TriggerConfig, &r.Conditions, &r.Actions, &r.Enabled, &r.LastTriggeredAt, &r.CreatedAt, &r.UpdatedAt) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, fmt.Errorf("查询规则失败: %w", err) } return &r, nil } // UpdateRule 更新规则 func (s *AutomationStore) UpdateRule(rule *AutomationRule) error { triggerConfig := jsonNull(rule.TriggerConfig) conditions := jsonNull(rule.Conditions) actions := jsonNull(rule.Actions) _, err := s.db.Exec( `UPDATE automation_rules SET name = $1, description = $2, trigger_type = $3, trigger_config = $4, conditions = $5, actions = $6, enabled = $7, updated_at = NOW() WHERE id = $8`, rule.Name, rule.Description, rule.TriggerType, triggerConfig, conditions, actions, rule.Enabled, rule.ID, ) if err != nil { return fmt.Errorf("更新规则失败: %w", err) } return nil } // DeleteRule 删除规则 func (s *AutomationStore) DeleteRule(id string) error { _, err := s.db.Exec(`DELETE FROM automation_rules WHERE id = $1`, id) if err != nil { return fmt.Errorf("删除规则失败: %w", err) } return nil } // GetEnabledRules 获取所有启用的规则(供引擎使用) func (s *AutomationStore) GetEnabledRules() ([]AutomationRule, error) { rows, err := s.db.Query( `SELECT id, user_id, name, description, trigger_type, trigger_config, conditions, actions, enabled, last_triggered_at, created_at, updated_at FROM automation_rules WHERE enabled = TRUE ORDER BY created_at ASC`, ) if err != nil { return nil, fmt.Errorf("查询启用的规则失败: %w", err) } defer rows.Close() var rules []AutomationRule for rows.Next() { var r AutomationRule if err := rows.Scan(&r.ID, &r.UserID, &r.Name, &r.Description, &r.TriggerType, &r.TriggerConfig, &r.Conditions, &r.Actions, &r.Enabled, &r.LastTriggeredAt, &r.CreatedAt, &r.UpdatedAt); err != nil { return nil, fmt.Errorf("扫描规则行失败: %w", err) } rules = append(rules, r) } if rules == nil { rules = []AutomationRule{} } return rules, rows.Err() } // MarkRuleTriggered 更新 last_triggered_at func (s *AutomationStore) MarkRuleTriggered(id string) error { _, err := s.db.Exec( `UPDATE automation_rules SET last_triggered_at = NOW(), updated_at = NOW() WHERE id = $1`, id, ) if err != nil { return fmt.Errorf("标记规则触发失败: %w", err) } return nil } // ========== Scene CRUD ========== // CreateScene 创建新场景 func (s *AutomationStore) CreateScene(scene *AutomationScene) error { ruleIDs := jsonNull(scene.RuleIDs) _, err := s.db.Exec( `INSERT INTO automation_scenes (id, user_id, name, icon, rule_ids, created_at) VALUES ($1, $2, $3, $4, $5, NOW())`, scene.ID, scene.UserID, scene.Name, scene.Icon, ruleIDs, ) if err != nil { return fmt.Errorf("创建场景失败: %w", err) } return nil } // GetScenesByUser 获取用户的所有场景 func (s *AutomationStore) GetScenesByUser(userID string) ([]AutomationScene, error) { rows, err := s.db.Query( `SELECT id, user_id, name, icon, rule_ids, created_at FROM automation_scenes WHERE user_id = $1 ORDER BY created_at DESC`, userID, ) if err != nil { return nil, fmt.Errorf("查询用户场景失败: %w", err) } defer rows.Close() var scenes []AutomationScene for rows.Next() { var sc AutomationScene if err := rows.Scan(&sc.ID, &sc.UserID, &sc.Name, &sc.Icon, &sc.RuleIDs, &sc.CreatedAt); err != nil { return nil, fmt.Errorf("扫描场景行失败: %w", err) } scenes = append(scenes, sc) } if scenes == nil { scenes = []AutomationScene{} } return scenes, rows.Err() } // GetScene 获取单个场景 func (s *AutomationStore) GetScene(id string) (*AutomationScene, error) { var sc AutomationScene err := s.db.QueryRow( `SELECT id, user_id, name, icon, rule_ids, created_at FROM automation_scenes WHERE id = $1`, id, ).Scan(&sc.ID, &sc.UserID, &sc.Name, &sc.Icon, &sc.RuleIDs, &sc.CreatedAt) if err != nil { if err == sql.ErrNoRows { return nil, nil } return nil, fmt.Errorf("查询场景失败: %w", err) } return &sc, nil } // UpdateScene 更新场景 func (s *AutomationStore) UpdateScene(scene *AutomationScene) error { ruleIDs := jsonNull(scene.RuleIDs) _, err := s.db.Exec( `UPDATE automation_scenes SET name = $1, icon = $2, rule_ids = $3 WHERE id = $4`, scene.Name, scene.Icon, ruleIDs, scene.ID, ) if err != nil { return fmt.Errorf("更新场景失败: %w", err) } return nil } // DeleteScene 删除场景 func (s *AutomationStore) DeleteScene(id string) error { _, err := s.db.Exec(`DELETE FROM automation_scenes WHERE id = $1`, id) if err != nil { return fmt.Errorf("删除场景失败: %w", err) } return nil } // GetSceneRules 根据 scene 的 rule_ids 取出所有关联的 rules func (s *AutomationStore) GetSceneRules(sceneID string) ([]AutomationRule, error) { sc, err := s.GetScene(sceneID) if err != nil { return nil, err } if sc == nil { return []AutomationRule{}, nil } var ruleIDs []string if sc.RuleIDs != nil { if err := json.Unmarshal(*sc.RuleIDs, &ruleIDs); err != nil { return nil, fmt.Errorf("解析场景规则ID失败: %w", err) } } if len(ruleIDs) == 0 { return []AutomationRule{}, nil } // 构建 IN 查询 var rules []AutomationRule for _, rid := range ruleIDs { r, err := s.GetRule(rid) if err != nil { return nil, fmt.Errorf("查询场景关联规则失败: %w", err) } if r != nil { rules = append(rules, *r) } } if rules == nil { rules = []AutomationRule{} } return rules, nil } // jsonNull 将 *json.RawMessage 转为可写入数据库的 JSON 或 null func jsonNull(raw *json.RawMessage) interface{} { if raw == nil { return nil } return []byte(*raw) }