add path to slug (#37)

This commit is contained in:
Kristian 2026-05-09 14:32:29 +02:00 committed by GitHub
parent 7e732f5212
commit 8a6543611e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 62 additions and 34 deletions

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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

Binary file not shown.

View file

@ -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,

View file

@ -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

View file

@ -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(&noteCount))
require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "subdir/templated-note").Scan(&noteCount))
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) {

View file

@ -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()