Skip to content
Snippets Groups Projects
main.go 2.91 KiB
Newer Older
gbe's avatar
gbe committed
package main

import (
	"embed"
gbe's avatar
gbe committed
	"errors"
	"fmt"
gbe's avatar
gbe committed
	"html/template"
gbe's avatar
gbe committed
	"io/fs"
gbe's avatar
gbe committed
	"log"
	"net/http"
gbe's avatar
gbe committed
	"sort"

	"github.com/jmoiron/sqlx"
	_ "modernc.org/sqlite" // Imported for side effects: registers DB driver
gbe's avatar
gbe committed
)

//go:embed templates/*.tpl
var templateFS embed.FS
var templates = template.Must(template.ParseFS(templateFS, "templates/*.tpl"))

//go:embed static/*
var staticFS embed.FS

gbe's avatar
gbe committed
//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")
}

gbe's avatar
gbe committed
func main() {
gbe's avatar
gbe committed
	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)
	}

gbe's avatar
gbe committed
	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)

gbe's avatar
gbe committed
	err = http.ListenAndServe(listenAddr, nil)
gbe's avatar
gbe committed
	if err != nil {
gbe's avatar
gbe committed
		log.Fatalln("http handler failed:", err)
gbe's avatar
gbe committed
	}
}