refactored out global
. long live AppState
This commit is contained in:
parent
3d674515ce
commit
384579ee5e
24 changed files with 350 additions and 375 deletions
182
main.go
182
main.go
|
@ -4,16 +4,18 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"arimelody-web/admin"
|
||||
"arimelody-web/api"
|
||||
"arimelody-web/colour"
|
||||
"arimelody-web/controller"
|
||||
"arimelody-web/global"
|
||||
"arimelody-web/model"
|
||||
"arimelody-web/templates"
|
||||
"arimelody-web/view"
|
||||
|
@ -30,48 +32,48 @@ const DEFAULT_PORT int64 = 8080
|
|||
func main() {
|
||||
fmt.Printf("made with <3 by ari melody\n\n")
|
||||
|
||||
// TODO: refactor `global` to `AppState`
|
||||
// this should contain `Config` and `DB`, and be passed through to all
|
||||
// handlers that need it. it's better than weird static globals everywhere!
|
||||
app := model.AppState{
|
||||
Config: controller.GetConfig(),
|
||||
}
|
||||
|
||||
// initialise database connection
|
||||
if global.Config.DB.Host == "" {
|
||||
if app.Config.DB.Host == "" {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: db.host not provided! Exiting...\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if global.Config.DB.Name == "" {
|
||||
if app.Config.DB.Name == "" {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: db.name not provided! Exiting...\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if global.Config.DB.User == "" {
|
||||
if app.Config.DB.User == "" {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: db.user not provided! Exiting...\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if global.Config.DB.Pass == "" {
|
||||
if app.Config.DB.Pass == "" {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: db.pass not provided! Exiting...\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var err error
|
||||
global.DB, err = sqlx.Connect(
|
||||
app.DB, err = sqlx.Connect(
|
||||
"postgres",
|
||||
fmt.Sprintf(
|
||||
"host=%s port=%d user=%s dbname=%s password='%s' sslmode=disable",
|
||||
global.Config.DB.Host,
|
||||
global.Config.DB.Port,
|
||||
global.Config.DB.User,
|
||||
global.Config.DB.Name,
|
||||
global.Config.DB.Pass,
|
||||
app.Config.DB.Host,
|
||||
app.Config.DB.Port,
|
||||
app.Config.DB.User,
|
||||
app.Config.DB.Name,
|
||||
app.Config.DB.Pass,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Unable to initialise database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
global.DB.SetConnMaxLifetime(time.Minute * 3)
|
||||
global.DB.SetMaxOpenConns(10)
|
||||
global.DB.SetMaxIdleConns(10)
|
||||
defer global.DB.Close()
|
||||
app.DB.SetConnMaxLifetime(time.Minute * 3)
|
||||
app.DB.SetMaxOpenConns(10)
|
||||
app.DB.SetMaxIdleConns(10)
|
||||
defer app.DB.Close()
|
||||
|
||||
// handle command arguments
|
||||
if len(os.Args) > 1 {
|
||||
|
@ -87,7 +89,7 @@ func main() {
|
|||
totpName := os.Args[3]
|
||||
secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH)
|
||||
|
||||
account, err := controller.GetAccount(global.DB, username)
|
||||
account, err := controller.GetAccount(app.DB, username)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||
os.Exit(1)
|
||||
|
@ -104,7 +106,7 @@ func main() {
|
|||
Secret: string(secret),
|
||||
}
|
||||
|
||||
err = controller.CreateTOTP(global.DB, &totp)
|
||||
err = controller.CreateTOTP(app.DB, &totp)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -122,7 +124,7 @@ func main() {
|
|||
username := os.Args[2]
|
||||
totpName := os.Args[3]
|
||||
|
||||
account, err := controller.GetAccount(global.DB, username)
|
||||
account, err := controller.GetAccount(app.DB, username)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||
os.Exit(1)
|
||||
|
@ -133,7 +135,7 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = controller.DeleteTOTP(global.DB, account.ID, totpName)
|
||||
err = controller.DeleteTOTP(app.DB, account.ID, totpName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -149,7 +151,7 @@ func main() {
|
|||
}
|
||||
username := os.Args[2]
|
||||
|
||||
account, err := controller.GetAccount(global.DB, username)
|
||||
account, err := controller.GetAccount(app.DB, username)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||
os.Exit(1)
|
||||
|
@ -160,7 +162,7 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
totps, err := controller.GetTOTPsForAccount(global.DB, account.ID)
|
||||
totps, err := controller.GetTOTPsForAccount(app.DB, account.ID)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP methods: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -182,7 +184,7 @@ func main() {
|
|||
username := os.Args[2]
|
||||
totpName := os.Args[3]
|
||||
|
||||
account, err := controller.GetAccount(global.DB, username)
|
||||
account, err := controller.GetAccount(app.DB, username)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||
os.Exit(1)
|
||||
|
@ -193,7 +195,7 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
totp, err := controller.GetTOTP(global.DB, account.ID, totpName)
|
||||
totp, err := controller.GetTOTP(app.DB, account.ID, totpName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fetch TOTP method \"%s\": %v\n", totpName, err)
|
||||
os.Exit(1)
|
||||
|
@ -210,7 +212,7 @@ func main() {
|
|||
|
||||
case "createInvite":
|
||||
fmt.Printf("Creating invite...\n")
|
||||
invite, err := controller.CreateInvite(global.DB, 16, time.Hour * 24)
|
||||
invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create invite code: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -221,7 +223,7 @@ func main() {
|
|||
|
||||
case "purgeInvites":
|
||||
fmt.Printf("Deleting all invites...\n")
|
||||
err := controller.DeleteAllInvites(global.DB)
|
||||
err := controller.DeleteAllInvites(app.DB)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to delete invites: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -231,7 +233,7 @@ func main() {
|
|||
return
|
||||
|
||||
case "listAccounts":
|
||||
accounts, err := controller.GetAllAccounts(global.DB)
|
||||
accounts, err := controller.GetAllAccounts(app.DB)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fetch accounts: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -259,7 +261,7 @@ func main() {
|
|||
username := os.Args[2]
|
||||
fmt.Printf("Deleting account \"%s\"...\n", username)
|
||||
|
||||
account, err := controller.GetAccount(global.DB, username)
|
||||
account, err := controller.GetAccount(app.DB, username)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||
os.Exit(1)
|
||||
|
@ -277,7 +279,7 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
err = controller.DeleteAccount(global.DB, username)
|
||||
err = controller.DeleteAccount(app.DB, username)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -305,20 +307,20 @@ func main() {
|
|||
}
|
||||
|
||||
// handle DB migrations
|
||||
controller.CheckDBVersionAndMigrate(global.DB)
|
||||
controller.CheckDBVersionAndMigrate(app.DB)
|
||||
|
||||
// initial invite code
|
||||
accountsCount := 0
|
||||
err = global.DB.Get(&accountsCount, "SELECT count(*) FROM account")
|
||||
err = app.DB.Get(&accountsCount, "SELECT count(*) FROM account")
|
||||
if err != nil { panic(err) }
|
||||
if accountsCount == 0 {
|
||||
_, err := global.DB.Exec("DELETE FROM invite")
|
||||
_, err := app.DB.Exec("DELETE FROM invite")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear existing invite codes: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
invite, err := controller.CreateInvite(global.DB, 16, time.Hour * 24)
|
||||
invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to create invite code: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -328,28 +330,28 @@ func main() {
|
|||
}
|
||||
|
||||
// delete expired invites
|
||||
err = controller.DeleteExpiredInvites(global.DB)
|
||||
err = controller.DeleteExpiredInvites(app.DB)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear expired invite codes: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// start the web server!
|
||||
mux := createServeMux()
|
||||
fmt.Printf("Now serving at %s:%d\n", global.Config.BaseUrl, global.Config.Port)
|
||||
mux := createServeMux(&app)
|
||||
fmt.Printf("Now serving at %s:%d\n", app.Config.BaseUrl, app.Config.Port)
|
||||
log.Fatal(
|
||||
http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port),
|
||||
global.HTTPLog(global.DefaultHeaders(mux)),
|
||||
http.ListenAndServe(fmt.Sprintf(":%d", app.Config.Port),
|
||||
HTTPLog(DefaultHeaders(mux)),
|
||||
))
|
||||
}
|
||||
|
||||
func createServeMux() *http.ServeMux {
|
||||
func createServeMux(app *model.AppState) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(global.DB)))
|
||||
mux.Handle("/api/", http.StripPrefix("/api", api.Handler(global.DB)))
|
||||
mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(global.DB)))
|
||||
mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(global.Config.DataDirectory, "uploads"))))
|
||||
mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app)))
|
||||
mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app)))
|
||||
mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app)))
|
||||
mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(app.Config.DataDirectory, "uploads"))))
|
||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodHead {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -390,3 +392,93 @@ func staticHandler(directory string) http.Handler {
|
|||
http.FileServer(http.Dir(directory)).ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
var PoweredByStrings = []string{
|
||||
"nerd rage",
|
||||
"estrogen",
|
||||
"your mother",
|
||||
"awesome powers beyond comprehension",
|
||||
"jared",
|
||||
"the weight of my sins",
|
||||
"the arc reactor",
|
||||
"AA batteries",
|
||||
"15 euro solar panel from ebay",
|
||||
"magnets, how do they work",
|
||||
"a fax machine",
|
||||
"dell optiplex",
|
||||
"a trans girl's nintendo wii",
|
||||
"BASS",
|
||||
"electricity, duh",
|
||||
"seven hamsters in a big wheel",
|
||||
"girls",
|
||||
"mzungu hosting",
|
||||
"golang",
|
||||
"the state of the world right now",
|
||||
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)",
|
||||
"the good folks at aperture science",
|
||||
"free2play CDs",
|
||||
"aridoodle",
|
||||
"the love of creating",
|
||||
"not for the sake of art; not for the sake of money; we like painting naked people",
|
||||
"30 billion dollars in VC funding",
|
||||
}
|
||||
|
||||
func DefaultHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Server", "arimelody.me")
|
||||
w.Header().Add("Do-Not-Stab", "1")
|
||||
w.Header().Add("X-Clacks-Overhead", "GNU Terry Pratchett")
|
||||
w.Header().Add("X-Hacker", "spare me please")
|
||||
w.Header().Add("X-Robots-TXT", "'; DROP TABLE pages;")
|
||||
w.Header().Add("X-Thinking-With", "Portals")
|
||||
w.Header().Add(
|
||||
"X-Powered-By",
|
||||
PoweredByStrings[rand.Intn(len(PoweredByStrings))],
|
||||
)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type LoggingResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
Status int
|
||||
}
|
||||
|
||||
func (lrw *LoggingResponseWriter) WriteHeader(status int) {
|
||||
lrw.Status = status
|
||||
lrw.ResponseWriter.WriteHeader(status)
|
||||
}
|
||||
|
||||
func HTTPLog(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
lrw := LoggingResponseWriter{w, http.StatusOK}
|
||||
|
||||
next.ServeHTTP(&lrw, r)
|
||||
|
||||
after := time.Now()
|
||||
difference := (after.Nanosecond() - start.Nanosecond()) / 1_000_000
|
||||
elapsed := "<1"
|
||||
if difference >= 1 {
|
||||
elapsed = strconv.Itoa(difference)
|
||||
}
|
||||
|
||||
statusColour := colour.Reset
|
||||
|
||||
if lrw.Status - 600 <= 0 { statusColour = colour.Red }
|
||||
if lrw.Status - 500 <= 0 { statusColour = colour.Yellow }
|
||||
if lrw.Status - 400 <= 0 { statusColour = colour.White }
|
||||
if lrw.Status - 300 <= 0 { statusColour = colour.Green }
|
||||
|
||||
fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n",
|
||||
after.Format(time.UnixDate),
|
||||
r.Method,
|
||||
r.URL.Path,
|
||||
statusColour,
|
||||
lrw.Status,
|
||||
colour.Reset,
|
||||
elapsed,
|
||||
r.Header["User-Agent"][0])
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue