feat: add absorb command — merge source entity into target
DB: Absorb() merges body (newline-separated), unions tags, demotes
crystallized sources, soft-deletes source. Rejects crystallized targets.
API: POST /api/entities/:id/absorb { source_id }
CLI: nib absorb <target> <source> with prefix ID resolution
Web: absorb button on fluid entities, 'a' keyboard shortcut,
source picker modal
This commit is contained in:
@@ -405,6 +405,63 @@ func (s *Store) SoftDelete(id string) (DeleteResult, error) {
|
||||
return DeletedSoft, err
|
||||
}
|
||||
|
||||
func (s *Store) Absorb(targetID, sourceID string) error {
|
||||
target, err := s.Get(targetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source, err := s.Get(sourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if target.CardType != nil {
|
||||
return ErrTargetCrystallized
|
||||
}
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
merged := target.Body + "\n" + source.Body
|
||||
|
||||
if _, err := tx.Exec("UPDATE entities SET body = ?, modified_at = ? WHERE id = ?",
|
||||
merged, now, targetID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
seen := map[string]bool{}
|
||||
for _, t := range target.Tags {
|
||||
seen[t] = true
|
||||
}
|
||||
for _, t := range source.Tags {
|
||||
if !seen[t] {
|
||||
if _, err := tx.Exec("INSERT OR IGNORE INTO entity_tags (entity_id, tag) VALUES (?, ?)",
|
||||
targetID, t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if source.CardType != nil {
|
||||
if _, err := tx.Exec(`UPDATE entities SET card_type = NULL, card_data = NULL,
|
||||
use_count = 0, last_used_at = NULL, modified_at = ? WHERE id = ?`,
|
||||
now, sourceID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := tx.Exec("UPDATE entities SET deleted_at = ? WHERE id = ?",
|
||||
now, sourceID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *Store) IncrementUse(id string) error {
|
||||
res, err := s.db.Exec(`
|
||||
UPDATE entities SET use_count = use_count + 1, last_used_at = ?
|
||||
|
||||
Reference in New Issue
Block a user