refactored out global
. long live AppState
This commit is contained in:
parent
3d674515ce
commit
384579ee5e
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@ uploads/
|
||||||
docker-compose*.yml
|
docker-compose*.yml
|
||||||
!docker-compose.example.yml
|
!docker-compose.example.yml
|
||||||
config*.toml
|
config*.toml
|
||||||
|
arimelody-web
|
||||||
|
|
|
@ -8,10 +8,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,11 +19,11 @@ type TemplateData struct {
|
||||||
Token string
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
account := r.Context().Value("account").(*model.Account)
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err)
|
fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
account, err := controller.GetAccountByRequest(db, r)
|
account, err := controller.GetAccountByRequest(app.DB, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
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"),
|
TOTP: r.Form.Get("totp"),
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := controller.GetAccount(db, credentials.Username)
|
account, err := controller.GetAccount(app.DB, credentials.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render(LoginResponse{ Message: "Invalid username or password" })
|
render(LoginResponse{ Message: "Invalid username or password" })
|
||||||
return
|
return
|
||||||
|
@ -123,7 +121,7 @@ func LoginHandler(db *sqlx.DB) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
totps, err := controller.GetTOTPsForAccount(db, account.ID)
|
totps, err := controller.GetTOTPsForAccount(app.DB, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err)
|
||||||
render(LoginResponse{ Message: "Something went wrong. Please try again." })
|
render(LoginResponse{ Message: "Something went wrong. Please try again." })
|
||||||
|
@ -147,7 +145,7 @@ func LoginHandler(db *sqlx.DB) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// login success!
|
// login success!
|
||||||
token, err := controller.CreateToken(db, account.ID, r.UserAgent())
|
token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err)
|
||||||
render(LoginResponse{ Message: "Something went wrong. Please try again." })
|
render(LoginResponse{ Message: "Something went wrong. Please try again." })
|
||||||
|
@ -155,10 +153,10 @@ func LoginHandler(db *sqlx.DB) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie := http.Cookie{}
|
cookie := http.Cookie{}
|
||||||
cookie.Name = global.COOKIE_TOKEN
|
cookie.Name = model.COOKIE_TOKEN
|
||||||
cookie.Value = token.Token
|
cookie.Value = token.Token
|
||||||
cookie.Expires = token.ExpiresAt
|
cookie.Expires = token.ExpiresAt
|
||||||
if strings.HasPrefix(global.Config.BaseUrl, "https") {
|
if strings.HasPrefix(app.Config.BaseUrl, "https") {
|
||||||
cookie.Secure = true
|
cookie.Secure = true
|
||||||
}
|
}
|
||||||
cookie.HttpOnly = 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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenStr := controller.GetTokenFromRequest(db, r)
|
tokenStr := controller.GetTokenFromRequest(app.DB, r)
|
||||||
|
|
||||||
if len(tokenStr) > 0 {
|
if len(tokenStr) > 0 {
|
||||||
err := controller.DeleteToken(db, tokenStr)
|
err := controller.DeleteToken(app.DB, tokenStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to revoke token: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to revoke token: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -188,10 +186,10 @@ func LogoutHandler(db *sqlx.DB) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie := http.Cookie{}
|
cookie := http.Cookie{}
|
||||||
cookie.Name = global.COOKIE_TOKEN
|
cookie.Name = model.COOKIE_TOKEN
|
||||||
cookie.Value = ""
|
cookie.Value = ""
|
||||||
cookie.Expires = time.Now()
|
cookie.Expires = time.Now()
|
||||||
if strings.HasPrefix(global.Config.BaseUrl, "https") {
|
if strings.HasPrefix(app.Config.BaseUrl, "https") {
|
||||||
cookie.Secure = true
|
cookie.Secure = true
|
||||||
}
|
}
|
||||||
cookie.HttpOnly = 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) {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch account: %s\n", err)
|
fmt.Printf("WARN: Failed to fetch account: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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
|
// make sure code exists in DB
|
||||||
invite, err := controller.GetInvite(db, credentials.Invite)
|
invite, err := controller.GetInvite(app.DB, credentials.Invite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err)
|
||||||
render(CreateAccountResponse{
|
render(CreateAccountResponse{
|
||||||
|
@ -270,7 +268,7 @@ func createAccountHandler(db *sqlx.DB) http.Handler {
|
||||||
}
|
}
|
||||||
if invite == nil || time.Now().After(invite.ExpiresAt) {
|
if invite == nil || time.Now().After(invite.ExpiresAt) {
|
||||||
if invite != nil {
|
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) }
|
if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) }
|
||||||
}
|
}
|
||||||
render(CreateAccountResponse{
|
render(CreateAccountResponse{
|
||||||
|
@ -294,7 +292,7 @@ func createAccountHandler(db *sqlx.DB) http.Handler {
|
||||||
Email: credentials.Email,
|
Email: credentials.Email,
|
||||||
AvatarURL: "/img/default-avatar.png",
|
AvatarURL: "/img/default-avatar.png",
|
||||||
}
|
}
|
||||||
err = controller.CreateAccount(db, &account)
|
err = controller.CreateAccount(app.DB, &account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
||||||
render(CreateAccountResponse{
|
render(CreateAccountResponse{
|
||||||
|
@ -309,11 +307,11 @@ func createAccountHandler(db *sqlx.DB) http.Handler {
|
||||||
return
|
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) }
|
if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) }
|
||||||
|
|
||||||
// registration success!
|
// registration success!
|
||||||
token, err := controller.CreateToken(db, account.ID, r.UserAgent())
|
token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err)
|
||||||
// gracefully redirect user to login page
|
// gracefully redirect user to login page
|
||||||
|
@ -322,10 +320,10 @@ func createAccountHandler(db *sqlx.DB) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie := http.Cookie{}
|
cookie := http.Cookie{}
|
||||||
cookie.Name = global.COOKIE_TOKEN
|
cookie.Name = model.COOKIE_TOKEN
|
||||||
cookie.Value = token.Token
|
cookie.Value = token.Token
|
||||||
cookie.Expires = token.ExpiresAt
|
cookie.Expires = token.ExpiresAt
|
||||||
if strings.HasPrefix(global.Config.BaseUrl, "https") {
|
if strings.HasPrefix(app.Config.BaseUrl, "https") {
|
||||||
cookie.Secure = true
|
cookie.Secure = true
|
||||||
}
|
}
|
||||||
cookie.HttpOnly = true
|
cookie.HttpOnly = true
|
||||||
|
|
|
@ -5,16 +5,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveArtist() http.Handler {
|
func serveArtist(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(r.URL.Path[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
id := slices[0]
|
id := slices[0]
|
||||||
artist, err := controller.GetArtist(global.DB, id)
|
artist, err := controller.GetArtist(app.DB, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if artist == nil {
|
if artist == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -25,7 +24,7 @@ func serveArtist() http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
credits, err := controller.GetArtistCredits(global.DB, artist.ID, true)
|
credits, err := controller.GetArtistCredits(app.DB, artist.ID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
@ -9,28 +9,26 @@ import (
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"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 := http.NewServeMux()
|
||||||
|
|
||||||
mux.Handle("/login", LoginHandler(db))
|
mux.Handle("/login", LoginHandler(app))
|
||||||
mux.Handle("/register", createAccountHandler(db))
|
mux.Handle("/register", createAccountHandler(app))
|
||||||
mux.Handle("/logout", RequireAccount(db, LogoutHandler(db)))
|
mux.Handle("/logout", RequireAccount(app, LogoutHandler(app)))
|
||||||
mux.Handle("/account", RequireAccount(db, AccountHandler(db)))
|
mux.Handle("/account", RequireAccount(app, AccountHandler(app)))
|
||||||
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
||||||
mux.Handle("/release/", RequireAccount(db, http.StripPrefix("/release", serveRelease())))
|
mux.Handle("/release/", RequireAccount(app, http.StripPrefix("/release", serveRelease(app))))
|
||||||
mux.Handle("/artist/", RequireAccount(db, http.StripPrefix("/artist", serveArtist())))
|
mux.Handle("/artist/", RequireAccount(app, http.StripPrefix("/artist", serveArtist(app))))
|
||||||
mux.Handle("/track/", RequireAccount(db, http.StripPrefix("/track", serveTrack())))
|
mux.Handle("/track/", RequireAccount(app, http.StripPrefix("/track", serveTrack(app))))
|
||||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := controller.GetAccountByRequest(db, r)
|
account, err := controller.GetAccountByRequest(app.DB, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %s\n", err)
|
||||||
}
|
}
|
||||||
|
@ -39,21 +37,21 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
releases, err := controller.GetAllReleases(db, false, 0, true)
|
releases, err := controller.GetAllReleases(app.DB, false, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
artists, err := controller.GetAllArtists(db)
|
artists, err := controller.GetAllArtists(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks, err := controller.GetOrphanTracks(db)
|
tracks, err := controller.GetOrphanTracks(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -83,9 +81,9 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
return mux
|
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) {
|
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 {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
||||||
|
|
|
@ -5,19 +5,18 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveRelease() http.Handler {
|
func serveRelease(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(r.URL.Path[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
releaseID := slices[0]
|
releaseID := slices[0]
|
||||||
|
|
||||||
account := r.Context().Value("account").(*model.Account)
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -34,10 +33,10 @@ func serveRelease() http.Handler {
|
||||||
serveEditCredits(release).ServeHTTP(w, r)
|
serveEditCredits(release).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "addcredit":
|
case "addcredit":
|
||||||
serveAddCredit(release).ServeHTTP(w, r)
|
serveAddCredit(app, release).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "newcredit":
|
case "newcredit":
|
||||||
serveNewCredit().ServeHTTP(w, r)
|
serveNewCredit(app).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "editlinks":
|
case "editlinks":
|
||||||
serveEditLinks(release).ServeHTTP(w, r)
|
serveEditLinks(release).ServeHTTP(w, r)
|
||||||
|
@ -46,10 +45,10 @@ func serveRelease() http.Handler {
|
||||||
serveEditTracks(release).ServeHTTP(w, r)
|
serveEditTracks(release).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "addtrack":
|
case "addtrack":
|
||||||
serveAddTrack(release).ServeHTTP(w, r)
|
serveAddTrack(app, release).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "newtrack":
|
case "newtrack":
|
||||||
serveNewTrack().ServeHTTP(w, r)
|
serveNewTrack(app).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.NotFound(w, r)
|
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) {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err)
|
fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
artistID := strings.Split(r.URL.Path, "/")[3]
|
artistID := strings.Split(r.URL.Path, "/")[3]
|
||||||
artist, err := controller.GetArtist(global.DB, artistID)
|
artist, err := controller.GetArtist(app.DB, artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err)
|
fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err)
|
fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
trackID := strings.Split(r.URL.Path, "/")[3]
|
trackID := strings.Split(r.URL.Path, "/")[3]
|
||||||
track, err := controller.GetTrack(global.DB, trackID)
|
track, err := controller.GetTrack(app.DB, trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err)
|
fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
@ -5,16 +5,15 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveTrack() http.Handler {
|
func serveTrack(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(r.URL.Path[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
id := slices[0]
|
id := slices[0]
|
||||||
track, err := controller.GetTrack(global.DB, id)
|
track, err := controller.GetTrack(app.DB, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -25,7 +24,7 @@ func serveTrack() http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
releases, err := controller.GetTrackReleases(global.DB, track.ID, true)
|
releases, err := controller.GetTrackReleases(app.DB, track.ID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err)
|
fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package api
|
||||||
import (
|
import (
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/global"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -14,7 +13,7 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -33,7 +32,7 @@ func handleLogin() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := controller.GetAccount(global.DB, credentials.Username)
|
account, err := controller.GetAccount(app.DB, credentials.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve account: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -50,7 +49,7 @@ func handleLogin() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := controller.CreateToken(global.DB, account.ID, r.UserAgent())
|
token, err := controller.CreateToken(app.DB, account.ID, r.UserAgent())
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
ExpiresAt time.Time `json:"expires_at"`
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -89,7 +88,7 @@ func handleAccountRegistration() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure code exists in DB
|
// 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 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -101,7 +100,7 @@ func handleAccountRegistration() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(invite.ExpiresAt) {
|
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) }
|
if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) }
|
||||||
http.Error(w, "Invalid invite code", http.StatusBadRequest)
|
http.Error(w, "Invalid invite code", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
@ -120,7 +119,7 @@ func handleAccountRegistration() http.HandlerFunc {
|
||||||
Email: credentials.Email,
|
Email: credentials.Email,
|
||||||
AvatarURL: "/img/default-avatar.png",
|
AvatarURL: "/img/default-avatar.png",
|
||||||
}
|
}
|
||||||
err = controller.CreateAccount(global.DB, &account)
|
err = controller.CreateAccount(app.DB, &account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
||||||
http.Error(w, "An account with that username already exists", http.StatusBadRequest)
|
http.Error(w, "An account with that username already exists", http.StatusBadRequest)
|
||||||
|
@ -131,10 +130,10 @@ func handleAccountRegistration() http.HandlerFunc {
|
||||||
return
|
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) }
|
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 {
|
type LoginResponse struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
ExpiresAt time.Time `json:"expires_at"`
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -170,7 +169,7 @@ func handleDeleteAccount() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := controller.GetAccount(global.DB, credentials.Username)
|
account, err := controller.GetAccount(app.DB, credentials.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.Error(w, "Invalid username or password", http.StatusBadRequest)
|
http.Error(w, "Invalid username or password", http.StatusBadRequest)
|
||||||
|
@ -189,7 +188,7 @@ func handleDeleteAccount() http.HandlerFunc {
|
||||||
|
|
||||||
// TODO: check TOTP
|
// TODO: check TOTP
|
||||||
|
|
||||||
err = controller.DeleteAccount(global.DB, account.Username)
|
err = controller.DeleteAccount(app.DB, account.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to delete account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to delete account: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
41
api/api.go
41
api/api.go
|
@ -7,11 +7,10 @@ import (
|
||||||
|
|
||||||
"arimelody-web/admin"
|
"arimelody-web/admin"
|
||||||
"arimelody-web/controller"
|
"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 := http.NewServeMux()
|
||||||
|
|
||||||
// ACCOUNT ENDPOINTS
|
// 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) {
|
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]
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -46,13 +45,13 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/artist/{id}
|
// GET /api/v1/artist/{id}
|
||||||
ServeArtist(artist).ServeHTTP(w, r)
|
ServeArtist(app, artist).ServeHTTP(w, r)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// PUT /api/v1/artist/{id} (admin)
|
// 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:
|
case http.MethodDelete:
|
||||||
// DELETE /api/v1/artist/{id} (admin)
|
// DELETE /api/v1/artist/{id} (admin)
|
||||||
admin.RequireAccount(db, DeleteArtist(artist)).ServeHTTP(w, r)
|
admin.RequireAccount(app, DeleteArtist(app, artist)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
@ -61,10 +60,10 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/artist
|
// GET /api/v1/artist
|
||||||
ServeAllArtists().ServeHTTP(w, r)
|
ServeAllArtists(app).ServeHTTP(w, r)
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
// POST /api/v1/artist (admin)
|
// POST /api/v1/artist (admin)
|
||||||
admin.RequireAccount(db, CreateArtist()).ServeHTTP(w, r)
|
admin.RequireAccount(app, CreateArtist(app)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
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) {
|
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]
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -88,13 +87,13 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/music/{id}
|
// GET /api/v1/music/{id}
|
||||||
ServeRelease(release).ServeHTTP(w, r)
|
ServeRelease(app, release).ServeHTTP(w, r)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// PUT /api/v1/music/{id} (admin)
|
// 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:
|
case http.MethodDelete:
|
||||||
// DELETE /api/v1/music/{id} (admin)
|
// DELETE /api/v1/music/{id} (admin)
|
||||||
admin.RequireAccount(db, DeleteRelease(release)).ServeHTTP(w, r)
|
admin.RequireAccount(app, DeleteRelease(app, release)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
@ -103,10 +102,10 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/music
|
// GET /api/v1/music
|
||||||
ServeCatalog().ServeHTTP(w, r)
|
ServeCatalog(app).ServeHTTP(w, r)
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
// POST /api/v1/music (admin)
|
// POST /api/v1/music (admin)
|
||||||
admin.RequireAccount(db, CreateRelease()).ServeHTTP(w, r)
|
admin.RequireAccount(app, CreateRelease(app)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
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) {
|
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]
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -130,13 +129,13 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/track/{id} (admin)
|
// 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:
|
case http.MethodPut:
|
||||||
// PUT /api/v1/track/{id} (admin)
|
// 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:
|
case http.MethodDelete:
|
||||||
// DELETE /api/v1/track/{id} (admin)
|
// DELETE /api/v1/track/{id} (admin)
|
||||||
admin.RequireAccount(db, DeleteTrack(track)).ServeHTTP(w, r)
|
admin.RequireAccount(app, DeleteTrack(app, track)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
@ -145,10 +144,10 @@ func Handler(db *sqlx.DB) http.Handler {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/track (admin)
|
// GET /api/v1/track (admin)
|
||||||
admin.RequireAccount(db, ServeAllTracks()).ServeHTTP(w, r)
|
admin.RequireAccount(app, ServeAllTracks(app)).ServeHTTP(w, r)
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
// POST /api/v1/track (admin)
|
// POST /api/v1/track (admin)
|
||||||
admin.RequireAccount(db, CreateTrack()).ServeHTTP(w, r)
|
admin.RequireAccount(app, CreateTrack(app)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,15 +10,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeAllArtists() http.Handler {
|
func ServeAllArtists(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var artists = []*model.Artist{}
|
var artists = []*model.Artist{}
|
||||||
artists, err := controller.GetAllArtists(global.DB)
|
artists, err := controller.GetAllArtists(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve all artists: %s\n", err)
|
fmt.Printf("WARN: Failed to serve all artists: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
type (
|
type (
|
||||||
creditJSON struct {
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err)
|
fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var artist model.Artist
|
var artist model.Artist
|
||||||
err := json.NewDecoder(r.Body).Decode(&artist)
|
err := json.NewDecoder(r.Body).Decode(&artist)
|
||||||
|
@ -107,7 +106,7 @@ func CreateArtist() http.Handler {
|
||||||
}
|
}
|
||||||
if artist.Name == "" { artist.Name = artist.ID }
|
if artist.Name == "" { artist.Name = artist.ID }
|
||||||
|
|
||||||
err = controller.CreateArtist(global.DB, &artist)
|
err = controller.CreateArtist(app.DB, &artist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := json.NewDecoder(r.Body).Decode(&artist)
|
err := json.NewDecoder(r.Body).Decode(&artist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -136,7 +135,7 @@ func UpdateArtist(artist *model.Artist) http.Handler {
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(artist.Avatar, ";base64,") {
|
if strings.Contains(artist.Avatar, ";base64,") {
|
||||||
var artworkDirectory = filepath.Join("uploads", "avatar")
|
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
|
// clean up files with this ID and different extensions
|
||||||
err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error {
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
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) {
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
|
|
@ -10,17 +10,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// only allow authorised users to view hidden releases
|
// only allow authorised users to view hidden releases
|
||||||
privileged := false
|
privileged := false
|
||||||
if !release.Visible {
|
if !release.Visible {
|
||||||
account, err := controller.GetAccountByRequest(global.DB, r)
|
account, err := controller.GetAccountByRequest(app.DB, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -67,14 +66,14 @@ func ServeRelease(release *model.Release) http.Handler {
|
||||||
|
|
||||||
if release.IsReleased() || privileged {
|
if release.IsReleased() || privileged {
|
||||||
// get credits
|
// get credits
|
||||||
credits, err := controller.GetReleaseCredits(global.DB, release.ID)
|
credits, err := controller.GetReleaseCredits(app.DB, release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Credits: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Credits: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, credit := range credits {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Artists: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Artists: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -89,7 +88,7 @@ func ServeRelease(release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get tracks
|
// get tracks
|
||||||
tracks, err := controller.GetReleaseTracks(global.DB, release.ID)
|
tracks, err := controller.GetReleaseTracks(app.DB, release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Tracks: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Tracks: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -104,7 +103,7 @@ func ServeRelease(release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get links
|
// get links
|
||||||
links, err := controller.GetReleaseLinks(global.DB, release.ID)
|
links, err := controller.GetReleaseLinks(app.DB, release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Links: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Links: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
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 {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -146,7 +145,7 @@ func ServeCatalog() http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := []Release{}
|
catalog := []Release{}
|
||||||
account, err := controller.GetAccountByRequest(global.DB, r)
|
account, err := controller.GetAccountByRequest(app.DB, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -220,7 +219,7 @@ func CreateRelease() http.Handler {
|
||||||
|
|
||||||
if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" }
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -255,11 +254,11 @@ func UpdateRelease(release *model.Release) http.Handler {
|
||||||
if len(segments) == 2 {
|
if len(segments) == 2 {
|
||||||
switch segments[1] {
|
switch segments[1] {
|
||||||
case "tracks":
|
case "tracks":
|
||||||
UpdateReleaseTracks(release).ServeHTTP(w, r)
|
UpdateReleaseTracks(app, release).ServeHTTP(w, r)
|
||||||
case "credits":
|
case "credits":
|
||||||
UpdateReleaseCredits(release).ServeHTTP(w, r)
|
UpdateReleaseCredits(app, release).ServeHTTP(w, r)
|
||||||
case "links":
|
case "links":
|
||||||
UpdateReleaseLinks(release).ServeHTTP(w, r)
|
UpdateReleaseLinks(app, release).ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -281,7 +280,7 @@ func UpdateRelease(release *model.Release) http.Handler {
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(release.Artwork, ";base64,") {
|
if strings.Contains(release.Artwork, ";base64,") {
|
||||||
var artworkDirectory = filepath.Join("uploads", "musicart")
|
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
|
// clean up files with this ID and different extensions
|
||||||
err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error {
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var trackIDs = []string{}
|
var trackIDs = []string{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&trackIDs)
|
err := json.NewDecoder(r.Body).Decode(&trackIDs)
|
||||||
|
@ -321,7 +320,7 @@ func UpdateReleaseTracks(release *model.Release) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateReleaseTracks(global.DB, release.ID, trackIDs)
|
err = controller.UpdateReleaseTracks(app.DB, release.ID, trackIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
type creditJSON struct {
|
type creditJSON struct {
|
||||||
Artist string
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -388,7 +387,7 @@ func UpdateReleaseLinks(release *model.Release) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateReleaseLinks(global.DB, release.ID, links)
|
err = controller.UpdateReleaseLinks(app.DB, release.ID, links)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
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) {
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if strings.Contains(err.Error(), "no rows") {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
|
21
api/track.go
21
api/track.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
type Track struct {
|
type Track struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -26,7 +25,7 @@ func ServeAllTracks() http.Handler {
|
||||||
var tracks = []Track{}
|
var tracks = []Track{}
|
||||||
|
|
||||||
var dbTracks = []*model.Track{}
|
var dbTracks = []*model.Track{}
|
||||||
dbTracks, err := controller.GetAllTracks(global.DB)
|
dbTracks, err := controller.GetAllTracks(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to pull tracks from DB: %s\n", err)
|
fmt.Printf("WARN: Failed to pull tracks from DB: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to pull track releases for %s from DB: %s\n", track.ID, err)
|
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)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -93,7 +92,7 @@ func CreateTrack() http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := controller.CreateTrack(global.DB, &track)
|
id, err := controller.CreateTrack(app.DB, &track)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to create track: %s\n", err)
|
fmt.Printf("WARN: Failed to create track: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut || r.URL.Path == "/" {
|
if r.Method != http.MethodPut || r.URL.Path == "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -124,7 +123,7 @@ func UpdateTrack(track *model.Track) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateTrack(global.DB, track)
|
err = controller.UpdateTrack(app.DB, track)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to update track %s: %s\n", track.ID, err)
|
fmt.Printf("WARN: Failed to update track %s: %s\n", track.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete || r.URL.Path == "/" {
|
if r.Method != http.MethodDelete || r.URL.Path == "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -149,7 +148,7 @@ func DeleteTrack(track *model.Track) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackID = r.URL.Path[1:]
|
var trackID = r.URL.Path[1:]
|
||||||
err := controller.DeleteTrack(global.DB, trackID)
|
err := controller.DeleteTrack(app.DB, trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to delete track %s: %s\n", trackID, err)
|
fmt.Printf("WARN: Failed to delete track %s: %s\n", trackID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/global"
|
"arimelody-web/model"
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -11,12 +11,12 @@ import (
|
||||||
"strings"
|
"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,")
|
split := strings.Split(*data, ";base64,")
|
||||||
header := split[0]
|
header := split[0]
|
||||||
imageData, err := base64.StdEncoding.DecodeString(split[1])
|
imageData, err := base64.StdEncoding.DecodeString(split[1])
|
||||||
ext, _ := strings.CutPrefix(header, "data:image/")
|
ext, _ := strings.CutPrefix(header, "data:image/")
|
||||||
directory = filepath.Join(global.Config.DataDirectory, directory)
|
directory = filepath.Join(app.Config.DataDirectory, directory)
|
||||||
|
|
||||||
switch ext {
|
switch ext {
|
||||||
case "png":
|
case "png":
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -72,7 +71,7 @@ func GetTokenFromRequest(db *sqlx.DB, r *http.Request) string {
|
||||||
return tokenStr
|
return tokenStr
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie, err := r.Cookie(global.COOKIE_TOKEN)
|
cookie, err := r.Cookie(model.COOKIE_TOKEN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package global
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -6,44 +6,21 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
func GetConfig() model.Config {
|
||||||
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 {
|
|
||||||
configFile := os.Getenv("ARIMELODY_CONFIG")
|
configFile := os.Getenv("ARIMELODY_CONFIG")
|
||||||
if configFile == "" {
|
if configFile == "" {
|
||||||
configFile = "config.toml"
|
configFile = "config.toml"
|
||||||
}
|
}
|
||||||
|
|
||||||
config := config{
|
config := model.Config{
|
||||||
BaseUrl: "https://arimelody.me",
|
BaseUrl: "https://arimelody.me",
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
DB: dbConfig{
|
DB: model.DBConfig{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 5432,
|
Port: 5432,
|
||||||
User: "arimelody",
|
User: "arimelody",
|
||||||
|
@ -63,20 +40,18 @@ var Config = func() config {
|
||||||
|
|
||||||
err = toml.Unmarshal([]byte(data), &config)
|
err = toml.Unmarshal([]byte(data), &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to parse configuration file: %v\n", err)
|
panic(fmt.Sprintf("FATAL: Failed to parse configuration file: %v\n", err))
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleConfigOverrides(&config)
|
err = handleConfigOverrides(&config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to parse environment variable %v\n", err)
|
panic(fmt.Sprintf("FATAL: Failed to parse environment variable %v\n", err))
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}()
|
}
|
||||||
|
|
||||||
func handleConfigOverrides(config *config) error {
|
func handleConfigOverrides(config *model.Config) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env }
|
if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env }
|
||||||
|
@ -101,5 +76,3 @@ func handleConfigOverrides(config *config) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var DB *sqlx.DB
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,17 @@
|
||||||
package discord
|
package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/global"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const API_ENDPOINT = "https://discord.com/api/v10"
|
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 (
|
type (
|
||||||
AccessTokenResponse struct {
|
AccessTokenResponse struct {
|
||||||
AccessToken string `json:"access_token"`
|
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!
|
// let's get an oauth token!
|
||||||
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/oauth2/token", API_ENDPOINT),
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/oauth2/token", API_ENDPOINT),
|
||||||
strings.NewReader(url.Values{
|
strings.NewReader(url.Values{
|
||||||
"client_id": {CLIENT_ID},
|
"client_id": {app.Config.Discord.ClientID},
|
||||||
"client_secret": {CLIENT_SECRET},
|
"client_secret": {app.Config.Discord.Secret},
|
||||||
"grant_type": {"authorization_code"},
|
"grant_type": {"authorization_code"},
|
||||||
"code": {code},
|
"code": {code},
|
||||||
"redirect_uri": {OAUTH_CALLBACK_URI},
|
"redirect_uri": {GetOAuthCallbackURI(app.Config.BaseUrl)},
|
||||||
}.Encode()))
|
}.Encode()))
|
||||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
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
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
package global
|
|
||||||
|
|
||||||
const COOKIE_TOKEN string = "AM_TOKEN"
|
|
101
global/funcs.go
101
global/funcs.go
|
@ -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])
|
|
||||||
})
|
|
||||||
}
|
|
182
main.go
182
main.go
|
@ -4,16 +4,18 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/admin"
|
"arimelody-web/admin"
|
||||||
"arimelody-web/api"
|
"arimelody-web/api"
|
||||||
|
"arimelody-web/colour"
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/global"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/templates"
|
"arimelody-web/templates"
|
||||||
"arimelody-web/view"
|
"arimelody-web/view"
|
||||||
|
@ -30,48 +32,48 @@ const DEFAULT_PORT int64 = 8080
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Printf("made with <3 by ari melody\n\n")
|
fmt.Printf("made with <3 by ari melody\n\n")
|
||||||
|
|
||||||
// TODO: refactor `global` to `AppState`
|
app := model.AppState{
|
||||||
// this should contain `Config` and `DB`, and be passed through to all
|
Config: controller.GetConfig(),
|
||||||
// handlers that need it. it's better than weird static globals everywhere!
|
}
|
||||||
|
|
||||||
// initialise database connection
|
// initialise database connection
|
||||||
if global.Config.DB.Host == "" {
|
if app.Config.DB.Host == "" {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: db.host not provided! Exiting...\n")
|
fmt.Fprintf(os.Stderr, "FATAL: db.host not provided! Exiting...\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if global.Config.DB.Name == "" {
|
if app.Config.DB.Name == "" {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: db.name not provided! Exiting...\n")
|
fmt.Fprintf(os.Stderr, "FATAL: db.name not provided! Exiting...\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if global.Config.DB.User == "" {
|
if app.Config.DB.User == "" {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: db.user not provided! Exiting...\n")
|
fmt.Fprintf(os.Stderr, "FATAL: db.user not provided! Exiting...\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if global.Config.DB.Pass == "" {
|
if app.Config.DB.Pass == "" {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: db.pass not provided! Exiting...\n")
|
fmt.Fprintf(os.Stderr, "FATAL: db.pass not provided! Exiting...\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
global.DB, err = sqlx.Connect(
|
app.DB, err = sqlx.Connect(
|
||||||
"postgres",
|
"postgres",
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"host=%s port=%d user=%s dbname=%s password='%s' sslmode=disable",
|
"host=%s port=%d user=%s dbname=%s password='%s' sslmode=disable",
|
||||||
global.Config.DB.Host,
|
app.Config.DB.Host,
|
||||||
global.Config.DB.Port,
|
app.Config.DB.Port,
|
||||||
global.Config.DB.User,
|
app.Config.DB.User,
|
||||||
global.Config.DB.Name,
|
app.Config.DB.Name,
|
||||||
global.Config.DB.Pass,
|
app.Config.DB.Pass,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Unable to initialise database: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Unable to initialise database: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
global.DB.SetConnMaxLifetime(time.Minute * 3)
|
app.DB.SetConnMaxLifetime(time.Minute * 3)
|
||||||
global.DB.SetMaxOpenConns(10)
|
app.DB.SetMaxOpenConns(10)
|
||||||
global.DB.SetMaxIdleConns(10)
|
app.DB.SetMaxIdleConns(10)
|
||||||
defer global.DB.Close()
|
defer app.DB.Close()
|
||||||
|
|
||||||
// handle command arguments
|
// handle command arguments
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
|
@ -87,7 +89,7 @@ func main() {
|
||||||
totpName := os.Args[3]
|
totpName := os.Args[3]
|
||||||
secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH)
|
secret := controller.GenerateTOTPSecret(controller.TOTP_SECRET_LENGTH)
|
||||||
|
|
||||||
account, err := controller.GetAccount(global.DB, username)
|
account, err := controller.GetAccount(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -104,7 +106,7 @@ func main() {
|
||||||
Secret: string(secret),
|
Secret: string(secret),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.CreateTOTP(global.DB, &totp)
|
err = controller.CreateTOTP(app.DB, &totp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -122,7 +124,7 @@ func main() {
|
||||||
username := os.Args[2]
|
username := os.Args[2]
|
||||||
totpName := os.Args[3]
|
totpName := os.Args[3]
|
||||||
|
|
||||||
account, err := controller.GetAccount(global.DB, username)
|
account, err := controller.GetAccount(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -133,7 +135,7 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.DeleteTOTP(global.DB, account.ID, totpName)
|
err = controller.DeleteTOTP(app.DB, account.ID, totpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -149,7 +151,7 @@ func main() {
|
||||||
}
|
}
|
||||||
username := os.Args[2]
|
username := os.Args[2]
|
||||||
|
|
||||||
account, err := controller.GetAccount(global.DB, username)
|
account, err := controller.GetAccount(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -160,7 +162,7 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
totps, err := controller.GetTOTPsForAccount(global.DB, account.ID)
|
totps, err := controller.GetTOTPsForAccount(app.DB, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP methods: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to create TOTP methods: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -182,7 +184,7 @@ func main() {
|
||||||
username := os.Args[2]
|
username := os.Args[2]
|
||||||
totpName := os.Args[3]
|
totpName := os.Args[3]
|
||||||
|
|
||||||
account, err := controller.GetAccount(global.DB, username)
|
account, err := controller.GetAccount(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -193,7 +195,7 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
totp, err := controller.GetTOTP(global.DB, account.ID, totpName)
|
totp, err := controller.GetTOTP(app.DB, account.ID, totpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch TOTP method \"%s\": %v\n", totpName, err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch TOTP method \"%s\": %v\n", totpName, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -210,7 +212,7 @@ func main() {
|
||||||
|
|
||||||
case "createInvite":
|
case "createInvite":
|
||||||
fmt.Printf("Creating invite...\n")
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create invite code: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to create invite code: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -221,7 +223,7 @@ func main() {
|
||||||
|
|
||||||
case "purgeInvites":
|
case "purgeInvites":
|
||||||
fmt.Printf("Deleting all invites...\n")
|
fmt.Printf("Deleting all invites...\n")
|
||||||
err := controller.DeleteAllInvites(global.DB)
|
err := controller.DeleteAllInvites(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to delete invites: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to delete invites: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -231,7 +233,7 @@ func main() {
|
||||||
return
|
return
|
||||||
|
|
||||||
case "listAccounts":
|
case "listAccounts":
|
||||||
accounts, err := controller.GetAllAccounts(global.DB)
|
accounts, err := controller.GetAllAccounts(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch accounts: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch accounts: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -259,7 +261,7 @@ func main() {
|
||||||
username := os.Args[2]
|
username := os.Args[2]
|
||||||
fmt.Printf("Deleting account \"%s\"...\n", username)
|
fmt.Printf("Deleting account \"%s\"...\n", username)
|
||||||
|
|
||||||
account, err := controller.GetAccount(global.DB, username)
|
account, err := controller.GetAccount(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -277,7 +279,7 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.DeleteAccount(global.DB, username)
|
err = controller.DeleteAccount(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -305,20 +307,20 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle DB migrations
|
// handle DB migrations
|
||||||
controller.CheckDBVersionAndMigrate(global.DB)
|
controller.CheckDBVersionAndMigrate(app.DB)
|
||||||
|
|
||||||
// initial invite code
|
// initial invite code
|
||||||
accountsCount := 0
|
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 err != nil { panic(err) }
|
||||||
if accountsCount == 0 {
|
if accountsCount == 0 {
|
||||||
_, err := global.DB.Exec("DELETE FROM invite")
|
_, err := app.DB.Exec("DELETE FROM invite")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear existing invite codes: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear existing invite codes: %v\n", err)
|
||||||
os.Exit(1)
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to create invite code: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to create invite code: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -328,28 +330,28 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete expired invites
|
// delete expired invites
|
||||||
err = controller.DeleteExpiredInvites(global.DB)
|
err = controller.DeleteExpiredInvites(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear expired invite codes: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to clear expired invite codes: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the web server!
|
// start the web server!
|
||||||
mux := createServeMux()
|
mux := createServeMux(&app)
|
||||||
fmt.Printf("Now serving at %s:%d\n", global.Config.BaseUrl, global.Config.Port)
|
fmt.Printf("Now serving at %s:%d\n", app.Config.BaseUrl, app.Config.Port)
|
||||||
log.Fatal(
|
log.Fatal(
|
||||||
http.ListenAndServe(fmt.Sprintf(":%d", global.Config.Port),
|
http.ListenAndServe(fmt.Sprintf(":%d", app.Config.Port),
|
||||||
global.HTTPLog(global.DefaultHeaders(mux)),
|
HTTPLog(DefaultHeaders(mux)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createServeMux() *http.ServeMux {
|
func createServeMux(app *model.AppState) *http.ServeMux {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(global.DB)))
|
mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler(app)))
|
||||||
mux.Handle("/api/", http.StripPrefix("/api", api.Handler(global.DB)))
|
mux.Handle("/api/", http.StripPrefix("/api", api.Handler(app)))
|
||||||
mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(global.DB)))
|
mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler(app)))
|
||||||
mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(global.Config.DataDirectory, "uploads"))))
|
mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler(filepath.Join(app.Config.DataDirectory, "uploads"))))
|
||||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodHead {
|
if r.Method == http.MethodHead {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
@ -390,3 +392,93 @@ func staticHandler(directory string) http.Handler {
|
||||||
http.FileServer(http.Dir(directory)).ServeHTTP(w, r)
|
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])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package model
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
const COOKIE_TOKEN string = "AM_TOKEN"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Account struct {
|
Account struct {
|
||||||
ID string `json:"id" db:"id"`
|
ID string `json:"id" db:"id"`
|
||||||
|
|
32
model/appstate.go
Normal file
32
model/appstate.go
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
)
|
|
@ -8,36 +8,34 @@ import (
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/templates"
|
"arimelody-web/templates"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP HANDLER METHODS
|
// HTTP HANDLER METHODS
|
||||||
|
|
||||||
func MusicHandler(db *sqlx.DB) http.Handler {
|
func MusicHandler(app *model.AppState) http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
ServeCatalog(db).ServeHTTP(w, r)
|
ServeCatalog(app).ServeHTTP(w, r)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ServeGateway(db, release).ServeHTTP(w, r)
|
ServeGateway(app, release).ServeHTTP(w, r)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return mux
|
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) {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err)
|
fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// only allow authorised users to view hidden releases
|
// only allow authorised users to view hidden releases
|
||||||
privileged := false
|
privileged := false
|
||||||
if !release.Visible {
|
if !release.Visible {
|
||||||
account, err := controller.GetAccountByRequest(db, r)
|
account, err := controller.GetAccountByRequest(app.DB, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
Loading…
Reference in a new issue