home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactored out `global`. long live `AppState`

+350 -375
+1
.gitignore
··· 7 7 docker-compose*.yml 8 8 !docker-compose.example.yml 9 9 config*.toml 10 + arimelody-web
+23 -25
admin/accounthttp.go
··· 8 8 "time" 9 9 10 10 "arimelody-web/controller" 11 - "arimelody-web/global" 12 11 "arimelody-web/model" 13 12 14 - "github.com/jmoiron/sqlx" 15 13 "golang.org/x/crypto/bcrypt" 16 14 ) 17 15 ··· 21 19 Token string 22 20 } 23 21 24 - func AccountHandler(db *sqlx.DB) http.Handler { 22 + func AccountHandler(app *model.AppState) http.Handler { 25 23 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 24 account := r.Context().Value("account").(*model.Account) 27 25 28 - totps, err := controller.GetTOTPsForAccount(db, account.ID) 26 + totps, err := controller.GetTOTPsForAccount(app.DB, account.ID) 29 27 if err != nil { 30 28 fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err) 31 29 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 47 45 }) 48 46 } 49 47 50 - func LoginHandler(db *sqlx.DB) http.Handler { 48 + func LoginHandler(app *model.AppState) http.Handler { 51 49 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 52 50 if r.Method == http.MethodGet { 53 - account, err := controller.GetAccountByRequest(db, r) 51 + account, err := controller.GetAccountByRequest(app.DB, r) 54 52 if err != nil { 55 53 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 56 54 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) ··· 107 105 TOTP: r.Form.Get("totp"), 108 106 } 109 107 110 - account, err := controller.GetAccount(db, credentials.Username) 108 + account, err := controller.GetAccount(app.DB, credentials.Username) 111 109 if err != nil { 112 110 render(LoginResponse{ Message: "Invalid username or password" }) 113 111 return ··· 123 121 return 124 122 } 125 123 126 - totps, err := controller.GetTOTPsForAccount(db, account.ID) 124 + totps, err := controller.GetTOTPsForAccount(app.DB, account.ID) 127 125 if err != nil { 128 126 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err) 129 127 render(LoginResponse{ Message: "Something went wrong. Please try again." }) ··· 147 145 } 148 146 149 147 // login success! 150 - token, err := controller.CreateToken(db, account.ID, r.UserAgent()) 148 + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) 151 149 if err != nil { 152 150 fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err) 153 151 render(LoginResponse{ Message: "Something went wrong. Please try again." }) ··· 155 153 } 156 154 157 155 cookie := http.Cookie{} 158 - cookie.Name = global.COOKIE_TOKEN 156 + cookie.Name = model.COOKIE_TOKEN 159 157 cookie.Value = token.Token 160 158 cookie.Expires = token.ExpiresAt 161 - if strings.HasPrefix(global.Config.BaseUrl, "https") { 159 + if strings.HasPrefix(app.Config.BaseUrl, "https") { 162 160 cookie.Secure = true 163 161 } 164 162 cookie.HttpOnly = true ··· 169 167 }) 170 168 } 171 169 172 - func LogoutHandler(db *sqlx.DB) http.Handler { 170 + func LogoutHandler(app *model.AppState) http.Handler { 173 171 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 174 172 if r.Method != http.MethodGet { 175 173 http.NotFound(w, r) 176 174 return 177 175 } 178 176 179 - tokenStr := controller.GetTokenFromRequest(db, r) 177 + tokenStr := controller.GetTokenFromRequest(app.DB, r) 180 178 181 179 if len(tokenStr) > 0 { 182 - err := controller.DeleteToken(db, tokenStr) 180 + err := controller.DeleteToken(app.DB, tokenStr) 183 181 if err != nil { 184 182 fmt.Fprintf(os.Stderr, "WARN: Failed to revoke token: %v\n", err) 185 183 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 188 186 } 189 187 190 188 cookie := http.Cookie{} 191 - cookie.Name = global.COOKIE_TOKEN 189 + cookie.Name = model.COOKIE_TOKEN 192 190 cookie.Value = "" 193 191 cookie.Expires = time.Now() 194 - if strings.HasPrefix(global.Config.BaseUrl, "https") { 192 + if strings.HasPrefix(app.Config.BaseUrl, "https") { 195 193 cookie.Secure = true 196 194 } 197 195 cookie.HttpOnly = true ··· 201 199 }) 202 200 } 203 201 204 - func createAccountHandler(db *sqlx.DB) http.Handler { 202 + func createAccountHandler(app *model.AppState) http.Handler { 205 203 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 206 - checkAccount, err := controller.GetAccountByRequest(db, r) 204 + checkAccount, err := controller.GetAccountByRequest(app.DB, r) 207 205 if err != nil { 208 206 fmt.Printf("WARN: Failed to fetch account: %s\n", err) 209 207 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 260 258 } 261 259 262 260 // make sure code exists in DB 263 - invite, err := controller.GetInvite(db, credentials.Invite) 261 + invite, err := controller.GetInvite(app.DB, credentials.Invite) 264 262 if err != nil { 265 263 fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err) 266 264 render(CreateAccountResponse{ ··· 270 268 } 271 269 if invite == nil || time.Now().After(invite.ExpiresAt) { 272 270 if invite != nil { 273 - err := controller.DeleteInvite(db, invite.Code) 271 + err := controller.DeleteInvite(app.DB, invite.Code) 274 272 if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } 275 273 } 276 274 render(CreateAccountResponse{ ··· 294 292 Email: credentials.Email, 295 293 AvatarURL: "/img/default-avatar.png", 296 294 } 297 - err = controller.CreateAccount(db, &account) 295 + err = controller.CreateAccount(app.DB, &account) 298 296 if err != nil { 299 297 if strings.HasPrefix(err.Error(), "pq: duplicate key") { 300 298 render(CreateAccountResponse{ ··· 309 307 return 310 308 } 311 309 312 - err = controller.DeleteInvite(db, invite.Code) 310 + err = controller.DeleteInvite(app.DB, invite.Code) 313 311 if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } 314 312 315 313 // registration success! 316 - token, err := controller.CreateToken(db, account.ID, r.UserAgent()) 314 + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) 317 315 if err != nil { 318 316 fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err) 319 317 // gracefully redirect user to login page ··· 322 320 } 323 321 324 322 cookie := http.Cookie{} 325 - cookie.Name = global.COOKIE_TOKEN 323 + cookie.Name = model.COOKIE_TOKEN 326 324 cookie.Value = token.Token 327 325 cookie.Expires = token.ExpiresAt 328 - if strings.HasPrefix(global.Config.BaseUrl, "https") { 326 + if strings.HasPrefix(app.Config.BaseUrl, "https") { 329 327 cookie.Secure = true 330 328 } 331 329 cookie.HttpOnly = true
+3 -4
admin/artisthttp.go
··· 5 5 "net/http" 6 6 "strings" 7 7 8 - "arimelody-web/global" 9 8 "arimelody-web/model" 10 9 "arimelody-web/controller" 11 10 ) 12 11 13 - func serveArtist() http.Handler { 12 + func serveArtist(app *model.AppState) http.Handler { 14 13 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 14 slices := strings.Split(r.URL.Path[1:], "/") 16 15 id := slices[0] 17 - artist, err := controller.GetArtist(global.DB, id) 16 + artist, err := controller.GetArtist(app.DB, id) 18 17 if err != nil { 19 18 if artist == nil { 20 19 http.NotFound(w, r) ··· 25 24 return 26 25 } 27 26 28 - credits, err := controller.GetArtistCredits(global.DB, artist.ID, true) 27 + credits, err := controller.GetArtistCredits(app.DB, artist.ID, true) 29 28 if err != nil { 30 29 fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 31 30 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+14 -16
admin/http.go
··· 9 9 10 10 "arimelody-web/controller" 11 11 "arimelody-web/model" 12 - 13 - "github.com/jmoiron/sqlx" 14 12 ) 15 13 16 - func Handler(db *sqlx.DB) http.Handler { 14 + func Handler(app *model.AppState) http.Handler { 17 15 mux := http.NewServeMux() 18 16 19 - mux.Handle("/login", LoginHandler(db)) 20 - mux.Handle("/register", createAccountHandler(db)) 21 - mux.Handle("/logout", RequireAccount(db, LogoutHandler(db))) 22 - mux.Handle("/account", RequireAccount(db, AccountHandler(db))) 17 + mux.Handle("/login", LoginHandler(app)) 18 + mux.Handle("/register", createAccountHandler(app)) 19 + mux.Handle("/logout", RequireAccount(app, LogoutHandler(app))) 20 + mux.Handle("/account", RequireAccount(app, AccountHandler(app))) 23 21 mux.Handle("/static/", http.StripPrefix("/static", staticHandler())) 24 - mux.Handle("/release/", RequireAccount(db, http.StripPrefix("/release", serveRelease()))) 25 - mux.Handle("/artist/", RequireAccount(db, http.StripPrefix("/artist", serveArtist()))) 26 - mux.Handle("/track/", RequireAccount(db, http.StripPrefix("/track", serveTrack()))) 22 + mux.Handle("/release/", RequireAccount(app, http.StripPrefix("/release", serveRelease(app)))) 23 + mux.Handle("/artist/", RequireAccount(app, http.StripPrefix("/artist", serveArtist(app)))) 24 + mux.Handle("/track/", RequireAccount(app, http.StripPrefix("/track", serveTrack(app)))) 27 25 mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 28 26 if r.URL.Path != "/" { 29 27 http.NotFound(w, r) 30 28 return 31 29 } 32 30 33 - account, err := controller.GetAccountByRequest(db, r) 31 + account, err := controller.GetAccountByRequest(app.DB, r) 34 32 if err != nil { 35 33 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %s\n", err) 36 34 } ··· 39 37 return 40 38 } 41 39 42 - releases, err := controller.GetAllReleases(db, false, 0, true) 40 + releases, err := controller.GetAllReleases(app.DB, false, 0, true) 43 41 if err != nil { 44 42 fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err) 45 43 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 46 44 return 47 45 } 48 46 49 - artists, err := controller.GetAllArtists(db) 47 + artists, err := controller.GetAllArtists(app.DB) 50 48 if err != nil { 51 49 fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err) 52 50 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 53 51 return 54 52 } 55 53 56 - tracks, err := controller.GetOrphanTracks(db) 54 + tracks, err := controller.GetOrphanTracks(app.DB) 57 55 if err != nil { 58 56 fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %s\n", err) 59 57 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 83 81 return mux 84 82 } 85 83 86 - func RequireAccount(db *sqlx.DB, next http.Handler) http.HandlerFunc { 84 + func RequireAccount(app *model.AppState, next http.Handler) http.HandlerFunc { 87 85 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 88 - account, err := controller.GetAccountByRequest(db, r) 86 + account, err := controller.GetAccountByRequest(app.DB, r) 89 87 if err != nil { 90 88 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 91 89 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
+14 -15
admin/releasehttp.go
··· 5 5 "net/http" 6 6 "strings" 7 7 8 - "arimelody-web/global" 9 8 "arimelody-web/controller" 10 9 "arimelody-web/model" 11 10 ) 12 11 13 - func serveRelease() http.Handler { 12 + func serveRelease(app *model.AppState) http.Handler { 14 13 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 14 slices := strings.Split(r.URL.Path[1:], "/") 16 15 releaseID := slices[0] 17 16 18 17 account := r.Context().Value("account").(*model.Account) 19 18 20 - release, err := controller.GetRelease(global.DB, releaseID, true) 19 + release, err := controller.GetRelease(app.DB, releaseID, true) 21 20 if err != nil { 22 21 if strings.Contains(err.Error(), "no rows") { 23 22 http.NotFound(w, r) ··· 34 33 serveEditCredits(release).ServeHTTP(w, r) 35 34 return 36 35 case "addcredit": 37 - serveAddCredit(release).ServeHTTP(w, r) 36 + serveAddCredit(app, release).ServeHTTP(w, r) 38 37 return 39 38 case "newcredit": 40 - serveNewCredit().ServeHTTP(w, r) 39 + serveNewCredit(app).ServeHTTP(w, r) 41 40 return 42 41 case "editlinks": 43 42 serveEditLinks(release).ServeHTTP(w, r) ··· 46 45 serveEditTracks(release).ServeHTTP(w, r) 47 46 return 48 47 case "addtrack": 49 - serveAddTrack(release).ServeHTTP(w, r) 48 + serveAddTrack(app, release).ServeHTTP(w, r) 50 49 return 51 50 case "newtrack": 52 - serveNewTrack().ServeHTTP(w, r) 51 + serveNewTrack(app).ServeHTTP(w, r) 53 52 return 54 53 } 55 54 http.NotFound(w, r) ··· 83 82 }) 84 83 } 85 84 86 - func serveAddCredit(release *model.Release) http.Handler { 85 + func serveAddCredit(app *model.AppState, release *model.Release) http.Handler { 87 86 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 88 - artists, err := controller.GetArtistsNotOnRelease(global.DB, release.ID) 87 + artists, err := controller.GetArtistsNotOnRelease(app.DB, release.ID) 89 88 if err != nil { 90 89 fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err) 91 90 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 109 108 }) 110 109 } 111 110 112 - func serveNewCredit() http.Handler { 111 + func serveNewCredit(app *model.AppState) http.Handler { 113 112 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 114 113 artistID := strings.Split(r.URL.Path, "/")[3] 115 - artist, err := controller.GetArtist(global.DB, artistID) 114 + artist, err := controller.GetArtist(app.DB, artistID) 116 115 if err != nil { 117 116 fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err) 118 117 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 154 153 }) 155 154 } 156 155 157 - func serveAddTrack(release *model.Release) http.Handler { 156 + func serveAddTrack(app *model.AppState, release *model.Release) http.Handler { 158 157 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 159 - tracks, err := controller.GetTracksNotOnRelease(global.DB, release.ID) 158 + tracks, err := controller.GetTracksNotOnRelease(app.DB, release.ID) 160 159 if err != nil { 161 160 fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err) 162 161 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 181 180 }) 182 181 } 183 182 184 - func serveNewTrack() http.Handler { 183 + func serveNewTrack(app *model.AppState) http.Handler { 185 184 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 186 185 trackID := strings.Split(r.URL.Path, "/")[3] 187 - track, err := controller.GetTrack(global.DB, trackID) 186 + track, err := controller.GetTrack(app.DB, trackID) 188 187 if err != nil { 189 188 fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err) 190 189 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+3 -4
admin/trackhttp.go
··· 5 5 "net/http" 6 6 "strings" 7 7 8 - "arimelody-web/global" 9 8 "arimelody-web/model" 10 9 "arimelody-web/controller" 11 10 ) 12 11 13 - func serveTrack() http.Handler { 12 + func serveTrack(app *model.AppState) http.Handler { 14 13 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 14 slices := strings.Split(r.URL.Path[1:], "/") 16 15 id := slices[0] 17 - track, err := controller.GetTrack(global.DB, id) 16 + track, err := controller.GetTrack(app.DB, id) 18 17 if err != nil { 19 18 fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 20 19 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 25 24 return 26 25 } 27 26 28 - releases, err := controller.GetTrackReleases(global.DB, track.ID, true) 27 + releases, err := controller.GetTrackReleases(app.DB, track.ID, true) 29 28 if err != nil { 30 29 fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err) 31 30 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+12 -13
api/account.go
··· 3 3 import ( 4 4 "arimelody-web/controller" 5 5 "arimelody-web/model" 6 - "arimelody-web/global" 7 6 "encoding/json" 8 7 "fmt" 9 8 "net/http" ··· 14 13 "golang.org/x/crypto/bcrypt" 15 14 ) 16 15 17 - func handleLogin() http.HandlerFunc { 16 + func handleLogin(app *model.AppState) http.HandlerFunc { 18 17 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 19 18 if r.Method != http.MethodPost { 20 19 http.NotFound(w, r) ··· 33 32 return 34 33 } 35 34 36 - account, err := controller.GetAccount(global.DB, credentials.Username) 35 + account, err := controller.GetAccount(app.DB, credentials.Username) 37 36 if err != nil { 38 37 fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %v\n", err) 39 38 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 50 49 return 51 50 } 52 51 53 - token, err := controller.CreateToken(global.DB, account.ID, r.UserAgent()) 52 + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) 54 53 type LoginResponse struct { 55 54 Token string `json:"token"` 56 55 ExpiresAt time.Time `json:"expires_at"` ··· 67 66 }) 68 67 } 69 68 70 - func handleAccountRegistration() http.HandlerFunc { 69 + func handleAccountRegistration(app *model.AppState) http.HandlerFunc { 71 70 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 72 71 if r.Method != http.MethodPost { 73 72 http.NotFound(w, r) ··· 89 88 } 90 89 91 90 // make sure code exists in DB 92 - invite, err := controller.GetInvite(global.DB, credentials.Invite) 91 + invite, err := controller.GetInvite(app.DB, credentials.Invite) 93 92 if err != nil { 94 93 fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err) 95 94 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 101 100 } 102 101 103 102 if time.Now().After(invite.ExpiresAt) { 104 - err := controller.DeleteInvite(global.DB, invite.Code) 103 + err := controller.DeleteInvite(app.DB, invite.Code) 105 104 if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } 106 105 http.Error(w, "Invalid invite code", http.StatusBadRequest) 107 106 return ··· 120 119 Email: credentials.Email, 121 120 AvatarURL: "/img/default-avatar.png", 122 121 } 123 - err = controller.CreateAccount(global.DB, &account) 122 + err = controller.CreateAccount(app.DB, &account) 124 123 if err != nil { 125 124 if strings.HasPrefix(err.Error(), "pq: duplicate key") { 126 125 http.Error(w, "An account with that username already exists", http.StatusBadRequest) ··· 131 130 return 132 131 } 133 132 134 - err = controller.DeleteInvite(global.DB, invite.Code) 133 + err = controller.DeleteInvite(app.DB, invite.Code) 135 134 if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) } 136 135 137 - token, err := controller.CreateToken(global.DB, account.ID, r.UserAgent()) 136 + token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent()) 138 137 type LoginResponse struct { 139 138 Token string `json:"token"` 140 139 ExpiresAt time.Time `json:"expires_at"` ··· 151 150 }) 152 151 } 153 152 154 - func handleDeleteAccount() http.HandlerFunc { 153 + func handleDeleteAccount(app *model.AppState) http.HandlerFunc { 155 154 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 156 155 if r.Method != http.MethodPost { 157 156 http.NotFound(w, r) ··· 170 169 return 171 170 } 172 171 173 - account, err := controller.GetAccount(global.DB, credentials.Username) 172 + account, err := controller.GetAccount(app.DB, credentials.Username) 174 173 if err != nil { 175 174 if strings.Contains(err.Error(), "no rows") { 176 175 http.Error(w, "Invalid username or password", http.StatusBadRequest) ··· 189 188 190 189 // TODO: check TOTP 191 190 192 - err = controller.DeleteAccount(global.DB, account.Username) 191 + err = controller.DeleteAccount(app.DB, account.Username) 193 192 if err != nil { 194 193 fmt.Fprintf(os.Stderr, "WARN: Failed to delete account: %v\n", err) 195 194 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+20 -21
api/api.go
··· 7 7 8 8 "arimelody-web/admin" 9 9 "arimelody-web/controller" 10 - 11 - "github.com/jmoiron/sqlx" 10 + "arimelody-web/model" 12 11 ) 13 12 14 - func Handler(db *sqlx.DB) http.Handler { 13 + func Handler(app *model.AppState) http.Handler { 15 14 mux := http.NewServeMux() 16 15 17 16 // ACCOUNT ENDPOINTS ··· 32 31 33 32 mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 33 var artistID = strings.Split(r.URL.Path[1:], "/")[0] 35 - artist, err := controller.GetArtist(db, artistID) 34 + artist, err := controller.GetArtist(app.DB, artistID) 36 35 if err != nil { 37 36 if strings.Contains(err.Error(), "no rows") { 38 37 http.NotFound(w, r) ··· 46 45 switch r.Method { 47 46 case http.MethodGet: 48 47 // GET /api/v1/artist/{id} 49 - ServeArtist(artist).ServeHTTP(w, r) 48 + ServeArtist(app, artist).ServeHTTP(w, r) 50 49 case http.MethodPut: 51 50 // PUT /api/v1/artist/{id} (admin) 52 - admin.RequireAccount(db, UpdateArtist(artist)).ServeHTTP(w, r) 51 + admin.RequireAccount(app, UpdateArtist(app, artist)).ServeHTTP(w, r) 53 52 case http.MethodDelete: 54 53 // DELETE /api/v1/artist/{id} (admin) 55 - admin.RequireAccount(db, DeleteArtist(artist)).ServeHTTP(w, r) 54 + admin.RequireAccount(app, DeleteArtist(app, artist)).ServeHTTP(w, r) 56 55 default: 57 56 http.NotFound(w, r) 58 57 } ··· 61 60 switch r.Method { 62 61 case http.MethodGet: 63 62 // GET /api/v1/artist 64 - ServeAllArtists().ServeHTTP(w, r) 63 + ServeAllArtists(app).ServeHTTP(w, r) 65 64 case http.MethodPost: 66 65 // POST /api/v1/artist (admin) 67 - admin.RequireAccount(db, CreateArtist()).ServeHTTP(w, r) 66 + admin.RequireAccount(app, CreateArtist(app)).ServeHTTP(w, r) 68 67 default: 69 68 http.NotFound(w, r) 70 69 } ··· 74 73 75 74 mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 76 75 var releaseID = strings.Split(r.URL.Path[1:], "/")[0] 77 - release, err := controller.GetRelease(db, releaseID, true) 76 + release, err := controller.GetRelease(app.DB, releaseID, true) 78 77 if err != nil { 79 78 if strings.Contains(err.Error(), "no rows") { 80 79 http.NotFound(w, r) ··· 88 87 switch r.Method { 89 88 case http.MethodGet: 90 89 // GET /api/v1/music/{id} 91 - ServeRelease(release).ServeHTTP(w, r) 90 + ServeRelease(app, release).ServeHTTP(w, r) 92 91 case http.MethodPut: 93 92 // PUT /api/v1/music/{id} (admin) 94 - admin.RequireAccount(db, UpdateRelease(release)).ServeHTTP(w, r) 93 + admin.RequireAccount(app, UpdateRelease(app, release)).ServeHTTP(w, r) 95 94 case http.MethodDelete: 96 95 // DELETE /api/v1/music/{id} (admin) 97 - admin.RequireAccount(db, DeleteRelease(release)).ServeHTTP(w, r) 96 + admin.RequireAccount(app, DeleteRelease(app, release)).ServeHTTP(w, r) 98 97 default: 99 98 http.NotFound(w, r) 100 99 } ··· 103 102 switch r.Method { 104 103 case http.MethodGet: 105 104 // GET /api/v1/music 106 - ServeCatalog().ServeHTTP(w, r) 105 + ServeCatalog(app).ServeHTTP(w, r) 107 106 case http.MethodPost: 108 107 // POST /api/v1/music (admin) 109 - admin.RequireAccount(db, CreateRelease()).ServeHTTP(w, r) 108 + admin.RequireAccount(app, CreateRelease(app)).ServeHTTP(w, r) 110 109 default: 111 110 http.NotFound(w, r) 112 111 } ··· 116 115 117 116 mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 118 117 var trackID = strings.Split(r.URL.Path[1:], "/")[0] 119 - track, err := controller.GetTrack(db, trackID) 118 + track, err := controller.GetTrack(app.DB, trackID) 120 119 if err != nil { 121 120 if strings.Contains(err.Error(), "no rows") { 122 121 http.NotFound(w, r) ··· 130 129 switch r.Method { 131 130 case http.MethodGet: 132 131 // GET /api/v1/track/{id} (admin) 133 - admin.RequireAccount(db, ServeTrack(track)).ServeHTTP(w, r) 132 + admin.RequireAccount(app, ServeTrack(app, track)).ServeHTTP(w, r) 134 133 case http.MethodPut: 135 134 // PUT /api/v1/track/{id} (admin) 136 - admin.RequireAccount(db, UpdateTrack(track)).ServeHTTP(w, r) 135 + admin.RequireAccount(app, UpdateTrack(app, track)).ServeHTTP(w, r) 137 136 case http.MethodDelete: 138 137 // DELETE /api/v1/track/{id} (admin) 139 - admin.RequireAccount(db, DeleteTrack(track)).ServeHTTP(w, r) 138 + admin.RequireAccount(app, DeleteTrack(app, track)).ServeHTTP(w, r) 140 139 default: 141 140 http.NotFound(w, r) 142 141 } ··· 145 144 switch r.Method { 146 145 case http.MethodGet: 147 146 // GET /api/v1/track (admin) 148 - admin.RequireAccount(db, ServeAllTracks()).ServeHTTP(w, r) 147 + admin.RequireAccount(app, ServeAllTracks(app)).ServeHTTP(w, r) 149 148 case http.MethodPost: 150 149 // POST /api/v1/track (admin) 151 - admin.RequireAccount(db, CreateTrack()).ServeHTTP(w, r) 150 + admin.RequireAccount(app, CreateTrack(app)).ServeHTTP(w, r) 152 151 default: 153 152 http.NotFound(w, r) 154 153 }
+12 -13
api/artist.go
··· 10 10 "strings" 11 11 "time" 12 12 13 - "arimelody-web/global" 14 13 "arimelody-web/controller" 15 14 "arimelody-web/model" 16 15 ) 17 16 18 - func ServeAllArtists() http.Handler { 17 + func ServeAllArtists(app *model.AppState) http.Handler { 19 18 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 19 var artists = []*model.Artist{} 21 - artists, err := controller.GetAllArtists(global.DB) 20 + artists, err := controller.GetAllArtists(app.DB) 22 21 if err != nil { 23 22 fmt.Printf("WARN: Failed to serve all artists: %s\n", err) 24 23 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 35 34 }) 36 35 } 37 36 38 - func ServeArtist(artist *model.Artist) http.Handler { 37 + func ServeArtist(app *model.AppState, artist *model.Artist) http.Handler { 39 38 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 39 type ( 41 40 creditJSON struct { ··· 52 51 } 53 52 ) 54 53 55 - account, err := controller.GetAccountByRequest(global.DB, r) 54 + account, err := controller.GetAccountByRequest(app.DB, r) 56 55 if err != nil { 57 56 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) 58 57 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 60 59 } 61 60 show_hidden_releases := account != nil 62 61 63 - dbCredits, err := controller.GetArtistCredits(global.DB, artist.ID, show_hidden_releases) 62 + dbCredits, err := controller.GetArtistCredits(app.DB, artist.ID, show_hidden_releases) 64 63 if err != nil { 65 64 fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err) 66 65 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 92 91 }) 93 92 } 94 93 95 - func CreateArtist() http.Handler { 94 + func CreateArtist(app *model.AppState) http.Handler { 96 95 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 97 96 var artist model.Artist 98 97 err := json.NewDecoder(r.Body).Decode(&artist) ··· 107 106 } 108 107 if artist.Name == "" { artist.Name = artist.ID } 109 108 110 - err = controller.CreateArtist(global.DB, &artist) 109 + err = controller.CreateArtist(app.DB, &artist) 111 110 if err != nil { 112 111 if strings.Contains(err.Error(), "duplicate key") { 113 112 http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest) ··· 122 121 }) 123 122 } 124 123 125 - func UpdateArtist(artist *model.Artist) http.Handler { 124 + func UpdateArtist(app *model.AppState, artist *model.Artist) http.Handler { 126 125 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 127 126 err := json.NewDecoder(r.Body).Decode(&artist) 128 127 if err != nil { ··· 136 135 } else { 137 136 if strings.Contains(artist.Avatar, ";base64,") { 138 137 var artworkDirectory = filepath.Join("uploads", "avatar") 139 - filename, err := HandleImageUpload(&artist.Avatar, artworkDirectory, artist.ID) 138 + filename, err := HandleImageUpload(app, &artist.Avatar, artworkDirectory, artist.ID) 140 139 141 140 // clean up files with this ID and different extensions 142 141 err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error { ··· 155 154 } 156 155 } 157 156 158 - err = controller.UpdateArtist(global.DB, artist) 157 + err = controller.UpdateArtist(app.DB, artist) 159 158 if err != nil { 160 159 if strings.Contains(err.Error(), "no rows") { 161 160 http.NotFound(w, r) ··· 167 166 }) 168 167 } 169 168 170 - func DeleteArtist(artist *model.Artist) http.Handler { 169 + func DeleteArtist(app *model.AppState, artist *model.Artist) http.Handler { 171 170 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 172 - err := controller.DeleteArtist(global.DB, artist.ID) 171 + err := controller.DeleteArtist(app.DB, artist.ID) 173 172 if err != nil { 174 173 if strings.Contains(err.Error(), "no rows") { 175 174 http.NotFound(w, r)
+25 -26
api/release.go
··· 10 10 "strings" 11 11 "time" 12 12 13 - "arimelody-web/global" 14 13 "arimelody-web/controller" 15 14 "arimelody-web/model" 16 15 ) 17 16 18 - func ServeRelease(release *model.Release) http.Handler { 17 + func ServeRelease(app *model.AppState, release *model.Release) http.Handler { 19 18 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 19 // only allow authorised users to view hidden releases 21 20 privileged := false 22 21 if !release.Visible { 23 - account, err := controller.GetAccountByRequest(global.DB, r) 22 + account, err := controller.GetAccountByRequest(app.DB, r) 24 23 if err != nil { 25 24 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) 26 25 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 67 66 68 67 if release.IsReleased() || privileged { 69 68 // get credits 70 - credits, err := controller.GetReleaseCredits(global.DB, release.ID) 69 + credits, err := controller.GetReleaseCredits(app.DB, release.ID) 71 70 if err != nil { 72 71 fmt.Printf("WARN: Failed to serve release %s: Credits: %s\n", release.ID, err) 73 72 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 74 73 return 75 74 } 76 75 for _, credit := range credits { 77 - artist, err := controller.GetArtist(global.DB, credit.Artist.ID) 76 + artist, err := controller.GetArtist(app.DB, credit.Artist.ID) 78 77 if err != nil { 79 78 fmt.Printf("WARN: Failed to serve release %s: Artists: %s\n", release.ID, err) 80 79 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 89 88 } 90 89 91 90 // get tracks 92 - tracks, err := controller.GetReleaseTracks(global.DB, release.ID) 91 + tracks, err := controller.GetReleaseTracks(app.DB, release.ID) 93 92 if err != nil { 94 93 fmt.Printf("WARN: Failed to serve release %s: Tracks: %s\n", release.ID, err) 95 94 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 104 103 } 105 104 106 105 // get links 107 - links, err := controller.GetReleaseLinks(global.DB, release.ID) 106 + links, err := controller.GetReleaseLinks(app.DB, release.ID) 108 107 if err != nil { 109 108 fmt.Printf("WARN: Failed to serve release %s: Links: %s\n", release.ID, err) 110 109 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 126 125 }) 127 126 } 128 127 129 - func ServeCatalog() http.Handler { 128 + func ServeCatalog(app *model.AppState) http.Handler { 130 129 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 131 - releases, err := controller.GetAllReleases(global.DB, false, 0, true) 130 + releases, err := controller.GetAllReleases(app.DB, false, 0, true) 132 131 if err != nil { 133 132 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 134 133 return ··· 146 145 } 147 146 148 147 catalog := []Release{} 149 - account, err := controller.GetAccountByRequest(global.DB, r) 148 + account, err := controller.GetAccountByRequest(app.DB, r) 150 149 if err != nil { 151 150 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) 152 151 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 192 191 }) 193 192 } 194 193 195 - func CreateRelease() http.Handler { 194 + func CreateRelease(app *model.AppState) http.Handler { 196 195 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 197 196 if r.Method != http.MethodPost { 198 197 http.NotFound(w, r) ··· 220 219 221 220 if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" } 222 221 223 - err = controller.CreateRelease(global.DB, &release) 222 + err = controller.CreateRelease(app.DB, &release) 224 223 if err != nil { 225 224 if strings.Contains(err.Error(), "duplicate key") { 226 225 http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest) ··· 243 242 }) 244 243 } 245 244 246 - func UpdateRelease(release *model.Release) http.Handler { 245 + func UpdateRelease(app *model.AppState, release *model.Release) http.Handler { 247 246 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 248 247 if r.URL.Path == "/" { 249 248 http.NotFound(w, r) ··· 255 254 if len(segments) == 2 { 256 255 switch segments[1] { 257 256 case "tracks": 258 - UpdateReleaseTracks(release).ServeHTTP(w, r) 257 + UpdateReleaseTracks(app, release).ServeHTTP(w, r) 259 258 case "credits": 260 - UpdateReleaseCredits(release).ServeHTTP(w, r) 259 + UpdateReleaseCredits(app, release).ServeHTTP(w, r) 261 260 case "links": 262 - UpdateReleaseLinks(release).ServeHTTP(w, r) 261 + UpdateReleaseLinks(app, release).ServeHTTP(w, r) 263 262 } 264 263 return 265 264 } ··· 281 280 } else { 282 281 if strings.Contains(release.Artwork, ";base64,") { 283 282 var artworkDirectory = filepath.Join("uploads", "musicart") 284 - filename, err := HandleImageUpload(&release.Artwork, artworkDirectory, release.ID) 283 + filename, err := HandleImageUpload(app, &release.Artwork, artworkDirectory, release.ID) 285 284 286 285 // clean up files with this ID and different extensions 287 286 err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error { ··· 300 299 } 301 300 } 302 301 303 - err = controller.UpdateRelease(global.DB, release) 302 + err = controller.UpdateRelease(app.DB, release) 304 303 if err != nil { 305 304 if strings.Contains(err.Error(), "no rows") { 306 305 http.NotFound(w, r) ··· 312 311 }) 313 312 } 314 313 315 - func UpdateReleaseTracks(release *model.Release) http.Handler { 314 + func UpdateReleaseTracks(app *model.AppState, release *model.Release) http.Handler { 316 315 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 317 316 var trackIDs = []string{} 318 317 err := json.NewDecoder(r.Body).Decode(&trackIDs) ··· 321 320 return 322 321 } 323 322 324 - err = controller.UpdateReleaseTracks(global.DB, release.ID, trackIDs) 323 + err = controller.UpdateReleaseTracks(app.DB, release.ID, trackIDs) 325 324 if err != nil { 326 325 if strings.Contains(err.Error(), "no rows") { 327 326 http.NotFound(w, r) ··· 333 332 }) 334 333 } 335 334 336 - func UpdateReleaseCredits(release *model.Release) http.Handler { 335 + func UpdateReleaseCredits(app *model.AppState, release *model.Release) http.Handler { 337 336 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 338 337 type creditJSON struct { 339 338 Artist string ··· 358 357 }) 359 358 } 360 359 361 - err = controller.UpdateReleaseCredits(global.DB, release.ID, credits) 360 + err = controller.UpdateReleaseCredits(app.DB, release.ID, credits) 362 361 if err != nil { 363 362 if strings.Contains(err.Error(), "duplicate key") { 364 363 http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest) ··· 374 373 }) 375 374 } 376 375 377 - func UpdateReleaseLinks(release *model.Release) http.Handler { 376 + func UpdateReleaseLinks(app *model.AppState, release *model.Release) http.Handler { 378 377 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 379 378 if r.Method != http.MethodPut { 380 379 http.NotFound(w, r) ··· 388 387 return 389 388 } 390 389 391 - err = controller.UpdateReleaseLinks(global.DB, release.ID, links) 390 + err = controller.UpdateReleaseLinks(app.DB, release.ID, links) 392 391 if err != nil { 393 392 if strings.Contains(err.Error(), "no rows") { 394 393 http.NotFound(w, r) ··· 400 399 }) 401 400 } 402 401 403 - func DeleteRelease(release *model.Release) http.Handler { 402 + func DeleteRelease(app *model.AppState, release *model.Release) http.Handler { 404 403 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 405 - err := controller.DeleteRelease(global.DB, release.ID) 404 + err := controller.DeleteRelease(app.DB, release.ID) 406 405 if err != nil { 407 406 if strings.Contains(err.Error(), "no rows") { 408 407 http.NotFound(w, r)
+10 -11
api/track.go
··· 5 5 "fmt" 6 6 "net/http" 7 7 8 - "arimelody-web/global" 9 8 "arimelody-web/controller" 10 9 "arimelody-web/model" 11 10 ) ··· 17 16 } 18 17 ) 19 18 20 - func ServeAllTracks() http.Handler { 19 + func ServeAllTracks(app *model.AppState) http.Handler { 21 20 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 21 type Track struct { 23 22 ID string `json:"id"` ··· 26 25 var tracks = []Track{} 27 26 28 27 var dbTracks = []*model.Track{} 29 - dbTracks, err := controller.GetAllTracks(global.DB) 28 + dbTracks, err := controller.GetAllTracks(app.DB) 30 29 if err != nil { 31 30 fmt.Printf("WARN: Failed to pull tracks from DB: %s\n", err) 32 31 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 50 49 }) 51 50 } 52 51 53 - func ServeTrack(track *model.Track) http.Handler { 52 + func ServeTrack(app *model.AppState, track *model.Track) http.Handler { 54 53 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 55 - dbReleases, err := controller.GetTrackReleases(global.DB, track.ID, false) 54 + dbReleases, err := controller.GetTrackReleases(app.DB, track.ID, false) 56 55 if err != nil { 57 56 fmt.Printf("WARN: Failed to pull track releases for %s from DB: %s\n", track.ID, err) 58 57 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 74 73 }) 75 74 } 76 75 77 - func CreateTrack() http.Handler { 76 + func CreateTrack(app *model.AppState) http.Handler { 78 77 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 79 78 if r.Method != http.MethodPost { 80 79 http.NotFound(w, r) ··· 93 92 return 94 93 } 95 94 96 - id, err := controller.CreateTrack(global.DB, &track) 95 + id, err := controller.CreateTrack(app.DB, &track) 97 96 if err != nil { 98 97 fmt.Printf("WARN: Failed to create track: %s\n", err) 99 98 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 106 105 }) 107 106 } 108 107 109 - func UpdateTrack(track *model.Track) http.Handler { 108 + func UpdateTrack(app *model.AppState, track *model.Track) http.Handler { 110 109 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 111 110 if r.Method != http.MethodPut || r.URL.Path == "/" { 112 111 http.NotFound(w, r) ··· 124 123 return 125 124 } 126 125 127 - err = controller.UpdateTrack(global.DB, track) 126 + err = controller.UpdateTrack(app.DB, track) 128 127 if err != nil { 129 128 fmt.Printf("WARN: Failed to update track %s: %s\n", track.ID, err) 130 129 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 141 140 }) 142 141 } 143 142 144 - func DeleteTrack(track *model.Track) http.Handler { 143 + func DeleteTrack(app *model.AppState, track *model.Track) http.Handler { 145 144 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 146 145 if r.Method != http.MethodDelete || r.URL.Path == "/" { 147 146 http.NotFound(w, r) ··· 149 148 } 150 149 151 150 var trackID = r.URL.Path[1:] 152 - err := controller.DeleteTrack(global.DB, trackID) 151 + err := controller.DeleteTrack(app.DB, trackID) 153 152 if err != nil { 154 153 fmt.Printf("WARN: Failed to delete track %s: %s\n", trackID, err) 155 154 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+3 -3
api/uploads.go
··· 1 1 package api 2 2 3 3 import ( 4 - "arimelody-web/global" 4 + "arimelody-web/model" 5 5 "bufio" 6 6 "encoding/base64" 7 7 "errors" ··· 11 11 "strings" 12 12 ) 13 13 14 - func HandleImageUpload(data *string, directory string, filename string) (string, error) { 14 + func HandleImageUpload(app *model.AppState, data *string, directory string, filename string) (string, error) { 15 15 split := strings.Split(*data, ";base64,") 16 16 header := split[0] 17 17 imageData, err := base64.StdEncoding.DecodeString(split[1]) 18 18 ext, _ := strings.CutPrefix(header, "data:image/") 19 - directory = filepath.Join(global.Config.DataDirectory, directory) 19 + directory = filepath.Join(app.Config.DataDirectory, directory) 20 20 21 21 switch ext { 22 22 case "png":
+1 -2
controller/account.go
··· 1 1 package controller 2 2 3 3 import ( 4 - "arimelody-web/global" 5 4 "arimelody-web/model" 6 5 "errors" 7 6 "fmt" ··· 72 71 return tokenStr 73 72 } 74 73 75 - cookie, err := r.Cookie(global.COOKIE_TOKEN) 74 + cookie, err := r.Cookie(model.COOKIE_TOKEN) 76 75 if err != nil { 77 76 return "" 78 77 }
+1
controller/artist.go
··· 2 2 3 3 import ( 4 4 "arimelody-web/model" 5 + 5 6 "github.com/jmoiron/sqlx" 6 7 ) 7 8
+1
controller/release.go
··· 5 5 "fmt" 6 6 7 7 "arimelody-web/model" 8 + 8 9 "github.com/jmoiron/sqlx" 9 10 ) 10 11
+1
controller/track.go
··· 2 2 3 3 import ( 4 4 "arimelody-web/model" 5 + 5 6 "github.com/jmoiron/sqlx" 6 7 ) 7 8
+17 -26
discord/discord.go
··· 1 1 package discord 2 2 3 3 import ( 4 + "arimelody-web/model" 4 5 "encoding/json" 5 6 "errors" 6 7 "fmt" 7 8 "net/http" 8 9 "net/url" 9 10 "strings" 10 - 11 - "arimelody-web/global" 12 11 ) 13 12 14 13 const API_ENDPOINT = "https://discord.com/api/v10" 15 14 16 - var CREDENTIALS_PROVIDED = true 17 - var CLIENT_ID = func() string { 18 - id := global.Config.Discord.ClientID 19 - if id == "" { 20 - // fmt.Printf("WARN: Discord client ID (DISCORD_CLIENT) was not provided.\n") 21 - CREDENTIALS_PROVIDED = false 22 - } 23 - return id 24 - }() 25 - var CLIENT_SECRET = func() string { 26 - secret := global.Config.Discord.Secret 27 - if secret == "" { 28 - // fmt.Printf("WARN: Discord secret (DISCORD_SECRET) was not provided.\n") 29 - CREDENTIALS_PROVIDED = false 30 - } 31 - return secret 32 - }() 33 - var OAUTH_CALLBACK_URI = fmt.Sprintf("%s/admin/login", global.Config.BaseUrl) 34 - 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) 35 - 36 15 type ( 37 16 AccessTokenResponse struct { 38 17 AccessToken string `json:"access_token"` ··· 68 47 } 69 48 ) 70 49 71 - func GetOAuthTokenFromCode(code string) (string, error) { 50 + func GetOAuthTokenFromCode(app *model.AppState, code string) (string, error) { 72 51 // let's get an oauth token! 73 52 req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/oauth2/token", API_ENDPOINT), 74 53 strings.NewReader(url.Values{ 75 - "client_id": {CLIENT_ID}, 76 - "client_secret": {CLIENT_SECRET}, 54 + "client_id": {app.Config.Discord.ClientID}, 55 + "client_secret": {app.Config.Discord.Secret}, 77 56 "grant_type": {"authorization_code"}, 78 57 "code": {code}, 79 - "redirect_uri": {OAUTH_CALLBACK_URI}, 58 + "redirect_uri": {GetOAuthCallbackURI(app.Config.BaseUrl)}, 80 59 }.Encode())) 81 60 req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 82 61 ··· 115 94 116 95 return auth_info.User, nil 117 96 } 97 + 98 + func GetOAuthCallbackURI(baseURL string) string { 99 + return fmt.Sprintf("%s/admin/login", baseURL) 100 + } 101 + 102 + func GetRedirectURI(app *model.AppState) string { 103 + return fmt.Sprintf( 104 + "https://discord.com/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=identify", 105 + app.Config.Discord.ClientID, 106 + GetOAuthCallbackURI(app.Config.BaseUrl), 107 + ) 108 + }
+10 -37
global/config.go controller/config.go
··· 1 - package global 1 + package controller 2 2 3 3 import ( 4 4 "errors" ··· 6 6 "os" 7 7 "strconv" 8 8 9 - "github.com/jmoiron/sqlx" 9 + "arimelody-web/model" 10 + 10 11 "github.com/pelletier/go-toml/v2" 11 12 ) 12 13 13 - type ( 14 - dbConfig struct { 15 - Host string `toml:"host"` 16 - Port int64 `toml:"port"` 17 - Name string `toml:"name"` 18 - User string `toml:"user"` 19 - Pass string `toml:"pass"` 20 - } 21 - 22 - discordConfig struct { 23 - AdminID string `toml:"admin_id" comment:"NOTE: admin_id to be deprecated in favour of local accounts and SSO."` 24 - ClientID string `toml:"client_id"` 25 - Secret string `toml:"secret"` 26 - } 27 - 28 - config struct { 29 - BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."` 30 - Port int64 `toml:"port"` 31 - DataDirectory string `toml:"data_dir"` 32 - DB dbConfig `toml:"db"` 33 - Discord discordConfig `toml:"discord"` 34 - } 35 - ) 36 - 37 - var Config = func() config { 14 + func GetConfig() model.Config { 38 15 configFile := os.Getenv("ARIMELODY_CONFIG") 39 16 if configFile == "" { 40 17 configFile = "config.toml" 41 18 } 42 19 43 - config := config{ 20 + config := model.Config{ 44 21 BaseUrl: "https://arimelody.me", 45 22 Port: 8080, 46 - DB: dbConfig{ 23 + DB: model.DBConfig{ 47 24 Host: "127.0.0.1", 48 25 Port: 5432, 49 26 User: "arimelody", ··· 63 40 64 41 err = toml.Unmarshal([]byte(data), &config) 65 42 if err != nil { 66 - fmt.Fprintf(os.Stderr, "FATAL: Failed to parse configuration file: %v\n", err) 67 - os.Exit(1) 43 + panic(fmt.Sprintf("FATAL: Failed to parse configuration file: %v\n", err)) 68 44 } 69 45 70 46 err = handleConfigOverrides(&config) 71 47 if err != nil { 72 - fmt.Fprintf(os.Stderr, "FATAL: Failed to parse environment variable %v\n", err) 73 - os.Exit(1) 48 + panic(fmt.Sprintf("FATAL: Failed to parse environment variable %v\n", err)) 74 49 } 75 50 76 51 return config 77 - }() 52 + } 78 53 79 - func handleConfigOverrides(config *config) error { 54 + func handleConfigOverrides(config *model.Config) error { 80 55 var err error 81 56 82 57 if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env } ··· 101 76 102 77 return nil 103 78 } 104 - 105 - var DB *sqlx.DB
-3
global/const.go
··· 1 - package global 2 - 3 - const COOKIE_TOKEN string = "AM_TOKEN"
-101
global/funcs.go
··· 1 - package global 2 - 3 - import ( 4 - "fmt" 5 - "math/rand" 6 - "net/http" 7 - "strconv" 8 - "time" 9 - 10 - "arimelody-web/colour" 11 - ) 12 - 13 - var PoweredByStrings = []string{ 14 - "nerd rage", 15 - "estrogen", 16 - "your mother", 17 - "awesome powers beyond comprehension", 18 - "jared", 19 - "the weight of my sins", 20 - "the arc reactor", 21 - "AA batteries", 22 - "15 euro solar panel from ebay", 23 - "magnets, how do they work", 24 - "a fax machine", 25 - "dell optiplex", 26 - "a trans girl's nintendo wii", 27 - "BASS", 28 - "electricity, duh", 29 - "seven hamsters in a big wheel", 30 - "girls", 31 - "mzungu hosting", 32 - "golang", 33 - "the state of the world right now", 34 - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)", 35 - "the good folks at aperture science", 36 - "free2play CDs", 37 - "aridoodle", 38 - "the love of creating", 39 - "not for the sake of art; not for the sake of money; we like painting naked people", 40 - "30 billion dollars in VC funding", 41 - } 42 - 43 - func DefaultHeaders(next http.Handler) http.Handler { 44 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 - w.Header().Add("Server", "arimelody.me") 46 - w.Header().Add("Do-Not-Stab", "1") 47 - w.Header().Add("X-Clacks-Overhead", "GNU Terry Pratchett") 48 - w.Header().Add("X-Hacker", "spare me please") 49 - w.Header().Add("X-Robots-TXT", "'; DROP TABLE pages;") 50 - w.Header().Add("X-Thinking-With", "Portals") 51 - w.Header().Add( 52 - "X-Powered-By", 53 - PoweredByStrings[rand.Intn(len(PoweredByStrings))], 54 - ) 55 - next.ServeHTTP(w, r) 56 - }) 57 - } 58 - 59 - type LoggingResponseWriter struct { 60 - http.ResponseWriter 61 - Status int 62 - } 63 - 64 - func (lrw *LoggingResponseWriter) WriteHeader(status int) { 65 - lrw.Status = status 66 - lrw.ResponseWriter.WriteHeader(status) 67 - } 68 - 69 - func HTTPLog(next http.Handler) http.Handler { 70 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 71 - start := time.Now() 72 - 73 - lrw := LoggingResponseWriter{w, http.StatusOK} 74 - 75 - next.ServeHTTP(&lrw, r) 76 - 77 - after := time.Now() 78 - difference := (after.Nanosecond() - start.Nanosecond()) / 1_000_000 79 - elapsed := "<1" 80 - if difference >= 1 { 81 - elapsed = strconv.Itoa(difference) 82 - } 83 - 84 - statusColour := colour.Reset 85 - 86 - if lrw.Status - 600 <= 0 { statusColour = colour.Red } 87 - if lrw.Status - 500 <= 0 { statusColour = colour.Yellow } 88 - if lrw.Status - 400 <= 0 { statusColour = colour.White } 89 - if lrw.Status - 300 <= 0 { statusColour = colour.Green } 90 - 91 - fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n", 92 - after.Format(time.UnixDate), 93 - r.Method, 94 - r.URL.Path, 95 - statusColour, 96 - lrw.Status, 97 - colour.Reset, 98 - elapsed, 99 - r.Header["User-Agent"][0]) 100 - }) 101 - }
+137 -45
main.go
··· 4 4 "errors" 5 5 "fmt" 6 6 "log" 7 + "math/rand" 7 8 "net/http" 8 9 "os" 9 10 "path/filepath" 11 + "strconv" 10 12 "strings" 11 13 "time" 12 14 13 15 "arimelody-web/admin" 14 16 "arimelody-web/api" 17 + "arimelody-web/colour" 15 18 "arimelody-web/controller" 16 - "arimelody-web/global" 17 19 "arimelody-web/model" 18 20 "arimelody-web/templates" 19 21 "arimelody-web/view" ··· 30 32 func main() { 31 33 fmt.Printf("made with <3 by ari melody\n\n") 32 34 33 - // TODO: refactor `global` to `AppState` 34 - // this should contain `Config` and `DB`, and be passed through to all 35 - // handlers that need it. it's better than weird static globals everywhere! 35 + app := model.AppState{ 36 + Config: controller.GetConfig(), 37 + } 36 38 37 39 // initialise database connection 38 - if global.Config.DB.Host == "" { 40 + if app.Config.DB.Host == "" { 39 41 fmt.Fprintf(os.Stderr, "FATAL: db.host not provided! Exiting...\n") 40 42 os.Exit(1) 41 43 } 42 - if global.Config.DB.Name == "" { 44 + if app.Config.DB.Name == "" { 43 45 fmt.Fprintf(os.Stderr, "FATAL: db.name not provided! Exiting...\n") 44 46 os.Exit(1) 45 47 } 46 - if global.Config.DB.User == "" { 48 + if app.Config.DB.User == "" { 47 49 fmt.Fprintf(os.Stderr, "FATAL: db.user not provided! Exiting...\n") 48 50 os.Exit(1) 49 51 } 50 - if global.Config.DB.Pass == "" { 52 + if app.Config.DB.Pass == "" { 51 53 fmt.Fprintf(os.Stderr, "FATAL: db.pass not provided! Exiting...\n") 52 54 os.Exit(1) 53 55 } 54 56 55 57 var err error 56 - global.DB, err = sqlx.Connect( 58 + app.DB, err = sqlx.Connect( 57 59 "postgres", 58 60 fmt.Sprintf( 59 61 "host=%s port=%d user=%s dbname=%s password='%s' sslmode=disable", 60 - global.Config.DB.Host, 61 - global.Config.DB.Port, 62 - global.Config.DB.User, 63 - global.Config.DB.Name, 64 - global.Config.DB.Pass, 62 + app.Config.DB.Host, 63 + app.Config.DB.Port, 64 + app.Config.DB.User, 65 + app.Config.DB.Name, 66 + app.Config.DB.Pass, 65 67 ), 66 68 ) 67 69 if err != nil { 68 70 fmt.Fprintf(os.Stderr, "FATAL: Unable to initialise database: %v\n", err) 69 71 os.Exit(1) 70 72 } 71 - global.DB.SetConnMaxLifetime(time.Minute * 3) 72 - global.DB.SetMaxOpenConns(10) 73 - global.DB.SetMaxIdleConns(10) 74 - defer global.DB.Close() 73 + app.DB.SetConnMaxLifetime(time.Minute * 3) 74 + app.DB.SetMaxOpenConns(10) 75 + app.DB.SetMaxIdleConns(10) 76 + defer app.DB.Close() 75 77 76 78 // handle command arguments 77 79 if len(os.Args) > 1 { ··· 87 89 totpName := os.Args[3] 88 90 secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH) 89 91 90 - account, err := controller.GetAccount(global.DB, username) 92 + account, err := controller.GetAccount(app.DB, username) 91 93 if err != nil { 92 94 fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err) 93 95 os.Exit(1) ··· 104 106 Secret: string(secret), 105 107 } 106 108 107 - err = controller.CreateTOTP(global.DB, &totp) 109 + err = controller.CreateTOTP(app.DB, &totp) 108 110 if err != nil { 109 111 fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err) 110 112 os.Exit(1) ··· 122 124 username := os.Args[2] 123 125 totpName := os.Args[3] 124 126 125 - account, err := controller.GetAccount(global.DB, username) 127 + account, err := controller.GetAccount(app.DB, username) 126 128 if err != nil { 127 129 fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err) 128 130 os.Exit(1) ··· 133 135 os.Exit(1) 134 136 } 135 137 136 - err = controller.DeleteTOTP(global.DB, account.ID, totpName) 138 + err = controller.DeleteTOTP(app.DB, account.ID, totpName) 137 139 if err != nil { 138 140 fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err) 139 141 os.Exit(1) ··· 149 151 } 150 152 username := os.Args[2] 151 153 152 - account, err := controller.GetAccount(global.DB, username) 154 + account, err := controller.GetAccount(app.DB, username) 153 155 if err != nil { 154 156 fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err) 155 157 os.Exit(1) ··· 160 162 os.Exit(1) 161 163 } 162 164 163 - totps, err := controller.GetTOTPsForAccount(global.DB, account.ID) 165 + totps, err := controller.GetTOTPsForAccount(app.DB, account.ID) 164 166 if err != nil { 165 167 fmt.Fprintf(os.Stderr, "Failed to create TOTP methods: %v\n", err) 166 168 os.Exit(1) ··· 182 184 username := os.Args[2] 183 185 totpName := os.Args[3] 184 186 185 - account, err := controller.GetAccount(global.DB, username) 187 + account, err := controller.GetAccount(app.DB, username) 186 188 if err != nil { 187 189 fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err) 188 190 os.Exit(1) ··· 193 195 os.Exit(1) 194 196 } 195 197 196 - totp, err := controller.GetTOTP(global.DB, account.ID, totpName) 198 + totp, err := controller.GetTOTP(app.DB, account.ID, totpName) 197 199 if err != nil { 198 200 fmt.Fprintf(os.Stderr, "Failed to fetch TOTP method \"%s\": %v\n", totpName, err) 199 201 os.Exit(1) ··· 210 212 211 213 case "createInvite": 212 214 fmt.Printf("Creating invite...\n") 213 - invite, err := controller.CreateInvite(global.DB, 16, time.Hour * 24) 215 + invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24) 214 216 if err != nil { 215 217 fmt.Fprintf(os.Stderr, "Failed to create invite code: %v\n", err) 216 218 os.Exit(1) ··· 221 223 222 224 case "purgeInvites": 223 225 fmt.Printf("Deleting all invites...\n") 224 - err := controller.DeleteAllInvites(global.DB) 226 + err := controller.DeleteAllInvites(app.DB) 225 227 if err != nil { 226 228 fmt.Fprintf(os.Stderr, "Failed to delete invites: %v\n", err) 227 229 os.Exit(1) ··· 231 233 return 232 234 233 235 case "listAccounts": 234 - accounts, err := controller.GetAllAccounts(global.DB) 236 + accounts, err := controller.GetAllAccounts(app.DB) 235 237 if err != nil { 236 238 fmt.Fprintf(os.Stderr, "Failed to fetch accounts: %v\n", err) 237 239 os.Exit(1) ··· 259 261 username := os.Args[2] 260 262 fmt.Printf("Deleting account \"%s\"...\n", username) 261 263 262 - account, err := controller.GetAccount(global.DB, username) 264 + account, err := controller.GetAccount(app.DB, username) 263 265 if err != nil { 264 266 fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err) 265 267 os.Exit(1) ··· 277 279 return 278 280 } 279 281 280 - err = controller.DeleteAccount(global.DB, username) 282 + err = controller.DeleteAccount(app.DB, username) 281 283 if err != nil { 282 284 fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err) 283 285 os.Exit(1) ··· 305 307 } 306 308 307 309 // handle DB migrations 308 - controller.CheckDBVersionAndMigrate(global.DB) 310 + controller.CheckDBVersionAndMigrate(app.DB) 309 311 310 312 // initial invite code 311 313 accountsCount := 0 312 - err = global.DB.Get(&accountsCount, "SELECT count(*) FROM account") 314 + err = app.DB.Get(&accountsCount, "SELECT count(*) FROM account") 313 315 if err != nil { panic(err) } 314 316 if accountsCount == 0 { 315 - _, err := global.DB.Exec("DELETE FROM invite") 317 + _, err := app.DB.Exec("DELETE FROM invite") 316 318 if err != nil { 317 319 fmt.Fprintf(os.Stderr, "FATAL: Failed to clear existing invite codes: %v\n", err) 318 320 os.Exit(1) 319 321 } 320 322 321 - invite, err := controller.CreateInvite(global.DB, 16, time.Hour * 24) 323 + invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24) 322 324 if err != nil { 323 325 fmt.Fprintf(os.Stderr, "FATAL: Failed to create invite code: %v\n", err) 324 326 os.Exit(1) ··· 328 330 } 329 331 330 332 // delete expired invites 331 - err = controller.DeleteExpiredInvites(global.DB) 333 + err = controller.DeleteExpiredInvites(app.DB) 332 334 if err != nil { 333 335 fmt.Fprintf(os.Stderr, "FATAL: Failed to clear expired invite codes: %v\n", err) 334 336 os.Exit(1) 335 337 } 336 338 337 339 // start the web server! 338 - mux := createServeMux() 339 - fmt.Printf("Now serving at %s:%d\n", global.Config.BaseUrl, global.Config.Port) 340 + mux := createServeMux(&app) 341 + fmt.Printf("Now serving at %s:%d\n", app.Config.BaseUrl, app.Config.Port) 340 342 log.Fatal( 341 - http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port), 342 - global.HTTPLog(global.DefaultHeaders(mux)), 343 + http.ListenAndServe(fmt.Sprintf(":%d", app.Config.Port), 344 + HTTPLog(DefaultHeaders(mux)), 343 345 )) 344 346 } 345 347 346 - func createServeMux() *http.ServeMux { 348 + func createServeMux(app *model.AppState) *http.ServeMux { 347 349 mux := http.NewServeMux() 348 350 349 - mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(global.DB))) 350 - mux.Handle("/api/", http.StripPrefix("/api", api.Handler(global.DB))) 351 - mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(global.DB))) 352 - mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(global.Config.DataDirectory, "uploads")))) 351 + mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app))) 352 + mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app))) 353 + mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app))) 354 + mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(app.Config.DataDirectory, "uploads")))) 353 355 mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 354 356 if r.Method == http.MethodHead { 355 357 w.WriteHeader(http.StatusOK) ··· 390 392 http.FileServer(http.Dir(directory)).ServeHTTP(w, r) 391 393 }) 392 394 } 395 + 396 + var PoweredByStrings = []string{ 397 + "nerd rage", 398 + "estrogen", 399 + "your mother", 400 + "awesome powers beyond comprehension", 401 + "jared", 402 + "the weight of my sins", 403 + "the arc reactor", 404 + "AA batteries", 405 + "15 euro solar panel from ebay", 406 + "magnets, how do they work", 407 + "a fax machine", 408 + "dell optiplex", 409 + "a trans girl's nintendo wii", 410 + "BASS", 411 + "electricity, duh", 412 + "seven hamsters in a big wheel", 413 + "girls", 414 + "mzungu hosting", 415 + "golang", 416 + "the state of the world right now", 417 + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)", 418 + "the good folks at aperture science", 419 + "free2play CDs", 420 + "aridoodle", 421 + "the love of creating", 422 + "not for the sake of art; not for the sake of money; we like painting naked people", 423 + "30 billion dollars in VC funding", 424 + } 425 + 426 + func DefaultHeaders(next http.Handler) http.Handler { 427 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 428 + w.Header().Add("Server", "arimelody.me") 429 + w.Header().Add("Do-Not-Stab", "1") 430 + w.Header().Add("X-Clacks-Overhead", "GNU Terry Pratchett") 431 + w.Header().Add("X-Hacker", "spare me please") 432 + w.Header().Add("X-Robots-TXT", "'; DROP TABLE pages;") 433 + w.Header().Add("X-Thinking-With", "Portals") 434 + w.Header().Add( 435 + "X-Powered-By", 436 + PoweredByStrings[rand.Intn(len(PoweredByStrings))], 437 + ) 438 + next.ServeHTTP(w, r) 439 + }) 440 + } 441 + 442 + type LoggingResponseWriter struct { 443 + http.ResponseWriter 444 + Status int 445 + } 446 + 447 + func (lrw *LoggingResponseWriter) WriteHeader(status int) { 448 + lrw.Status = status 449 + lrw.ResponseWriter.WriteHeader(status) 450 + } 451 + 452 + func HTTPLog(next http.Handler) http.Handler { 453 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 454 + start := time.Now() 455 + 456 + lrw := LoggingResponseWriter{w, http.StatusOK} 457 + 458 + next.ServeHTTP(&lrw, r) 459 + 460 + after := time.Now() 461 + difference := (after.Nanosecond() - start.Nanosecond()) / 1_000_000 462 + elapsed := "<1" 463 + if difference >= 1 { 464 + elapsed = strconv.Itoa(difference) 465 + } 466 + 467 + statusColour := colour.Reset 468 + 469 + if lrw.Status - 600 <= 0 { statusColour = colour.Red } 470 + if lrw.Status - 500 <= 0 { statusColour = colour.Yellow } 471 + if lrw.Status - 400 <= 0 { statusColour = colour.White } 472 + if lrw.Status - 300 <= 0 { statusColour = colour.Green } 473 + 474 + fmt.Printf("[%s] %s %s - %s%d%s (%sms) (%s)\n", 475 + after.Format(time.UnixDate), 476 + r.Method, 477 + r.URL.Path, 478 + statusColour, 479 + lrw.Status, 480 + colour.Reset, 481 + elapsed, 482 + r.Header["User-Agent"][0]) 483 + }) 484 + }
+2
model/account.go
··· 2 2 3 3 import "time" 4 4 5 + const COOKIE_TOKEN string = "AM_TOKEN" 6 + 5 7 type ( 6 8 Account struct { 7 9 ID string `json:"id" db:"id"`
+32
model/appstate.go
··· 1 + package model 2 + 3 + import "github.com/jmoiron/sqlx" 4 + 5 + type ( 6 + DBConfig struct { 7 + Host string `toml:"host"` 8 + Port int64 `toml:"port"` 9 + Name string `toml:"name"` 10 + User string `toml:"user"` 11 + Pass string `toml:"pass"` 12 + } 13 + 14 + DiscordConfig struct { 15 + AdminID string `toml:"admin_id" comment:"NOTE: admin_id to be deprecated in favour of local accounts and SSO."` 16 + ClientID string `toml:"client_id"` 17 + Secret string `toml:"secret"` 18 + } 19 + 20 + Config struct { 21 + BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."` 22 + Port int64 `toml:"port"` 23 + DataDirectory string `toml:"data_dir"` 24 + DB DBConfig `toml:"db"` 25 + Discord DiscordConfig `toml:"discord"` 26 + } 27 + 28 + AppState struct { 29 + DB *sqlx.DB 30 + Config Config 31 + } 32 + )
+8 -10
view/music.go
··· 8 8 "arimelody-web/controller" 9 9 "arimelody-web/model" 10 10 "arimelody-web/templates" 11 - 12 - "github.com/jmoiron/sqlx" 13 11 ) 14 12 15 13 // HTTP HANDLER METHODS 16 14 17 - func MusicHandler(db *sqlx.DB) http.Handler { 15 + func MusicHandler(app *model.AppState) http.Handler { 18 16 mux := http.NewServeMux() 19 17 20 18 mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 19 if r.URL.Path == "/" { 22 - ServeCatalog(db).ServeHTTP(w, r) 20 + ServeCatalog(app).ServeHTTP(w, r) 23 21 return 24 22 } 25 23 26 - release, err := controller.GetRelease(db, r.URL.Path[1:], true) 24 + release, err := controller.GetRelease(app.DB, r.URL.Path[1:], true) 27 25 if err != nil { 28 26 http.NotFound(w, r) 29 27 return 30 28 } 31 29 32 - ServeGateway(db, release).ServeHTTP(w, r) 30 + ServeGateway(app, release).ServeHTTP(w, r) 33 31 })) 34 32 35 33 return mux 36 34 } 37 35 38 - func ServeCatalog(db *sqlx.DB) http.Handler { 36 + func ServeCatalog(app *model.AppState) http.Handler { 39 37 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 - releases, err := controller.GetAllReleases(db, true, 0, true) 38 + releases, err := controller.GetAllReleases(app.DB, true, 0, true) 41 39 if err != nil { 42 40 fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err) 43 41 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 57 55 }) 58 56 } 59 57 60 - func ServeGateway(db *sqlx.DB, release *model.Release) http.Handler { 58 + func ServeGateway(app *model.AppState, release *model.Release) http.Handler { 61 59 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 62 60 // only allow authorised users to view hidden releases 63 61 privileged := false 64 62 if !release.Visible { 65 - account, err := controller.GetAccountByRequest(db, r) 63 + account, err := controller.GetAccountByRequest(app.DB, r) 66 64 if err != nil { 67 65 fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err) 68 66 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)