diff --git a/cmd/main.go b/cmd/main.go index 810c6dd..0f5b8d4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -26,8 +26,9 @@ func main() { tagService := services.NewTagService(tagRepo) linkService := services.NewLinkService(linkRepo) noteService := services.NewNoteService(tagRepo, linkRepo, noteRepo) + idxr := persistence.NewIndexRebuilder() - server.RegisterHandler("vault/init", vault.NewInitializeHandler()) + server.RegisterHandler("vault/init", vault.NewInitializeHandler(idxr)) server.RegisterHandler("note/create", note.NewCreateNoteHandler(uow, tagService, noteRepo)) server.RegisterHandler("note/save", note.NewSaveNoteHandler(uow, noteRepo, tagService, noteService, linkService)) server.RegisterHandler("note/goto", note.NewGotoNoteHandler(noteRepo)) diff --git a/core/handlers/vault/initialize_handler.go b/core/handlers/vault/initialize_handler.go index 17d18a6..e9bccf4 100644 --- a/core/handlers/vault/initialize_handler.go +++ b/core/handlers/vault/initialize_handler.go @@ -14,10 +14,12 @@ type initializeCommand struct { TemplateDirectory string `json:"templateDirectory"` } -type InitializeHandler struct{} +type InitializeHandler struct { + idxRebuilder persistence.IIndexRebuilder +} -func NewInitializeHandler() *InitializeHandler { - return &InitializeHandler{} +func NewInitializeHandler(idxR persistence.IIndexRebuilder) *InitializeHandler { + return &InitializeHandler{idxRebuilder: idxR} } func (h InitializeHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) { @@ -34,5 +36,9 @@ func (h InitializeHandler) Handle(ctx context.Context, raw json.RawMessage) (any return nil, err } + if err = h.idxRebuilder.RebuildIndex(ctx, cmd.VaultPath); err != nil { + return nil, err + } + return nil, nil } diff --git a/dendrite b/dendrite index 3e1187f..acea509 100755 Binary files a/dendrite and b/dendrite differ diff --git a/persistence/index_rebuilder.go b/persistence/index_rebuilder.go new file mode 100644 index 0000000..da8bab5 --- /dev/null +++ b/persistence/index_rebuilder.go @@ -0,0 +1,188 @@ +package persistence + +import ( + "context" + "database/sql" + "io/fs" + "log/slog" + "path/filepath" + + filehandling "github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling" + "github.com/KristianJBorgwarth/dendrite.daemon/core/models" +) + +type IIndexRebuilder interface { + RebuildIndex(ctx context.Context, vaultRoot string) error +} + +type IndexRebuilder struct { +} + +func NewIndexRebuilder() *IndexRebuilder { + return &IndexRebuilder{} +} + +func (r *IndexRebuilder) RebuildIndex(ctx context.Context, vaultRoot string) error { + files, err := r.ReadFiles(vaultRoot) + if err != nil { + slog.Debug("Failed to read files from vault", "vaultRoot", vaultRoot, "error", err) + return err + } + + notes, links, tags, noteTags := r.buildDBModels(files) + + tx, err := GetDBContext().DB.Begin() + if err != nil { + return err + } + + if err = r.wipeIndex(ctx, tx); err != nil { + tx.Rollback() + slog.Debug("Failed to wipe index, rolling back transaction", "error", err) + return err + } + + if err = r.InsertNotes(ctx, tx, notes); err != nil { + tx.Rollback() + return err + } + + if err = r.InsertLinks(ctx, tx, links); err != nil { + tx.Rollback() + return err + } + + if err = r.InsertTags(ctx, tx, tags); err != nil { + tx.Rollback() + return err + } + + if err = r.InsertNoteTags(ctx, tx, noteTags); err != nil { + tx.Rollback() + return err + } + + tx.Commit() + + return nil +} + +func (r *IndexRebuilder) InsertNotes(ctx context.Context, tx *sql.Tx, notes []*models.Note) error { + noteStmt, err := tx.Prepare(`INSERT INTO note (id, title, slug, path) VALUES (?, ?, ?, ?)`) + if err != nil { + return err + } + for _, note := range notes { + if _, err = noteStmt.ExecContext(ctx, note.ID(), note.Title(), note.Slug(), note.Path()); err != nil { + return err + } + } + + return nil +} + +func (r *IndexRebuilder) InsertLinks(ctx context.Context, tx *sql.Tx, links []*models.Link) error { + linkStmt, err := tx.Prepare(`INSERT INTO link (id, from_note_id, target_slug, display, raw, line, col) VALUES (?, ?, ?, ?, ?, ?, ?)`) + if err != nil { + return err + } + + for _, link := range links { + if _, err = linkStmt.ExecContext(ctx, link.ID(), link.FromNoteID(), link.TargetSlug(), link.Display(), link.Raw(), link.Line(), link.Col()); err != nil { + return err + } + } + + return nil +} + +func (r *IndexRebuilder) InsertTags(ctx context.Context, tx *sql.Tx, tags []*models.Tag) error { + tagStmst, err := tx.Prepare(`INSERT INTO tag (name) VALUES (?)`) + if err != nil { + return err + } + + for _, tag := range tags { + if _, err = tagStmst.ExecContext(ctx, tag.Name()); err != nil { + return err + } + } + + return nil +} + +func (r *IndexRebuilder) InsertNoteTags(ctx context.Context, tx *sql.Tx, noteTags []*models.NoteTag) error { + noteTagStmt, err := tx.Prepare(`INSERT INTO note_tag (note_id, tag_id) VALUES (?, ?)`) + if err != nil { + return err + } + + for _, noteTag := range noteTags { + if _, err = noteTagStmt.ExecContext(ctx, noteTag.NoteID(), noteTag.TagID()); err != nil { + return err + } + } + + return nil +} + +func (r *IndexRebuilder) ReadFiles(vault string) ([]*filehandling.File, error) { + var files []*filehandling.File + + err := filepath.WalkDir(vault, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() || filepath.Ext(path) != ".md" { + return nil + } + pendingFile, err := filehandling.ReadFile(path) + if err != nil { + return err + } + + files = append(files, pendingFile) + + return nil + }) + if err != nil { + return nil, err + } + + return files, nil +} + +func (r *IndexRebuilder) buildDBModels(files []*filehandling.File) ([]*models.Note, []*models.Link, []*models.Tag, []*models.NoteTag) { + var notes []*models.Note + var links []*models.Link + tagMap := make(map[string]*models.Tag) + var noteTags []*models.NoteTag + + for _, file := range files { + note := models.CreateNote(file.Path, file.Title, file.Slug) + notes = append(notes, note) + for _, t := range models.CreateTags(file.FrontMatter.Tags) { + tagMap[t.Name()] = t + } + noteTags = append(noteTags, models.CreateNoteTags(note.ID(), file.FrontMatter.Tags)...) + links = append(links, models.MapToLinkModel(note.ID(), file.ExtractedLinks)...) + } + + tags := make([]*models.Tag, 0, len(tagMap)) + for _, tag := range tagMap { + tags = append(tags, tag) + } + + return notes, links, tags, noteTags +} + +func (r *IndexRebuilder) wipeIndex(ctx context.Context, tx *sql.Tx) error { + cmd := `DELETE FROM notes; + DELETE FROM tags; + DELETE FROM note_tags; + DELETE FROM link;` + + _, err := tx.ExecContext(ctx, cmd) + return err +} diff --git a/persistence/services/doc.go b/persistence/services/doc.go deleted file mode 100644 index 829c45f..0000000 --- a/persistence/services/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package peristenceServices provides services for data persistence, including database interactions and storage management. -package services diff --git a/persistence/services/index_rebuilder.go b/persistence/services/index_rebuilder.go deleted file mode 100644 index c1c650d..0000000 --- a/persistence/services/index_rebuilder.go +++ /dev/null @@ -1,149 +0,0 @@ -package services - -import ( - "database/sql" - "io/fs" - "path/filepath" - - filehandling "github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling" - "github.com/KristianJBorgwarth/dendrite.daemon/core/models" -) - -type IIndexRebuilder interface { - RebuildIndex(vaultRoot string) error -} - -type IndexRebuilder struct { - db *sql.DB -} - -func (r *IndexRebuilder) RebuildIndex(vaultRoot string) error { - files, err := r.ReadFiles(vaultRoot) - if err != nil { - return err - } - - notes, links, tags, noteTags := r.buildDBModels(files) - - tx, err := r.db.Begin() - if err != nil { - return err - } - - if err = r.wipeIndex(tx); err != nil { - tx.Rollback() - return err - } - - if err = r.InsertNotes(tx, notes); err != nil { - tx.Rollback() - return err - } - - if err = r.InsertLinks(tx, links); err != nil { - tx.Rollback() - return err - } - - if err = r.InsertTags(tx, tags); err != nil { - tx.Rollback() - return err - } - - if err = r.InsertNoteTags(tx, noteTags); err != nil { - tx.Rollback() - return err - } - - tx.Commit() - - return nil -} - - -func (r *IndexRebuilder) InsertNotes(tx *sql.Tx, notes []*models.Note) error { - noteStmt, err := tx.Prepare(`INSERT INTO note (id, title, slug, path) VALUES (?, ?, ?, ?)`) - if err != nil { - return err - } - for _, note := range notes { - if _, err = noteStmt.Exec(note.ID(), note.Title(), note.Slug(), note.Path()); err != nil { - return err - } - } - - return nil -} - -func (r *IndexRebuilder) InsertLinks(tx *sql.Tx, links []*models.Link) error { - panic("not implemented") -} - -func (r *IndexRebuilder) InsertTags(tx *sql.Tx, tags []*models.Tag) error { - panic("not implemented") -} - -func (r *IndexRebuilder) InsertNoteTags(tx *sql.Tx, noteTags []*models.NoteTag) error { - panic("not implemented") -} - -func (r *IndexRebuilder) ReadFiles(vault string) ([]*filehandling.File, error) { - var files []*filehandling.File - - err := filepath.WalkDir(vault, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if d.IsDir() || filepath.Ext(path) != ".md" { - return nil - } - pendingFile, err := filehandling.ReadFile(path) - if err != nil { - return err - } - - files = append(files, pendingFile) - - return nil - }) - if err != nil { - return nil, err - } - - return files, nil -} - -func (r *IndexRebuilder) buildDBModels(files []*filehandling.File) ([]*models.Note, []*models.Link, []*models.Tag, []*models.NoteTag) { - var notes []*models.Note - var links []*models.Link - tagMap := make(map[string]*models.Tag) - var noteTags []*models.NoteTag - - for _, file := range files { - note := models.CreateNote(file.Path, file.Title, file.Slug) - notes = append(notes, note) - for _, t := range models.CreateTags(file.FrontMatter.Tags) { - tagMap[t.Name()] = t - } - noteTags = append(noteTags, models.CreateNoteTags(note.ID(), file.FrontMatter.Tags)...) - links = append(links, models.MapToLinkModel(note.ID(), file.ExtractedLinks)...) - } - - tags := make([]*models.Tag, 0, len(tagMap)) - for _, tag := range tagMap { - tags = append(tags, tag) - } - - return notes, links, tags, noteTags -} - -func (r *IndexRebuilder) wipeIndex(tx *sql.Tx) error { - cmd := `DELETE FROM notes; - DELETE FROM tags; - DELETE FROM note_tags; - DELETE FROM link;` - - _, err := tx.Exec(cmd) - return err -}