feat(note_save): save new and old notes (#19)
* feat(map): added extracted_link -> model.link map * feat(note/save): setup for handle_new_note * feat(save_note): save new and old notes
This commit is contained in:
parent
0219248db7
commit
754a4c5435
8 changed files with 168 additions and 14 deletions
|
|
@ -9,11 +9,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
|
Path string
|
||||||
Title string
|
Title string
|
||||||
Slug string
|
Slug string
|
||||||
FrontMatter FrontMatter
|
FrontMatter FrontMatter
|
||||||
Content []string
|
Content []string
|
||||||
Links []ExtractedLink
|
ExtractedLinks []*ExtractedLink
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtractedLink struct {
|
type ExtractedLink struct {
|
||||||
|
|
@ -38,18 +39,19 @@ func ReadFile(path string) (*File, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &File{
|
return &File{
|
||||||
|
Path: path,
|
||||||
Title: fm.Title,
|
Title: fm.Title,
|
||||||
Slug: strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)),
|
Slug: strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)),
|
||||||
FrontMatter: *fm,
|
FrontMatter: *fm,
|
||||||
Content: strings.Split(string(body), "\n"),
|
Content: strings.Split(string(body), "\n"),
|
||||||
Links: ExtractLinks(body),
|
ExtractedLinks: ExtractLinks(body),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var linkRegex = regexp.MustCompile(`\[\[([^\]|]+)(?:\|([^\]]+))?\]\]`)
|
var linkRegex = regexp.MustCompile(`\[\[([^\]|]+)(?:\|([^\]]+))?\]\]`)
|
||||||
|
|
||||||
func ExtractLinks(body []byte) []ExtractedLink {
|
func ExtractLinks(body []byte) []*ExtractedLink {
|
||||||
var links []ExtractedLink
|
var links []*ExtractedLink
|
||||||
|
|
||||||
lineStart := 0
|
lineStart := 0
|
||||||
lineNum := 1
|
lineNum := 1
|
||||||
|
|
@ -65,7 +67,7 @@ func ExtractLinks(body []byte) []ExtractedLink {
|
||||||
if m[4] != -1 {
|
if m[4] != -1 {
|
||||||
display = string(line[m[4]:m[5]])
|
display = string(line[m[4]:m[5]])
|
||||||
}
|
}
|
||||||
links = append(links, ExtractedLink{
|
links = append(links, &ExtractedLink{
|
||||||
TargetSlug: slug,
|
TargetSlug: slug,
|
||||||
Raw: raw,
|
Raw: raw,
|
||||||
Display: display,
|
Display: display,
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ package note
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
filehandling "github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling"
|
filehandling "github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling"
|
||||||
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
||||||
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
|
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
|
||||||
)
|
)
|
||||||
|
|
||||||
type saveNoteCommand struct {
|
type saveNoteCommand struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaveNoteHandler struct {
|
type SaveNoteHandler struct {
|
||||||
|
|
@ -22,6 +24,7 @@ func NewSaveNoteHandler() *SaveNoteHandler {
|
||||||
|
|
||||||
func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
|
func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
|
||||||
var cmd saveNoteCommand
|
var cmd saveNoteCommand
|
||||||
|
slog.Debug("Handling SaveNoteCommand", "raw", string(raw))
|
||||||
|
|
||||||
if err := json.Unmarshal(raw, &cmd); err != nil {
|
if err := json.Unmarshal(raw, &cmd); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -29,6 +32,7 @@ func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any,
|
||||||
|
|
||||||
file, err := filehandling.ReadFile(cmd.Path)
|
file, err := filehandling.ReadFile(cmd.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Debug("Failed to read file", "path", cmd.Path, "error", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,16 +43,98 @@ func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any,
|
||||||
|
|
||||||
defer h.uow.Rollback()
|
defer h.uow.Rollback()
|
||||||
noteRepo := repositories.NewNoteRepository(tx)
|
noteRepo := repositories.NewNoteRepository(tx)
|
||||||
|
linkRepo := repositories.NewLinkRepository(tx)
|
||||||
|
tagRepo := repositories.NewTagRepository(tx)
|
||||||
|
|
||||||
|
|
||||||
note, err := noteRepo.GetBySlug(ctx, file.Slug)
|
note, err := noteRepo.GetBySlug(ctx, file.Slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Debug("Failed to get note by slug", "slug", file.Slug, "error", err)
|
||||||
return nil, 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
|
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")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
func (l *Link) FromNoteID() string {
|
||||||
return l.fromNoteID
|
return l.fromNoteID
|
||||||
}
|
}
|
||||||
|
|
@ -51,3 +55,15 @@ func (l *Link) TargetSlug() string {
|
||||||
func (l *Link) Raw() string {
|
func (l *Link) Raw() string {
|
||||||
return l.raw
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
11
core/models/mappers.go
Normal file
11
core/models/mappers.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
BIN
dendrite
BIN
dendrite
Binary file not shown.
|
|
@ -1,6 +1,7 @@
|
||||||
package repositories
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
||||||
|
|
@ -8,9 +9,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ILinkRepository interface {
|
type ILinkRepository interface {
|
||||||
|
Insert(ctx context.Context, links []*models.Link) error
|
||||||
GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error)
|
GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error)
|
||||||
GetBySlug(ctx context.Context, targetSlug string) ([]*models.Link, error)
|
GetBySlug(ctx context.Context, targetSlug string) ([]*models.Link, error)
|
||||||
Search(ctx context.Context, query string) ([]*models.Link, error)
|
Search(ctx context.Context, query string) ([]*models.Link, error)
|
||||||
|
Delete(ctx context.Context, fromNoteID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type linkRepository struct {
|
type linkRepository struct {
|
||||||
|
|
@ -21,8 +24,27 @@ func NewLinkRepository(ctx persistence.IDbContext) ILinkRepository {
|
||||||
return &linkRepository{dbContext: ctx}
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -80,3 +102,8 @@ func (r *linkRepository) Search(ctx context.Context, query string) ([]*models.Li
|
||||||
|
|
||||||
return links, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
||||||
"github.com/KristianJBorgwarth/dendrite.daemon/persistence"
|
"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) {
|
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)
|
row := r.dbContext.QueryRowContext(ctx, query, slug)
|
||||||
|
|
||||||
var id, title, path, createdAt, updatedAt string
|
var id, title, path, createdAt, updatedAt string
|
||||||
err := row.Scan(&id, &title, &path, &slug, &createdAt, &updatedAt)
|
err := row.Scan(&id, &title, &path, &slug, &createdAt, &updatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ type ITagRepository interface {
|
||||||
Insert(ctx context.Context, tags []*models.Tag) error
|
Insert(ctx context.Context, tags []*models.Tag) error
|
||||||
InsertNoteTags(ctx context.Context, noteID string, tagIDs []string) error
|
InsertNoteTags(ctx context.Context, noteID string, tagIDs []string) error
|
||||||
GetByNames(ctx context.Context, names []string) ([]*models.Tag, error)
|
GetByNames(ctx context.Context, names []string) ([]*models.Tag, error)
|
||||||
|
DeleteNoteTags(ctx context.Context, noteID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type tagRepository struct {
|
type tagRepository struct {
|
||||||
|
|
@ -97,3 +98,9 @@ func (r *tagRepository) GetByNames(ctx context.Context, names []string) ([]*mode
|
||||||
|
|
||||||
return tags, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue