add path to slug (#37)
This commit is contained in:
parent
7e732f5212
commit
8a6543611e
11 changed files with 62 additions and 34 deletions
|
|
@ -25,7 +25,7 @@ type ExtractedLink struct {
|
|||
Col int
|
||||
}
|
||||
|
||||
func ReadFile(path string) (*File, error) {
|
||||
func ReadFile(vaultRoot, path string) (*File, error) {
|
||||
body, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -47,13 +47,14 @@ func ReadFile(path string) (*File, error) {
|
|||
return &File{
|
||||
Path: path,
|
||||
Title: fm.Title,
|
||||
Slug: strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)),
|
||||
Slug: SlugRelative(vaultRoot, path),
|
||||
FrontMatter: *fm,
|
||||
Content: strings.Split(string(body), "\n"),
|
||||
ExtractedLinks: ExtractLinks(body),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
var linkRegex = regexp.MustCompile(`\[\[([^\]|]+)(?:\|([^\]]+))?\]\]`)
|
||||
|
||||
func ExtractLinks(body []byte) []*ExtractedLink {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package filehandling
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Slugify(s string) string {
|
||||
|
|
@ -17,3 +19,9 @@ func Slugify(s string) string {
|
|||
}
|
||||
return slug.String()
|
||||
}
|
||||
|
||||
func SlugRelative(base, path string) string {
|
||||
rel := strings.TrimPrefix(path, base+string(filepath.Separator))
|
||||
rel = strings.TrimSuffix(rel, filepath.Ext(rel))
|
||||
return rel
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package filehandling
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -12,16 +13,22 @@ type Template struct {
|
|||
Content []byte
|
||||
Title string
|
||||
Slug string
|
||||
Filename string
|
||||
FrontMatter *FrontMatter
|
||||
}
|
||||
|
||||
func NewTemplate(templateName string, title string) (*Template, error) {
|
||||
func NewTemplate(templateName, title, directory string) (*Template, error) {
|
||||
var templatePath string
|
||||
if templateName != "" {
|
||||
templatePath = store.GetVaultStore().GetTemplatePath(templateName)
|
||||
}
|
||||
slug := Slugify(title)
|
||||
content, err := renderTemplate(templatePath, title, slug)
|
||||
|
||||
fileNameSlug := Slugify(title)
|
||||
|
||||
pathSlug := filepath.ToSlash(filepath.Join(directory, fileNameSlug))
|
||||
filename := fileNameSlug + ".md"
|
||||
|
||||
content, err := renderTemplate(templatePath, title, pathSlug, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -34,12 +41,13 @@ func NewTemplate(templateName string, title string) (*Template, error) {
|
|||
return &Template{
|
||||
Content: content,
|
||||
Title: title,
|
||||
Slug: slug,
|
||||
Slug: pathSlug,
|
||||
Filename: filename,
|
||||
FrontMatter: fm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func renderTemplate(templatePath string, title string, slug string) ([]byte, error) {
|
||||
func renderTemplate(templatePath, title, slug, filename string) ([]byte, error) {
|
||||
template, err := readTemplate(templatePath, title)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -49,7 +57,7 @@ func renderTemplate(templatePath string, title string, slug string) ([]byte, err
|
|||
"{{title}}", title,
|
||||
"{{date}}", time.Now().Format("2006-01-02"),
|
||||
"{{slug}}", slug,
|
||||
"{{file}}", slug+".md",
|
||||
"{{file}}", filename,
|
||||
)
|
||||
|
||||
return []byte(r.Replace(string(template))), nil
|
||||
|
|
|
|||
|
|
@ -39,12 +39,12 @@ func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (an
|
|||
return nil, err
|
||||
}
|
||||
|
||||
template, err := filehandling.NewTemplate(cmd.TemplateName, cmd.Title)
|
||||
template, err := filehandling.NewTemplate(cmd.TemplateName, cmd.Title, cmd.Directory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notePath := filepath.Join(store.GetVaultStore().Config.VaultPath(), cmd.Directory, template.Slug+".md")
|
||||
notePath := filepath.Join(store.GetVaultStore().Config.VaultPath(), cmd.Directory, template.Filename)
|
||||
|
||||
dbCtx, err := h.uow.Begin()
|
||||
if err != nil {
|
||||
|
|
@ -53,6 +53,15 @@ func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (an
|
|||
|
||||
defer h.uow.Rollback()
|
||||
|
||||
existingNote, err := h.noteRepo.GetBySlug(ctx, template.Slug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existingNote != nil {
|
||||
return notePath, nil
|
||||
}
|
||||
|
||||
tagModels, err := h.tagService.CreateTags(ctx, dbCtx, template.FrontMatter.Tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
|
||||
filehandling "github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling"
|
||||
"github.com/KristianJBorgwarth/dendrite.daemon/core/file_handling"
|
||||
"github.com/KristianJBorgwarth/dendrite.daemon/core/services"
|
||||
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
|
||||
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/store"
|
||||
)
|
||||
|
||||
type saveNoteCommand struct {
|
||||
|
|
@ -38,7 +39,9 @@ func (h *SaveNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
file, err := filehandling.ReadFile(cmd.Path)
|
||||
vaultRoot := store.GetVaultStore().Config.VaultPath()
|
||||
|
||||
file, err := filehandling.ReadFile(vaultRoot, cmd.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ func (r *indexRebuilder) readFiles(vault string) ([]*filehandling.File, error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
pendingFile, err := filehandling.ReadFile(path)
|
||||
pendingFile, err := filehandling.ReadFile(vault, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
BIN
dendrite
BIN
dendrite
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS note (
|
||||
id TEXT PRIMARY KEY,
|
||||
path TEXT UNIQUE,
|
||||
path TEXT UNIQUE NOT NULL,
|
||||
title TEXT,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
|
|
|
|||
|
|
@ -33,9 +33,7 @@ func (r *noteRepository) Insert(ctx context.Context, dbContext persistence.IDbCo
|
|||
query := `
|
||||
INSERT INTO note (id, title, path, slug, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
ON CONFLICT (slug) DO UPDATE
|
||||
SET title = EXCLUDED.title,
|
||||
path = EXCLUDED.path;
|
||||
ON CONFLICT DO NOTHING;
|
||||
`
|
||||
_, err := dbContext.ExecContext(ctx, query, note.ID(), note.Title(), note.Path(), note.Slug())
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func TestCreateNoteHandler_WithTemplate_CreatesNoteFileWithTagsAndReturnsPath(t
|
|||
assert.Equal(t, notePath, result)
|
||||
|
||||
var noteCount int
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "templated-note").Scan(¬eCount))
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "subdir/templated-note").Scan(¬eCount))
|
||||
assert.Equal(t, 1, noteCount)
|
||||
|
||||
rows, err := Fixture.DB.Query(`SELECT name FROM tag WHERE name IN ('go', 'testing')`)
|
||||
|
|
@ -98,7 +98,7 @@ func TestCreateNoteHandler_WithTemplate_CreatesNoteFileWithTagsAndReturnsPath(t
|
|||
assert.NoError(t, statErr)
|
||||
}
|
||||
|
||||
func TestCreateNoteHandler_DuplicateSlug_Upserts(t *testing.T) {
|
||||
func TestCreateNoteHandler_DuplicateSlug_ReturnsExistingPath(t *testing.T) {
|
||||
// Arrange
|
||||
handler := newCreateNoteHandler()
|
||||
|
||||
|
|
@ -107,29 +107,22 @@ func TestCreateNoteHandler_DuplicateSlug_Upserts(t *testing.T) {
|
|||
require.NoError(t, os.MkdirAll(filepath.Join(vaultPath, subDir), 0o755))
|
||||
defer os.RemoveAll(filepath.Join(vaultPath, subDir))
|
||||
|
||||
params1, _ := json.Marshal(map[string]any{"title": "Dup Note", "templateName": "", "directory": subDir})
|
||||
_, err := handler.Handle(Fixture.TestContext, params1)
|
||||
params, _ := json.Marshal(map[string]any{"title": "Dup Note", "templateName": "", "directory": subDir})
|
||||
_, err := handler.Handle(Fixture.TestContext, params)
|
||||
require.NoError(t, err)
|
||||
|
||||
subDir2 := "dup-dir-moved"
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(vaultPath, subDir2), 0o755))
|
||||
defer os.RemoveAll(filepath.Join(vaultPath, subDir2))
|
||||
params2, _ := json.Marshal(map[string]any{"title": "Dup Note", "templateName": "", "directory": subDir2})
|
||||
|
||||
// Act
|
||||
_, err = handler.Handle(Fixture.TestContext, params2)
|
||||
// Act — same title and directory produces the same slug, handler returns early
|
||||
result, err := handler.Handle(Fixture.TestContext, params)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err)
|
||||
|
||||
var count int
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "dup-note").Scan(&count))
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "dup-dir/dup-note").Scan(&count))
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
expectedPath := filepath.Join(vaultPath, subDir2, "dup-note.md")
|
||||
var path string
|
||||
require.NoError(t, Fixture.DB.QueryRow(`SELECT path FROM note WHERE slug = ?`, "dup-note").Scan(&path))
|
||||
assert.Equal(t, expectedPath, path)
|
||||
expectedPath := filepath.Join(vaultPath, subDir, "dup-note.md")
|
||||
assert.Equal(t, expectedPath, result)
|
||||
}
|
||||
|
||||
func TestCreateNoteHandler_InvalidJSON_ReturnsError(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,15 @@ func NewDBFixture() *DBFixture {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(os.TempDir(), ".index", "index.db")
|
||||
xdg := os.Getenv("XDG_DATA_HOME")
|
||||
if xdg == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
xdg = filepath.Join(home, ".local", "share")
|
||||
}
|
||||
dbPath := filepath.Join(xdg, "dendrite", vaultPath, "index.db")
|
||||
|
||||
dbContext := persistence.GetDBContext()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue