feat(database): migration setup

This commit is contained in:
Kristian Borgwarth 2026-03-20 00:11:05 +01:00
parent 69096b79a6
commit f1ee076bcb
10 changed files with 164 additions and 0 deletions

View file

28
cmd/main.go Normal file
View file

@ -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")
}

12
go.mod
View file

@ -2,3 +2,15 @@ module github.com/KristianJBorgwarth/dendrite.daemon
go 1.26 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
)

21
go.sum
View file

@ -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=

View file

@ -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
}

BIN
persistence/index.db Normal file

Binary file not shown.

View file

View file

@ -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);

View file

@ -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
);