package main import ( "context" "embed" "flag" "net/http" "os" "time" "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" "modernc.org/ql" _ "modernc.org/ql/driver" "git.c3pb.de/gbe/invinoveritas/auth" "git.c3pb.de/gbe/invinoveritas/session" ) //go:embed templates/*.tpl var templateFS embed.FS //go:embed static/* var staticFS embed.FS func httpError(w http.ResponseWriter, msg string, err error, status int) { if err != nil { msg += ": " + err.Error() } log.WithField("status", status). WithError(err). Error(msg) http.Error(w, msg, status) } type sessionProvider interface { ListSessions(context.Context) ([]session.Info, error) DeleteSession(ctx context.Context, token string) error } type userProvider interface { ListUsers(context.Context) ([]string, error) CreateUser(ctx context.Context, name string) (string, error) DeleteUser(ctx context.Context, name string) error UpdatePassword(ctx context.Context, name string, oldPW, newPW string) error } type Handler struct { sqlDB *sqlx.DB sp sessionProvider up userProvider } func logRequest(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() defer func() { d := time.Since(start) remote := r.Header.Get("X-Forwarded-For") if remote == "" { remote = r.RemoteAddr } log.WithFields(log.Fields{ "method": r.Method, "url": r.URL, "remote": r.RemoteAddr, "duration": d, "auth": auth.Get(r), }).Info("handling request") }() next.ServeHTTP(w, r) }) } func addCacheHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Cache-Control", "public, max-age=86400, immutable") next.ServeHTTP(w, r) }) } func openQLdb(path string) (*sqlx.DB, error) { var retried bool path += ".ql" retry: db, err := sqlx.Open("ql2", path) if err != nil { return nil, err } log.Info("running test-select on db") _, err = db.Exec(`SELECT count(*) FROM __Table`) if err != nil && !retried { log.Println("got err:", err) retried = true // WAL may be corrupted. Manually remove it and re-try open. See // https://gitlab.com/cznic/ql/-/issues/227 for more info. err := os.Remove(ql.WalName(path)) if err != nil { return nil, err } goto retry } if err != nil { return nil, err } return db, nil } func main() { formatter := log.TextFormatter{ DisableColors: true, } log.StandardLogger().Formatter = &formatter dbPath := flag.String("db", "vino", "Path to database file") listenAddr := flag.String("listen", "127.0.0.1:7878", "Listening address") debug := flag.Bool("debug", false, "Enable debug logging") flag.Parse() if *debug { log.SetLevel(log.DebugLevel) } db, err := openQLdb(*dbPath) if err != nil { log.WithError(err).Fatalln("can't open DB:") } defer db.Close() err = initDB(context.TODO(), db) if err != nil { log.WithField("wal_name", ql.WalName(*dbPath+".ql")). WithError(err). Fatalln("can't initalize DB") } http.HandleFunc("/favicon.ico", http.NotFound) http.Handle("/static/", logRequest(addCacheHeaders(http.FileServer(http.FS(staticFS))))) sessions := session.Provider{ DB: db, } handler := Handler{ sqlDB: db, sp: sessions, up: sessions, } http.Handle("/details/img/", auth.Require(logRequest(addCacheHeaders(handler.img())), sessions.Handler(templateFS), sessions)) http.Handle("/details/", auth.Require(logRequest(handler.details()), sessions.Handler(templateFS), sessions)) http.Handle("/user/", auth.Require(logRequest(handler.user()), sessions.Handler(templateFS), sessions)) http.Handle("/", auth.Require(logRequest(handler.index()), sessions.Handler(templateFS), sessions)) log.Printf("here we go, listening on http://%s", *listenAddr) err = http.ListenAndServe(*listenAddr, nil) if err != nil { log.WithError(err).Fatalln("http handler failed") } }