feat(uow): added filestore for cross boundary tx

This commit is contained in:
Kristian Borgwarth 2026-04-02 17:56:35 +02:00
parent f070674cdb
commit 884e9cd207
6 changed files with 87 additions and 58 deletions

View file

@ -1,2 +0,0 @@
// Package files provides utilities for working with files and directories.
package files

View file

@ -1,19 +0,0 @@
package files
import "os"
func WriteToFile(path string, data []byte) (filePath string, err error) {
if checkIfFileExists(path) {
return path, nil
}
err = os.WriteFile(path, data, 0o644)
if err != nil {
return "", err
}
return path, nil
}
func checkIfFileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}

View file

@ -2,11 +2,10 @@ package handlers
import ( import (
"context" "context"
"database/sql"
"encoding/json" "encoding/json"
"os"
"github.com/KristianJBorgwarth/dendrite.daemon/core/frontmatter" "github.com/KristianJBorgwarth/dendrite.daemon/core/frontmatter"
"github.com/KristianJBorgwarth/dendrite.daemon/core/template"
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories" "github.com/KristianJBorgwarth/dendrite.daemon/persistence/repositories"
) )
@ -17,55 +16,56 @@ type createNoteCommand struct {
Vars map[string]string `json:"vars"` Vars map[string]string `json:"vars"`
} }
type CreateNoteHandler struct {} type CreateNoteHandler struct {
uow *repositories.UnitOfWork
}
func NewCreateNoteHandler(uow *repositories.UnitOfWork) *CreateNoteHandler func NewCreateNoteHandler(uow *repositories.UnitOfWork) *CreateNoteHandler {
return &CreateNoteHandler{uow: uow}
}
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
} }
data, err := h.getTemplate(cmd.TemplatePath) data, err := template.GenerateTemplate(cmd.TemplatePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if data == nil {
data = []byte("---\ntitle: " + cmd.Title + "\ntags: []\n---\n")
}
tags, err := frontmatter.ParseTags(data) tags, err := frontmatter.ParseTags(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = h.uow.Execute(ctx, func(tx *sql.Tx) error { tx, err := h.uow.Begin()
if err != nil {
return nil, err
}
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 { if err = tagRepo.Upsert(ctx, tags); err != nil {
return err return nil, err
} }
if err = noteRepo.Upsert(ctx, cmd.Title, cmd.Path, frontmatter.Slugify(cmd.Title)); err != nil { if err = noteRepo.Upsert(ctx, cmd.Title, cmd.Path, frontmatter.Slugify(cmd.Title)); err != nil {
return err return nil, err
} }
return nil h.uow.FileStore.Stage(cmd.Path, data)
})
if err != nil { if err = h.uow.Commit(); err != nil {
return nil, err return nil, err
} }
return cmd.Path, nil return cmd.Path, nil
} }
func (h *CreateNoteHandler) getTemplate(templatePath string) ([]byte, error) {
if templatePath == "" {
return nil, nil
}
data, err := os.ReadFile(templatePath)
if err != nil {
return nil, err
}
return data, nil
}

View file

@ -1,13 +1,15 @@
package repositories package repositories
import ( import (
"database/sql" "database/sql"
"github.com/KristianJBorgwarth/dendrite.daemon/persistence/store"
) )
type UnitOfWork struct { type UnitOfWork struct {
db *sql.DB db *sql.DB
Transaction *sql.Tx Transaction *sql.Tx
FileStore *store.FileStore
} }
func NewUnitOfWork(db *sql.DB) *UnitOfWork { func NewUnitOfWork(db *sql.DB) *UnitOfWork {
@ -30,10 +32,10 @@ func (u *UnitOfWork) Commit() error {
return u.Transaction.Commit() return u.Transaction.Commit()
} }
func (u *UnitOfWork) Rollback() error { func (u *UnitOfWork) Rollback() {
if u.Transaction == nil { if u.Transaction == nil {
return nil return
} }
return u.Transaction.Rollback() u.Transaction.Rollback()
u.FileStore.Rollback()
} }

2
persistence/store/doc.go Normal file
View file

@ -0,0 +1,2 @@
// Package store provides access to any in memory stores related to files and index
package store

View file

@ -0,0 +1,46 @@
package store
import "os"
type FileStore struct {
staged []stagedFile
commited []string
}
type stagedFile struct {
path string
data []byte
}
func (fs *FileStore) Stage(path string, data []byte) {
fs.staged = append(fs.staged, stagedFile{path: path, data: data})
}
func (fs *FileStore) Flush() error {
for _, file := range fs.staged {
if fs.fileExists(file.path) {
continue
}
if err := os.WriteFile(file.path, file.data, 0o644); err != nil {
return err
}
fs.commited = append(fs.commited, file.path)
}
fs.staged = nil
return nil
}
func (fs *FileStore) Rollback() error {
for _, path := range fs.commited {
if err := os.Remove(path); err != nil {
return err
}
}
fs.commited = nil
return nil
}
func (fs *FileStore) fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}