diff --git a/core/file_handling/file.go b/core/file_handling/file.go index d15bfd4..e6705f8 100644 --- a/core/file_handling/file.go +++ b/core/file_handling/file.go @@ -9,11 +9,12 @@ import ( ) type File struct { + Path string Title string Slug string FrontMatter FrontMatter Content []string - Links []ExtractedLink + ExtractedLinks []*ExtractedLink } type ExtractedLink struct { @@ -38,18 +39,19 @@ func ReadFile(path string) (*File, error) { } return &File{ + Path: path, Title: fm.Title, Slug: strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)), FrontMatter: *fm, Content: strings.Split(string(body), "\n"), - Links: ExtractLinks(body), + ExtractedLinks: ExtractLinks(body), }, nil } var linkRegex = regexp.MustCompile(`\[\[([^\]|]+)(?:\|([^\]]+))?\]\]`) -func ExtractLinks(body []byte) []ExtractedLink { - var links []ExtractedLink +func ExtractLinks(body []byte) []*ExtractedLink { + var links []*ExtractedLink lineStart := 0 lineNum := 1 @@ -65,7 +67,7 @@ func ExtractLinks(body []byte) []ExtractedLink { if m[4] != -1 { display = string(line[m[4]:m[5]]) } - links = append(links, ExtractedLink{ + links = append(links, &ExtractedLink{ TargetSlug: slug, Raw: raw, Display: display, diff --git a/core/handlers/note/save_note.go b/core/handlers/note/save_note.go index c6701d5..405f2aa 100644 --- a/core/handlers/note/save_note.go +++ b/core/handlers/note/save_note.go @@ -3,13 +3,15 @@ package note import ( "context" "encoding/json" + "log/slog" filehandling "github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling" + "github.com/KristianJBorgwarth/dendrite.daemon/core/models" "github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories" ) type saveNoteCommand struct { - Path string `json:"path"` + Path string `json:"path"` } type SaveNoteHandler struct { @@ -22,6 +24,7 @@ func NewSaveNoteHandler() *SaveNoteHandler { func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) { var cmd saveNoteCommand + slog.Debug("Handling SaveNoteCommand", "raw", string(raw)) if err := json.Unmarshal(raw, &cmd); err != nil { return nil, err @@ -29,6 +32,7 @@ func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, file, err := filehandling.ReadFile(cmd.Path) if err != nil { + slog.Debug("Failed to read file", "path", cmd.Path, "error", err) return nil, err } @@ -39,16 +43,98 @@ func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, defer h.uow.Rollback() noteRepo := repositories.NewNoteRepository(tx) + linkRepo := repositories.NewLinkRepository(tx) + tagRepo := repositories.NewTagRepository(tx) - note, err := noteRepo.GetBySlug(ctx, file.Slug) if err != nil { + slog.Debug("Failed to get note by slug", "slug", file.Slug, "error", err) return nil, err } - println("Saving note:", note.Title(), "at path:", note.Path()) - - + if note == nil { + if err := h.handleNewNote(ctx, noteRepo, linkRepo, tagRepo, file); err != nil { + return nil, err + } + } else { + if err := h.handleExistingNote(ctx, linkRepo, tagRepo, note, file); err != nil { + return nil, err + } + } + if err := h.uow.Commit(); err != nil { + return nil, err + } return nil, nil } + +func (h *SaveNoteHandler) handleNewNote( + ctx context.Context, + noteRepo repositories.NoteRepository, + linkRepo repositories.ILinkRepository, + tagRepo repositories.ITagRepository, + file *filehandling.File, +) error { + note := models.CreateNote(file.Path, file.Title, file.Slug) + slog.Debug("EXTRACTED FILE", "path", file.Path, "title", file.Title, "slug", file.Slug, "links", file.ExtractedLinks, "tags", file.FrontMatter.Tags) + + if err := noteRepo.Insert(ctx, note); err != nil { + slog.Debug("Failed to insert new note", "noteID", note.ID(), "error", err) + return err + } + + links := models.MapToLinkModel(note.ID(), file.ExtractedLinks) + + if err := linkRepo.Insert(ctx, links); err != nil { + slog.Debug("Failed to insert links for new note", "noteID", note.ID(), "error", err) + return err + } + + if err := tagRepo.InsertNoteTags(ctx, note.ID(), file.FrontMatter.Tags); err != nil { + slog.Debug("Failed to insert tags for new note", "noteID", note.ID(), "error", err) + return err + } + + return nil +} + +func (h *SaveNoteHandler) handleExistingNote(ctx context.Context, + linkRepo repositories.ILinkRepository, + tagRepo repositories.ITagRepository, + note *models.Note, + file *filehandling.File, +) error { + err := h.deleteExistingNoteRelations(ctx, linkRepo, tagRepo, note.ID()) + if err != nil { + slog.Debug("Failed to delete existing note relations", "noteID", note.ID(), "error", err) + return err + } + + links := models.MapToLinkModel(note.ID(), file.ExtractedLinks) + + if err = linkRepo.Insert(ctx, links); err != nil { + slog.Debug("Failed to insert links for existing note", "noteID", note.ID(), "error", err) + return err + } + + if err = tagRepo.InsertNoteTags(ctx, note.ID(), file.FrontMatter.Tags); err != nil { + slog.Debug("Failed to insert tags for existing note", "noteID", note.ID(), "error", err) + return err + } + + return nil +} + +func (h *SaveNoteHandler) deleteExistingNoteRelations(ctx context.Context, linkRepo repositories.ILinkRepository, tagRepo repositories.ITagRepository, noteID string) error { + if err := linkRepo.Delete(ctx, noteID); err != nil { + return err + } + if err := tagRepo.DeleteNoteTags(ctx, noteID); err != nil { + return err + } + return nil +} + +func (h *SaveNoteHandler) handleTags(ctx context.Context, tagRepo repositories.ITagRepository, noteID string, tags []string) error { + panic("not implemented") +} diff --git a/core/models/links.go b/core/models/links.go index 97dc13b..b005370 100644 --- a/core/models/links.go +++ b/core/models/links.go @@ -40,6 +40,10 @@ func CreateLink(fromNoteID, targetSlug, raw, display string, line, col int) *Lin } } +func (l *Link) ID() string { + return l.id +} + func (l *Link) FromNoteID() string { return l.fromNoteID } @@ -51,3 +55,15 @@ func (l *Link) TargetSlug() string { func (l *Link) Raw() string { return l.raw } + +func (l *Link) Display() string { + return l.display +} + +func (l *Link) Line() int { + return l.line +} + +func (l *Link) Col() int { + return l.col +} diff --git a/core/models/mappers.go b/core/models/mappers.go new file mode 100644 index 0000000..5e2af24 --- /dev/null +++ b/core/models/mappers.go @@ -0,0 +1,11 @@ +package models + +import filehandling "github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling" + +func MapToLinkModel(noteID string, extractedLinks []*filehandling.ExtractedLink) []*Link { + var links []*Link + for _, link := range extractedLinks { + links = append(links, CreateLink(noteID, link.TargetSlug, link.Raw, link.Display, link.Line, link.Col)) + } + return links +} diff --git a/dendrite b/dendrite index 321c518..c9a2ce5 100755 Binary files a/dendrite and b/dendrite differ diff --git a/persistence/repositories/link_repository.go b/persistence/repositories/link_repository.go index 5a11c94..3bc30ff 100644 --- a/persistence/repositories/link_repository.go +++ b/persistence/repositories/link_repository.go @@ -1,6 +1,7 @@ package repositories import ( + "strings" "context" "github.com/KristianJBorgwarth/dendrite.daemon/core/models" @@ -8,9 +9,11 @@ import ( ) type ILinkRepository interface { + Insert(ctx context.Context, links []*models.Link) error GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error) GetBySlug(ctx context.Context, targetSlug string) ([]*models.Link, error) Search(ctx context.Context, query string) ([]*models.Link, error) + Delete(ctx context.Context, fromNoteID string) error } type linkRepository struct { @@ -21,8 +24,27 @@ func NewLinkRepository(ctx persistence.IDbContext) ILinkRepository { return &linkRepository{dbContext: ctx} } +func (r *linkRepository) Insert(ctx context.Context, links []*models.Link) error { + if len(links) == 0 { + return nil + } + + placeholders := make([]string, 0, len(links)) + args := make([]any, 0, len(links)) + + for _, link := range links { + placeholders = append(placeholders, "(?, ?, ?, ?, ?, ?, ?)") + args = append(args, link.ID(), link.FromNoteID(), link.TargetSlug(), link.Raw(), link.Display(), link.Line(), link.Col()) + } + + query := "INSERT OR IGNORE INTO link(id, from_note_id, target_slug, raw, display, line, col) VALUES " + strings.Join(placeholders, ",") + + _, err := r.dbContext.ExecContext(ctx, query, args...) + return err +} + func (r *linkRepository) GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error) { - rows, err := r.dbContext.QueryContext(ctx, "SELECT id, from_note_id, target_slug, raw, display, line, col FROM links WHERE from_note_id = ?", fromNoteID) + rows, err := r.dbContext.QueryContext(ctx, "SELECT id, from_note_id, target_slug, raw, display, line, col FROM link WHERE from_note_id = ?", fromNoteID) if err != nil { return nil, err } @@ -42,7 +64,7 @@ func (r *linkRepository) GetByNoteID(ctx context.Context, fromNoteID string) ([] } func (r *linkRepository) GetBySlug(ctx context.Context, targetSlug string) ([]*models.Link, error) { - rows, err := r.dbContext.QueryContext(ctx, `SELECT id, from_note_id, target_slug, raw, display, line, col FROM links WHERE target_slug = ?`, targetSlug) + rows, err := r.dbContext.QueryContext(ctx, `SELECT id, from_note_id, target_slug, raw, display, line, col FROM link WHERE target_slug = ?`, targetSlug) if err != nil { return nil, err } @@ -62,7 +84,7 @@ func (r *linkRepository) GetBySlug(ctx context.Context, targetSlug string) ([]*m } func (r *linkRepository) Search(ctx context.Context, query string) ([]*models.Link, error) { - rows, err := r.dbContext.QueryContext(ctx, `SELECT id, from_note_id, target_slug, raw, display, line, col FROM links WHERE raw LIKE ?`, "%"+query+"%") + rows, err := r.dbContext.QueryContext(ctx, `SELECT id, from_note_id, target_slug, raw, display, line, col FROM link WHERE raw LIKE ?`, "%"+query+"%") if err != nil { return nil, err } @@ -80,3 +102,8 @@ func (r *linkRepository) Search(ctx context.Context, query string) ([]*models.Li return links, nil } + +func (r *linkRepository) Delete(ctx context.Context, fromNoteID string) error { + _, err := r.dbContext.ExecContext(ctx, "DELETE FROM link WHERE from_note_id = ?", fromNoteID) + return err +} diff --git a/persistence/repositories/note_repository.go b/persistence/repositories/note_repository.go index 093c1b4..a690b1d 100644 --- a/persistence/repositories/note_repository.go +++ b/persistence/repositories/note_repository.go @@ -2,6 +2,8 @@ package repositories import ( "context" + "database/sql" + "errors" "github.com/KristianJBorgwarth/dendrite.daemon/core/models" "github.com/KristianJBorgwarth/dendrite.daemon/persistence" @@ -33,12 +35,15 @@ func (r *noteRepository) Insert(ctx context.Context, note *models.Note) error { } func (r *noteRepository) GetBySlug(ctx context.Context, slug string) (*models.Note, error) { - query := `SELECT id, title, path, slug, created_at, updated_at FROM notes WHERE slug = ?` + query := `SELECT id, title, path, slug, created_at, updated_at FROM note WHERE slug = ?` row := r.dbContext.QueryRowContext(ctx, query, slug) var id, title, path, createdAt, updatedAt string err := row.Scan(&id, &title, &path, &slug, &createdAt, &updatedAt) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } return nil, err } diff --git a/persistence/repositories/tag_repository.go b/persistence/repositories/tag_repository.go index 5593a6d..ec7ccd9 100644 --- a/persistence/repositories/tag_repository.go +++ b/persistence/repositories/tag_repository.go @@ -12,6 +12,7 @@ type ITagRepository interface { Insert(ctx context.Context, tags []*models.Tag) error InsertNoteTags(ctx context.Context, noteID string, tagIDs []string) error GetByNames(ctx context.Context, names []string) ([]*models.Tag, error) + DeleteNoteTags(ctx context.Context, noteID string) error } type tagRepository struct { @@ -97,3 +98,9 @@ func (r *tagRepository) GetByNames(ctx context.Context, names []string) ([]*mode return tags, nil } + +func (r *tagRepository) DeleteNoteTags(ctx context.Context, noteID string) error { + query := "DELETE FROM note_tag WHERE note_id = ?" + _, err := r.dbContext.ExecContext(ctx, query, noteID) + return err +}