Newer
Older
"sort"
"github.com/jmoiron/sqlx"
_ "modernc.org/sqlite" // Imported for side effects: registers DB driver
)
//go:embed templates/*.tpl
var templateFS embed.FS
var templates = template.Must(template.ParseFS(templateFS, "templates/*.tpl"))
//go:embed static/*
var staticFS embed.FS
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//go:embed migrations/*.sql
var migrationFS embed.FS
func initDB(db *sqlx.DB) error {
// Create table tracking migration state
const query = `CREATE TABLE IF NOT EXISTS migrations (name TEXT UNIQUE);`
_, err := db.Exec(query)
if err != nil {
return err
}
entries, err := migrationFS.ReadDir("migrations")
if err != nil {
return err
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
for _, e := range entries {
data, err := fs.ReadFile(migrationFS, "migrations/"+e.Name())
if err != nil {
return err
}
tx, err := db.Begin()
if err != nil {
return err
}
row := tx.QueryRow("SELECT count(*) FROM migrations WHERE name = ?", e.Name())
var howMany int
err = row.Scan(&howMany)
if err != nil {
tx.Rollback()
return fmt.Errorf("checking status for migration %s: %w", e.Name(), err)
}
switch howMany {
case 0:
// not yet applied
case 1:
// applied, no need to do anything
log.Printf("skipping migration %s: already applied", e.Name())
tx.Rollback()
continue
default:
// very weird
tx.Rollback()
return fmt.Errorf("unexpected migration count for %s: %d", e.Name(), howMany)
}
log.Println("applying migration", e.Name())
_, err = tx.Exec(string(data))
if err != nil {
tx.Rollback()
return fmt.Errorf("applying migration %s: %w", e.Name(), err)
}
// Record migration as applied
_, err = tx.Exec("INSERT INTO migrations (name) VALUES (?)", e.Name())
if err != nil {
tx.Rollback()
return fmt.Errorf("recording migration %s: %w", e.Name(), err)
}
err = tx.Commit()
if err != nil {
return err
}
log.Println("it has", len(data), "bytes")
}
return errors.New("here be dragons")
}
db, err := sqlx.Open("sqlite", "vino.sqlite")
if err != nil {
log.Fatalln("can't open DB:", err)
}
defer db.Close()
err = initDB(db)
if err != nil {
log.Fatalln("can't initialize DB:", err)
}
http.Handle("/static/", http.FileServer(http.FS(staticFS)))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("handling", r.Method, r.URL, "from", r.RemoteAddr)
if r.Method == "GET" {
w.Header().Add("content-type", "text/html")
err := templates.ExecuteTemplate(w, "index.tpl", nil)
if err != nil {
log.Println("can't execute index template:", err)
}
return
}
})
const listenAddr = "127.0.0.1:7878"
log.Printf("here we go, listening on http://%s", listenAddr)