diff --git a/cmd/main.go b/cmd/main.go index 4b4890f..b2e2e3d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -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) diff --git a/core/handlers/create_note_handler.go b/core/handlers/create_note_handler.go index a61c7fe..43a2199 100644 --- a/core/handlers/create_note_handler.go +++ b/core/handlers/create_note_handler.go @@ -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 } diff --git a/core/handlers/initialize_handler.go b/core/handlers/initialize_handler.go index 6e33f12..c698f62 100644 --- a/core/handlers/initialize_handler.go +++ b/core/handlers/initialize_handler.go @@ -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, ¶ms); 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 } diff --git a/core/server/server.go b/core/server/server.go index 6561a5d..4a1c8dd 100644 --- a/core/server/server.go +++ b/core/server/server.go @@ -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 } diff --git a/go.mod b/go.mod index c43cce8..2da1664 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 7508947..6109b5b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/persistence/db_context.go b/persistence/db_context.go new file mode 100644 index 0000000..4195b0a --- /dev/null +++ b/persistence/db_context.go @@ -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 +} diff --git a/persistence/db_migrator.go b/persistence/db_migrator.go index 2de7a75..1ddc700 100644 --- a/persistence/db_migrator.go +++ b/persistence/db_migrator.go @@ -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 { diff --git a/persistence/repositories/tag_repository.go b/persistence/repositories/tag_repository.go index 334e846..4710728 100644 --- a/persistence/repositories/tag_repository.go +++ b/persistence/repositories/tag_repository.go @@ -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 } diff --git a/persistence/repositories/uow.go b/persistence/repositories/uow.go index 49db413..3f0abe5 100644 --- a/persistence/repositories/uow.go +++ b/persistence/repositories/uow.go @@ -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 } diff --git a/test/test_integration/create_note_handler_test.go b/test/test_integration/create_note_handler_test.go index 093539b..28f085e 100644 --- a/test/test_integration/create_note_handler_test.go +++ b/test/test_integration/create_note_handler_test.go @@ -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"), diff --git a/test/test_integration/main_test.go b/test/test_integration/main_test.go index 363ed8f..8551caf 100644 --- a/test/test_integration/main_test.go +++ b/test/test_integration/main_test.go @@ -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(), }