From 384579ee5e0a45daf98d53432e3ec3d057a7f009 Mon Sep 17 00:00:00 2001 From: ari melody Date: Tue, 21 Jan 2025 14:53:18 +0000 Subject: [PATCH] refactored out `global`. long live `AppState` --- .gitignore | 1 + admin/accounthttp.go | 48 ++++---- admin/artisthttp.go | 7 +- admin/http.go | 30 +++-- admin/releasehttp.go | 29 +++-- admin/trackhttp.go | 7 +- api/account.go | 25 ++--- api/api.go | 41 ++++--- api/artist.go | 25 ++--- api/release.go | 51 +++++---- api/track.go | 21 ++-- api/uploads.go | 6 +- controller/account.go | 3 +- controller/artist.go | 1 + {global => controller}/config.go | 47 ++------ controller/release.go | 1 + controller/track.go | 1 + discord/discord.go | 43 +++----- global/const.go | 3 - global/funcs.go | 101 ----------------- main.go | 182 +++++++++++++++++++++++-------- model/account.go | 2 + model/appstate.go | 32 ++++++ view/music.go | 18 ++- 24 files changed, 350 insertions(+), 375 deletions(-) rename {global => controller}/config.go (65%) delete mode 100644 global/const.go delete mode 100644 global/funcs.go create mode 100644 model/appstate.go diff --git a/.gitignore b/.gitignore index cccde2b..9bdf788 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ uploads/ docker-compose*.yml !docker-compose.example.yml config*.toml +arimelody-web diff --git a/admin/accounthttp.go b/admin/accounthttp.go index f0990df..b2cf18a 100644 --- a/admin/accounthttp.go +++ b/admin/accounthttp.go @@ -8,10 +8,8 @@ import ( "time" "arimelody-web/controller" - "arimelody-web/global" "arimelody-web/model" - "github.com/jmoiron/sqlx" "golang.org/x/crypto/bcrypt" ) @@ -21,11 +19,11 @@ type TemplateData struct { Token string } -func AccountHandler(db *sqlx.DB) http.Handler { +func AccountHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { account := r.Context().Value("account").(*model.Account) - totps, err := controller.GetTOTPsForAccount(db, account.ID) + totps, err := controller.GetTOTPsForAccount(app.DB, account.ID) if err != nil { fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -47,10 +45,10 @@ func AccountHandler(db *sqlx.DB) http.Handler { }) } -func LoginHandler(db *sqlx.DB) http.Handler { +func LoginHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { - account, err := controller.GetAccountByRequest(db, r) + account, err := controller.GetAccountByRequest(app.DB, r) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) @@ -107,7 +105,7 @@ func LoginHandler(db *sqlx.DB) http.Handler { TOTP: r.Form.Get("totp"), } - account, err := controller.GetAccount(db, credentials.Username) + account, err := controller.GetAccount(app.DB, credentials.Username) if err != nil { render(LoginResponse{ Message: "Invalid username or password" }) return @@ -123,7 +121,7 @@ func LoginHandler(db *sqlx.DB) http.Handler { return } - totps, err := controller.GetTOTPsForAccount(db, account.ID) + totps, err := controller.GetTOTPsForAccount(app.DB, account.ID) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err) render(LoginResponse{ Message: "Something went wrong. Please try again." }) @@ -147,7 +145,7 @@ func LoginHandler(db *sqlx.DB) http.Handler { } // login success! - token, err := controller.CreateToken(db, account.ID, r.UserAgent()) + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err) render(LoginResponse{ Message: "Something went wrong. Please try again." }) @@ -155,10 +153,10 @@ func LoginHandler(db *sqlx.DB) http.Handler { } cookie := http.Cookie{} - cookie.Name = global.COOKIE_TOKEN + cookie.Name = model.COOKIE_TOKEN cookie.Value = token.Token cookie.Expires = token.ExpiresAt - if strings.HasPrefix(global.Config.BaseUrl, "https") { + if strings.HasPrefix(app.Config.BaseUrl, "https") { cookie.Secure = true } cookie.HttpOnly = true @@ -169,17 +167,17 @@ func LoginHandler(db *sqlx.DB) http.Handler { }) } -func LogoutHandler(db *sqlx.DB) http.Handler { +func LogoutHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.NotFound(w, r) return } - tokenStr := controller.GetTokenFromRequest(db, r) + tokenStr := controller.GetTokenFromRequest(app.DB, r) if len(tokenStr) > 0 { - err := controller.DeleteToken(db, tokenStr) + err := controller.DeleteToken(app.DB, tokenStr) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to revoke token: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -188,10 +186,10 @@ func LogoutHandler(db *sqlx.DB) http.Handler { } cookie := http.Cookie{} - cookie.Name = global.COOKIE_TOKEN + cookie.Name = model.COOKIE_TOKEN cookie.Value = "" cookie.Expires = time.Now() - if strings.HasPrefix(global.Config.BaseUrl, "https") { + if strings.HasPrefix(app.Config.BaseUrl, "https") { cookie.Secure = true } cookie.HttpOnly = true @@ -201,9 +199,9 @@ func LogoutHandler(db *sqlx.DB) http.Handler { }) } -func createAccountHandler(db *sqlx.DB) http.Handler { +func createAccountHandler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - checkAccount, err := controller.GetAccountByRequest(db, r) + checkAccount, err := controller.GetAccountByRequest(app.DB, r) if err != nil { fmt.Printf("WARN: Failed to fetch account: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -260,7 +258,7 @@ func createAccountHandler(db *sqlx.DB) http.Handler { } // make sure code exists in DB - invite, err := controller.GetInvite(db, credentials.Invite) + invite, err := controller.GetInvite(app.DB, credentials.Invite) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err) render(CreateAccountResponse{ @@ -270,7 +268,7 @@ func createAccountHandler(db *sqlx.DB) http.Handler { } if invite == nil || time.Now().After(invite.ExpiresAt) { if invite != nil { - err := controller.DeleteInvite(db, invite.Code) + err := controller.DeleteInvite(app.DB, invite.Code) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } } render(CreateAccountResponse{ @@ -294,7 +292,7 @@ func createAccountHandler(db *sqlx.DB) http.Handler { Email: credentials.Email, AvatarURL: "/img/default-avatar.png", } - err = controller.CreateAccount(db, &account) + err = controller.CreateAccount(app.DB, &account) if err != nil { if strings.HasPrefix(err.Error(), "pq: duplicate key") { render(CreateAccountResponse{ @@ -309,11 +307,11 @@ func createAccountHandler(db *sqlx.DB) http.Handler { return } - err = controller.DeleteInvite(db, invite.Code) + err = controller.DeleteInvite(app.DB, invite.Code) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } // registration success! - token, err := controller.CreateToken(db, account.ID, r.UserAgent()) + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err) // gracefully redirect user to login page @@ -322,10 +320,10 @@ func createAccountHandler(db *sqlx.DB) http.Handler { } cookie := http.Cookie{} - cookie.Name = global.COOKIE_TOKEN + cookie.Name = model.COOKIE_TOKEN cookie.Value = token.Token cookie.Expires = token.ExpiresAt - if strings.HasPrefix(global.Config.BaseUrl, "https") { + if strings.HasPrefix(app.Config.BaseUrl, "https") { cookie.Secure = true } cookie.HttpOnly = true diff --git a/admin/artisthttp.go b/admin/artisthttp.go index af42cb1..d6a5e76 100644 --- a/admin/artisthttp.go +++ b/admin/artisthttp.go @@ -5,16 +5,15 @@ import ( "net/http" "strings" - "arimelody-web/global" "arimelody-web/model" "arimelody-web/controller" ) -func serveArtist() http.Handler { +func serveArtist(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slices := strings.Split(r.URL.Path[1:], "/") id := slices[0] - artist, err := controller.GetArtist(global.DB, id) + artist, err := controller.GetArtist(app.DB, id) if err != nil { if artist == nil { http.NotFound(w, r) @@ -25,7 +24,7 @@ func serveArtist() http.Handler { return } - credits, err := controller.GetArtistCredits(global.DB, artist.ID, true) + credits, err := controller.GetArtistCredits(app.DB, artist.ID, true) if err != nil { fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/admin/http.go b/admin/http.go index d118647..b44cfa9 100644 --- a/admin/http.go +++ b/admin/http.go @@ -9,28 +9,26 @@ import ( "arimelody-web/controller" "arimelody-web/model" - - "github.com/jmoiron/sqlx" ) -func Handler(db *sqlx.DB) http.Handler { +func Handler(app *model.AppState) http.Handler { mux := http.NewServeMux() - mux.Handle("/login", LoginHandler(db)) - mux.Handle("/register", createAccountHandler(db)) - mux.Handle("/logout", RequireAccount(db, LogoutHandler(db))) - mux.Handle("/account", RequireAccount(db, AccountHandler(db))) + mux.Handle("/login", LoginHandler(app)) + mux.Handle("/register", createAccountHandler(app)) + mux.Handle("/logout", RequireAccount(app, LogoutHandler(app))) + mux.Handle("/account", RequireAccount(app, AccountHandler(app))) mux.Handle("/static/", http.StripPrefix("/static", staticHandler())) - mux.Handle("/release/", RequireAccount(db, http.StripPrefix("/release", serveRelease()))) - mux.Handle("/artist/", RequireAccount(db, http.StripPrefix("/artist", serveArtist()))) - mux.Handle("/track/", RequireAccount(db, http.StripPrefix("/track", serveTrack()))) + mux.Handle("/release/", RequireAccount(app, http.StripPrefix("/release", serveRelease(app)))) + mux.Handle("/artist/", RequireAccount(app, http.StripPrefix("/artist", serveArtist(app)))) + mux.Handle("/track/", RequireAccount(app, http.StripPrefix("/track", serveTrack(app)))) mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } - account, err := controller.GetAccountByRequest(db, r) + account, err := controller.GetAccountByRequest(app.DB, r) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %s\n", err) } @@ -39,21 +37,21 @@ func Handler(db *sqlx.DB) http.Handler { return } - releases, err := controller.GetAllReleases(db, false, 0, true) + releases, err := controller.GetAllReleases(app.DB, false, 0, true) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - artists, err := controller.GetAllArtists(db) + artists, err := controller.GetAllArtists(app.DB) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - tracks, err := controller.GetOrphanTracks(db) + tracks, err := controller.GetOrphanTracks(app.DB) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -83,9 +81,9 @@ func Handler(db *sqlx.DB) http.Handler { return mux } -func RequireAccount(db *sqlx.DB, next http.Handler) http.HandlerFunc { +func RequireAccount(app *model.AppState, next http.Handler) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - account, err := controller.GetAccountByRequest(db, r) + account, err := controller.GetAccountByRequest(app.DB, r) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) diff --git a/admin/releasehttp.go b/admin/releasehttp.go index 9132fe8..503166b 100644 --- a/admin/releasehttp.go +++ b/admin/releasehttp.go @@ -5,19 +5,18 @@ import ( "net/http" "strings" - "arimelody-web/global" "arimelody-web/controller" "arimelody-web/model" ) -func serveRelease() http.Handler { +func serveRelease(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slices := strings.Split(r.URL.Path[1:], "/") releaseID := slices[0] account := r.Context().Value("account").(*model.Account) - release, err := controller.GetRelease(global.DB, releaseID, true) + release, err := controller.GetRelease(app.DB, releaseID, true) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -34,10 +33,10 @@ func serveRelease() http.Handler { serveEditCredits(release).ServeHTTP(w, r) return case "addcredit": - serveAddCredit(release).ServeHTTP(w, r) + serveAddCredit(app, release).ServeHTTP(w, r) return case "newcredit": - serveNewCredit().ServeHTTP(w, r) + serveNewCredit(app).ServeHTTP(w, r) return case "editlinks": serveEditLinks(release).ServeHTTP(w, r) @@ -46,10 +45,10 @@ func serveRelease() http.Handler { serveEditTracks(release).ServeHTTP(w, r) return case "addtrack": - serveAddTrack(release).ServeHTTP(w, r) + serveAddTrack(app, release).ServeHTTP(w, r) return case "newtrack": - serveNewTrack().ServeHTTP(w, r) + serveNewTrack(app).ServeHTTP(w, r) return } http.NotFound(w, r) @@ -83,9 +82,9 @@ func serveEditCredits(release *model.Release) http.Handler { }) } -func serveAddCredit(release *model.Release) http.Handler { +func serveAddCredit(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - artists, err := controller.GetArtistsNotOnRelease(global.DB, release.ID) + artists, err := controller.GetArtistsNotOnRelease(app.DB, release.ID) if err != nil { fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -109,10 +108,10 @@ func serveAddCredit(release *model.Release) http.Handler { }) } -func serveNewCredit() http.Handler { +func serveNewCredit(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { artistID := strings.Split(r.URL.Path, "/")[3] - artist, err := controller.GetArtist(global.DB, artistID) + artist, err := controller.GetArtist(app.DB, artistID) if err != nil { fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -154,9 +153,9 @@ func serveEditTracks(release *model.Release) http.Handler { }) } -func serveAddTrack(release *model.Release) http.Handler { +func serveAddTrack(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tracks, err := controller.GetTracksNotOnRelease(global.DB, release.ID) + tracks, err := controller.GetTracksNotOnRelease(app.DB, release.ID) if err != nil { fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -181,10 +180,10 @@ func serveAddTrack(release *model.Release) http.Handler { }) } -func serveNewTrack() http.Handler { +func serveNewTrack(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { trackID := strings.Split(r.URL.Path, "/")[3] - track, err := controller.GetTrack(global.DB, trackID) + track, err := controller.GetTrack(app.DB, trackID) if err != nil { fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/admin/trackhttp.go b/admin/trackhttp.go index 2cea123..fa49b53 100644 --- a/admin/trackhttp.go +++ b/admin/trackhttp.go @@ -5,16 +5,15 @@ import ( "net/http" "strings" - "arimelody-web/global" "arimelody-web/model" "arimelody-web/controller" ) -func serveTrack() http.Handler { +func serveTrack(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slices := strings.Split(r.URL.Path[1:], "/") id := slices[0] - track, err := controller.GetTrack(global.DB, id) + track, err := controller.GetTrack(app.DB, id) if err != nil { fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -25,7 +24,7 @@ func serveTrack() http.Handler { return } - releases, err := controller.GetTrackReleases(global.DB, track.ID, true) + releases, err := controller.GetTrackReleases(app.DB, track.ID, true) if err != nil { fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/api/account.go b/api/account.go index 3ce52c8..0a9a7f9 100644 --- a/api/account.go +++ b/api/account.go @@ -3,7 +3,6 @@ package api import ( "arimelody-web/controller" "arimelody-web/model" - "arimelody-web/global" "encoding/json" "fmt" "net/http" @@ -14,7 +13,7 @@ import ( "golang.org/x/crypto/bcrypt" ) -func handleLogin() http.HandlerFunc { +func handleLogin(app *model.AppState) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) @@ -33,7 +32,7 @@ func handleLogin() http.HandlerFunc { return } - account, err := controller.GetAccount(global.DB, credentials.Username) + account, err := controller.GetAccount(app.DB, credentials.Username) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -50,7 +49,7 @@ func handleLogin() http.HandlerFunc { return } - token, err := controller.CreateToken(global.DB, account.ID, r.UserAgent()) + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) type LoginResponse struct { Token string `json:"token"` ExpiresAt time.Time `json:"expires_at"` @@ -67,7 +66,7 @@ func handleLogin() http.HandlerFunc { }) } -func handleAccountRegistration() http.HandlerFunc { +func handleAccountRegistration(app *model.AppState) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) @@ -89,7 +88,7 @@ func handleAccountRegistration() http.HandlerFunc { } // make sure code exists in DB - invite, err := controller.GetInvite(global.DB, credentials.Invite) + invite, err := controller.GetInvite(app.DB, credentials.Invite) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -101,7 +100,7 @@ func handleAccountRegistration() http.HandlerFunc { } if time.Now().After(invite.ExpiresAt) { - err := controller.DeleteInvite(global.DB, invite.Code) + err := controller.DeleteInvite(app.DB, invite.Code) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } http.Error(w, "Invalid invite code", http.StatusBadRequest) return @@ -120,7 +119,7 @@ func handleAccountRegistration() http.HandlerFunc { Email: credentials.Email, AvatarURL: "/img/default-avatar.png", } - err = controller.CreateAccount(global.DB, &account) + err = controller.CreateAccount(app.DB, &account) if err != nil { if strings.HasPrefix(err.Error(), "pq: duplicate key") { http.Error(w, "An account with that username already exists", http.StatusBadRequest) @@ -131,10 +130,10 @@ func handleAccountRegistration() http.HandlerFunc { return } - err = controller.DeleteInvite(global.DB, invite.Code) + err = controller.DeleteInvite(app.DB, invite.Code) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } - token, err := controller.CreateToken(global.DB, account.ID, r.UserAgent()) + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) type LoginResponse struct { Token string `json:"token"` ExpiresAt time.Time `json:"expires_at"` @@ -151,7 +150,7 @@ func handleAccountRegistration() http.HandlerFunc { }) } -func handleDeleteAccount() http.HandlerFunc { +func handleDeleteAccount(app *model.AppState) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) @@ -170,7 +169,7 @@ func handleDeleteAccount() http.HandlerFunc { return } - account, err := controller.GetAccount(global.DB, credentials.Username) + account, err := controller.GetAccount(app.DB, credentials.Username) if err != nil { if strings.Contains(err.Error(), "no rows") { http.Error(w, "Invalid username or password", http.StatusBadRequest) @@ -189,7 +188,7 @@ func handleDeleteAccount() http.HandlerFunc { // TODO: check TOTP - err = controller.DeleteAccount(global.DB, account.Username) + err = controller.DeleteAccount(app.DB, account.Username) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete account: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/api/api.go b/api/api.go index 26e2255..b16d45d 100644 --- a/api/api.go +++ b/api/api.go @@ -7,11 +7,10 @@ import ( "arimelody-web/admin" "arimelody-web/controller" - - "github.com/jmoiron/sqlx" + "arimelody-web/model" ) -func Handler(db *sqlx.DB) http.Handler { +func Handler(app *model.AppState) http.Handler { mux := http.NewServeMux() // ACCOUNT ENDPOINTS @@ -32,7 +31,7 @@ func Handler(db *sqlx.DB) http.Handler { mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var artistID = strings.Split(r.URL.Path[1:], "/")[0] - artist, err := controller.GetArtist(db, artistID) + artist, err := controller.GetArtist(app.DB, artistID) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -46,13 +45,13 @@ func Handler(db *sqlx.DB) http.Handler { switch r.Method { case http.MethodGet: // GET /api/v1/artist/{id} - ServeArtist(artist).ServeHTTP(w, r) + ServeArtist(app, artist).ServeHTTP(w, r) case http.MethodPut: // PUT /api/v1/artist/{id} (admin) - admin.RequireAccount(db, UpdateArtist(artist)).ServeHTTP(w, r) + admin.RequireAccount(app, UpdateArtist(app, artist)).ServeHTTP(w, r) case http.MethodDelete: // DELETE /api/v1/artist/{id} (admin) - admin.RequireAccount(db, DeleteArtist(artist)).ServeHTTP(w, r) + admin.RequireAccount(app, DeleteArtist(app, artist)).ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -61,10 +60,10 @@ func Handler(db *sqlx.DB) http.Handler { switch r.Method { case http.MethodGet: // GET /api/v1/artist - ServeAllArtists().ServeHTTP(w, r) + ServeAllArtists(app).ServeHTTP(w, r) case http.MethodPost: // POST /api/v1/artist (admin) - admin.RequireAccount(db, CreateArtist()).ServeHTTP(w, r) + admin.RequireAccount(app, CreateArtist(app)).ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -74,7 +73,7 @@ func Handler(db *sqlx.DB) http.Handler { mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var releaseID = strings.Split(r.URL.Path[1:], "/")[0] - release, err := controller.GetRelease(db, releaseID, true) + release, err := controller.GetRelease(app.DB, releaseID, true) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -88,13 +87,13 @@ func Handler(db *sqlx.DB) http.Handler { switch r.Method { case http.MethodGet: // GET /api/v1/music/{id} - ServeRelease(release).ServeHTTP(w, r) + ServeRelease(app, release).ServeHTTP(w, r) case http.MethodPut: // PUT /api/v1/music/{id} (admin) - admin.RequireAccount(db, UpdateRelease(release)).ServeHTTP(w, r) + admin.RequireAccount(app, UpdateRelease(app, release)).ServeHTTP(w, r) case http.MethodDelete: // DELETE /api/v1/music/{id} (admin) - admin.RequireAccount(db, DeleteRelease(release)).ServeHTTP(w, r) + admin.RequireAccount(app, DeleteRelease(app, release)).ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -103,10 +102,10 @@ func Handler(db *sqlx.DB) http.Handler { switch r.Method { case http.MethodGet: // GET /api/v1/music - ServeCatalog().ServeHTTP(w, r) + ServeCatalog(app).ServeHTTP(w, r) case http.MethodPost: // POST /api/v1/music (admin) - admin.RequireAccount(db, CreateRelease()).ServeHTTP(w, r) + admin.RequireAccount(app, CreateRelease(app)).ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -116,7 +115,7 @@ func Handler(db *sqlx.DB) http.Handler { mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var trackID = strings.Split(r.URL.Path[1:], "/")[0] - track, err := controller.GetTrack(db, trackID) + track, err := controller.GetTrack(app.DB, trackID) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -130,13 +129,13 @@ func Handler(db *sqlx.DB) http.Handler { switch r.Method { case http.MethodGet: // GET /api/v1/track/{id} (admin) - admin.RequireAccount(db, ServeTrack(track)).ServeHTTP(w, r) + admin.RequireAccount(app, ServeTrack(app, track)).ServeHTTP(w, r) case http.MethodPut: // PUT /api/v1/track/{id} (admin) - admin.RequireAccount(db, UpdateTrack(track)).ServeHTTP(w, r) + admin.RequireAccount(app, UpdateTrack(app, track)).ServeHTTP(w, r) case http.MethodDelete: // DELETE /api/v1/track/{id} (admin) - admin.RequireAccount(db, DeleteTrack(track)).ServeHTTP(w, r) + admin.RequireAccount(app, DeleteTrack(app, track)).ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -145,10 +144,10 @@ func Handler(db *sqlx.DB) http.Handler { switch r.Method { case http.MethodGet: // GET /api/v1/track (admin) - admin.RequireAccount(db, ServeAllTracks()).ServeHTTP(w, r) + admin.RequireAccount(app, ServeAllTracks(app)).ServeHTTP(w, r) case http.MethodPost: // POST /api/v1/track (admin) - admin.RequireAccount(db, CreateTrack()).ServeHTTP(w, r) + admin.RequireAccount(app, CreateTrack(app)).ServeHTTP(w, r) default: http.NotFound(w, r) } diff --git a/api/artist.go b/api/artist.go index c46db59..a9676b1 100644 --- a/api/artist.go +++ b/api/artist.go @@ -10,15 +10,14 @@ import ( "strings" "time" - "arimelody-web/global" "arimelody-web/controller" "arimelody-web/model" ) -func ServeAllArtists() http.Handler { +func ServeAllArtists(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var artists = []*model.Artist{} - artists, err := controller.GetAllArtists(global.DB) + artists, err := controller.GetAllArtists(app.DB) if err != nil { fmt.Printf("WARN: Failed to serve all artists: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -35,7 +34,7 @@ func ServeAllArtists() http.Handler { }) } -func ServeArtist(artist *model.Artist) http.Handler { +func ServeArtist(app *model.AppState, artist *model.Artist) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type ( creditJSON struct { @@ -52,7 +51,7 @@ func ServeArtist(artist *model.Artist) http.Handler { } ) - account, err := controller.GetAccountByRequest(global.DB, r) + account, err := controller.GetAccountByRequest(app.DB, r) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -60,7 +59,7 @@ func ServeArtist(artist *model.Artist) http.Handler { } show_hidden_releases := account != nil - dbCredits, err := controller.GetArtistCredits(global.DB, artist.ID, show_hidden_releases) + dbCredits, err := controller.GetArtistCredits(app.DB, artist.ID, show_hidden_releases) if err != nil { fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -92,7 +91,7 @@ func ServeArtist(artist *model.Artist) http.Handler { }) } -func CreateArtist() http.Handler { +func CreateArtist(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var artist model.Artist err := json.NewDecoder(r.Body).Decode(&artist) @@ -107,7 +106,7 @@ func CreateArtist() http.Handler { } if artist.Name == "" { artist.Name = artist.ID } - err = controller.CreateArtist(global.DB, &artist) + err = controller.CreateArtist(app.DB, &artist) if err != nil { if strings.Contains(err.Error(), "duplicate key") { http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest) @@ -122,7 +121,7 @@ func CreateArtist() http.Handler { }) } -func UpdateArtist(artist *model.Artist) http.Handler { +func UpdateArtist(app *model.AppState, artist *model.Artist) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(&artist) if err != nil { @@ -136,7 +135,7 @@ func UpdateArtist(artist *model.Artist) http.Handler { } else { if strings.Contains(artist.Avatar, ";base64,") { var artworkDirectory = filepath.Join("uploads", "avatar") - filename, err := HandleImageUpload(&artist.Avatar, artworkDirectory, artist.ID) + filename, err := HandleImageUpload(app, &artist.Avatar, artworkDirectory, artist.ID) // clean up files with this ID and different extensions err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error { @@ -155,7 +154,7 @@ func UpdateArtist(artist *model.Artist) http.Handler { } } - err = controller.UpdateArtist(global.DB, artist) + err = controller.UpdateArtist(app.DB, artist) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -167,9 +166,9 @@ func UpdateArtist(artist *model.Artist) http.Handler { }) } -func DeleteArtist(artist *model.Artist) http.Handler { +func DeleteArtist(app *model.AppState, artist *model.Artist) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - err := controller.DeleteArtist(global.DB, artist.ID) + err := controller.DeleteArtist(app.DB, artist.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) diff --git a/api/release.go b/api/release.go index d17fb5f..c71043e 100644 --- a/api/release.go +++ b/api/release.go @@ -10,17 +10,16 @@ import ( "strings" "time" - "arimelody-web/global" "arimelody-web/controller" "arimelody-web/model" ) -func ServeRelease(release *model.Release) http.Handler { +func ServeRelease(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // only allow authorised users to view hidden releases privileged := false if !release.Visible { - account, err := controller.GetAccountByRequest(global.DB, r) + account, err := controller.GetAccountByRequest(app.DB, r) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -67,14 +66,14 @@ func ServeRelease(release *model.Release) http.Handler { if release.IsReleased() || privileged { // get credits - credits, err := controller.GetReleaseCredits(global.DB, release.ID) + credits, err := controller.GetReleaseCredits(app.DB, release.ID) if err != nil { fmt.Printf("WARN: Failed to serve release %s: Credits: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } for _, credit := range credits { - artist, err := controller.GetArtist(global.DB, credit.Artist.ID) + artist, err := controller.GetArtist(app.DB, credit.Artist.ID) if err != nil { fmt.Printf("WARN: Failed to serve release %s: Artists: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -89,7 +88,7 @@ func ServeRelease(release *model.Release) http.Handler { } // get tracks - tracks, err := controller.GetReleaseTracks(global.DB, release.ID) + tracks, err := controller.GetReleaseTracks(app.DB, release.ID) if err != nil { fmt.Printf("WARN: Failed to serve release %s: Tracks: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -104,7 +103,7 @@ func ServeRelease(release *model.Release) http.Handler { } // get links - links, err := controller.GetReleaseLinks(global.DB, release.ID) + links, err := controller.GetReleaseLinks(app.DB, release.ID) if err != nil { fmt.Printf("WARN: Failed to serve release %s: Links: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -126,9 +125,9 @@ func ServeRelease(release *model.Release) http.Handler { }) } -func ServeCatalog() http.Handler { +func ServeCatalog(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - releases, err := controller.GetAllReleases(global.DB, false, 0, true) + releases, err := controller.GetAllReleases(app.DB, false, 0, true) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -146,7 +145,7 @@ func ServeCatalog() http.Handler { } catalog := []Release{} - account, err := controller.GetAccountByRequest(global.DB, r) + account, err := controller.GetAccountByRequest(app.DB, r) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -192,7 +191,7 @@ func ServeCatalog() http.Handler { }) } -func CreateRelease() http.Handler { +func CreateRelease(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) @@ -220,7 +219,7 @@ func CreateRelease() http.Handler { if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" } - err = controller.CreateRelease(global.DB, &release) + err = controller.CreateRelease(app.DB, &release) if err != nil { if strings.Contains(err.Error(), "duplicate key") { http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest) @@ -243,7 +242,7 @@ func CreateRelease() http.Handler { }) } -func UpdateRelease(release *model.Release) http.Handler { +func UpdateRelease(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.NotFound(w, r) @@ -255,11 +254,11 @@ func UpdateRelease(release *model.Release) http.Handler { if len(segments) == 2 { switch segments[1] { case "tracks": - UpdateReleaseTracks(release).ServeHTTP(w, r) + UpdateReleaseTracks(app, release).ServeHTTP(w, r) case "credits": - UpdateReleaseCredits(release).ServeHTTP(w, r) + UpdateReleaseCredits(app, release).ServeHTTP(w, r) case "links": - UpdateReleaseLinks(release).ServeHTTP(w, r) + UpdateReleaseLinks(app, release).ServeHTTP(w, r) } return } @@ -281,7 +280,7 @@ func UpdateRelease(release *model.Release) http.Handler { } else { if strings.Contains(release.Artwork, ";base64,") { var artworkDirectory = filepath.Join("uploads", "musicart") - filename, err := HandleImageUpload(&release.Artwork, artworkDirectory, release.ID) + filename, err := HandleImageUpload(app, &release.Artwork, artworkDirectory, release.ID) // clean up files with this ID and different extensions err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error { @@ -300,7 +299,7 @@ func UpdateRelease(release *model.Release) http.Handler { } } - err = controller.UpdateRelease(global.DB, release) + err = controller.UpdateRelease(app.DB, release) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -312,7 +311,7 @@ func UpdateRelease(release *model.Release) http.Handler { }) } -func UpdateReleaseTracks(release *model.Release) http.Handler { +func UpdateReleaseTracks(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var trackIDs = []string{} err := json.NewDecoder(r.Body).Decode(&trackIDs) @@ -321,7 +320,7 @@ func UpdateReleaseTracks(release *model.Release) http.Handler { return } - err = controller.UpdateReleaseTracks(global.DB, release.ID, trackIDs) + err = controller.UpdateReleaseTracks(app.DB, release.ID, trackIDs) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -333,7 +332,7 @@ func UpdateReleaseTracks(release *model.Release) http.Handler { }) } -func UpdateReleaseCredits(release *model.Release) http.Handler { +func UpdateReleaseCredits(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type creditJSON struct { Artist string @@ -358,7 +357,7 @@ func UpdateReleaseCredits(release *model.Release) http.Handler { }) } - err = controller.UpdateReleaseCredits(global.DB, release.ID, credits) + err = controller.UpdateReleaseCredits(app.DB, release.ID, credits) if err != nil { if strings.Contains(err.Error(), "duplicate key") { http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest) @@ -374,7 +373,7 @@ func UpdateReleaseCredits(release *model.Release) http.Handler { }) } -func UpdateReleaseLinks(release *model.Release) http.Handler { +func UpdateReleaseLinks(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut { http.NotFound(w, r) @@ -388,7 +387,7 @@ func UpdateReleaseLinks(release *model.Release) http.Handler { return } - err = controller.UpdateReleaseLinks(global.DB, release.ID, links) + err = controller.UpdateReleaseLinks(app.DB, release.ID, links) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) @@ -400,9 +399,9 @@ func UpdateReleaseLinks(release *model.Release) http.Handler { }) } -func DeleteRelease(release *model.Release) http.Handler { +func DeleteRelease(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - err := controller.DeleteRelease(global.DB, release.ID) + err := controller.DeleteRelease(app.DB, release.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) diff --git a/api/track.go b/api/track.go index 8727b4f..c342e08 100644 --- a/api/track.go +++ b/api/track.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" - "arimelody-web/global" "arimelody-web/controller" "arimelody-web/model" ) @@ -17,7 +16,7 @@ type ( } ) -func ServeAllTracks() http.Handler { +func ServeAllTracks(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type Track struct { ID string `json:"id"` @@ -26,7 +25,7 @@ func ServeAllTracks() http.Handler { var tracks = []Track{} var dbTracks = []*model.Track{} - dbTracks, err := controller.GetAllTracks(global.DB) + dbTracks, err := controller.GetAllTracks(app.DB) if err != nil { fmt.Printf("WARN: Failed to pull tracks from DB: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -50,9 +49,9 @@ func ServeAllTracks() http.Handler { }) } -func ServeTrack(track *model.Track) http.Handler { +func ServeTrack(app *model.AppState, track *model.Track) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - dbReleases, err := controller.GetTrackReleases(global.DB, track.ID, false) + dbReleases, err := controller.GetTrackReleases(app.DB, track.ID, false) if err != nil { fmt.Printf("WARN: Failed to pull track releases for %s from DB: %s\n", track.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -74,7 +73,7 @@ func ServeTrack(track *model.Track) http.Handler { }) } -func CreateTrack() http.Handler { +func CreateTrack(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) @@ -93,7 +92,7 @@ func CreateTrack() http.Handler { return } - id, err := controller.CreateTrack(global.DB, &track) + id, err := controller.CreateTrack(app.DB, &track) if err != nil { fmt.Printf("WARN: Failed to create track: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -106,7 +105,7 @@ func CreateTrack() http.Handler { }) } -func UpdateTrack(track *model.Track) http.Handler { +func UpdateTrack(app *model.AppState, track *model.Track) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut || r.URL.Path == "/" { http.NotFound(w, r) @@ -124,7 +123,7 @@ func UpdateTrack(track *model.Track) http.Handler { return } - err = controller.UpdateTrack(global.DB, track) + err = controller.UpdateTrack(app.DB, track) if err != nil { fmt.Printf("WARN: Failed to update track %s: %s\n", track.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -141,7 +140,7 @@ func UpdateTrack(track *model.Track) http.Handler { }) } -func DeleteTrack(track *model.Track) http.Handler { +func DeleteTrack(app *model.AppState, track *model.Track) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete || r.URL.Path == "/" { http.NotFound(w, r) @@ -149,7 +148,7 @@ func DeleteTrack(track *model.Track) http.Handler { } var trackID = r.URL.Path[1:] - err := controller.DeleteTrack(global.DB, trackID) + err := controller.DeleteTrack(app.DB, trackID) if err != nil { fmt.Printf("WARN: Failed to delete track %s: %s\n", trackID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/api/uploads.go b/api/uploads.go index 6b1c496..ddcf6ee 100644 --- a/api/uploads.go +++ b/api/uploads.go @@ -1,7 +1,7 @@ package api import ( - "arimelody-web/global" + "arimelody-web/model" "bufio" "encoding/base64" "errors" @@ -11,12 +11,12 @@ import ( "strings" ) -func HandleImageUpload(data *string, directory string, filename string) (string, error) { +func HandleImageUpload(app *model.AppState, data *string, directory string, filename string) (string, error) { split := strings.Split(*data, ";base64,") header := split[0] imageData, err := base64.StdEncoding.DecodeString(split[1]) ext, _ := strings.CutPrefix(header, "data:image/") - directory = filepath.Join(global.Config.DataDirectory, directory) + directory = filepath.Join(app.Config.DataDirectory, directory) switch ext { case "png": diff --git a/controller/account.go b/controller/account.go index 3547d35..044faec 100644 --- a/controller/account.go +++ b/controller/account.go @@ -1,7 +1,6 @@ package controller import ( - "arimelody-web/global" "arimelody-web/model" "errors" "fmt" @@ -72,7 +71,7 @@ func GetTokenFromRequest(db *sqlx.DB, r *http.Request) string { return tokenStr } - cookie, err := r.Cookie(global.COOKIE_TOKEN) + cookie, err := r.Cookie(model.COOKIE_TOKEN) if err != nil { return "" } diff --git a/controller/artist.go b/controller/artist.go index c52b78d..1a613aa 100644 --- a/controller/artist.go +++ b/controller/artist.go @@ -2,6 +2,7 @@ package controller import ( "arimelody-web/model" + "github.com/jmoiron/sqlx" ) diff --git a/global/config.go b/controller/config.go similarity index 65% rename from global/config.go rename to controller/config.go index 20e152f..28d4be4 100644 --- a/global/config.go +++ b/controller/config.go @@ -1,4 +1,4 @@ -package global +package controller import ( "errors" @@ -6,44 +6,21 @@ import ( "os" "strconv" - "github.com/jmoiron/sqlx" + "arimelody-web/model" + "github.com/pelletier/go-toml/v2" ) -type ( - dbConfig struct { - Host string `toml:"host"` - Port int64 `toml:"port"` - Name string `toml:"name"` - User string `toml:"user"` - Pass string `toml:"pass"` - } - - discordConfig struct { - AdminID string `toml:"admin_id" comment:"NOTE: admin_id to be deprecated in favour of local accounts and SSO."` - ClientID string `toml:"client_id"` - Secret string `toml:"secret"` - } - - config struct { - BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."` - Port int64 `toml:"port"` - DataDirectory string `toml:"data_dir"` - DB dbConfig `toml:"db"` - Discord discordConfig `toml:"discord"` - } -) - -var Config = func() config { +func GetConfig() model.Config { configFile := os.Getenv("ARIMELODY_CONFIG") if configFile == "" { configFile = "config.toml" } - config := config{ + config := model.Config{ BaseUrl: "https://arimelody.me", Port: 8080, - DB: dbConfig{ + DB: model.DBConfig{ Host: "127.0.0.1", Port: 5432, User: "arimelody", @@ -63,20 +40,18 @@ var Config = func() config { err = toml.Unmarshal([]byte(data), &config) if err != nil { - fmt.Fprintf(os.Stderr, "FATAL: Failed to parse configuration file: %v\n", err) - os.Exit(1) + panic(fmt.Sprintf("FATAL: Failed to parse configuration file: %v\n", err)) } err = handleConfigOverrides(&config) if err != nil { - fmt.Fprintf(os.Stderr, "FATAL: Failed to parse environment variable %v\n", err) - os.Exit(1) + panic(fmt.Sprintf("FATAL: Failed to parse environment variable %v\n", err)) } return config -}() +} -func handleConfigOverrides(config *config) error { +func handleConfigOverrides(config *model.Config) error { var err error if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env } @@ -101,5 +76,3 @@ func handleConfigOverrides(config *config) error { return nil } - -var DB *sqlx.DB diff --git a/controller/release.go b/controller/release.go index c9791ac..362669a 100644 --- a/controller/release.go +++ b/controller/release.go @@ -5,6 +5,7 @@ import ( "fmt" "arimelody-web/model" + "github.com/jmoiron/sqlx" ) diff --git a/controller/track.go b/controller/track.go index d302045..fa4efc1 100644 --- a/controller/track.go +++ b/controller/track.go @@ -2,6 +2,7 @@ package controller import ( "arimelody-web/model" + "github.com/jmoiron/sqlx" ) diff --git a/discord/discord.go b/discord/discord.go index 8952c85..d46f32d 100644 --- a/discord/discord.go +++ b/discord/discord.go @@ -1,38 +1,17 @@ package discord import ( + "arimelody-web/model" "encoding/json" "errors" "fmt" "net/http" "net/url" "strings" - - "arimelody-web/global" ) const API_ENDPOINT = "https://discord.com/api/v10" -var CREDENTIALS_PROVIDED = true -var CLIENT_ID = func() string { - id := global.Config.Discord.ClientID - if id == "" { - // fmt.Printf("WARN: Discord client ID (DISCORD_CLIENT) was not provided.\n") - CREDENTIALS_PROVIDED = false - } - return id -}() -var CLIENT_SECRET = func() string { - secret := global.Config.Discord.Secret - if secret == "" { - // fmt.Printf("WARN: Discord secret (DISCORD_SECRET) was not provided.\n") - CREDENTIALS_PROVIDED = false - } - return secret -}() -var OAUTH_CALLBACK_URI = fmt.Sprintf("%s/admin/login", global.Config.BaseUrl) -var REDIRECT_URI = fmt.Sprintf("https://discord.com/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=identify", CLIENT_ID, OAUTH_CALLBACK_URI) - type ( AccessTokenResponse struct { AccessToken string `json:"access_token"` @@ -68,15 +47,15 @@ type ( } ) -func GetOAuthTokenFromCode(code string) (string, error) { +func GetOAuthTokenFromCode(app *model.AppState, code string) (string, error) { // let's get an oauth token! req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/oauth2/token", API_ENDPOINT), strings.NewReader(url.Values{ - "client_id": {CLIENT_ID}, - "client_secret": {CLIENT_SECRET}, + "client_id": {app.Config.Discord.ClientID}, + "client_secret": {app.Config.Discord.Secret}, "grant_type": {"authorization_code"}, "code": {code}, - "redirect_uri": {OAUTH_CALLBACK_URI}, + "redirect_uri": {GetOAuthCallbackURI(app.Config.BaseUrl)}, }.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") @@ -115,3 +94,15 @@ func GetDiscordUserFromAuth(token string) (DiscordUser, error) { return auth_info.User, nil } + +func GetOAuthCallbackURI(baseURL string) string { + return fmt.Sprintf("%s/admin/login", baseURL) +} + +func GetRedirectURI(app *model.AppState) string { + return fmt.Sprintf( + "https://discord.com/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=identify", + app.Config.Discord.ClientID, + GetOAuthCallbackURI(app.Config.BaseUrl), + ) +} diff --git a/global/const.go b/global/const.go deleted file mode 100644 index 157668d..0000000 --- a/global/const.go +++ /dev/null @@ -1,3 +0,0 @@ -package global - -const COOKIE_TOKEN string = "AM_TOKEN" diff --git a/global/funcs.go b/global/funcs.go deleted file mode 100644 index 49edb01..0000000 --- a/global/funcs.go +++ /dev/null @@ -1,101 +0,0 @@ -package global - -import ( - "fmt" - "math/rand" - "net/http" - "strconv" - "time" - - "arimelody-web/colour" -) - -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]) - }) -} diff --git a/main.go b/main.go index cbda0a7..23fc997 100644 --- a/main.go +++ b/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]) + }) +} diff --git a/model/account.go b/model/account.go index 031cae9..72720a0 100644 --- a/model/account.go +++ b/model/account.go @@ -2,6 +2,8 @@ package model import "time" +const COOKIE_TOKEN string = "AM_TOKEN" + type ( Account struct { ID string `json:"id" db:"id"` diff --git a/model/appstate.go b/model/appstate.go new file mode 100644 index 0000000..08016b7 --- /dev/null +++ b/model/appstate.go @@ -0,0 +1,32 @@ +package model + +import "github.com/jmoiron/sqlx" + +type ( + DBConfig struct { + Host string `toml:"host"` + Port int64 `toml:"port"` + Name string `toml:"name"` + User string `toml:"user"` + Pass string `toml:"pass"` + } + + DiscordConfig struct { + AdminID string `toml:"admin_id" comment:"NOTE: admin_id to be deprecated in favour of local accounts and SSO."` + ClientID string `toml:"client_id"` + Secret string `toml:"secret"` + } + + Config struct { + BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."` + Port int64 `toml:"port"` + DataDirectory string `toml:"data_dir"` + DB DBConfig `toml:"db"` + Discord DiscordConfig `toml:"discord"` + } + + AppState struct { + DB *sqlx.DB + Config Config + } +) diff --git a/view/music.go b/view/music.go index 227aa5c..cae325f 100644 --- a/view/music.go +++ b/view/music.go @@ -8,36 +8,34 @@ import ( "arimelody-web/controller" "arimelody-web/model" "arimelody-web/templates" - - "github.com/jmoiron/sqlx" ) // HTTP HANDLER METHODS -func MusicHandler(db *sqlx.DB) http.Handler { +func MusicHandler(app *model.AppState) http.Handler { mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { - ServeCatalog(db).ServeHTTP(w, r) + ServeCatalog(app).ServeHTTP(w, r) return } - release, err := controller.GetRelease(db, r.URL.Path[1:], true) + release, err := controller.GetRelease(app.DB, r.URL.Path[1:], true) if err != nil { http.NotFound(w, r) return } - ServeGateway(db, release).ServeHTTP(w, r) + ServeGateway(app, release).ServeHTTP(w, r) })) return mux } -func ServeCatalog(db *sqlx.DB) http.Handler { +func ServeCatalog(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - releases, err := controller.GetAllReleases(db, true, 0, true) + releases, err := controller.GetAllReleases(app.DB, true, 0, true) if err != nil { fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -57,12 +55,12 @@ func ServeCatalog(db *sqlx.DB) http.Handler { }) } -func ServeGateway(db *sqlx.DB, release *model.Release) http.Handler { +func ServeGateway(app *model.AppState, release *model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // only allow authorised users to view hidden releases privileged := false if !release.Visible { - account, err := controller.GetAccountByRequest(db, r) + account, err := controller.GetAccountByRequest(app.DB, r) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)