diff --git a/app/main.go b/app/main.go deleted file mode 100644 index e69de29..0000000 diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..33b8a1c --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "database/sql" + "log" + "path/filepath" + + migrate "github.com/KristianJBorgwarth/dendrite.daemon/persistence" + _ "modernc.org/sqlite" +) + +func main() { + dbPath := filepath.Join("persistence", "index.db") + + db, err := sql.Open("sqlite", dbPath) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + migrationsDir := filepath.Join("persistence", "migrations") + + if err := migrate.Run(db, migrationsDir); err != nil { + log.Fatal(err) + } + + log.Println("migrations applied successfully") +} diff --git a/go.mod b/go.mod index 176ad5e..ceb31eb 100644 --- a/go.mod +++ b/go.mod @@ -2,3 +2,15 @@ module github.com/KristianJBorgwarth/dendrite.daemon go 1.26 +require ( + 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/ncruces/go-strftime v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/sys v0.42.0 // indirect + modernc.org/libc v1.70.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.47.0 // indirect +) diff --git a/go.sum b/go.sum index e69de29..988cc37 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,21 @@ +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= +modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk= +modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= diff --git a/persistence/db_migrator.go b/persistence/db_migrator.go new file mode 100644 index 0000000..867d049 --- /dev/null +++ b/persistence/db_migrator.go @@ -0,0 +1,70 @@ +// Package migrate handles database schema migrations +package migrate + +import ( + "database/sql" + "os" + "path/filepath" + "sort" +) + +func Run(db *sql.DB, dir string) error { + _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (version TEXT PRIMARY KEY);`) + if err != nil { + return err + } + + entries, err := os.ReadDir(dir) + if err != nil { + return err + } + + sort.Slice(entries, func(i, j int) bool { + return entries[i].Name() < entries[j].Name() + }) + + for _, e := range entries { + if e.IsDir(){ + continue + } + + name := e.Name() + + applied, err := isMigrationApplied(db, name) + if err != nil { + return err + } + + if applied { + continue + } + + path := filepath.Join(dir, name) + + sqlBytes, err := os.ReadFile(path) + if err != nil { + return err + } + + if _, err := db.Exec(string(sqlBytes)); err != nil { + return err + } + + if _, err := db.Exec(`INSERT INTO schema_migrations (version) VALUES (?)`, name); err != nil { + return err + } + } + + return nil +} + +func isMigrationApplied(db *sql.DB, version string) (bool, error) { + var v string + err := db.QueryRow(`SELECT version FROM schema_migrations WHERE version= ?`, version).Scan(&v) + + if err == sql.ErrNoRows { + return false, nil + } + + return err == nil, err +} diff --git a/persistence/index.db b/persistence/index.db new file mode 100644 index 0000000..d7c1874 Binary files /dev/null and b/persistence/index.db differ diff --git a/persistence/index.go b/persistence/index.go deleted file mode 100644 index e69de29..0000000 diff --git a/persistence/migrations/001_dendrite_db_init.sql b/persistence/migrations/001_dendrite_db_init.sql new file mode 100644 index 0000000..1ebfc49 --- /dev/null +++ b/persistence/migrations/001_dendrite_db_init.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS notes ( + id INTEGER 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 INTEGER NOT NULL, + to_note_id INTEGER 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); + diff --git a/persistence/migrations/002_tags.sql b/persistence/migrations/002_tags.sql new file mode 100644 index 0000000..739968a --- /dev/null +++ b/persistence/migrations/002_tags.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS tags ( + id INTEGER PRIMARY KEY, + name TEXT UNIQUE +); + +CREATE TABLE IF NOT EXISTS note_tags ( + note_id INTEGER, + tag_id INTEGER, + PRIMARY KEY (note_id, tag_id), + FOREIGN KEY(note_id) REFERENCES notes(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE +); diff --git a/persistence/repository/note_repository.go b/persistence/repository/note_repository.go deleted file mode 100644 index e69de29..0000000