package main import ( "embed" "errors" "fmt" "html/template" "io/fs" "log" "net/http" "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 //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") } func main() { 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) err = http.ListenAndServe(listenAddr, nil) if err != nil { log.Fatalln("http handler failed:", err) } }