- 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>
201 lines
3.4 KiB
Go
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)
|
|
} |