Merge pull request #10 from KristianJBorgwarth/feat/handler-register

feat(handlers): register handlers and dbcontext
This commit is contained in:
Kristian 2026-04-04 00:28:47 +02:00 committed by GitHub
commit 45eca33e7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 90 additions and 39 deletions

View file

@ -12,7 +12,8 @@ import (
func main() {
server := server.NewServer()
server.Register("initialize", handlers.InitializeHandler{})
server.RegisterHandler("initialize", handlers.NewInitializeHandler())
server.RegisterHandler("createNote", handlers.NewCreateNoteHandler())
if err := server.Run(os.Stdin, os.Stdout); err != nil {
slog.Error("server error", "error", err)

View file

@ -18,12 +18,12 @@ type createNoteCommand struct {
Vars map[string]string `json:"vars"`
}
type CreateNoteHandler struct {
type CreateNoteHandler struct{
uow *repositories.UnitOfWork
}
func NewCreateNoteHandler(uow *repositories.UnitOfWork) *CreateNoteHandler {
return &CreateNoteHandler{uow: uow}
func NewCreateNoteHandler() *CreateNoteHandler {
return &CreateNoteHandler{repositories.NewUnitOfWork()}
}
func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
@ -70,7 +70,7 @@ func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (an
return nil, err
}
if err = tagRepo.UpsertNoteTags(note.ID(), utils.Select(tagModels, func(t *models.Tag) string { return t.ID() })); err != nil {
if err = tagRepo.UpsertNoteTags(ctx, note.ID(), utils.Select(tagModels, func(t *models.Tag) string { return t.ID() })); err != nil {
return nil, err
}

View file

@ -12,14 +12,18 @@ type initializeCommand struct {
type InitializeHandler struct{}
func (h InitializeHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
var params initializeCommand
func NewInitializeHandler() *InitializeHandler {
return &InitializeHandler{}
}
if err := json.Unmarshal(raw, &params); err != nil {
func (h InitializeHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
var cmd initializeCommand
if err := json.Unmarshal(raw, &cmd); err != nil {
return nil, err
}
err := persistence.InitializeIndex(params.VaultPath)
err := persistence.InitializeDBContext(cmd.VaultPath)
if err != nil {
return nil, err
}

View file

@ -22,7 +22,7 @@ func NewServer() *Server {
}
}
func (s *Server) Register(method string, handler handlers.Handler) {
func (s *Server) RegisterHandler(method string, handler handlers.Handler) {
s.handlers[method] = handler
}

3
go.mod
View file

@ -5,9 +5,12 @@ go 1.26
require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.11.1
golang.org/x/tools v0.42.0
modernc.org/sqlite v1.47.0
)
require github.com/yuin/goldmark v1.4.13 // indirect
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect

2
go.sum
View file

@ -18,6 +18,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=

38
persistence/db_context.go Normal file
View file

@ -0,0 +1,38 @@
package persistence
import (
"database/sql"
)
type DBContext struct {
DB *sql.DB
}
var dbContext *DBContext
func InitializeDBContext(vaultPath string) (error) {
db, err := InitializeIndex(vaultPath)
if err != nil {
return err
}
dbContext = &DBContext{DB: db}
return nil
}
func GetDBContext() (*DBContext, error) {
if dbContext == nil {
panic("DBContext is not initialized. Call InitializeDbContext first.")
}
return dbContext, nil
}
func CloseDBContext() error {
if dbContext == nil {
return nil
}
err := dbContext.DB.Close()
dbContext = nil
return err
}

View file

@ -12,12 +12,12 @@ import (
//go:embed migrations/*.sql
var migrationsFS embed.FS
func InitializeIndex(vaultPath string) error {
func InitializeIndex(vaultPath string) (*sql.DB, error) {
indexDir := filepath.Join(vaultPath, ".index")
if err := os.MkdirAll(indexDir, 0755); err != nil {
slog.Error("failed to create index directory", "error", err)
return err
return nil, err
}
dbPath := filepath.Join(indexDir, "index.db")
@ -25,17 +25,16 @@ func InitializeIndex(vaultPath string) error {
db, err := sql.Open("sqlite", dbPath)
if err != nil {
slog.Error("failed to open database", "error", err)
return err
return nil, err
}
defer db.Close()
if err := applyMigrations(db); err != nil {
slog.Error("failed to apply migrations", "error", err)
return err
return nil, err
}
return nil
return db, nil
}
func applyMigrations(db *sql.DB) error {

View file

@ -10,7 +10,7 @@ import (
type ITagRepository interface {
Upsert(ctx context.Context, tags []*models.Tag) error
UpsertNoteTags(noteID string, tagIDs []string) error
UpsertNoteTags(ctx context.Context, noteID string, tagIDs []string) error
}
type tagRepository struct {
@ -40,7 +40,7 @@ func (r *tagRepository) Upsert(ctx context.Context, tags []*models.Tag) error {
return err
}
func (r *tagRepository) UpsertNoteTags(noteID string, tagIDs []string) error {
func (r *tagRepository) UpsertNoteTags(ctx context.Context ,noteID string, tagIDs []string) error {
if len(tagIDs) == 0 {
return nil
}
@ -55,7 +55,7 @@ func (r *tagRepository) UpsertNoteTags(noteID string, tagIDs []string) error {
query := "INSERT OR IGNORE INTO note_tags(note_id, tag_id) VALUES " + strings.Join(placeholders, ",")
_, err := r.Transaction.ExecContext(context.Background(), query, args...)
_, err := r.Transaction.ExecContext(ctx, query, args...)
if err != nil {
return err
}

View file

@ -3,21 +3,26 @@ package repositories
import (
"database/sql"
"github.com/KristianJBorgwarth/dendrite.daemon/persistence"
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/store"
)
type UnitOfWork struct {
db *sql.DB
dbContext *sql.DB
Transaction *sql.Tx
FileStore *store.FileStore
}
func NewUnitOfWork(db *sql.DB) *UnitOfWork {
return &UnitOfWork{db: db, FileStore: store.NewFileStore()}
func NewUnitOfWork() *UnitOfWork {
db, err := persistence.GetDBContext()
if err != nil {
panic("failed to get DB context: " + err.Error())
}
return &UnitOfWork{dbContext: db.DB, FileStore: store.NewFileStore()}
}
func (u *UnitOfWork) Begin() (tx *sql.Tx, err error) {
tx, err = u.db.Begin()
tx, err = u.dbContext.Begin()
if err != nil {
return nil, err
}

View file

@ -7,19 +7,14 @@ import (
"testing"
"github.com/KristianJBorgwarth/dendrite.daemon/core/handlers"
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newCreateNoteHandler() *handlers.CreateNoteHandler {
uow := repositories.NewUnitOfWork(Fixture.DB)
return handlers.NewCreateNoteHandler(uow)
}
func TestCreateNoteHandler_NoTemplate_CreatesNoteFileAndReturnsPath(t *testing.T) {
// Arrange
handler := newCreateNoteHandler()
handler := handlers.NewCreateNoteHandler()
notePath := filepath.Join(t.TempDir(), "my-note.md")
params, _ := json.Marshal(map[string]any{
"title": "My Note",
@ -43,7 +38,7 @@ func TestCreateNoteHandler_NoTemplate_CreatesNoteFileAndReturnsPath(t *testing.T
func TestCreateNoteHandler_WithTemplate_CreatesNoteFileWithTagsAndReturnsPath(t *testing.T) {
// Arrange
handler := newCreateNoteHandler()
handler := handlers.NewCreateNoteHandler()
dir := t.TempDir()
templatePath := filepath.Join(dir, "template.md")
@ -86,17 +81,19 @@ func TestCreateNoteHandler_WithTemplate_CreatesNoteFileWithTagsAndReturnsPath(t
func TestCreateNoteHandler_DuplicateSlug_Upserts(t *testing.T) {
// Arrange
dir := t.TempDir()
handler := handlers.NewCreateNoteHandler()
path1 := filepath.Join(dir, "dup-note.md")
params1, _ := json.Marshal(map[string]any{"title": "Dup Note", "path": path1})
_, err := newCreateNoteHandler().Handle(Fixture.TestContext, params1)
_, err := handler.Handle(Fixture.TestContext, params1)
require.NoError(t, err)
path2 := filepath.Join(dir, "dup-note-moved.md")
params2, _ := json.Marshal(map[string]any{"title": "Dup Note", "path": path2})
// Act
_, err = newCreateNoteHandler().Handle(Fixture.TestContext, params2)
_, err = handler.Handle(Fixture.TestContext, params2)
// Assert
require.NoError(t, err)
@ -112,7 +109,8 @@ func TestCreateNoteHandler_DuplicateSlug_Upserts(t *testing.T) {
func TestCreateNoteHandler_InvalidJSON_ReturnsError(t *testing.T) {
// Arrange
handler := newCreateNoteHandler()
handler := handlers.NewCreateNoteHandler()
// Act
_, err := handler.Handle(Fixture.TestContext, json.RawMessage(`{invalid json}`))
@ -123,7 +121,8 @@ func TestCreateNoteHandler_InvalidJSON_ReturnsError(t *testing.T) {
func TestCreateNoteHandler_NonExistentTemplatePath_ReturnsError(t *testing.T) {
// Arrange
handler := newCreateNoteHandler()
handler := handlers.NewCreateNoteHandler()
params, _ := json.Marshal(map[string]any{
"title": "Ghost Note",
"path": filepath.Join(t.TempDir(), "ghost.md"),

View file

@ -22,20 +22,20 @@ type DBFixture struct {
func NewDBFixture() *DBFixture {
vaultPath := os.TempDir()
err := persistence.InitializeIndex(vaultPath)
err := persistence.InitializeDBContext(vaultPath)
if err != nil {
panic(err)
}
dbPath := filepath.Join(os.TempDir(), ".index", "index.db")
db, err := sql.Open("sqlite", dbPath)
dbContext, err := persistence.GetDBContext()
if err != nil {
panic(err)
panic("failed to get DB context: " + err.Error())
}
return &DBFixture{
DB: db,
DB: dbContext.DB,
DBPath: dbPath,
TestContext: context.Background(),
}