Files
obsidian-mcp/internal/domain/vault.go
Andrey Epifancev da289d4a7e feat: Implement foundation layer with domain entities and repository interfaces
- Add complete domain layer: Note, Vault, WikiLink, Tag, Frontmatter, Graph entities
- Implement repository interfaces for data access abstraction
- Create comprehensive configuration system with YAML and env support
- Add CLI entry point with signal handling and graceful shutdown
- Fix mermaid diagram syntax in design.md (array notation)
- Add CLAUDE.md for development guidance

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 10:22:28 +04:00

201 lines
3.4 KiB
Go

package domain
import (
"sync"
)
type VaultStats struct {
TotalNotes int
TotalWords int
TotalLinks int
TotalTags int
AvgNoteLength int
OrphanedNotes int
}
type Vault struct {
rootPath string
notes map[string]*Note
graph *Graph
tags map[string]int
mutex sync.RWMutex
}
func NewVault(rootPath string) *Vault {
return &Vault{
rootPath: rootPath,
notes: make(map[string]*Note),
graph: NewGraph(),
tags: make(map[string]int),
}
}
func (v *Vault) RootPath() string {
return v.rootPath
}
func (v *Vault) AddNote(note *Note) error {
v.mutex.Lock()
defer v.mutex.Unlock()
if _, exists := v.notes[note.Path()]; exists {
return ErrNoteAlreadyExists
}
v.notes[note.Path()] = note
for _, link := range note.ExtractLinks() {
v.graph.AddEdge(note.Path(), link.Target())
}
for _, tag := range note.Tags() {
v.tags[tag.Name()]++
}
return nil
}
func (v *Vault) RemoveNote(path string) error {
v.mutex.Lock()
defer v.mutex.Unlock()
note, exists := v.notes[path]
if !exists {
return ErrNoteNotFound
}
for _, tag := range note.Tags() {
v.tags[tag.Name()]--
if v.tags[tag.Name()] <= 0 {
delete(v.tags, tag.Name())
}
}
v.graph.RemoveNode(path)
delete(v.notes, path)
return nil
}
func (v *Vault) GetNote(path string) (*Note, error) {
v.mutex.RLock()
defer v.mutex.RUnlock()
note, exists := v.notes[path]
if !exists {
return nil, ErrNoteNotFound
}
return note, nil
}
func (v *Vault) UpdateNote(note *Note) error {
v.mutex.Lock()
defer v.mutex.Unlock()
existingNote, exists := v.notes[note.Path()]
if !exists {
return ErrNoteNotFound
}
for _, tag := range existingNote.Tags() {
v.tags[tag.Name()]--
if v.tags[tag.Name()] <= 0 {
delete(v.tags, tag.Name())
}
}
v.graph.RemoveNode(note.Path())
v.notes[note.Path()] = note
for _, link := range note.ExtractLinks() {
v.graph.AddEdge(note.Path(), link.Target())
}
for _, tag := range note.Tags() {
v.tags[tag.Name()]++
}
return nil
}
func (v *Vault) ListNotes() []*Note {
v.mutex.RLock()
defer v.mutex.RUnlock()
notes := make([]*Note, 0, len(v.notes))
for _, note := range v.notes {
notes = append(notes, note)
}
return notes
}
func (v *Vault) GetStats() VaultStats {
v.mutex.RLock()
defer v.mutex.RUnlock()
totalWords := 0
totalLinks := 0
for _, note := range v.notes {
totalWords += note.WordCount()
totalLinks += len(note.ExtractLinks())
}
avgLength := 0
if len(v.notes) > 0 {
avgLength = totalWords / len(v.notes)
}
orphaned := v.countOrphanedNotes()
return VaultStats{
TotalNotes: len(v.notes),
TotalWords: totalWords,
TotalLinks: totalLinks,
TotalTags: len(v.tags),
AvgNoteLength: avgLength,
OrphanedNotes: orphaned,
}
}
func (v *Vault) GetBacklinks(path string) []string {
return v.graph.GetBacklinks(path)
}
func (v *Vault) GetOutlinks(path string) []string {
return v.graph.GetOutlinks(path)
}
func (v *Vault) GetAllTags() map[string]int {
v.mutex.RLock()
defer v.mutex.RUnlock()
tags := make(map[string]int)
for tag, count := range v.tags {
tags[tag] = count
}
return tags
}
func (v *Vault) countOrphanedNotes() int {
orphaned := 0
for path := range v.notes {
if len(v.graph.GetBacklinks(path)) == 0 && len(v.graph.GetOutlinks(path)) == 0 {
orphaned++
}
}
return orphaned
}
func (v *Vault) Clear() {
v.mutex.Lock()
defer v.mutex.Unlock()
v.notes = make(map[string]*Note)
v.graph.Clear()
v.tags = make(map[string]int)
}