diff --git a/core/models/links.go b/core/models/links.go index c870840..97dc13b 100644 --- a/core/models/links.go +++ b/core/models/links.go @@ -1,16 +1,42 @@ package models +import "github.com/google/uuid" + type Link struct { + id string fromNoteID string - toNoteID string + targetSlug string raw string + display string + line int + col int } -func NewLink(fromNoteID, toNoteID, raw string) *Link { +func NewLink(id, fromNoteID, targetSlug, raw, display string, line, col int) *Link { return &Link{ + id: id, fromNoteID: fromNoteID, - toNoteID: toNoteID, + targetSlug: targetSlug, raw: raw, + display: display, + line: line, + col: col, + } +} + +func CreateLink(fromNoteID, targetSlug, raw, display string, line, col int) *Link { + id, err := uuid.NewV7() + if err != nil { + panic(err) + } + return &Link{ + id: id.String(), + fromNoteID: fromNoteID, + targetSlug: targetSlug, + raw: raw, + display: display, + line: line, + col: col, } } @@ -18,8 +44,8 @@ func (l *Link) FromNoteID() string { return l.fromNoteID } -func (l *Link) ToNoteID() string { - return l.toNoteID +func (l *Link) TargetSlug() string { + return l.targetSlug } func (l *Link) Raw() string { diff --git a/persistence/migrations/001_dendrite_db_init.sql b/persistence/migrations/001_dendrite_db_init.sql deleted file mode 100644 index 90584bc..0000000 --- a/persistence/migrations/001_dendrite_db_init.sql +++ /dev/null @@ -1,21 +0,0 @@ -CREATE TABLE IF NOT EXISTS notes ( - id TEXT PRIMARY KEY, - path TEXT UNIQUE, - title TEXT, - slug TEXT UNIQUE NOT NULL, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS links ( - from_note_id TEXT NOT NULL, - to_note_id TEXT NOT NULL, - raw TEXT, - FOREIGN KEY(from_note_id) REFERENCES notes(id) ON DELETE CASCADE, - FOREIGN KEY(to_note_id) REFERENCES notes(id) ON DELETE CASCADE -); - -CREATE INDEX IF NOT EXISTS idx_notes_slug ON notes(slug); -CREATE INDEX IF NOT EXISTS idx_links_from ON links(from_note_id); -CREATE INDEX IF NOT EXISTS idx_links_to ON links(to_note_id); - diff --git a/persistence/migrations/001_note.sql b/persistence/migrations/001_note.sql new file mode 100644 index 0000000..9a5214b --- /dev/null +++ b/persistence/migrations/001_note.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS note ( + id TEXT PRIMARY KEY, + path TEXT UNIQUE, + title TEXT, + slug TEXT UNIQUE NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + + +CREATE INDEX IF NOT EXISTS idx_note_slug ON note(slug); + diff --git a/persistence/migrations/002_tags.sql b/persistence/migrations/002_tags.sql index f8e59dc..bf0bc27 100644 --- a/persistence/migrations/002_tags.sql +++ b/persistence/migrations/002_tags.sql @@ -1,12 +1,12 @@ -CREATE TABLE IF NOT EXISTS tags ( +CREATE TABLE IF NOT EXISTS tag ( id TEXT PRIMARY KEY, name TEXT UNIQUE ); -CREATE TABLE IF NOT EXISTS note_tags ( +CREATE TABLE IF NOT EXISTS note_tag ( note_id TEXT, tag_id TEXT, PRIMARY KEY (note_id, tag_id), - FOREIGN KEY(note_id) REFERENCES notes(id) ON DELETE CASCADE, - FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE + FOREIGN KEY(note_id) REFERENCES note(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tag(id) ON DELETE CASCADE ); diff --git a/persistence/migrations/003_link.sql b/persistence/migrations/003_link.sql new file mode 100644 index 0000000..10e39da --- /dev/null +++ b/persistence/migrations/003_link.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS link ( + id TEXT PRIMARY KEY, + from_note_id TEXT NOT NULL, + target_slug TEXT NOT NULL, + display TEXT, + raw TEXT NOT NULL, + line INTEGER NOT NULL, + col INTEGER NOT NULL, + FOREIGN KEY(from_note_id) REFERENCES note(id) ON DELETE CASCADE +); + +CREATE INDEX idx_link_from ON link(from_note_id); +CREATE INDEX idx_link_target ON link(target_slug); + diff --git a/persistence/repositories/link_repository.go b/persistence/repositories/link_repository.go new file mode 100644 index 0000000..75375cc --- /dev/null +++ b/persistence/repositories/link_repository.go @@ -0,0 +1,61 @@ +package repositories + +import ( + "context" + "database/sql" + + "github.com/KristianJBorgwarth/dendrite.daemon/core/models" +) + +type ILinkRepository interface { + GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error) + GetBySlug(ctx context.Context, targetSlug string) ([]*models.Link, error) +} + +type linkRepository struct { + Transaction *sql.Tx +} + +func NewLinkRepository(tx *sql.Tx) ILinkRepository { + return &linkRepository{Transaction: tx} +} + +func (r *linkRepository) GetByNoteID(ctx context.Context, fromNoteID string) ([]*models.Link, error) { + rows, err := r.Transaction.QueryContext(ctx, "SELECT id, from_note_id, target_slug, raw, display, line, col FROM links WHERE from_note_id = ?", fromNoteID) + if err != nil { + return nil, err + } + defer rows.Close() + + var links []*models.Link + for rows.Next() { + var id, fromNoteID, targetSlug, raw, display string + var line, col int + if err := rows.Scan(&id, &fromNoteID, &targetSlug, &raw, &display, &line, &col); err != nil { + return nil, err + } + links = append(links, models.NewLink(id, fromNoteID, targetSlug, raw, display, line, col)) + } + + return links, nil +} + +func (r *linkRepository) GetBySlug(ctx context.Context, targetSlug string) ([]*models.Link, error) { + rows, err := r.Transaction.QueryContext(ctx, `SELECT id, from_note_id, target_slug, raw, display, line, col FROM links WHERE target_slug = ?`, targetSlug) + if err != nil { + return nil, err + } + defer rows.Close() + + var links []*models.Link + for rows.Next() { + var id, fromNoteID, targetSlug, raw, display string + var line, col int + if err := rows.Scan(&id, &fromNoteID, &targetSlug, &raw, &display, &line, &col); err != nil { + return nil, err + } + links = append(links, models.NewLink(id, fromNoteID, targetSlug, raw, display, line, col)) + } + + return links, nil +} diff --git a/persistence/repositories/note_repository.go b/persistence/repositories/note_repository.go index e7d3425..e7fc96e 100644 --- a/persistence/repositories/note_repository.go +++ b/persistence/repositories/note_repository.go @@ -21,7 +21,7 @@ func NewNoteRepository(tx *sql.Tx) NoteRepository { func (r *noteRepository) Upsert(ctx context.Context, note *models.Note) error { query := ` - INSERT INTO notes (id, title, path, slug, created_at, updated_at) + 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, diff --git a/persistence/repositories/tag_repository.go b/persistence/repositories/tag_repository.go index 27fcd2f..e73663c 100644 --- a/persistence/repositories/tag_repository.go +++ b/persistence/repositories/tag_repository.go @@ -35,7 +35,7 @@ func (r *tagRepository) Upsert(ctx context.Context, tags []*models.Tag) error { args = append(args, tag.ID(), tag.Name()) } - query := "INSERT OR IGNORE INTO tags(id, name) VALUES " + strings.Join(placeholders, ",") + query := "INSERT OR IGNORE INTO tag(id, name) VALUES " + strings.Join(placeholders, ",") _, err := r.Transaction.ExecContext(ctx, query, args...) return err @@ -54,7 +54,7 @@ func (r *tagRepository) UpsertNoteTags(ctx context.Context ,noteID string, tagID args = append(args, noteID, tagID) } - query := "INSERT OR IGNORE INTO note_tags(note_id, tag_id) VALUES " + strings.Join(placeholders, ",") + query := "INSERT OR IGNORE INTO note_tag(note_id, tag_id) VALUES " + strings.Join(placeholders, ",") _, err := r.Transaction.ExecContext(ctx, query, args...) if err != nil { @@ -77,7 +77,7 @@ func (r *tagRepository) GetByNames(ctx context.Context, names []string) ([]*mode args = append(args, name) } - query := "SELECT id, name FROM tags WHERE name IN (" + strings.Join(placeholders, ",") + ")" + query := "SELECT id, name FROM tag WHERE name IN (" + strings.Join(placeholders, ",") + ")" rows, err := r.Transaction.QueryContext(ctx, query, args...) if err != nil { diff --git a/test/test_integration/create_note_handler_test.go b/test/test_integration/create_note_handler_test.go index ec71470..c964129 100644 --- a/test/test_integration/create_note_handler_test.go +++ b/test/test_integration/create_note_handler_test.go @@ -31,7 +31,7 @@ func TestCreateNoteHandler_NoTemplate_CreatesNoteFileAndReturnsPath(t *testing.T assert.Equal(t, notePath, result) var count int - require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM notes WHERE slug = ?`, "my-note").Scan(&count)) + require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "my-note").Scan(&count)) assert.Equal(t, 1, count) _, statErr := os.Stat(notePath) @@ -68,10 +68,10 @@ func TestCreateNoteHandler_WithTemplate_CreatesNoteFileWithTagsAndReturnsPath(t assert.Equal(t, notePath, result) var noteCount int - require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM notes WHERE slug = ?`, "templated-note").Scan(¬eCount)) + require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "templated-note").Scan(¬eCount)) assert.Equal(t, 1, noteCount) - rows, err := Fixture.DB.Query(`SELECT name FROM tags WHERE name IN ('go', 'testing')`) + rows, err := Fixture.DB.Query(`SELECT name FROM tag WHERE name IN ('go', 'testing')`) require.NoError(t, err) defer rows.Close() @@ -112,12 +112,12 @@ func TestCreateNoteHandler_DuplicateSlug_Upserts(t *testing.T) { require.NoError(t, err) var count int - require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM notes WHERE slug = ?`, "dup-note").Scan(&count)) + require.NoError(t, Fixture.DB.QueryRow(`SELECT COUNT(*) FROM note WHERE slug = ?`, "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 notes WHERE slug = ?`, "dup-note").Scan(&path)) + require.NoError(t, Fixture.DB.QueryRow(`SELECT path FROM note WHERE slug = ?`, "dup-note").Scan(&path)) assert.Equal(t, expectedPath, path) }