Merge pull request #15 from KristianJBorgwarth/ref/link-model
feat(link_model): updated link model and added link repo
This commit is contained in:
commit
25098f0778
9 changed files with 131 additions and 39 deletions
|
|
@ -1,16 +1,42 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
|
id string
|
||||||
fromNoteID string
|
fromNoteID string
|
||||||
toNoteID string
|
targetSlug string
|
||||||
raw 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{
|
return &Link{
|
||||||
|
id: id,
|
||||||
fromNoteID: fromNoteID,
|
fromNoteID: fromNoteID,
|
||||||
toNoteID: toNoteID,
|
targetSlug: targetSlug,
|
||||||
raw: raw,
|
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
|
return l.fromNoteID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Link) ToNoteID() string {
|
func (l *Link) TargetSlug() string {
|
||||||
return l.toNoteID
|
return l.targetSlug
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Link) Raw() string {
|
func (l *Link) Raw() string {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
||||||
12
persistence/migrations/001_note.sql
Normal file
12
persistence/migrations/001_note.sql
Normal file
|
|
@ -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);
|
||||||
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
CREATE TABLE IF NOT EXISTS tags (
|
CREATE TABLE IF NOT EXISTS tag (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
name TEXT UNIQUE
|
name TEXT UNIQUE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS note_tags (
|
CREATE TABLE IF NOT EXISTS note_tag (
|
||||||
note_id TEXT,
|
note_id TEXT,
|
||||||
tag_id TEXT,
|
tag_id TEXT,
|
||||||
PRIMARY KEY (note_id, tag_id),
|
PRIMARY KEY (note_id, tag_id),
|
||||||
FOREIGN KEY(note_id) REFERENCES notes(id) ON DELETE CASCADE,
|
FOREIGN KEY(note_id) REFERENCES note(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
FOREIGN KEY(tag_id) REFERENCES tag(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
|
||||||
14
persistence/migrations/003_link.sql
Normal file
14
persistence/migrations/003_link.sql
Normal file
|
|
@ -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);
|
||||||
|
|
||||||
61
persistence/repositories/link_repository.go
Normal file
61
persistence/repositories/link_repository.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ func NewNoteRepository(tx *sql.Tx) NoteRepository {
|
||||||
|
|
||||||
func (r *noteRepository) Upsert(ctx context.Context, note *models.Note) error {
|
func (r *noteRepository) Upsert(ctx context.Context, note *models.Note) error {
|
||||||
query := `
|
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'))
|
VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||||
ON CONFLICT (slug) DO UPDATE
|
ON CONFLICT (slug) DO UPDATE
|
||||||
SET title = EXCLUDED.title,
|
SET title = EXCLUDED.title,
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func (r *tagRepository) Upsert(ctx context.Context, tags []*models.Tag) error {
|
||||||
args = append(args, tag.ID(), tag.Name())
|
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...)
|
_, err := r.Transaction.ExecContext(ctx, query, args...)
|
||||||
return err
|
return err
|
||||||
|
|
@ -54,7 +54,7 @@ func (r *tagRepository) UpsertNoteTags(ctx context.Context ,noteID string, tagID
|
||||||
args = append(args, noteID, 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...)
|
_, err := r.Transaction.ExecContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -77,7 +77,7 @@ func (r *tagRepository) GetByNames(ctx context.Context, names []string) ([]*mode
|
||||||
args = append(args, name)
|
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...)
|
rows, err := r.Transaction.QueryContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func TestCreateNoteHandler_NoTemplate_CreatesNoteFileAndReturnsPath(t *testing.T
|
||||||
assert.Equal(t, notePath, result)
|
assert.Equal(t, notePath, result)
|
||||||
|
|
||||||
var count int
|
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)
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
_, statErr := os.Stat(notePath)
|
_, statErr := os.Stat(notePath)
|
||||||
|
|
@ -68,10 +68,10 @@ func TestCreateNoteHandler_WithTemplate_CreatesNoteFileWithTagsAndReturnsPath(t
|
||||||
assert.Equal(t, notePath, result)
|
assert.Equal(t, notePath, result)
|
||||||
|
|
||||||
var noteCount int
|
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)
|
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)
|
require.NoError(t, err)
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
|
|
@ -112,12 +112,12 @@ func TestCreateNoteHandler_DuplicateSlug_Upserts(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var count int
|
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)
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
expectedPath := filepath.Join(vaultPath, subDir2, "dup-note.md")
|
expectedPath := filepath.Join(vaultPath, subDir2, "dup-note.md")
|
||||||
var path string
|
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)
|
assert.Equal(t, expectedPath, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue