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>
This commit is contained in:
201
internal/domain/vault.go
Normal file
201
internal/domain/vault.go
Normal file
@@ -0,0 +1,201 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user