diff --git a/cmd/main.go b/cmd/main.go index c7369c7..b602fc6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -37,6 +37,7 @@ func main() { 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)) + server.RegisterHandler("note/backlinks", note.NewGetBackLinksHandler(linkRepo, noteRepo)) server.RegisterHandler("completion/link", completion.NewCompleteLinkHandler(linkRepo)) server.RegisterHandler("completion/tag", completion.NewCompleteTagHandler(tagRepo)) diff --git a/core/dtos/backlink.go b/core/dtos/backlink.go new file mode 100644 index 0000000..c2201b1 --- /dev/null +++ b/core/dtos/backlink.go @@ -0,0 +1,23 @@ +package dtos + +type Backlink struct { + ID string `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Path string `json:"path"` + Raw string `json:"raw"` + Line int `json:"line"` + Col int `json:"col"` +} + +func NewBacklink(id, title, slug, path, raw string, col, line int) *Backlink { + return &Backlink{ + ID: id, + Title: title, + Slug: slug, + Path: path, + Raw: raw, + Line: line, + Col: col, + } +} diff --git a/core/dtos/doc.go b/core/dtos/doc.go new file mode 100644 index 0000000..a0ce901 --- /dev/null +++ b/core/dtos/doc.go @@ -0,0 +1,2 @@ +// Package dtos contains the data transfer objects used in the application. +package dtos diff --git a/core/handlers/note/get_backlinks_handler.go b/core/handlers/note/get_backlinks_handler.go new file mode 100644 index 0000000..0c4c343 --- /dev/null +++ b/core/handlers/note/get_backlinks_handler.go @@ -0,0 +1,40 @@ +package note + +import ( + "context" + "encoding/json" + + "github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories" +) + +type getBacklinksCommand struct { + Path string `json:"path"` +} + +type GetBackLinksHandler struct { + linkRepo repositories.ILinkRepository + noteRepo repositories.INoteRepository +} + +func NewGetBackLinksHandler(lr repositories.ILinkRepository, nr repositories.INoteRepository) *GetBackLinksHandler { + return &GetBackLinksHandler{linkRepo: lr, noteRepo: nr} +} + +func (h *GetBackLinksHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) { + var cmd getBacklinksCommand + if err := json.Unmarshal(raw, &cmd); err != nil { + return nil, err + } + + note, err := h.noteRepo.GetByPath(ctx, cmd.Path) + if err != nil { + return nil, err + } + + links, err := h.linkRepo.GetBacklinks(ctx, note.Slug()) + if err != nil { + return nil, err + } + + return links, nil +} diff --git a/dendrite b/dendrite index 7a84465..72347f1 100755 Binary files a/dendrite and b/dendrite differ diff --git a/persistence/repositories/link_repository.go b/persistence/repositories/link_repository.go index 2c63136..6e20053 100644 --- a/persistence/repositories/link_repository.go +++ b/persistence/repositories/link_repository.go @@ -4,6 +4,7 @@ import ( "context" "strings" + "github.com/KristianJBorgwarth/dendrite.daemon/core/dtos" "github.com/KristianJBorgwarth/dendrite.daemon/core/models" "github.com/KristianJBorgwarth/dendrite.daemon/persistence" ) @@ -14,6 +15,7 @@ type ILinkRepository interface { GetByNoteID(ctx context.Context, dbContext persistence.IDbContext, fromNoteID string) ([]*models.Link, error) GetBySlug(ctx context.Context, dbContext persistence.IDbContext, targetSlug string) ([]*models.Link, error) Search(ctx context.Context, query string) ([]*models.Link, error) + GetBacklinks(ctx context.Context, slug string) ([]*dtos.Backlink, error) Delete(ctx context.Context, dbContext persistence.IDbContext, fromNoteID string) error } @@ -123,6 +125,32 @@ func (r *linkRepository) Search(ctx context.Context, query string) ([]*models.Li return links, nil } +func (r *linkRepository) GetBacklinks(ctx context.Context, slug string) ([]*dtos.Backlink, error) { + query := ` + SELECT n.id, n.slug, n.title, n.path, l.raw, l.line, l.col + FROM link l + JOIN note n ON l.from_note_id = n.id + WHERE l.target_slug = ?` + + rows, err := r.readDBContext.QueryContext(ctx, query, slug) + if err != nil { + return nil, err + } + defer rows.Close() + + backlinks := make([]*dtos.Backlink, 0) + for rows.Next() { + var noteID, noteSlug, noteTitle, notePath, raw string + var line, col int + if err := rows.Scan(¬eID, ¬eSlug, ¬eTitle, ¬ePath, &raw, &line, &col); err != nil { + return nil, err + } + backlinks = append(backlinks, dtos.NewBacklink(noteID, noteTitle, noteSlug, notePath, raw, line, col)) + } + + return backlinks, nil +} + func (r *linkRepository) Delete(ctx context.Context, dbContext persistence.IDbContext, fromNoteID string) error { _, err := 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 c700e51..ae061ed 100644 --- a/persistence/repositories/note_repository.go +++ b/persistence/repositories/note_repository.go @@ -14,6 +14,7 @@ type INoteRepository interface { InsertRange(ctx context.Context, dbContext persistence.IDbContext, note []*models.Note) error Update(ctx context.Context, dbContext persistence.IDbContext, noteID, path, title, slug string) error GetBySlug(ctx context.Context, slug string) (*models.Note, error) + GetByPath(ctx context.Context, path string) (*models.Note, error) GetNoteCount(ctx context.Context) (int, error) } @@ -84,6 +85,22 @@ func (r *noteRepository) GetBySlug(ctx context.Context, slug string) (*models.No return models.NewNote(id, path, title, slug, createdAt, updatedAt), nil } +func (r *noteRepository) GetByPath(ctx context.Context, path string) (*models.Note, error) { + query := `SELECT id, title, path, slug, created_at, updated_at FROM note WHERE path = ?` + row := r.readDBContext.QueryRowContext(ctx, query, path) + + var id, title, slug, 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 + } + + return models.NewNote(id, path, title, slug, createdAt, updatedAt), nil +} + func (r *noteRepository) GetNoteCount(ctx context.Context) (int, error) { query := `SELECT COUNT(*) FROM note` row := r.readDBContext.QueryRowContext(ctx, query)