Merge pull request #9 from KristianJBorgwarth/feat/uuidv7-pks
feat(model_ids): use uuidv7 for model id/pkl
This commit is contained in:
commit
b4c114f713
11 changed files with 118 additions and 49 deletions
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/KristianJBorgwarth/dendrite.daemon/core/frontmatter"
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/frontmatter"
|
||||||
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
||||||
"github.com/KristianJBorgwarth/dendrite.daemon/core/template"
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/template"
|
||||||
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/utils"
|
||||||
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
|
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -26,6 +28,7 @@ func NewCreateNoteHandler(uow *repositories.UnitOfWork) *CreateNoteHandler {
|
||||||
|
|
||||||
func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
|
func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
|
||||||
var cmd createNoteCommand
|
var cmd createNoteCommand
|
||||||
|
|
||||||
if err := json.Unmarshal(raw, &cmd); err != nil {
|
if err := json.Unmarshal(raw, &cmd); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -46,16 +49,28 @@ func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (an
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer h.uow.Rollback()
|
defer h.uow.Rollback()
|
||||||
|
|
||||||
tagRepo := repositories.NewTagRepository(tx)
|
tagRepo := repositories.NewTagRepository(tx)
|
||||||
noteRepo := repositories.NewNoteRepository(tx)
|
noteRepo := repositories.NewNoteRepository(tx)
|
||||||
|
|
||||||
if err = tagRepo.Upsert(ctx, tags); err != nil {
|
tagModels, err := models.CreateTags(tags)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = noteRepo.Upsert(ctx, cmd.Title, cmd.Path, slug); err != nil {
|
if err = tagRepo.Upsert(ctx, tagModels); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
note := models.CreateNote(cmd.Path, cmd.Title, slug)
|
||||||
|
|
||||||
|
if err = noteRepo.Upsert(ctx, note); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tagRepo.UpsertNoteTags(note.ID(), utils.Select(tagModels, func(t *models.Tag) string { return t.ID() })); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
fromNoteID int
|
fromNoteID string
|
||||||
toNoteID int
|
toNoteID string
|
||||||
raw string
|
raw string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLink(fromNoteID, toNoteID int, raw string) *Link {
|
func NewLink(fromNoteID, toNoteID, raw string) *Link {
|
||||||
return &Link{
|
return &Link{
|
||||||
fromNoteID: fromNoteID,
|
fromNoteID: fromNoteID,
|
||||||
toNoteID: toNoteID,
|
toNoteID: toNoteID,
|
||||||
|
|
@ -14,11 +14,11 @@ func NewLink(fromNoteID, toNoteID int, raw string) *Link {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Link) FromNoteID() int {
|
func (l *Link) FromNoteID() string {
|
||||||
return l.fromNoteID
|
return l.fromNoteID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Link) ToNoteID() int {
|
func (l *Link) ToNoteID() string {
|
||||||
return l.toNoteID
|
return l.toNoteID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
type Note struct {
|
type Note struct {
|
||||||
id int
|
id string
|
||||||
path string
|
path string
|
||||||
title string
|
title string
|
||||||
slug string
|
slug string
|
||||||
|
|
@ -9,7 +15,7 @@ type Note struct {
|
||||||
updatedAt string
|
updatedAt string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNote(id int, path, title, slug, createdAt, updatedAt string) *Note {
|
func NewNote(id, path, title, slug, createdAt, updatedAt string) *Note {
|
||||||
return &Note{
|
return &Note{
|
||||||
id: id,
|
id: id,
|
||||||
path: path,
|
path: path,
|
||||||
|
|
@ -20,7 +26,22 @@ func NewNote(id int, path, title, slug, createdAt, updatedAt string) *Note {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Note) ID() int {
|
func CreateNote(path, title, slug string) *Note {
|
||||||
|
id, err := uuid.NewV7()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &Note{
|
||||||
|
id: id.String(),
|
||||||
|
path: path,
|
||||||
|
title: title,
|
||||||
|
slug: slug,
|
||||||
|
createdAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
updatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Note) ID() string {
|
||||||
return n.id
|
return n.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,29 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type NoteTag struct {
|
type NoteTag struct {
|
||||||
noteID int
|
noteID string
|
||||||
tagID int
|
tagID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNoteTag(noteID, tagID int) *NoteTag {
|
func NewNoteTag(noteID, tagID string) *NoteTag {
|
||||||
return &NoteTag{
|
return &NoteTag{
|
||||||
noteID: noteID,
|
noteID: noteID,
|
||||||
tagID: tagID,
|
tagID: tagID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nt *NoteTag) NoteID() int {
|
func NewNoteTags(noteID string, tagIDs []string) []*NoteTag {
|
||||||
|
noteTags := make([]*NoteTag, len(tagIDs))
|
||||||
|
for i, tagID := range tagIDs {
|
||||||
|
noteTags[i] = NewNoteTag(noteID, tagID)
|
||||||
|
}
|
||||||
|
return noteTags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nt *NoteTag) NoteID() string {
|
||||||
return nt.noteID
|
return nt.noteID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nt *NoteTag) TagID() int {
|
func (nt *NoteTag) TagID() string {
|
||||||
return nt.tagID
|
return nt.tagID
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,33 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
id int
|
id string
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTag(id int, name string) *Tag {
|
func NewTag(name string) *Tag {
|
||||||
|
id, err := uuid.NewV7()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
return &Tag{
|
return &Tag{
|
||||||
id: id,
|
id: id.String(),
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tag) ID() int {
|
func CreateTags(tags []string) ([]*Tag, error) {
|
||||||
|
tagModels := make([]*Tag, len(tags))
|
||||||
|
for i, tag := range tags {
|
||||||
|
tagModels[i] = NewTag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagModels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tag) ID() string {
|
||||||
return t.id
|
return t.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
10
core/utils/doc.go
Normal file
10
core/utils/doc.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Package utils provides BASIC FUCKING utilities
|
||||||
|
package utils
|
||||||
|
|
||||||
|
func Select[T any, U any](input []T, mapper func(T) U) []U {
|
||||||
|
output := make([]U, len(input))
|
||||||
|
for i, item := range input {
|
||||||
|
output[i] = mapper(item)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
|
|
@ -3,6 +3,7 @@ module github.com/KristianJBorgwarth/dendrite.daemon
|
||||||
go 1.26
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
modernc.org/sqlite v1.47.0
|
modernc.org/sqlite v1.47.0
|
||||||
)
|
)
|
||||||
|
|
@ -10,7 +11,6 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
CREATE TABLE IF NOT EXISTS notes (
|
CREATE TABLE IF NOT EXISTS notes (
|
||||||
id INTEGER PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
path TEXT UNIQUE,
|
path TEXT UNIQUE,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
slug TEXT UNIQUE NOT NULL,
|
slug TEXT UNIQUE NOT NULL,
|
||||||
|
|
@ -8,8 +8,8 @@ CREATE TABLE IF NOT EXISTS notes (
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS links (
|
CREATE TABLE IF NOT EXISTS links (
|
||||||
from_note_id INTEGER NOT NULL,
|
from_note_id TEXT NOT NULL,
|
||||||
to_note_id INTEGER NOT NULL,
|
to_note_id TEXT NOT NULL,
|
||||||
raw TEXT,
|
raw TEXT,
|
||||||
FOREIGN KEY(from_note_id) REFERENCES notes(id) ON DELETE CASCADE,
|
FOREIGN KEY(from_note_id) REFERENCES notes(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY(to_note_id) REFERENCES notes(id) ON DELETE CASCADE
|
FOREIGN KEY(to_note_id) REFERENCES notes(id) ON DELETE CASCADE
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
CREATE TABLE IF NOT EXISTS tags (
|
CREATE TABLE IF NOT EXISTS tags (
|
||||||
id INTEGER 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_tags (
|
||||||
note_id INTEGER,
|
note_id TEXT,
|
||||||
tag_id INTEGER,
|
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 notes(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ package repositories
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NoteRepository interface {
|
type NoteRepository interface {
|
||||||
Upsert(ctx context.Context, title string, path string, slug string) error
|
Upsert(ctx context.Context, note *models.Note) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type noteRepository struct {
|
type noteRepository struct {
|
||||||
|
|
@ -17,14 +19,14 @@ func NewNoteRepository(tx *sql.Tx) NoteRepository {
|
||||||
return ¬eRepository{Transaction: tx}
|
return ¬eRepository{Transaction: tx}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *noteRepository) Upsert(ctx context.Context, title string, path string, slug string) error {
|
func (r *noteRepository) Upsert(ctx context.Context, note *models.Note) error {
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO notes (title, path, slug, created_at, updated_at)
|
INSERT INTO notes (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,
|
||||||
path = EXCLUDED.path;
|
path = EXCLUDED.path;
|
||||||
`
|
`
|
||||||
_, err := r.Transaction.ExecContext(ctx, query, title, path, slug)
|
_, err := r.Transaction.ExecContext(ctx, query, note.ID(), note.Title(), note.Path(), note.Slug())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,40 +4,43 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/KristianJBorgwarth/dendrite.daemon/core/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TagRepository interface {
|
type ITagRepository interface {
|
||||||
Upsert(ctx context.Context, names []string) error
|
Upsert(ctx context.Context, tags []*models.Tag) error
|
||||||
|
UpsertNoteTags(noteID string, tagIDs []string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type tagRepository struct {
|
type tagRepository struct {
|
||||||
Transaction *sql.Tx
|
Transaction *sql.Tx
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTagRepository(tx *sql.Tx) TagRepository {
|
func NewTagRepository(tx *sql.Tx) ITagRepository {
|
||||||
return &tagRepository{Transaction: tx}
|
return &tagRepository{Transaction: tx}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tagRepository) Upsert(ctx context.Context, names []string) error {
|
func (r *tagRepository) Upsert(ctx context.Context, tags []*models.Tag) error {
|
||||||
if len(names) == 0 {
|
if len(tags) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
placeholders := make([]string, 0, len(names))
|
placeholders := make([]string, 0, len(tags))
|
||||||
args := make([]any, 0, len(names))
|
args := make([]any, 0, len(tags))
|
||||||
|
|
||||||
for _, name := range names {
|
for _, tag := range tags {
|
||||||
placeholders = append(placeholders, "(?)")
|
placeholders = append(placeholders, "(?, ?)")
|
||||||
args = append(args, name)
|
args = append(args, tag.ID(), tag.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
query := "INSERT OR IGNORE INTO tags(name) VALUES " + strings.Join(placeholders, ",")
|
query := "INSERT OR IGNORE INTO tags(id, name) VALUES " + strings.Join(placeholders, ",")
|
||||||
|
|
||||||
_, err := r.Transaction.ExecContext(ctx, query, args...)
|
_, err := r.Transaction.ExecContext(ctx, query, args...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tagRepository) UpsertNoteTags(noteID int64, tagIDs []int64) error {
|
func (r *tagRepository) UpsertNoteTags(noteID string, tagIDs []string) error {
|
||||||
if len(tagIDs) == 0 {
|
if len(tagIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -50,12 +53,7 @@ func (r *tagRepository) UpsertNoteTags(noteID int64, tagIDs []int64) error {
|
||||||
args = append(args, noteID, tagID)
|
args = append(args, noteID, tagID)
|
||||||
}
|
}
|
||||||
|
|
||||||
query := "WITH input(note_id, tag_id) AS (VALUES " +
|
query := "INSERT OR IGNORE INTO note_tags(note_id, tag_id) VALUES " + strings.Join(placeholders, ",")
|
||||||
strings.Join(placeholders, ",") +
|
|
||||||
") INSERT INTO note_tags(note_id, tag_id) " +
|
|
||||||
"SELECT note_id, tag_id FROM input " +
|
|
||||||
"ON CONFLICT(note_id, tag_id) DO NOTHING" +
|
|
||||||
"SELECT note_id, tag_id FROM input;"
|
|
||||||
|
|
||||||
_, err := r.Transaction.ExecContext(context.Background(), query, args...)
|
_, err := r.Transaction.ExecContext(context.Background(), query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue